local Inventory = require("storage.inventory") local Sentinel = require("storage.sentinel") local ItemStack = Sentinel:tag({}, Sentinel.ITEMSTACK) ItemStack.__index = ItemStack function ItemStack:fromDetail(inv, detail, slot) local obj = { slot = slot, inv = inv } if detail == nil then obj.maxCount = inv.getItemLimit(slot) else obj.name = detail.name obj.damage = detail.damage obj.maxDamage = detail.maxDamage obj.count = detail.count obj.maxCount = detail.maxCount obj.enchantments = detail.enchantments obj.displayName = detail.displayName obj.nbt = detail.nbt end setmetatable(obj, self) obj.__index = obj return obj end function ItemStack:clone(withSlot) local obj = { slot = withSlot or self.slot, inv = self.inv, name = self.name, damage = self.damage, maxDamage = self.maxDamage, count = self.count, maxCount = self.maxCount, enchantments = self.enchantments, displayName = self.displayName, nbt = self.nbt } setmetatable(obj, self) obj.__index = obj return obj end function ItemStack:toSerializable() local ser = { slot = self.slot, maxCount = self.maxCount } if not self:isEmpty() then -- Not empty ser.name = self.name ser.damage = self.damage ser.maxDamage = self.maxDamage ser.count = self.count ser.enchantments = self.enchantments ser.displayName = self.displayName ser.nbt = self.nbt end return ser end function ItemStack:fromSerializable(ser, inv) ser.inv = inv setmetatable(ser, self) ser.__index = ser return ser end function ItemStack:getCount() return self.count or 0 end function ItemStack:getName() return self.name end function ItemStack:getDamage() return self.damage end function ItemStack:getMaxDamage() return self.maxDamage end function ItemStack:getMaxCount() return self.maxCount end function ItemStack:getEnchantments() return self.enchantments end function ItemStack:getSlot() return self.slot end function ItemStack:getInventory() return self.inv end function ItemStack:getNBT() return self.nbt end function ItemStack:isEmpty() return self.count == nil or self.count == 0 end function ItemStack:getDisplayName() return self.displayName end function ItemStack:getSimpleName() local displayName = self:getDisplayName() if displayName ~= nil then return displayName end local name = self:getName() if name ~= nil then local _, e = name:find(":") if e == nil then return name end local simpleName = name:sub(e + 1) if #simpleName == 0 then return name end return simpleName end return Inventory.getSimpleName(self:getInventory()).."["..(self:getSlot() or "NO_SLOT").."]" end function ItemStack:hasChanged(listObj, thorough) local listItem = listObj[self.slot] if listItem == nil or listItem.name ~= self.name or listItem.count ~= self.count then return true end return thorough and (self ~= self:fromDetail(self.inv, self.inv.getItemDetail(self.slot), self.slot)) end function ItemStack:_modify(countDelta, stack) local newCount = self:getCount() + countDelta if newCount < 0 then error("ERROR: New stack count is negative: "..newCount) end if newCount == 0 then -- Clear data self.maxCount = self.inv.getItemLimit(self.slot) self.name = nil self.damage = nil self.maxDamage = nil self.count = nil self.maxCount = nil self.enchantments = nil self.displayName = nil self.nbt = nil else -- If stack is empty, copy stack data from source if self:isEmpty() then self.maxCount = stack.maxCount self.name = stack.name self.damage = stack.damage self.maxDamage = stack.maxDamage self.maxCount = stack.maxCount self.enchantments = stack.enchantments self.displayName = stack.displayName self.nbt = stack.nbt end self.count = newCount end end function ItemStack:transferTo(target, count) local cap = math.min(count or self:getCount(), target:getMaxCount() - target:getCount(), self:getCount()) -- If we can't transfer any data, then if cap == 0 then return count == 0, 0 end local result, xfer = pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot()) if not result then return false, xfer end target:_modify(xfer, self) self:_modify(-xfer, self) return xfer == count, xfer end local function objEquals(o1, o2) local objType = type(o1) if objType ~= type(o2) then return false end if objType == "table" then if #o1 ~= #o2 then return false end for i,v in ipairs(o1) do if not objEquals(v, o2[i]) then return false end end for k,v in pairs(o1) do if not objEquals(v, o2[k]) then return false end end return true else return o1 == o2 end end function ItemStack:__eq(other) return type(other) == "table" and self.count == other.count and self.maxCount == other.maxCount and self:canTransfer(other) end -- Determines if two stacks can be transferred to eachother -- Empty stacks are always valid transfer nodes function ItemStack:canTransfer(stack) return self:isEmpty() or stack:isEmpty() or (self.name == stack.name and self.damage == stack.damage and self.maxDamage == stack.maxDamage and self.displayName == stack.displayName and self.nbt == stack.nbt and objEquals(self.enchantments, stack.enchantments)) end local function queryField(query, field) local queryType = type(query) if queryType == "nil" then return true elseif queryType == "function" then return query(field) end return objEquals(query, field) end --- Matches a query object/function(s) --- @param query table | function function ItemStack:matches(query) if query == nil then return true elseif type(query) == "function" then return query(self) end return queryField(query.name, self.name) and queryField(query.damage, self.damage) and queryField(query.count, self.count) and queryField(query.maxDamage, self.maxDamage) and queryField(query.enchantments, self.enchantments) and queryField(query.maxCount, self.maxCount) and queryField(query.nbt, self.nbt) and queryField(query.displayName, self.displayName) end return ItemStack