cc-utilities/storage/itemstack.lua
2024-12-03 22:36:23 +01:00

319 lines
7.6 KiB
Lua

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