diff --git a/QuickStackToBag/Lua/Autorun/init.lua b/QuickStackToBag/Lua/Autorun/init.lua index c7f3ca8..803f20e 100644 --- a/QuickStackToBag/Lua/Autorun/init.lua +++ b/QuickStackToBag/Lua/Autorun/init.lua @@ -10,16 +10,16 @@ LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "s -- Simple configuration local CONFIG = { - TRIGGER_KEY = Keys.F, -- Key to press for quick stacking - NESTED_CONTAINERS = true, -- Whether to include nested containers - DEBUG_MODE = true, -- Print debug messages - HAND_SLOTS = { 6, 7 }, -- Slot numbers for hands (typically slots 6 and 7) + TRIGGER_KEY = Keys.F, -- Key to press for quick stacking + NESTED_CONTAINERS = true, -- Whether to include nested containers + DEBUG_MODE = true, -- Print debug messages + HAND_SLOTS = { 6, 7 }, -- Slot numbers for hands (typically slots 6 and 7) PRIORITY_CONTAINERS = { "toolbelt", "backpack", "pouch" }, -- Priority order for containers - EXCLUDE_TOOLS = true, -- Exclude tools from container list + EXCLUDE_TOOLS = true, -- Exclude tools from container list TOOL_IDENTIFIERS = { "welding", "gun", "weapon", "revolver", "smg", "rifle", "shotgun", - "diving", "oxygen", "scanner", "card", "id", "fuel", "rod", "battery", - "fabricator", "deconstructor" }, -- Common tool identifiers to exclude - ALWAYS_INCLUDE = { "toolbelt", "backpack", "pouch" } -- Always include these items even if they match tool criteria + "diving", "oxygen", "scanner", "card", "id", "fuel", "rod", "battery", + "fabricator", "deconstructor" }, -- Common tool identifiers to exclude + ALWAYS_INCLUDE = { "toolbelt", "backpack", "pouch" } -- Always include these items even if they match tool criteria } -- MOD INFO @@ -35,6 +35,26 @@ local function debugPrint(message) end end +---@param table table +---@param depth number? +function DumpTable(table, depth) + if depth == nil then + depth = 0 + end + if (depth > 200) then + print("Error: Depth > 200 in dumpTable()") + return + end + for k, v in pairs(table) do + if (type(v) == "table") then + print(string.rep(" ", depth) .. k .. ":") + DumpTable(v, depth + 1) + else + print(string.rep(" ", depth) .. k .. ": ", v) + end + end +end + -- Show notification to player -- Helper function to check if an item is a tool rather than a container local function isTool(item) @@ -120,7 +140,7 @@ local function moveItemToContainer(item, containerInv) -- Get max stack size for this item (default to 1 if we can't determine) local maxStackSize = 60 pcall(function() - print(item.Prefab.MaxStackSize) + print(item.Prefab.MaxStackSize) if item.Prefab and item.Prefab.MaxStackSize then maxStackSize = item.Prefab.MaxStackSize end @@ -178,7 +198,8 @@ local function moveItemToContainer(item, containerInv) for _, matchingSlot in ipairs(matchingSlots) do -- Only try to put in slots that have space if matchingSlot.remainingSpace > 0 then - debugPrint("Trying to stack " .. item.Name .. " with existing items (slot has " .. matchingSlot.remainingSpace .. " space)") + debugPrint("Trying to stack " .. + item.Name .. " with existing items (slot has " .. matchingSlot.remainingSpace .. " space)") -- Try to move the full item if containerInv.TryPutItem(item, matchingSlot.slotIndex, true, false, nil) then @@ -313,6 +334,63 @@ local function findAllContainers(inventory, containers) return containers end +---@class ItemLocation +---@field inventory Barotrauma.ItemInventory +---@field slotIndex number +---@field maxFits number + +-- The resulting item tree is a table where the key is an ID of an item +-- And the value is an object that represents where that item is located +-- In our inventory +-- Special case are empty slots where any item fits +---@param inventory Barotrauma.ItemInventory +---@param itemTree table +---@return table +local function buildItemTree(inventory, itemTree, iter) + iter = iter or 0 + itemTree = itemTree or {} + if not inventory or not inventory.slots then return itemTree end + + -- One slot can have one item but multiple of it + -- The number of an item in a slot is #slot.items + for slotIndex, slot in ipairs(inventory.slots) do + -- This iteration shit is done only to skip the player inventory as a potential destination + -- Since it will always be 0th iteration and there's no point stacking + -- Items from the player inventory to itself + if iter > 0 then + if #slot.items == 0 then + itemTree['empty'] = itemTree['empty'] or {} + itemTree['empty'][#itemTree['empty'] + 1] = { + inventory = inventory, + slotIndex = slotIndex, + maxFits = 60 + } + else + ---@type Barotrauma.Item + local item = slot.items[1] + local maxStackSize = item.Prefab.MaxStackSize or 60 + local identifier = item.Prefab.Identifier.Value + itemTree[identifier] = itemTree[identifier] or {} + itemTree[identifier][#itemTree[identifier] + 1] = { + inventory = inventory, + slotIndex = slotIndex, + maxFits = maxStackSize - #slot.items + } + end + end + + if #slot.items > 1 then + local item = slot.items[1] + if item.OwnInventory then + print("Searching inside " .. item.Name .. " for nested containers") + buildItemTree(item.OwnInventory, itemTree, iter + 1) + end + end + end + + return itemTree +end + -- Find the currently open container - improved version local function getOpenContainer() debugPrint("Attempting to find open container...") @@ -354,7 +432,7 @@ local function getOpenContainer() local selectedItem = Character.Controlled.SelectedItem -- Check if selected item is interacting with something if selectedItem.InteractingWith and selectedItem.InteractingWith.OwnInventory and - selectedItem.InteractingWith ~= Character.Controlled then + selectedItem.InteractingWith ~= Character.Controlled then openContainer = selectedItem.InteractingWith hasVisibleInventory = true debugPrint("Found open container via current interaction: " .. openContainer.Name) @@ -372,7 +450,7 @@ local function getOpenContainer() pcall(function() -- Common method to check if inventory is visible - look for visual components if openContainer.OwnInventory and openContainer.OwnInventory.visualSlots and - #openContainer.OwnInventory.visualSlots > 0 then + #openContainer.OwnInventory.visualSlots > 0 then isVisible = true end end) @@ -468,146 +546,149 @@ local function quickStackItems(character) return end + local itemTree = buildItemTree(inventory, {}) + DumpTable(itemTree) + -- Find all containers in player inventory, including nested ones - local containers = findAllContainers(inventory, {}) + -- local containers = findAllContainers(inventory, {}) - -- Fallback: If no containers found, try a direct search for known container types - if #containers == 0 then - debugPrint("No containers found with standard method, trying fallback method...") + -- -- Fallback: If no containers found, try a direct search for known container types + -- if #containers == 0 then + -- debugPrint("No containers found with standard method, trying fallback method...") - -- Direct slot search for common container types - for _, slot in ipairs(inventory.slots) do - for _, item in ipairs(slot.items) do - if item.OwnInventory then - local identifier = item.Prefab.Identifier.Value:lower() - -- Force include specific items that we know should be containers - if identifier:find("toolbelt") or identifier:find("backpack") or - identifier:find("pouch") or identifier:find("container") or - identifier:find("box") or identifier:find("crate") or - item.Name:find("Container") or item.Name:find("Box") then + -- -- Direct slot search for common container types + -- for _, slot in ipairs(inventory.slots) do + -- for _, item in ipairs(slot.items) do + -- if item.OwnInventory then + -- local identifier = item.Prefab.Identifier.Value:lower() + -- -- Force include specific items that we know should be containers + -- if identifier:find("toolbelt") or identifier:find("backpack") or + -- identifier:find("pouch") or identifier:find("container") or + -- identifier:find("box") or identifier:find("crate") or + -- item.Name:find("Container") or item.Name:find("Box") then - debugPrint("Fallback: Force including known container: " .. item.Name) - table.insert(containers, item) + -- debugPrint("Fallback: Force including known container: " .. item.Name) + -- table.insert(containers, item) - -- Also check container slots - if CONFIG.NESTED_CONTAINERS and #item.OwnInventory.slots > 2 then - for _, containerSlot in ipairs(item.OwnInventory.slots) do - for _, containerItem in ipairs(containerSlot.items) do - if containerItem.OwnInventory and - (containerItem.Prefab.Identifier.Value:lower():find("container") or - containerItem.Name:find("Container")) then + -- -- Also check container slots + -- if CONFIG.NESTED_CONTAINERS and #item.OwnInventory.slots > 2 then + -- for _, containerSlot in ipairs(item.OwnInventory.slots) do + -- for _, containerItem in ipairs(containerSlot.items) do + -- if containerItem.OwnInventory and + -- (containerItem.Prefab.Identifier.Value:lower():find("container") or + -- containerItem.Name:find("Container")) then - debugPrint("Fallback: Found nested container: " .. containerItem.Name) - table.insert(containers, containerItem) - end - end - end - end - end - end - end - end - end + -- debugPrint("Fallback: Found nested container: " .. containerItem.Name) + -- table.insert(containers, containerItem) + -- end + -- end + -- end + -- end + -- end + -- end + -- end + -- end + -- end - debugPrint("Found " .. #containers .. " containers" .. (CONFIG.NESTED_CONTAINERS and " (including nested)" or "")) - if #containers == 0 then - debugPrint("No containers with inventory found!") - showNotification("No containers found! Make sure you have a backpack, toolbelt, or storage box.") - return - end + -- debugPrint("Found " .. #containers .. " containers" .. (CONFIG.NESTED_CONTAINERS and " (including nested)" or "")) + -- if #containers == 0 then + -- debugPrint("No containers with inventory found!") + -- showNotification("No containers found! Make sure you have a backpack, toolbelt, or storage box.") + -- return + -- end - -- Sort containers by priority - containers = sortContainersByPriority(containers) - for i, container in ipairs(containers) do - debugPrint(i .. ": " .. container.Name .. " - priority container: " .. - tostring(container.Prefab.Identifier.Value:find(CONFIG.PRIORITY_CONTAINERS[1]) ~= nil)) - end + -- -- Sort containers by priority + -- containers = sortContainersByPriority(containers) + -- for i, container in ipairs(containers) do + -- debugPrint(i .. ": " .. container.Name .. " - priority container: " .. + -- tostring(container.Prefab.Identifier.Value:find(CONFIG.PRIORITY_CONTAINERS[1]) ~= nil)) + -- end - local itemsMoved = 0 + -- local itemsMoved = 0 - -- Create a cache of processable items to avoid redundant checks - local itemsToProcess = {} - local processedIdentifiers = {} + -- -- Create a cache of processable items to avoid redundant checks + -- local itemsToProcess = {} + -- local processedIdentifiers = {} - -- First collect all items we might want to stack - for slotIndex, slot in ipairs(inventory.slots) do - -- Skip hand slots - local isHandSlot = false - for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do - if slotIndex == handSlot then - isHandSlot = true - break - end - end - if isHandSlot then - debugPrint("Skipping hand slot: " .. slotIndex) - goto continueSlot - end + -- -- First collect all items we might want to stack + -- for slotIndex, slot in ipairs(inventory.slots) do + -- -- Skip hand slots + -- local isHandSlot = false + -- for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do + -- if slotIndex == handSlot then + -- isHandSlot = true + -- break + -- end + -- end + -- if isHandSlot then + -- debugPrint("Skipping hand slot: " .. slotIndex) + -- goto continueSlot + -- end - -- Process items in the slot - for i = #slot.items, 1, -1 do - local item = slot.items[i] - local identifierValue = item.Prefab.Identifier.Value + -- -- Process items in the slot + -- for i = #slot.items, 1, -1 do + -- local item = slot.items[i] + -- local identifierValue = item.Prefab.Identifier.Value - -- Skip container items - if item.OwnInventory then - debugPrint("Skipping container item: " .. item.Name) - goto nextItem - end + -- -- Skip container items + -- if item.OwnInventory then + -- debugPrint("Skipping container item: " .. item.Name) + -- goto nextItem + -- end - -- Skip if we've already processed an item of this type - if processedIdentifiers[identifierValue] then - debugPrint("Already processed items of type: " .. identifierValue) - goto nextItem - end + -- -- Skip if we've already processed an item of this type + -- if processedIdentifiers[identifierValue] then + -- debugPrint("Already processed items of type: " .. identifierValue) + -- goto nextItem + -- end - debugPrint("Adding to process list: " .. item.Name) - table.insert(itemsToProcess, { - item = item, - slotIndex = slotIndex - }) + -- debugPrint("Adding to process list: " .. item.Name) + -- table.insert(itemsToProcess, { + -- item = item, + -- slotIndex = slotIndex + -- }) - -- Mark identifier as scheduled for processing - processedIdentifiers[identifierValue] = true + -- -- Mark identifier as scheduled for processing + -- processedIdentifiers[identifierValue] = true - ::nextItem:: - end + -- ::nextItem:: + -- end - ::continueSlot:: - end + -- ::continueSlot:: + -- end - -- Now process the collected items - this greatly reduces redundant container checks - for _, itemData in ipairs(itemsToProcess) do - local item = itemData.item + -- -- Now process the collected items - this greatly reduces redundant container checks + -- for _, itemData in ipairs(itemsToProcess) do + -- local item = itemData.item - debugPrint("Processing inventory item: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")") + -- debugPrint("Processing inventory item: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")") - -- Try to move the item to each container - for _, container in ipairs(containers) do - debugPrint("Trying container: " .. container.Name) - if moveItemToContainer(item, container.OwnInventory) then - debugPrint("Stacked " .. item.Name .. " into " .. container.Name) - itemsMoved = itemsMoved + 1 - break - end - end - end + -- -- Try to move the item to each container + -- for _, container in ipairs(containers) do + -- debugPrint("Trying container: " .. container.Name) + -- if moveItemToContainer(item, container.OwnInventory) then + -- debugPrint("Stacked " .. item.Name .. " into " .. container.Name) + -- itemsMoved = itemsMoved + 1 + -- break + -- end + -- end + -- end - -- Check if there's an open container to stack from - local openContainer = getOpenContainer() - if openContainer then - debugPrint("Found open container to stack from: " .. openContainer.Name) - local openContainerItemsMoved = stackFromOpenContainer(character, containers, openContainer) - itemsMoved = itemsMoved + openContainerItemsMoved - else - debugPrint("No open container found to stack from") - end + -- -- Check if there's an open container to stack from + -- local openContainer = getOpenContainer() + -- if openContainer then + -- debugPrint("Found open container to stack from: " .. openContainer.Name) + -- local openContainerItemsMoved = stackFromOpenContainer(character, containers, openContainer) + -- itemsMoved = itemsMoved + openContainerItemsMoved + -- else + -- debugPrint("No open container found to stack from") + -- end - if itemsMoved > 0 then - debugPrint("Stacked " .. itemsMoved .. " items into containers") - else - debugPrint("No matching items to stack") - end + -- if itemsMoved > 0 then + -- debugPrint("Stacked " .. itemsMoved .. " items into containers") + -- else + -- debugPrint("No matching items to stack") + -- end end -- Hook into player control to listen for key press