diff --git a/milo/apis/storage.lua b/milo/apis/storage.lua index dfd2061..d6c8df1 100644 --- a/milo/apis/storage.lua +++ b/milo/apis/storage.lua @@ -291,6 +291,89 @@ function Storage:listItems(throttle) return self.cache end +-- provide a raw list of all the items in all storage chests +-- it might be beneficial to move this to the adapter class at some point and cache the raw item list in this class at some point +function Storage:listItemsRaw() + local res = {} + + for _, v in pairs(self.nodes) do + if v.category == "storage" then + local chest = device[v.name] + local items = chest.list() + + for slot, item in pairs(items) do + items[slot] = itemDB:get(item, function() return chest.getItemMeta(slot) end) + end + + res[v.name] = items + end + end + + return res +end + +-- provide a list of items and which chests provide them +function Storage:listProviders() + local res = {} + + local rawItems = self:listItemsRaw() + + for chest, items in pairs(rawItems) do + for slot, item in pairs(items) do + local key = table.concat({item.name, item.damage, item.nbtHash}, ":") + if not res[key] then + res[key] = {} + end + table.insert(res[key], {item = item, device = device[chest], slot = slot}) + end + end + return res +end + +-- defrags the storage system +function Storage:defrag() + local items = self:listProviders() + + for _, providers in pairs(items) do + table.sort(providers, function(a, b) + return a.item.count < b.item.count + end) + + -- We're done when we either compressed the stacks so far, that there's only one left (#providers == 1) + -- Or when we've compressed so far, that the there's only one stack which has a lower count than the maxCount + -- Because of the sorting, we know that this will be the stack in providers[1], so we check if providers[2] is at the maxCount + while #providers > 1 and providers[2].item.count ~= providers[2].item.maxCount do + local from = providers[1] + local to + + -- We're pushing to the highest stack which is still below the maxCount, this way as many slots as possible will be filled + -- This loop is guarenteed to assign a value to "to", as the only cases where it wouldn't (#providers == 1 or no provider with less than maxCount) + -- are ruled out by the condition of the outer while loop + for i = 2, #providers do + if providers[i].item.count < providers[i].item.maxCount then + to = providers[i] + else + -- As this slot is already at maxCount, all the remaining ones will also be due to sorting + break + end + end + + local toMove = math.min(to.item.maxCount - to.item.count, from.item.count) + from.device.pushItems(to.device.name, from.slot, toMove, to.slot) + to.item.count = to.item.count + toMove + from.item.count = from.item.count - toMove + + if from.item.count <= 0 then + table.remove(providers, 1) + end + + table.sort(providers, function(a, b) + return a.item.count < b.item.count + end) + end + end +end + function Storage:updateCache(adapter, item, count) if not adapter.cache then adapter.dirty = true