-- 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 for i, visualSlot in ipairs(containerInventory.visualSlots) do if visualSlot:MouseOn() then local slot = containerInventory.slots[i] mouseoverSlots[#mouseoverSlots + 1] = { inventory = containerInventory, slotIndex = i, slot = slot } end end 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, }