cc-utilities/storage/itemstack.lua
2024-11-29 23:40:24 +01:00

325 lines
7.6 KiB
Lua

local Inventory = require("storage.inventory")
local Sentinel = require("storage.sentinel")
local Logging = require("logging")
local Logger = Logging.getGlobalLogger()
local Debugger = require("debugger")
local ItemStack = Sentinel:tag({}, Sentinel.ITEMSTACK)
ItemStack.__index = ItemStack
local function hookDebugger(context)
while true do
local errorCond = Debugger.debugREPL(function(retval)
Logger:error("->", retval)
end, context)
Logger:error("x>", errorCond)
end
end
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)
if target:getMaxCount() == nil then
Logger:error("Max count is nil?", target, "\n", self, "\n", count)
hookDebugger({ target = target, count = count, self = self })
end
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
if result[2] == nil then
Logger:error(
"Error transferring item", self:getInventory().pushItems, "\n",
peripheral.getName(target:getInventory()), "\n",
errC, "\n",
cap, "\n",
self, "\n",
target, "\n",
target:getInventory().getItemLimit(target:getSlot()), "\n",
result
)
hookDebugger({ target = target, count = count, self = self, result = result, errC = errC, cap = cap })
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
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