diff --git a/QuickStackToBag/Lua/Autorun/init.lua b/QuickStackToBag/Lua/Autorun/init.lua index 995ca53..2e5ce2c 100644 --- a/QuickStackToBag/Lua/Autorun/init.lua +++ b/QuickStackToBag/Lua/Autorun/init.lua @@ -8,236 +8,162 @@ LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.Items.Components.ItemCon LuaUserData.MakeFieldAccessible(Descriptors['Barotrauma.ItemInventory'], 'slots') LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "slots") --- Configuration -local CONFIG = { - TRIGGER_KEY = Keys.F, -- Key to press for quick stacking - BAG_TAGS = { "container", "cargocontainer" }, -- Tags that identify bags/containers - MAX_ITEMS_TO_PROCESS = 100, -- Safety limit to prevent infinite loops - PREFER_EXISTING_STACKS = true, -- Prefer stacking into existing item stacks - CHECK_ALL_CONTAINERS = true, -- Check all containers in inventory, not just bag slot - PRIMARY_BAG_SLOT = 8, -- Slot number for the primary bag (typically slot 8) - SKIP_HAND_SLOTS = true, -- Skip hand slots when processing items - HAND_SLOTS = { 6, 7 }, -- Slot numbers for hands (typically slots 6 and 7) - SKIP_DIVING_GEAR = true, -- Skip diving gear when processing items - DIVING_GEAR_TAGS = { "divingsuit", "divingmask", "oxygensource" } -- Tags for diving gear -} +-- Simple configuration +local HAND_SLOTS = { 6, 7 } +local PRIORITY_CONTAINERS = { "toolbelt", "backpack", "pouch" } -- Identifiers for priority containers --- Function to check if an item is a bag/container -local function isContainer(item) - if not item then return false 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 - for _, tag in ipairs(CONFIG.BAG_TAGS) do - if item.HasTag(tag) then return true end + print("[QuickStack] 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 + print("[QuickStack] Found matching item in container") + 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 + print("[QuickStack] No matching items in container, skipping") + 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 + print("[QuickStack] Stacking with existing item") + if containerInv.TryPutItem(item, slotIndex, true, false, nil) then + return true + end + end + end + end + + -- We don't try to place in empty slots anymore, only stack with existing items + print("[QuickStack] Failed to stack with existing items") return false end --- Function to check if an item is diving gear -local function isDivingGear(item) - if not CONFIG.SKIP_DIVING_GEAR or not item then return false end +-- Sort containers by priority +local function sortContainersByPriority(containers) + table.sort(containers, function(a, b) + local aPriority = -1 + local bPriority = -1 - for _, tag in ipairs(CONFIG.DIVING_GEAR_TAGS) do - if item.HasTag(tag) then return true end - end - return false + -- Check priority for first container + for i, identifier in ipairs(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(PRIORITY_CONTAINERS) do + if b.Prefab.Identifier.Value:find(identifier) then + bPriority = i + break + end + end + + return aPriority < bPriority + end) + + return containers end --- Function to find all containers in inventory -local function findContainersInInventory(character) - local containers = {} +-- Function to quickly stack items from inventory to containers +local function quickStackItems(character) + if not character then return end + print("[QuickStack] Function called") + local inventory = character.Inventory + if not inventory or not inventory.slots then return end - if not inventory or not inventory.slots then return containers end + -- First identify all containers in inventory + local containers = {} - -- First add the primary bag if available (priority container) - if #inventory.slots >= CONFIG.PRIMARY_BAG_SLOT then - for _, item in ipairs(inventory.slots[CONFIG.PRIMARY_BAG_SLOT].items) do - if isContainer(item) and item.OwnInventory then + for slotIndex, slot in ipairs(inventory.slots) do + for _, item in ipairs(slot.items) do + -- Add to containers if it has its own inventory + if item.OwnInventory then + print("[QuickStack] Found container: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")") table.insert(containers, item) end end end - -- Add all other containers if configured to do so - if CONFIG.CHECK_ALL_CONTAINERS then - for slotIndex, slot in ipairs(inventory.slots) do - -- Skip the primary bag slot as we already processed it - if slotIndex == CONFIG.PRIMARY_BAG_SLOT then goto continueSlot end - - -- Skip hand slots if configured to do so - if CONFIG.SKIP_HAND_SLOTS then - local isHandSlot = false - for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do - if slotIndex == handSlot then - isHandSlot = true - break - end - end - if isHandSlot then goto continueSlot end - end - - for _, item in ipairs(slot.items) do - if isContainer(item) and item.OwnInventory and not isDivingGear(item) then - table.insert(containers, item) - end - end - - ::continueSlot:: - end + print("[QuickStack] Found " .. #containers .. " containers") + if #containers == 0 then + print("[QuickStack] No containers with inventory found!") + return end - return containers -end + -- Sort containers by priority + containers = sortContainersByPriority(containers) --- Function to check if an item can be stacked with another -local function canStackWith(targetItem, sourceItem) - if not targetItem or not sourceItem then return false end + local itemsMoved = 0 - -- Check if items have same identifier - if not targetItem.Prefab.Identifier.Equals(sourceItem.Prefab.Identifier) then return false end - - -- Additional stacking checks could be added here - return true -end - --- Function to try stacking an item into a specific slot in container -local function tryStackItemInSlot(container, sourceItem, slotIndex) - if not container or not sourceItem then return false end - - local slot = container.slots[slotIndex + 1] -- +1 because Lua is 1-indexed but slots are 0-indexed - - -- Check if slot exists - if not slot then return false end - - -- Try to find an existing stack of the same type - for _, targetItem in ipairs(slot.items) do - if canStackWith(targetItem, sourceItem) then - return container.TryPutItem(sourceItem, slotIndex, true, false, nil) - end - end - - -- If no existing stack and slot has space, try to put item there - if container.CanBePutInSlot(sourceItem, slotIndex) then - return container.TryPutItem(sourceItem, slotIndex, false, false, nil) - end - - return false -end - --- Function to process a single inventory slot -local function processInventorySlot(playerInv, slotIndex, containers, processedCount, stackedCount) - -- Check if this slot should be skipped (hand slots) - if CONFIG.SKIP_HAND_SLOTS then - for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do + -- Process inventory slots + for slotIndex, slot in ipairs(inventory.slots) do + -- Skip hand slots + local isHandSlot = false + for _, handSlot in ipairs(HAND_SLOTS) do if slotIndex == handSlot then - return processedCount, stackedCount + isHandSlot = true + break end end - end + if isHandSlot then goto continueSlot end - local slot = playerInv.slots[slotIndex] - if not slot then return processedCount, stackedCount end + -- Process items in the slot + for i = #slot.items, 1, -1 do + local item = slot.items[i] - -- Process items in the slot - for i = #slot.items, 1, -1 do -- Iterate backwards to safely remove - local item = slot.items[i] - local success = false + -- Skip container items + if item.OwnInventory then goto nextItem end - -- Skip containers themselves - if isContainer(item) then goto nextItem end - - -- Skip diving gear if configured to do so - if isDivingGear(item) then goto nextItem end - - processedCount = processedCount + 1 - if processedCount > CONFIG.MAX_ITEMS_TO_PROCESS then - print("QuickStack: Safety limit reached") - return processedCount, stackedCount - end - - -- Try each container in order - for _, container in ipairs(containers) do - local containerInv = container.OwnInventory - if not containerInv then goto nextContainer end - - -- First try to stack with existing stacks if configured to do so - if CONFIG.PREFER_EXISTING_STACKS then - for bagSlotIndex = 0, #containerInv.slots - 1 do - for _, containerItem in ipairs(containerInv.slots[bagSlotIndex + 1].items) do - if canStackWith(containerItem, item) then - success = tryStackItemInSlot(containerInv, item, bagSlotIndex) - if success then break end - end - end - if success then break end + -- Try to move the item to each container + for _, container in ipairs(containers) do + if moveItemToContainer(item, container.OwnInventory) then + print("[QuickStack] Stacked " .. item.Name .. " into " .. container.Name) + itemsMoved = itemsMoved + 1 + break end end - -- If not stacked yet, try any valid slot - if not success then - for bagSlotIndex = 0, #containerInv.slots - 1 do - success = tryStackItemInSlot(containerInv, item, bagSlotIndex) - if success then break end - end - end - - if success then - stackedCount = stackedCount + 1 - break -- Stop trying containers if we succeeded - end - - ::nextContainer:: + ::nextItem:: end - ::nextItem:: + ::continueSlot:: end - return processedCount, stackedCount -end - --- Function to quick stack items into containers -local function quickStackToContainers(character, containers) - if not character or #containers == 0 then return 0 end - - local playerInv = character.Inventory - local processedCount = 0 - local stackedCount = 0 - - -- Process each inventory slot - for slotIndex = 1, #playerInv.slots do - processedCount, stackedCount = processInventorySlot( - playerInv, - slotIndex, - containers, - processedCount, - stackedCount - ) + if itemsMoved > 0 then + print("[QuickStack] Stacked " .. itemsMoved .. " items into containers") + else + print("[QuickStack] No matching items to stack") end - - return stackedCount end --- Hook into the player control to listen for key press +-- 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 + if not PlayerInput.KeyHit(Keys.F) then return end local character = instance if not character then return end - -- Find all containers in inventory - local containers = findContainersInInventory(character) - if #containers == 0 then - Game.ShowChatMessage("No containers found in inventory", ChatMessageType.Server) - return - end - - -- Perform quick stacking - local stackedCount = quickStackToContainers(character, containers) - - -- Notify player - if stackedCount and stackedCount > 0 then - Game.ShowChatMessage("Quick stacked " .. stackedCount .. " items to containers", ChatMessageType.Server) - else - Game.ShowChatMessage("No items could be stacked to containers", ChatMessageType.Server) - end + quickStackItems(character) end, Hook.HookMethodType.After)