local Sentinel = require("storage.sentinel") local ItemGroup = Sentinel:tag({}, Sentinel.ITEMGROUP) ItemGroup.__index = ItemGroup --- Create a group of managed itemstacks ---@param stack table ItemStack to compare to ---@return table ItemGroup instance representing stack type function ItemGroup:from(stack) local obj = { stack } setmetatable(obj, self) obj.__index = obj return obj end function ItemGroup.collectStacks(stacks) local groups = {} for _,stack in ipairs(stacks) do for _,group in ipairs(groups) do if group:addStack(stack) then goto continue end end table.insert(groups, ItemGroup:from(stack)) ::continue:: end return groups end function ItemGroup:getStackCount() return #self end function ItemGroup:getDisplayName() return self:_getIdentityStack():getDisplayName() end function ItemGroup:getSimpleName() return self:isEmpty() and "[EMPTY]" or self:_getIdentityStack():getSimpleName() end function ItemGroup:isEmpty() return self:_getIdentityStack():isEmpty() end function ItemGroup:getName() return self:_getIdentityStack():getName() end function ItemGroup:getEnchantments() return self:_getIdentityStack():getEnchantments() end function ItemGroup:getNBT() return self:_getIdentityStack():getNBT() end function ItemGroup:getDamage() return self:_getIdentityStack():getDamage() end function ItemGroup:getMaxDamage() return self:_getIdentityStack():getMaxDamage() end function ItemGroup:_getIdentityStack() return self[1] end function ItemGroup:_iterateStacks() return ipairs(self) end function ItemGroup:_addStack(stack) table.insert(self, stack) end function ItemGroup:_removeStack(stackOrIndex) if type(stackOrIndex) == "number" then return table.remove(self, stackOrIndex) ~= nil else for index,stack in self:_iterateStacks() do if stack == stackOrIndex then return self:_removeStack(index) end end end end function ItemGroup:canAddStack(stack) if self:_getIdentityStack():canTransfer(stack) then for _,chkStack in self:_iterateStacks() do if chkStack == stack then -- Already in the group return true end end return true end return false end function ItemGroup:addStack(stack) if self:canAddStack(stack) then self:_addStack(stack) return true end return false end function ItemGroup:transferTo(target, itemCount) local targetGroup = nil if Sentinel:is(target, Sentinel.CHEST) or Sentinel:is(target, Sentinel.STORAGE) then local identity = self:_getIdentityStack() local find = target:find(function(stack) return identity:canTransfer(stack) end) if #find == 0 then return itemCount == nil or itemCount == 0, 0 end targetGroup = ItemGroup:from(find[1]) for i=2,#find do targetGroup:_addStack(find[i]) end elseif Sentinel:is(target, Sentinel.ITEMSTACK) then targetGroup = ItemGroup:from(target) elseif Sentinel:is(target, Sentinel.ITEMGROUP) then targetGroup = target end if targetGroup == nil then error("Unexpected transfer target for ItemGroup:transferTo") end local targetCap = 0 for _,stack in targetGroup:_iterateStacks() do targetCap = targetCap + (stack:getMaxCount() - stack:getCount()) end -- TODO: Not efficient local transferMax = math.min(itemCount or targetCap, targetCap, self:getItemCount()) local transfer = 0 for _,stack in targetGroup:_iterateStacks() do for _,from in self:_iterateStacks() do if transfer >= transferMax then goto complete end if stack:getCount() == stack:getMaxCount() then goto continue end local _, xfer = from:transferTo(stack, math.min(stack:getMaxCount() - stack:getCount(), transferMax - transfer)) transfer = transfer + xfer end ::continue:: end ::complete:: return itemCount == nil or (itemCount == transfer), transfer end function ItemGroup:getItemCount() if self:_getIdentityStack():isEmpty() then return 0 end local sum = 0 for _,stack in self:_iterateStacks() do sum = sum + stack:getCount() end return sum end function ItemGroup:_copyTrackedStackList() local collect = {} for index,stack in self:_iterateStacks() do table.insert(collect, { stack = stack, index = index }) end return collect end function ItemGroup:defragment() if self:_getIdentityStack():isEmpty() then return 0 end local entries = self:_copyStackList() table.sort(entries, function(a, b) return a.stack:getCount() > b.stack:getCount() end) local endPtr = #entries local startPtr = endPtr for i=1,endPtr do local entry = entries[i].stack if entry:getMaxCount() ~= entry:getCount() then startPtr = i end end local toRemove = {} while startPtr < endPtr do local from = entries[endPtr] local to = entries[startPtr] local emptied, _ = from.stack:transferTo(to.stack) if emptied then table.insert(toRemove, from.index) endPtr = endPtr - 1 end if (not emptied) or (to.stack:getCount() == to.stack:getMaxCount()) then startPtr = startPtr + 1 end end table.sort(toRemove, function(a, b) return a > b end) for _,index in ipairs(toRemove) do self:_removeStack(index) end return #toRemove end return ItemGroup