Files
barotrauma-localmods/QuickStackToBag/Lua/Cyka/utils.lua
2025-03-31 00:22:49 +02:00

362 lines
12 KiB
Lua

-- luacheck: globals Character MyModGlobal
-- luacheck: max line length 420
---@class ItemRefs
---@field item Barotrauma.Item
---@field inventory Barotrauma.ItemInventory
---@field slot Barotrauma.ItemInventory.Slot
---@class InventorySlot
---@field slot Barotrauma.ItemSlot
---@field inventory Barotrauma.ItemInventory
---@field slotIndex number
-- We got to do this shit because enqueueInventory calls enqueueItem
-- And enqueueItem calls enqueueInventory
-- So unless we define them both before using them
-- We will get an error saying either is undefined
local enqueueItem
local enqueueSlot
local enqueueInventory
local allPlayerItems
local allSubmarineItems
local allOwnedItems
local _
---@alias FilterPredicate fun(item: Barotrauma.Item, inventoryRef?: Barotrauma.ItemInventory, slotRef: Barotrauma.ItemInventory.Slot): boolean
-- Loading refs is optional because it MAY have a performance impact
---@param item Barotrauma.Item
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueItem = function(item, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- debugPrint(string.format("Enqueuing item: %s", item.Prefab.Identifier.Value))
-- local err
-- This should make it breadth first, right...?
-- No, not yet...
if not item then return queue, "No item" end
local ok, stop = predicate(item, itemRef)
if ok then
queue[#queue + 1] = item
end
if stop then return queue, "Stop" end
if item.OwnInventory then
-- As far as I know every item has only one inventory
-- Only machines have multiple
-- So inventrorY should be fine here
-- debugPrint("Item has its own inventory, enqueuing inventory...")
if loadRefs then
itemRef.item = item
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate, loadRefs, itemRef)
else
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate, itemRef)
end
-- if err then
-- debugPrint(string.format("Error enqueuing inventory: %s", err))
-- end
end
-- debugPrint(string.format("Item enqueued. Current queue size: %d", #queue))
return queue, nil
end
---@param slot Barotrauma.ItemInventory.Slot
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueSlot = function(slot, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- debugPrint(string.format("Enqueuing slot with %d items.", #slot.items))
-- We don't want to shadow queue
local err
-- If the slot is empty there's nothing to iterate
-- And we will naturally return queue as is
if not slot then return queue, "No slot" end
if not slot.items then return queue, "No items" end
for _, item in ipairs(slot.items) do
-- Only the final leaf nodes decide upon the predicate
if loadRefs then
itemRef.slot = slot
queue, err = enqueueItem(item, queue, predicate, loadRefs, itemRef)
else
queue, err = enqueueItem(item, queue, predicate)
end
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing slot. Current queue size: %d", #queue))
return queue
end
---@param inventory Barotrauma.ItemInventory
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueInventory = function(inventory, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- debugPrint(string.format("Enqueuing inventory with %d slots.", #inventory.slots))
local err
if not inventory then return queue, "No inventory" end
if not inventory.slots then return queue, "No slots" end
for _, slot in ipairs(inventory.slots) do
-- Only the final leaf nodes decide upon the predicate
if loadRefs then
itemRef.inventory = inventory
queue, err = enqueueSlot(slot, queue, predicate, loadRefs, itemRef)
else
queue, err = enqueueSlot(slot, queue, predicate)
end
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing inventory. Current queue size: %d", #queue))
return queue
end
local relevantPlayerInventorySlots = { 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, MyModGlobal.BAG_SLOT }
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@return Barotrauma.Item[], string?
allPlayerItems = function(queue, predicate, loadRefs)
queue = queue or {}
predicate = predicate or function() return true end
local character = Character.Controlled
if not character then return queue, "No character" end
local inventory = character.Inventory
if not inventory then return queue, "No inventory" end
for _, slotid in ipairs(relevantPlayerInventorySlots) do
local slot = inventory.slots[slotid]
local err
if not slot then goto continue end
if #slot.items == 0 then goto continue end
queue, err = enqueueSlot(slot, queue, predicate, loadRefs)
if err then return queue, err end
::continue::
end
return queue
end
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@return Barotrauma.Item[], string?
allSubmarineItems = function(queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- This only exists so predicate does not explode
-- Even if its empty
local itemRef = {}
local character = Character.Controlled
if not character then return queue, "No character" end
local submarine = character.Submarine
if not submarine then return queue, "No submarine" end
for item in submarine.GetItems(false) do
-- We do NOT want to call enqueueItem here because enqueueItem
-- Is recursive
-- And this call (GetItems) already gets all items
-- So we would be doing double the work (at best case)
-- It also means we won't have refs here which sucks
local ok, stop = predicate(item, itemRef)
if ok then
queue[#queue + 1] = item
end
if stop then return queue, "Stop" end
end
return queue
end
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@return Barotrauma.Item[], string?
allOwnedItems = function(queue, predicate, loadRefs)
queue = queue or {}
predicate = predicate or function() return true end
local err
queue, err = allPlayerItems(queue, predicate, loadRefs)
if err then return queue, err end
queue, err = allSubmarineItems(queue, predicate)
if err then return queue, err end
return queue
end
---@return Barotrauma.Item[], string?
local function getOpenContainers()
local controlledCharacter = Character.Controlled
if not controlledCharacter then return {}, "No controlled character" end
local selectedItem = controlledCharacter.SelectedItem
if not selectedItem then return {}, "No selected item" end
return { selectedItem }, nil
end
---@return Barotrauma.Item, string?
local function getFirstOpenContainer()
local containers, err = getOpenContainers()
if err then return nil, err end
if #containers == 0 then return nil, "No open containers" end
return containers[1], nil
end
-- There is actually no need to recurse deep
-- Because we can only have an item in the inventory open
-- And not an item in an item in the inventory
-- So in theory we only need to recurse 1 deep
---@param inventory Barotrauma.Inventory
---@param slots InventorySlot[]
---@param depth number
---@return InventorySlot[], string?
local function getMouseoverSlots(inventory, slots, depth)
slots = slots or {}
depth = depth or 0
if depth > 1 then return slots, nil end
local visualSlots = inventory.visualSlots
if not visualSlots then return nil, "Inventory has no visual slots" end
for i, visualSlot in ipairs(visualSlots) do
local item
local itemInventory
-- local err
local slot = inventory.slots[i]
if not slot then
-- MyModGlobal.debugPrint("Slot is not a valid slot")
goto continue
end
if #slot.items == 0 then
goto mouseover
end
item = slot.items[1]
if not item then
goto mouseover
end
itemInventory = item.OwnInventory
if not itemInventory then
goto mouseover
end
-- print("Before: " .. #slots)--
getMouseoverSlots(itemInventory, slots, depth + 1)
-- if err then
-- MyModGlobal.debugPrint(string.format("Error getting mouseover slots: %s", err))
-- end
-- print("After: " .. #slots)
::mouseover::
if visualSlot:MouseOn() then
slots[#slots + 1] = {
inventory = inventory,
slotIndex = i,
slot = slot
}
end
::continue::
end
return slots, nil
end
---@return InventorySlot[], string?
local function getSlotsUnderCursor()
-- Make sure we have a controlled character
local controlledCharacter = Character.Controlled
if not controlledCharacter then return nil, "No controlled character" end
local inventory = controlledCharacter.Inventory
if not inventory then return nil, "No inventory" end
local mouseoverSlots, err = getMouseoverSlots(inventory)
if err then return mouseoverSlots, err end
-- Even if we don't get them we're still fine
local openContainers, _ = getOpenContainers()
-- if err then return mouseoverSlots, err end
for _, container in ipairs(openContainers) do
local containerInventories = container.OwnInventories
for containerInventory in containerInventories do
local slot
if not containerInventory or not containerInventory.visualSlots then
MyModGlobal.debugPrint("Container inventory has no visual slots")
goto continue
end
for i, visualSlot in ipairs(containerInventory.visualSlots) do
if visualSlot:MouseOn() then
slot = containerInventory.slots[i]
mouseoverSlots[#mouseoverSlots + 1] = {
inventory = containerInventory,
slotIndex = i,
slot = slot
}
end
end
::continue::
end
end
return mouseoverSlots, nil
end
---@return InventorySlot, string?
local function getFirstSlotUnderCursor()
local slots, err = getSlotsUnderCursor()
if err then return nil, err end
if #slots == 0 then return nil, "No slots found under cursor" end
for _, slot in ipairs(slots) do
if #slot.items > 0 then
return slot
end
end
return slots[1]
end
return {
enqueueItem = enqueueItem,
enqueueSlot = enqueueSlot,
enqueueInventory = enqueueInventory,
enqueueAllPlayerItems = allPlayerItems,
enqueueAllSubmarineItems = allSubmarineItems,
enqueueAllOwnedItems = allOwnedItems,
getOpenContainers = getOpenContainers,
getFirstOpenContainer = getFirstOpenContainer,
getSlotsUnderCursor = getSlotsUnderCursor,
getFirstSlotUnderCursor = getFirstSlotUnderCursor,
}