From bc3ea216c77e9d76f0d8226f026f4fe19423c67e Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sat, 29 Mar 2025 21:01:01 +0100 Subject: [PATCH] Remove some bullshit from quickstack --- QuickStackToBag/Lua/Autorun/init.lua | 484 +++++++++++++++++++-------- 1 file changed, 353 insertions(+), 131 deletions(-) diff --git a/QuickStackToBag/Lua/Autorun/init.lua b/QuickStackToBag/Lua/Autorun/init.lua index 9588948..c7f3ca8 100644 --- a/QuickStackToBag/Lua/Autorun/init.lua +++ b/QuickStackToBag/Lua/Autorun/init.lua @@ -14,7 +14,12 @@ local CONFIG = { 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 + PRIORITY_CONTAINERS = { "toolbelt", "backpack", "pouch" }, -- Priority order for containers + 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 } -- MOD INFO @@ -31,19 +36,79 @@ local function debugPrint(message) end -- Show notification to player -local function showNotification(message) - -- Try different methods to show a message to the player - debugPrint(message) +-- Helper function to check if an item is a tool rather than a container +local function isTool(item) + if not item or not item.Prefab or not item.Prefab.Identifier then return false end - -- Method 1: Print to console (always works) - print("[" .. MOD_NAME .. "] " .. message) + -- Never exclude priority containers + local identifier = item.Prefab.Identifier.Value:lower() + for _, alwaysInclude in ipairs(CONFIG.ALWAYS_INCLUDE) do + if identifier:find(alwaysInclude) then + debugPrint("Always including high priority container: " .. item.Name) + return false + end + end - -- Method 2: Try to use GUIMessageBox if available + -- Check tags first - most reliable method + local hasTags = false pcall(function() - if GUI and GUI.AddMessage then - GUI.AddMessage(message, GUI.Font.Default) + if item.Tags and item.Tags ~= "" then + hasTags = true + local tags = item.Tags:lower() + + -- If item has explicit container tag, it's a container + if tags:find("container") then + debugPrint("Item has container tag: " .. item.Name) + return false + end + + -- If item has tool or weapon tag, it's a tool + if tags:find("tool") or tags:find("weapon") then + debugPrint("Item has tool/weapon tag: " .. item.Name) + return true + end end end) + + -- If we found tags and made a decision based on them, return the result + if hasTags then + -- If we got here, the tags didn't conclusively identify this as a tool + -- For items with tags but no tool/weapon tag, check the number of slots + if item.OwnInventory and item.OwnInventory.slots and #item.OwnInventory.slots > 1 then + -- Items with multiple slots are likely containers + return false + end + end + + -- Fall back to identifier checks + for _, toolId in ipairs(CONFIG.TOOL_IDENTIFIERS) do + if identifier:find(toolId) then + -- Skip known exceptions + if toolId == "tool" and identifier:find("toolbox") or identifier:find("toolbelt") then + return false + end + return true + end + end + + -- Final check based on slots - tools typically have 1 slot, containers have multiple + if item.OwnInventory and item.OwnInventory.slots then + -- Consider items with more than 2 slots as containers + if #item.OwnInventory.slots > 2 then + return false + elseif #item.OwnInventory.slots == 1 then + -- Most single-slot inventories are tools, but check for exceptions + -- Some special container items might have 1 slot but still be containers + if identifier:find("container") or identifier:find("box") or identifier:find("crate") then + return false + end + -- Otherwise likely a tool + return true + end + end + + -- When in doubt, don't classify as a tool + return false end -- Helper function to move an item to a container, but only if matching items exist @@ -52,19 +117,50 @@ local function moveItemToContainer(item, containerInv) debugPrint("Attempting to stack with existing items in container (" .. #containerInv.slots .. " slots)") - -- Only try to find matching items to stack with - local foundMatchingItem = false + -- Get max stack size for this item (default to 1 if we can't determine) + local maxStackSize = 60 + pcall(function() + print(item.Prefab.MaxStackSize) + if item.Prefab and item.Prefab.MaxStackSize then + maxStackSize = item.Prefab.MaxStackSize + end + end) + + -- Get current condition/quality of item (for proper stacking) + local itemCondition = 100 + pcall(function() + if item.Condition then + itemCondition = item.Condition + end + end) -- Check if the container has any matching items first + local foundMatchingItem = false + local matchingSlots = {} + + -- First pass: find all matching slots 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 + -- Check similar condition/quality (within 10%) + local containerItemCondition = 100 + pcall(function() + if containerItem.Condition then + containerItemCondition = containerItem.Condition + end + end) + + -- Only consider similar quality items + if math.abs(containerItemCondition - itemCondition) <= 10 then + foundMatchingItem = true + table.insert(matchingSlots, { + slotIndex = slotIndex, + currentSize = #containerInv.slots[slotIndex + 1].items, + remainingSpace = maxStackSize - #containerInv.slots[slotIndex + 1].items + }) + end end end - if foundMatchingItem then break end end -- If no matching items exist in the container, don't move the item @@ -73,24 +169,89 @@ local function moveItemToContainer(item, containerInv) 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 + -- Sort slots by most available space first + table.sort(matchingSlots, function(a, b) + return a.remainingSpace > b.remainingSpace + end) + + -- Try to stack with existing items that have space + 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)") + + -- Try to move the full item + if containerInv.TryPutItem(item, matchingSlot.slotIndex, true, false, nil) then + debugPrint("Successfully stacked " .. item.Name) + return true + else + debugPrint("Failed full stack, likely due to size limits") + + -- If full stack failed, try to split and move part of it (if possible) + pcall(function() + -- Check if we can split stacks and if the item is a stack + if item.SplitStack and maxStackSize > 1 then + local currentStackSize = 1 + -- Try to determine current stack size + pcall(function() + if item.Count then currentStackSize = item.Count end + end) + + -- If current stack is larger than 1, try to split it + if currentStackSize > 1 then + -- Calculate how much we can move + local amountToMove = math.min(matchingSlot.remainingSpace, currentStackSize - 1) + + if amountToMove > 0 then + debugPrint("Attempting to split stack and move " .. amountToMove .. " items") + + -- Try to split the stack + local splitItem = item:SplitStack(amountToMove) + if splitItem then + -- Try to put the split item into the slot + if containerInv.TryPutItem(splitItem, matchingSlot.slotIndex, true, false, nil) then + debugPrint("Successfully moved partial stack of " .. splitItem.Name) + return true + else + debugPrint("Failed to move split stack") + -- If it fails, try to recombine the original stack + pcall(function() + item:AddItem(splitItem) + end) + end + else + debugPrint("Failed to split stack") + end + end + end + end + 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") + -- If we couldn't stack with existing partial stacks, look for empty slots + -- But only if we're still stacking by finding matching items + debugPrint("Failed to stack with existing items, checking for empty slots") + + -- Find empty slots + local emptySlots = {} + for slotIndex = 0, #containerInv.slots - 1 do + if #containerInv.slots[slotIndex + 1].items == 0 then + table.insert(emptySlots, slotIndex) + end + end + + -- If there are empty slots, try to put the item there + for _, slotIndex in ipairs(emptySlots) do + debugPrint("Trying to put " .. item.Name .. " in empty slot " .. slotIndex) + if containerInv.TryPutItem(item, slotIndex, false, false, nil) then + debugPrint("Successfully put " .. item.Name .. " in empty slot") + return true + end + end + + debugPrint("Failed to stack with existing items or find empty slots") return false end @@ -131,14 +292,19 @@ local function findAllContainers(inventory, containers) 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) + -- Skip tools if configured to do so + if CONFIG.EXCLUDE_TOOLS and isTool(item) then + debugPrint("Skipping tool: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")") + else + -- 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) + -- 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 @@ -147,93 +313,73 @@ local function findAllContainers(inventory, containers) return containers end --- Find the currently open container +-- Find the currently open container - improved version local function getOpenContainer() debugPrint("Attempting to find open container...") - -- Method 1: Check if Inventory.OpenState is accessible and has an open container + local openContainer = nil + local hasVisibleInventory = false + + -- Method 1: Try to use Inventory.CurrentInventory or similar properties 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 + if Inventory and Inventory.CurrentContainer and Inventory.CurrentContainer.Owner then + openContainer = Inventory.CurrentContainer.Owner + hasVisibleInventory = true + debugPrint("Found open container via CurrentContainer: " .. openContainer.Name) end end) - -- Method 2: Inspect GUI components to find inventory GUI components + if hasVisibleInventory and openContainer then return openContainer end + + -- Method 2: Check if Inventory.OpenInventories exists and has entries 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 + if Inventory and Inventory.OpenInventories and #Inventory.OpenInventories > 0 then + -- Get the last opened inventory + for _, inv in ipairs(Inventory.OpenInventories) do + if inv and inv.Owner and inv.Owner ~= Character.Controlled then + openContainer = inv.Owner + hasVisibleInventory = true + debugPrint("Found open container via OpenInventories: " .. openContainer.Name) + break end end end end) + if hasVisibleInventory and openContainer then return openContainer 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 + if selectedItem.InteractingWith and selectedItem.InteractingWith.OwnInventory and + selectedItem.InteractingWith ~= Character.Controlled then + openContainer = selectedItem.InteractingWith + hasVisibleInventory = true + debugPrint("Found open container via current interaction: " .. openContainer.Name) 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) + if hasVisibleInventory and openContainer then return openContainer 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 + -- For safety, add a visual check - only return a container if it has a visible UI element + if openContainer then + -- Verify this container actually has a visible UI + local isVisible = false - -- 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 + 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 + isVisible = true end + end) + + if not isVisible then + debugPrint("Container found but not visibly open in UI: " .. openContainer.Name) + return nil end end @@ -250,6 +396,10 @@ local function stackFromOpenContainer(character, playerContainers, openContainer local itemsMoved = 0 local openContainerInv = openContainer.OwnInventory + -- Create a cache of processable items to avoid redundant checks + local itemsToProcess = {} + local processedIdentifiers = {} + -- Process each slot in the open container for slotIndex = 0, #openContainerInv.slots - 1 do local slot = openContainerInv.slots[slotIndex + 1] @@ -257,6 +407,7 @@ local function stackFromOpenContainer(character, playerContainers, openContainer -- 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 @@ -264,22 +415,41 @@ local function stackFromOpenContainer(character, playerContainers, openContainer 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 + -- Skip if we've already processed an item of this type + if processedIdentifiers[identifierValue] then + debugPrint("Already processed items of type: " .. identifierValue .. " from open container") + goto nextItem end + table.insert(itemsToProcess, { + item = item, + slotIndex = slotIndex + }) + + -- Mark identifier as scheduled for processing + processedIdentifiers[identifierValue] = true + ::nextItem:: end end + -- Now process the collected items - this greatly reduces redundant container checks + for _, itemData in ipairs(itemsToProcess) do + local item = itemData.item + + 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 + end + return itemsMoved end @@ -301,9 +471,48 @@ local function quickStackItems(character) -- Find all containers in player inventory, including nested ones 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...") + + -- 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) + + -- 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("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 @@ -316,7 +525,11 @@ local function quickStackItems(character) local itemsMoved = 0 - -- Process inventory slots + -- 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 @@ -334,6 +547,7 @@ local function quickStackItems(character) -- 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 @@ -341,24 +555,44 @@ local function quickStackItems(character) 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 + -- 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 + }) + + -- Mark identifier as scheduled for processing + processedIdentifiers[identifierValue] = true + ::nextItem:: end ::continueSlot:: end + -- 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 .. ")") + + -- 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 @@ -371,23 +605,11 @@ local function quickStackItems(character) 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