diff --git a/src/libs/inv.lua b/src/libs/inv.lua index 1aa9841..cdec35d 100644 --- a/src/libs/inv.lua +++ b/src/libs/inv.lua @@ -4,6 +4,13 @@ -- This can also transfer to / from normal inventories, just pass in the peripheral name. -- Use {optimal=false} to transfer to / from non-inventory peripherals. +-- Now you can wrap arbratrary slot ranges +-- To do so, rather than passing in the inventory name when constructing (or adding/removing inventories) +-- you simply pass in a table of the following format +-- {name: string, minSlot: integer?, maxSlot: integer?, slots: integer[]?} +-- If slots is provided that overwrites anything in minSlot and maxSlot +-- minSlot defaults to 1, and maxSlot defaults to the inventory size + -- Transfers with this inventory are parallel safe iff -- * assumeLimits = true -- * The limits of the abstractInventorys involved have already been cached @@ -38,7 +45,7 @@ ---@field capacity number ---Wrap inventories and create an abstractInventory ----@param inventories table Table of inventory peripheral names to wrap +---@param inventories table Table of inventory peripheral names to wrap ---@param assumeLimits nil|boolean Default true, assume the limit of each slot is the same, saves a TON of time ---@return AbstractInventory function abstractInventory(inventories, assumeLimits) @@ -52,32 +59,75 @@ api.assumeLimits = true end + ---@type table> local itemNameNBTLUT = {} -- [item.name][nbt][CachedItem] -> CachedItem + ---@type table> local itemSpaceLUT = {} -- [item.name][nbt][CachedItem] -> CachedItem + ---@type table> local inventorySlotLUT = {} -- [inventory][slot] = CachedItem + ---@type table local inventoryLimit = {} -- [inventory] = number + ---@type table> local emptySlotLUT = {} -- [inventory][slot] = true|nil + ---@type table local slotNumberLUT = {} -- [global slot] -> {inventory:string, slot:number} + ---@type table> local inventorySlotNumberLUT = {} -- [inventory][slot] -> global slot:number + ---@type table> + local tagLUT = {} + -- [tag] -> string[] + + ---@type table> + local deepItemLUT = {} + -- [name][nbt] -> ItemInfo + + local executeLimit = 128 -- limit of functions to run in parallel + + ---Execute a table of functions in batches + ---@param func function[] + local function batchExecute(func) + local batches = math.ceil(#func / executeLimit) + for batch = 1, batches do + local start = ((batch - 1) * executeLimit) + 1 + local batch_end = math.min(start + executeLimit - 1, #func) + parallel.waitForAll(table.unpack(func, start, batch_end)) + end + end local function ate(table, item) -- add to end table[#table + 1] = item end + local function shallowClone(t) + local ct = {} + for k, v in pairs(t) do + ct[k] = v + end + return ct + end + + local function removeSlotFromEmptySlots(inventory, slot) + emptySlotLUT[inventory] = emptySlotLUT[inventory] or {} + emptySlotLUT[inventory][slot] = nil + if not next(emptySlotLUT[inventory]) then + emptySlotLUT[inventory] = nil + end + end + ---Cache a given item, ensuring that whatever was in the slot beforehand is wiped properly ---And the caches are managed correctly. ---@param item table|nil @@ -88,14 +138,6 @@ expect(1, item, "table", "nil") expect(2, inventory, "string") expect(3, slot, "number") - local validInventory = false - for k, v in pairs(inventories) do - if v == inventory then - validInventory = true - break - end - end - assert(validInventory, "Attempted to cache invalid inventory") local nbt = (item and item.nbt) or "NONE" if item and item.name == "" then item = nil @@ -115,6 +157,7 @@ end end end + removeSlotFromEmptySlots(inventory, slot) if not inventorySlotLUT[inventory][slot] then inventorySlotLUT[inventory][slot] = { item = item, @@ -138,11 +181,17 @@ itemNameNBTLUT[item.name] = itemNameNBTLUT[item.name] or {} itemNameNBTLUT[item.name][nbt] = itemNameNBTLUT[item.name][nbt] or {} itemNameNBTLUT[item.name][nbt][cachedItem] = cachedItem + if item.tags then + for k, v in pairs(item.tags) do + tagLUT[k] = tagLUT[k] or {} + tagLUT[k][item.name] = true + end + end if emptySlotLUT[inventory] then -- There's an item in this slot, therefor this slot is not empty emptySlotLUT[inventory][slot] = nil end - if item.count < cachedItem.capacity then + if item.count < (item.maxCount or cachedItem.capacity) then -- There's space left in this slot, add it to the cache itemSpaceLUT[item.name] = itemSpaceLUT[item.name] or {} itemSpaceLUT[item.name][nbt] = itemSpaceLUT[item.name][nbt] or {} @@ -170,39 +219,83 @@ cacheSlot(item.inventory, item.slot) end - ---Recache the inventory contents - ---@param deep nil|boolean call getItemDetail on every slot - function api.refreshStorage(deep) - itemNameNBTLUT = {} - emptySlotLUT = {} - inventorySlotLUT = {} + local function refreshInventory(inventory, deep) local deepCacheFunctions = {} - for _, inventory in pairs(inventories) do - emptySlotLUT[inventory] = {} - local size = peripheral.call(inventory, "size") or 0 - for i = 1, size do - emptySlotLUT[inventory][i] = true - local slotnumber = #slotNumberLUT + 1 - slotNumberLUT[slotnumber] = { inventory = inventory, slot = i } - inventorySlotNumberLUT[inventory] = inventorySlotNumberLUT[inventory] or {} - inventorySlotNumberLUT[inventory][i] = slotnumber + local inventoryName, slots, minSlot, maxSlot + if type(inventory) == "table" then + inventoryName = inventory.name + slots = inventory.slots + minSlot = inventory.minSlot or 1 + maxSlot = inventory.maxSlot or + assert(peripheral.call(inventoryName, "size"), ("%s is not a valid inventory."):format(inventoryName)) + else + inventoryName = inventory + minSlot = 1 + maxSlot = assert(peripheral.call(inventoryName, "size"), ("%s is not a valid inventory."):format(inventoryName)) + end + if not slots then + slots = {} + for i = minSlot, maxSlot do + slots[#slots + 1] = i end - inventoryLimit[inventory] = peripheral.call(inventory, "getItemLimit", 1) -- this should make transfers from/to this inventory parallel safe. - if not deep then - for slot, item in pairs(peripheral.call(inventory, "list") or {}) do - cacheItem(item, inventory, slot) + end + emptySlotLUT[inventoryName] = {} + for _, i in ipairs(slots) do + emptySlotLUT[inventoryName][i] = true + local slotnumber = #slotNumberLUT + 1 + slotNumberLUT[slotnumber] = { inventory = inventoryName, slot = i } + inventorySlotNumberLUT[inventoryName] = inventorySlotNumberLUT[inventoryName] or {} + inventorySlotNumberLUT[inventoryName][i] = slotnumber + end + inventoryLimit[inventoryName] = peripheral.call(inventoryName, "getItemLimit", 1) -- this should make transfers from/to this inventory parallel safe. + local listings = peripheral.call(inventoryName, "list") + if not deep then + for _, i in ipairs(slots) do + if listings[i] then + cacheItem(listings[i], inventoryName, i) end - else - deepCacheFunctions[#deepCacheFunctions + 1] = function() - for slot, _ in pairs(peripheral.call(inventory, "list")) do - cacheSlot(inventory, slot) + end + else + for _, i in ipairs(slots) do + local listing = listings[i] + if listing then + deepCacheFunctions[#deepCacheFunctions + 1] = function() + deepItemLUT[listing.name] = deepItemLUT[listing.name] or {} + if deepItemLUT[listing.name][listing.nbt or "NONE"] then + local item = shallowClone(deepItemLUT[listing.name][listing.nbt or "NONE"]) + item.count = listing.count + cacheItem(item, inventoryName, i) + else + local item = peripheral.call(inventoryName, "getItemDetail", i) + cacheItem(item, inventoryName, i) + deepItemLUT[item.name][item.nbt or "NONE"] = item + end end end end end - if deep then - parallel.waitForAll(table.unpack(deepCacheFunctions)) + return deepCacheFunctions + end + + ---Recache the inventory contents + ---@param deep nil|boolean call getItemDetail on every slot + function api.refreshStorage(deep) + if type(deep) == "nil" then + deep = true end + itemNameNBTLUT, itemSpaceLUT, inventorySlotLUT, inventoryLimit, emptySlotLUT, slotNumberLUT, inventorySlotNumberLUT, + tagLUT, deepItemLUT = {}, {}, {}, {}, {}, {}, {}, {}, {} + local inventoryRefreshers = {} + local deepCacheFunctions = {} + for _, inventory in pairs(inventories) do + table.insert(inventoryRefreshers, function() + for k, v in ipairs(refreshInventory(inventory, deep) or {}) do + deepCacheFunctions[#deepCacheFunctions + 1] = v + end + end) + end + batchExecute(inventoryRefreshers) + batchExecute(deepCacheFunctions) end ---Get an inventory slot for a given item @@ -262,26 +355,6 @@ return freeSlot, inv, space end - ---@param inventory string - ---@param slot integer - ---@return integer|nil slot - ---@return string|nil inventory - ---@return integer|nil capacity - ---@deprecated do not use - local function getSpaceForItem(inventory, slot) - local itemInfo = peripheral.call(inventory, "getItemDetail", slot) - if itemInfo and itemInfo.name then - local cachedItem = getSlotWithSpace(itemInfo.name, itemInfo.nbt) - if cachedItem then - return cachedItem.slot, cachedItem.inventory, cachedItem.capacity - cachedItem.item.count - else - return getEmptySpace() - end - else - return getEmptySpace() - end - end - ---@param name string ---@param nbt string|nil ---@return CachedItem|nil @@ -333,14 +406,6 @@ return slotNumberLUT[slot] end - local function shallowClone(t) - local ct = {} - for k, v in pairs(t) do - ct[k] = v - end - return ct - end - local defaultOptions = { optimal = true, allowBadTransfers = false, @@ -348,6 +413,157 @@ itemMovedCallback = nil, } + local function pushItemsUnoptimal(targetInventory, name, amount, toSlot, nbt, options) + -- This is to a normal inventory + local totalMoved = 0 + local rep = true + while totalMoved < amount and rep do + local item + if type(name) == "number" then + -- perform lookup + item = getGlobalSlot(name) + else + item = getItem(name, nbt) + end + if not item then + return totalMoved -- no items to move + end + local itemCount = item.item.count + rep = (itemCount - totalMoved) < amount + local expectedMove = math.min(amount - totalMoved, 64) + local remainingItems = math.max(0, itemCount - expectedMove) + item.item.count = remainingItems + if item.count == 0 then + cacheItem(nil, item.inventory, item.slot) + else + cacheItem(item.item, item.inventory, item.slot) + end + local amountMoved = peripheral.call(item.inventory, "pushItems", targetInventory, item.slot, amount - totalMoved, + toSlot) + totalMoved = totalMoved + amountMoved + refreshItem(item) + if options.itemMovedCallback then + options.itemMovedCallback() + end + if amountMoved < itemCount then + return totalMoved -- target slot full + end + end + return totalMoved + end + + local function pushItemsOptimal(targetInventory, name, amount, toSlot, nbt, options) + if type(targetInventory) == "string" then + -- We'll see if this is a good optimization or not + targetInventory = abstractInventory({ targetInventory }) + targetInventory.refreshStorage() + end + local theoreticalAmountMoved = 0 + local actualAmountMoved = 0 + local transferCache = {} + local totalTime = 0 + local badTransfer + while theoreticalAmountMoved < amount do + local t0 = os.clock() + -- find the cachedItem item in self + ---@type CachedItem|nil + local cachedItem + if type(name) == "number" then + cachedItem = getGlobalSlot(name) + if not (cachedItem and cachedItem.item) then + -- this slot is empty + break + end + else + cachedItem = getItem(name, nbt) + if not (cachedItem and cachedItem.item) then + -- no slots with this item + break + end + end + -- check how many items there are available to move + local itemsToMove = cachedItem.item.count + -- ask the other inventory for a slot with space + local destinationInfo + if toSlot then + destinationInfo = targetInventory._getGlobalSlot(toSlot) + if not destinationInfo then + local info = targetInventory._getLookupSlot(toSlot) + destinationInfo = cacheItem(nil, info.inventory, info.slot) + end + else + destinationInfo = targetInventory._getSlotFor(cachedItem.item.name, nbt) + if not destinationInfo then + local slot, inventory, capacity = targetInventory._getEmptySpace() + if not (slot and inventory) then + break + end + destinationInfo = targetInventory._updateItem(nil, inventory, slot) + end + end + -- determine the amount of items that should get moved + local slotCapacity = cachedItem.item.maxCount or destinationInfo.capacity + if destinationInfo.item then + slotCapacity = slotCapacity - destinationInfo.item.count + end + itemsToMove = math.min(itemsToMove, slotCapacity, amount - theoreticalAmountMoved) + if destinationInfo.item and (destinationInfo.item.name ~= cachedItem.item.name) then + itemsToMove = 0 + end + if itemsToMove == 0 then + break + end + -- queue a transfer of that item + local fromInv, toInv, fromSlot, limit, slot = cachedItem.inventory, destinationInfo.inventory, cachedItem.slot, + itemsToMove, destinationInfo.slot + if limit ~= 0 then + ate(transferCache, function() + local itemsMoved = peripheral.call(fromInv, "pushItems", toInv, fromSlot, limit, slot) + if options.itemMovedCallback then + options.itemMovedCallback() + end + actualAmountMoved = actualAmountMoved + itemsMoved + if not options.allowBadTransfers then + assert(itemsToMove == itemsMoved, ("Expected to move %u items, moved %u"):format(itemsToMove, itemsMoved)) + elseif not itemsToMove == itemsMoved then + badTransfer = true + end + end) + end + -- update our cache of that item to include the predicted transfer + local updatedItem = shallowClone(cachedItem.item) + updatedItem.count = updatedItem.count - itemsToMove + -- update the other inventory's cache to include the predicted transfer + if not destinationInfo.item then + destinationInfo.item = shallowClone(cachedItem.item) + destinationInfo.item.count = 0 + end + destinationInfo.item.count = destinationInfo.item.count + itemsToMove + + if updatedItem.count == 0 then + cacheItem(nil, cachedItem.inventory, cachedItem.slot) + else + cacheItem(updatedItem, cachedItem.inventory, cachedItem.slot) + end + + targetInventory._updateItem(destinationInfo.item, destinationInfo.inventory, destinationInfo.slot) + + --- Timing stuff + local dt = os.clock() - t0 + totalTime = totalTime + dt + theoreticalAmountMoved = theoreticalAmountMoved + itemsToMove + end + -- execute the inventory transfers + -- return amount of items moved + batchExecute(transferCache) + if badTransfer then + -- refresh inventories + api.refreshStorage(options.autoDeepRefresh) + targetInventory.refreshStorage(options.autoDeepRefresh) + end + return actualAmountMoved + end + --[[ .########..##.....##..######..##.....## .##.....##.##.....##.##....##.##.....## @@ -379,145 +595,16 @@ options[k] = v end end + if type(targetInventory) == "string" then + local test = peripheral.wrap(targetInventory) + if not (test and test.size) then + options.optimal = false + end + end if type(targetInventory) == "string" and not options.optimal then - -- This is to a normal inventory - local totalMoved = 0 - local rep = true - while totalMoved < amount and rep do - local item - if type(name) == "number" then - -- perform lookup - item = getGlobalSlot(name) - else - item = getItem(name, nbt) - end - if not item then - return totalMoved -- no items to move - end - local itemCount = item.item.count - rep = (itemCount - totalMoved) < amount - local amountMoved = peripheral.call(item.inventory, "pushItems", targetInventory, item.slot, amount - totalMoved - , toSlot) - totalMoved = totalMoved + amountMoved - refreshItem(item) - if options.itemMovedCallback then - options.itemMovedCallback() - end - if amountMoved < itemCount then - return totalMoved -- target slot full - end - end - return totalMoved + return pushItemsUnoptimal(targetInventory, name, amount, toSlot, nbt, options) else - if type(targetInventory) == "string" then - -- We'll see if this is a good optimization or not - targetInventory = abstractInventory({ targetInventory }) - targetInventory.refreshStorage() - end - local theoreticalAmountMoved = 0 - local actualAmountMoved = 0 - local transferCache = {} - local totalTime = 0 - local badTransfer - while theoreticalAmountMoved < amount do - local t0 = os.clock() - -- find the cachedItem item in self - ---@type CachedItem|nil - local cachedItem - if type(name) == "number" then - cachedItem = getGlobalSlot(name) - if not (cachedItem and cachedItem.item) then - -- this slot is empty - break - end - else - cachedItem = getItem(name, nbt) - if not (cachedItem and cachedItem.item) then - -- no slots with this item - break - end - end - -- check how many items there are available to move - local itemsToMove = cachedItem.item.count - -- ask the other inventory for a slot with space - local destinationInfo - if toSlot then - destinationInfo = targetInventory._getGlobalSlot(toSlot) - if not destinationInfo then - local info = targetInventory._getLookupSlot(toSlot) - destinationInfo = cacheItem(nil, info.inventory, info.slot) - end - else - destinationInfo = targetInventory._getSlotFor(cachedItem.item.name, nbt) - if not destinationInfo then - local slot, inventory, capacity = targetInventory._getEmptySpace() - if not (slot and inventory) then - break - end - destinationInfo = targetInventory._updateItem(nil, inventory, slot) - end - end - -- determine the amount of items that should get moved - local slotCapacity = destinationInfo.capacity - if destinationInfo.item then - slotCapacity = slotCapacity - destinationInfo.item.count - end - itemsToMove = math.min(itemsToMove, slotCapacity, amount - theoreticalAmountMoved) - if destinationInfo.item and (destinationInfo.item.name ~= cachedItem.item.name) then - itemsToMove = 0 - end - if itemsToMove == 0 then - break - end - -- queue a transfer of that item - local fromInv, toInv, fromSlot, limit, slot = cachedItem.inventory, destinationInfo.inventory, cachedItem.slot, - itemsToMove, destinationInfo.slot - if limit ~= 0 then - ate(transferCache, function() - local itemsMoved = peripheral.call(fromInv, "pushItems", toInv, fromSlot, limit, slot) - if options.itemMovedCallback then - options.itemMovedCallback() - end - actualAmountMoved = actualAmountMoved + itemsMoved - if not options.allowBadTransfers then - assert(itemsToMove == itemsMoved, ("Expected to move %u items, moved %u"):format(itemsToMove, itemsMoved)) - elseif not itemsToMove == itemsMoved then - badTransfer = true - end - end) - end - -- update our cache of that item to include the predicted transfer - local updatedItem = shallowClone(cachedItem.item) - updatedItem.count = updatedItem.count - itemsToMove - -- update the other inventory's cache to include the predicted transfer - if not destinationInfo.item then - destinationInfo.item = shallowClone(cachedItem.item) - destinationInfo.item.count = 0 - end - destinationInfo.item.count = destinationInfo.item.count + itemsToMove - - if updatedItem.count == 0 then - cacheItem(nil, cachedItem.inventory, cachedItem.slot) - else - cacheItem(updatedItem, cachedItem.inventory, cachedItem.slot) - end - - targetInventory._updateItem(destinationInfo.item, destinationInfo.inventory, destinationInfo.slot) - - --- Timing stuff - local dt = os.clock() - t0 - totalTime = totalTime + dt - theoreticalAmountMoved = theoreticalAmountMoved + itemsToMove - end - -- execute the inventory transfers - -- return amount of items moved - parallel.waitForAll(table.unpack(transferCache)) - if badTransfer then - -- refresh inventories - api.refreshStorage(options.autoDeepRefresh) - targetInventory.refreshStorage(options.autoDeepRefresh) - end - return actualAmountMoved + return pushItemsOptimal(targetInventory, name, amount, toSlot, nbt, options) end error("Invalid targetInventory") end @@ -555,6 +642,12 @@ local rep, itemsPulled = false, 0 amount = amount or 64 nbt = nbt or "NONE" + if type(fromInventory) == "string" then + local test = peripheral.wrap(fromInventory) + if not (test and test.size) then + options.optimal = false + end + end if options.optimal == nil then options.optimal = true end if type(fromInventory) == "string" and not options.optimal then assert(type(fromSlot) == "number", "Must pull from a slot #") @@ -565,6 +658,7 @@ return itemsPulled end local limit = math.min(amount - itemsPulled, space) + cacheItem({ name = "UNKNOWN", count = math.huge }, freeInventory, freeSlot) local moved = peripheral.call(freeInventory, "pullItems", fromInventory, fromSlot, limit, freeSlot) cacheSlot(freeInventory, freeSlot) if options.itemMovedCallback then @@ -624,7 +718,7 @@ end end - local slotCapacity = destinationInfo.capacity or 64 + local slotCapacity = cachedItem.item.maxCount or destinationInfo.capacity or 64 if destinationInfo.item then slotCapacity = slotCapacity - destinationInfo.item.count end @@ -677,7 +771,7 @@ end - parallel.waitForAll(table.unpack(transferCache)) + batchExecute(transferCache) if badTransfer then -- refresh inventories api.refreshStorage(options.autoDeepRefresh) @@ -712,9 +806,6 @@ local t = {} for name, nbtt in pairs(itemNameNBTLUT) do for nbt, cachedItem in pairs(nbtt) do - if nbt == "NONE" then - nbt = nil - end ate(t, cachedItem) end end @@ -742,6 +833,43 @@ return t end + ---Rearrange items to make the most efficient use of space + function api.defrag() + local f = {} + for _, name in pairs(api.listNames()) do + local nbt = "NONE" + table.insert(f, function() + local optimal = false + while not optimal do + local item = getSlotWithSpace(name, nbt) + if not item then + optimal = true + break + end + local count = item.item.count + cacheItem(nil, item.inventory, item.slot) + while count > 0 do + local toItem = getSlotWithSpace(name, nbt) + if toItem then + local toMove = math.min(count, (toItem.item.maxCount or toItem.capacity) - toItem.item.count) + toItem.item.count = toItem.item.count + toMove + cacheItem(toItem, toItem.inventory, toItem.slot) + peripheral.call(item.inventory, "pushItems", toItem.inventory, item.slot, toMove, toItem.slot) + refreshItem(item) + refreshItem(toItem) + count = count - toMove + else + optimal = true + break + end + end + end + end) + end + batchExecute(f) + api.refreshStorage(true) -- this messes with the cache in some way I currently cannot figure out. + end + ---Get a CachedItem by name/nbt ---@param name string ---@param nbt nil|string @@ -760,6 +888,25 @@ return getGlobalSlot(slot) end + ---Change the max number of functions to run in parallel + ---@param n integer + function api.setBatchLimit(n) + expect(1, n, "number") + assert(n > 0, "Attempt to set negative/0 batch limit.") + if n < 10 then + print(string.format("Warning, setting a very low batch limit (%u), abstractInvLib will be very slow."):format(n)) + end + if n > 250 then + error(string.format("Attempt to set batch limit to %u, the event queue is 256 elements long. This is very likely to result in dropped events." + , n), 2) + end + if n > 150 then + print(string.format("Warning, the event queue is 256 elements long and the batch limit is %u. It is possible that you may have dropped events" + , n)) + end + executeLimit = n + end + ---Get an inventory peripheral compatible list of items in this storage ---@return table function api.list() @@ -774,6 +921,42 @@ return t end + ---Get a list of item name indexed counts of each item + ---@return table + function api.listItemAmounts() + local t = {} + for _, itemName in ipairs(api.listNames()) do + t[itemName] = 0 + for _, nbt in ipairs(api.listNBT(itemName)) do + t[itemName] = t[itemName] + api.getCount(itemName, nbt) + end + end + return t + end + + ---Get a list of items with the given tag + ---@return string[] + function api.getTag(tag) + local t = {} + for k, v in pairs(tagLUT[tag] or {}) do + table.insert(t, k) + end + return t + end + + ---Get the slot usage of this inventory + ---@return {free: integer, used:integer, total:integer} + function api.getUsage() + local ret = {} + ret.total = api.size() + ret.used = 0 + for i, _ in pairs(api.list()) do + ret.used = ret.used + 1 + end + ret.free = ret.total - ret.used + return ret + end + ---Get the amount of slots in this inventory ---@return integer function api.size() @@ -816,6 +999,41 @@ return moved end + local function getItemIndex(t, item) + for k, v in ipairs(t) do + if v == item then + return k + end + end + end + + ---Add an inventory to the storage object + ---@param inventory string + ---@return boolean success + function api.addInventory(inventory) + expect(1, inventory, "table") + if getItemIndex(inventories, inventory) then + return false + end + table.insert(inventories, inventory) + api.refreshStorage(true) + return true + end + + ---Remove an inventory from the storage object + ---@param inventory string + ---@return boolean success + function api.removeInventory(inventory) + expect(1, inventory, "string") + local index = getItemIndex(inventories, inventory) + if not index then + return false + end + table.remove(inventories, index) + api.refreshStorage(true) + return true + end + ---Get the number of free slots in this inventory ---@return integer function api.freeSpace() @@ -844,13 +1062,14 @@ nbt = nbt or "NONE" if itemSpaceLUT[name] and itemSpaceLUT[name][nbt] then for _, cached in pairs(itemSpaceLUT[name][nbt]) do - print("item?") count = count + (cached.capacity - cached.item.count) end end return count end + api.refreshStorage(true) + return api end