-- 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 ---@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 -- 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 enqueueOpenContainers 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 ---@param loadRefs? boolean ---@return Barotrauma.Item[], string? enqueueOpenContainers = function(queue, predicate, loadRefs) queue = queue or {} predicate = predicate or function() return true end local containers, err = getOpenContainers() if err then return queue, err end for _, container in ipairs(containers) do local inventories = container.OwnInventories if not inventories then goto continue end for containerInventory in inventories do queue, err = enqueueInventory(containerInventory, queue, predicate, loadRefs) if err then return queue, err end 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 -- 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.slot.items and #slot.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, enqueueOpenContainers = enqueueOpenContainers, getOpenContainers = getOpenContainers, getFirstOpenContainer = getFirstOpenContainer, getSlotsUnderCursor = getSlotsUnderCursor, getFirstSlotUnderCursor = getFirstSlotUnderCursor, }