if SERVER then return end -- Register necessary types and make fields accessible LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions") LuaUserData.RegisterType( 'System.Collections.Immutable.ImmutableArray`1[[Barotrauma.Items.Components.ItemContainer+SlotRestrictions, Barotrauma]]') LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.Items.Components.ItemContainer'], 'slotRestrictions') LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ItemInventory'], 'slots') LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "slots") -- 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) PRIORITY_CONTAINERS = { "toolbelt", "backpack", "pouch" } -- Priority order for containers } -- MOD INFO local MOD_NAME = "Quick Stack To Containers" local MOD_VERSION = "1.1.0" print(MOD_NAME .. " v" .. MOD_VERSION .. " loaded!") -- Debugging helper function local function debugPrint(message) if CONFIG.DEBUG_MODE then print("[" .. MOD_NAME .. "] " .. message) end end -- Show notification to player local function showNotification(message) -- Try different methods to show a message to the player debugPrint(message) -- Method 1: Print to console (always works) print("[" .. MOD_NAME .. "] " .. message) -- Method 2: Try to use GUIMessageBox if available pcall(function() if GUI and GUI.AddMessage then GUI.AddMessage(message, GUI.Font.Default) end end) end -- Helper function to move an item to a container, but only if matching items exist local function moveItemToContainer(item, containerInv) if not item or not containerInv then return false end debugPrint("Attempting to stack with existing items in container (" .. #containerInv.slots .. " slots)") -- Only try to find matching items to stack with local foundMatchingItem = false -- Check if the container has any matching items first for slotIndex = 0, #containerInv.slots - 1 do for _, containerItem in ipairs(containerInv.slots[slotIndex + 1].items) do if containerItem.Prefab.Identifier.Equals(item.Prefab.Identifier) then foundMatchingItem = true debugPrint("Found matching item in container: " .. containerItem.Name) break end end if foundMatchingItem then break end end -- If no matching items exist in the container, don't move the item if not foundMatchingItem then debugPrint("No matching items in container, skipping " .. item.Name) return false end -- Try to stack with existing items for slotIndex = 0, #containerInv.slots - 1 do for _, containerItem in ipairs(containerInv.slots[slotIndex + 1].items) do if containerItem.Prefab.Identifier.Equals(item.Prefab.Identifier) then -- Try to stack with existing item debugPrint("Trying to stack " .. item.Name .. " with existing item") if containerInv.TryPutItem(item, slotIndex, true, false, nil) then debugPrint("Successfully stacked " .. item.Name) return true else debugPrint("Failed to stack " .. item.Name .. " - TryPutItem failed") end end end end -- We don't try to place in empty slots anymore, only stack with existing items debugPrint("Failed to stack with existing items") return false end -- Sort containers by priority local function sortContainersByPriority(containers) table.sort(containers, function(a, b) local aPriority = -1 local bPriority = -1 -- Check priority for first container for i, identifier in ipairs(CONFIG.PRIORITY_CONTAINERS) do if a.Prefab.Identifier.Value:find(identifier) then aPriority = i break end end -- Check priority for second container for i, identifier in ipairs(CONFIG.PRIORITY_CONTAINERS) do if b.Prefab.Identifier.Value:find(identifier) then bPriority = i break end end return aPriority < bPriority end) return containers end -- Recursively find all containers in the player's inventory, including nested ones local function findAllContainers(inventory, containers) if not inventory or not inventory.slots then return containers end containers = containers or {} for _, slot in ipairs(inventory.slots) do for _, item in ipairs(slot.items) do if item.OwnInventory then -- Add this container debugPrint("Found container: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")") table.insert(containers, item) -- Recursively search inside this container if enabled if CONFIG.NESTED_CONTAINERS then debugPrint("Searching inside " .. item.Name .. " for nested containers") findAllContainers(item.OwnInventory, containers) end end end end return containers end -- Find the currently open container local function getOpenContainer() debugPrint("Attempting to find open container...") -- Method 1: Check if Inventory.OpenState is accessible and has an open container pcall(function() if Character.Controlled and Character.Controlled.SelectedCharacter then local selectedChar = Character.Controlled.SelectedCharacter if selectedChar.Inventory then debugPrint("Checking selected character's inventory") -- This is likely the container the player is interacting with return selectedChar end end end) -- Method 2: Inspect GUI components to find inventory GUI components pcall(function() if GUI and GUI.Components then for _, component in pairs(GUI.Components) do -- Look for UI components related to inventory if component.RectTransform and component.RectTransform.GUIComponent and component.RectTransform.GUIComponent.UserData and component.RectTransform.GUIComponent.UserData.Item then local item = component.RectTransform.GUIComponent.UserData.Item if item.OwnInventory then debugPrint("Found open container via GUI component: " .. item.Name) return item end end end end end) -- Method 3: Check if currently selected item is interacting with a container pcall(function() if Character.Controlled and Character.Controlled.SelectedItem then local selectedItem = Character.Controlled.SelectedItem -- Check if selected item is interacting with something if selectedItem.InteractingWith and selectedItem.InteractingWith.OwnInventory then debugPrint("Found open container via current interaction: " .. selectedItem.InteractingWith.Name) return selectedItem.InteractingWith end end end) -- Method 4: Try to use Inventory.DraggingInventory (if it exists) pcall(function() if Inventory and Inventory.DraggingInventory and Inventory.DraggingInventory.Owner and Inventory.DraggingInventory.Owner ~= Character.Controlled then debugPrint("Found open container via DraggingInventory: " .. Inventory.DraggingInventory.Owner.Name) return Inventory.DraggingInventory.Owner end end) -- Method 5: Check all items in the game with open inventories for item in Item.ItemList do if item and item.OwnInventory then local isOpened = false -- Try different ways to detect if an inventory is opened -- Method 5.1: Check IsOpenedByPlayer if available pcall(function() if item.OwnInventory.IsOpenedByPlayer then isOpened = item.OwnInventory.IsOpenedByPlayer end end) -- Method 5.2: Check if inventory has visualSlots created pcall(function() if item.OwnInventory.visualSlots and #item.OwnInventory.visualSlots > 0 then isOpened = true end end) -- Method 5.3: Check if OpenState > 0 pcall(function() if item.OwnInventory.OpenState and item.OwnInventory.OpenState > 0 then isOpened = true end end) if isOpened then debugPrint("Found open container: " .. item.Name) return item end end end debugPrint("No open container found") return nil end -- Function to stack from open container to player containers local function stackFromOpenContainer(character, playerContainers, openContainer) if not character or not openContainer or not openContainer.OwnInventory then return 0 end debugPrint("Stacking from open container: " .. openContainer.Name) local itemsMoved = 0 local openContainerInv = openContainer.OwnInventory -- Process each slot in the open container for slotIndex = 0, #openContainerInv.slots - 1 do local slot = openContainerInv.slots[slotIndex + 1] -- Process items in the slot for i = #slot.items, 1, -1 do local item = slot.items[i] -- Skip container items if item.OwnInventory then debugPrint("Skipping container item: " .. item.Name) goto nextItem end debugPrint("Processing item from container: " .. item.Name) -- Try to move the item to each player container for _, container in ipairs(playerContainers) do debugPrint("Trying to stack " .. item.Name .. " into " .. container.Name) if moveItemToContainer(item, container.OwnInventory) then debugPrint("Stacked " .. item.Name .. " from open container into " .. container.Name) itemsMoved = itemsMoved + 1 break end end ::nextItem:: end end return itemsMoved end -- Function to quickly stack items from inventory to containers local function quickStackItems(character) if not character then debugPrint("No character found") return end debugPrint("Quick stack function called") local inventory = character.Inventory if not inventory or not inventory.slots then debugPrint("Character has no inventory") return end -- Find all containers in player inventory, including nested ones local containers = findAllContainers(inventory, {}) debugPrint("Found " .. #containers .. " containers" .. (CONFIG.NESTED_CONTAINERS and " (including nested)" or "")) if #containers == 0 then debugPrint("No containers with inventory found!") 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 local itemsMoved = 0 -- Process inventory slots 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] -- Skip container items if item.OwnInventory then debugPrint("Skipping container item: " .. item.Name) goto nextItem end 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 ::nextItem:: end ::continueSlot:: 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") pcall(function() Game.PlaySound("hit") end) showNotification("Stacked " .. itemsMoved .. " items") else debugPrint("No matching items to stack") showNotification("No matching items to stack") end end -- Show a small message to the user when the mod loads Hook.Add("think", "quickStackInitMessage", function() -- Use a safer method to show a message showNotification(MOD_NAME .. " loaded! Press F to stack items.") Hook.Remove("think", "quickStackInitMessage") end) -- Hook into player control to listen for key press Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) if not PlayerInput.KeyHit(CONFIG.TRIGGER_KEY) then return end local character = instance if not character then return end quickStackItems(character) end, Hook.HookMethodType.After)