local Inventory = require("storage.inventory") local Sentinel = require("storage.sentinel") local Logging = require("logging") local Logger = Logging.getGlobalLogger() 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 or 64 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:getSlot(), inv = self:getInventory(), name = self:getName(), damage = self:getDamage(), maxDamage = self:getMaxDamage(), count = self:getCount(), maxCount = self:getMaxCount(), enchantments = self:getEnchantments(), displayName = self:getDisplayName(), nbt = self:getNBT() } setmetatable(obj, self) obj.__index = obj return obj end function ItemStack:toSerializable() local ser = { slot = self:getSlot(), maxCount = self:getMaxCount() } if not self:isEmpty() then -- Not empty ser.name = self:getName() ser.damage = self:getDamage() ser.maxDamage = self:getMaxDamage() ser.count = self:getCount() ser.enchantments = self:getEnchantments() ser.displayName = self:getDisplayName() ser.nbt = self:getNBT() 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:getSlot()] if listItem == nil or listItem.name ~= self.name or listItem.count ~= self:getCount() then return true end local inv = self:getInventory() local slot = self:getSlot() return thorough and (self ~= self:fromDetail(inv, inv.getItemDetail(slot), 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.enchantments = nil self.displayName = nil self.nbt = nil else -- If stack is empty, copy stack data from source if self:isEmpty() then self.name = stack:getName() self.damage = stack:getDamage() self.maxDamage = stack:getMaxDamage() self.maxCount = stack:getMaxCount() self.enchantments = stack:getEnchantments() self.displayName = stack:getDisplayName() self.nbt = stack:getNBT() 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 errC = 0 local result repeat result = { pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot()) } errC = errC + 1 until (not result[1]) or (result[2] ~= nil) or (errC > 8) if not result[1] then return false, result[2] end target:_modify(result[2], self) self:_modify(-result[2], self) return result[2] == count, result[2] 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 local function runGetter(obj, name) local fun = obj[name] if type(fun) ~= "function" then return false, ("Function '%s' cannot be found in object"):format(name) end return pcall(fun, obj) end local function checkEqual(obj1, obj2, funcName) local result1 = { runGetter(obj1, funcName) } local result2 = { runGetter(obj2, funcName) } return result1[1] and result2[2] and (result1[2] == result2[2]) end local function checkEnchantments(obj1, obj2) local GETTER_FUNC_NAME = "getEnchantments" local result1 = { runGetter(obj1, GETTER_FUNC_NAME) } local result2 = { runGetter(obj2, GETTER_FUNC_NAME) } return result1[1] and result2[1] and objEquals(result1[2], result2[2]) end function ItemStack:__eq(other) return type(other) == "table" and checkEqual(self, other, "getCount") and checkEqual(self, other, "getMaxCount") 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 ( checkEqual(self, stack, "getName") and checkEqual(self, stack, "getDamage") and checkEqual(self, stack, "getMaxDamage") and checkEqual(self, stack, "getDisplayName") and checkEqual(self, stack, "getNBT") and checkEnchantments(self, stack) ) 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:getName()) and queryField(query.damage, self:getDamage()) and queryField(query.count, self:getCount()) and queryField(query.maxDamage, self:getMaxDamage()) and queryField(query.enchantments, self:getEnchantments()) and queryField(query.maxCount, self:getMaxCount()) and queryField(query.nbt, self:getNBT()) and queryField(query.displayName, self:getDisplayName()) end return ItemStack