From a56a1897bcedb7e0915cc2e515177460b9e45fd0 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Sun, 30 Mar 2025 23:26:48 +0200 Subject: [PATCH] Implement quickstack to existing stacks in inventory --- QuickStackToBag/Lua/Autorun/init.lua | 10 +++ QuickStackToBag/Lua/Cyka/quickstack.lua | 109 ++++++++++++++++------- QuickStackToBag/Lua/Cyka/quickunload.lua | 3 +- QuickStackToBag/Lua/Cyka/utils.lua | 7 +- 4 files changed, 93 insertions(+), 36 deletions(-) diff --git a/QuickStackToBag/Lua/Autorun/init.lua b/QuickStackToBag/Lua/Autorun/init.lua index 46c1c74..e8efb17 100644 --- a/QuickStackToBag/Lua/Autorun/init.lua +++ b/QuickStackToBag/Lua/Autorun/init.lua @@ -18,6 +18,7 @@ MyModGlobal = { FIX = Keys.R, UNLOAD = Keys.E, RELOAD = Keys.R, + STACK_TO_CURSOR = Keys.G, NESTED_CONTAINERS = true, DEBUG_MODE = true, }, @@ -86,6 +87,15 @@ Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptab quickstack.quickStackItems(instance) end, Hook.HookMethodType.After) +Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) + if not PlayerInput.KeyHit(MyModGlobal.CONFIG.STACK_TO_CURSOR) then return end + if not PlayerInput.IsShiftDown() then + quickstack.stackToCursor() + else + quickstack.stackAllToCursor() + end +end, Hook.HookMethodType.After) + Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) if not PlayerInput.KeyHit(MyModGlobal.CONFIG.FABRICATOR_KEY) then return end fabricatorstack.tryStackFabricator(instance) diff --git a/QuickStackToBag/Lua/Cyka/quickstack.lua b/QuickStackToBag/Lua/Cyka/quickstack.lua index a552bc2..63dbe0e 100644 --- a/QuickStackToBag/Lua/Cyka/quickstack.lua +++ b/QuickStackToBag/Lua/Cyka/quickstack.lua @@ -1,4 +1,5 @@ -- luacheck: globals MyModGlobal Character +-- luacheck: max line length 420 local utils = require("Cyka.utils") ---@class ItemLocation @@ -188,37 +189,6 @@ local function tryMoveItems(items, itemTree, force) return errs end - ----@return Barotrauma.Item[] -local function getOpenContainers() - -- MyModGlobal.debugPrint("Attempting to find open container...") - -- local containers = {} - -- for item in Item.ItemList do - -- ---@cast item Barotrauma.Item - -- local isok = true - -- isok = isok and item ~= nil - -- isok = isok and item.OwnInventory ~= nil - -- isok = isok and item.OwnInventory.visualSlots ~= nil - -- isok = isok and #item.OwnInventory.visualSlots > 0 - -- -- I don't know what rootContainer is - -- -- It seems to be the parent of the current item...? - -- -- Maybe the world object... - -- -- Either way - static objects that we may open have it - -- -- And our own inventory does not - -- -- So it's a good selector for now - -- isok = isok and item.rootContainer ~= nil - -- if isok then - -- containers[#containers + 1] = item - -- end - -- end - - local controlledCharacter = Character.Controlled - if not controlledCharacter then return {} end - local selectedItem = controlledCharacter.SelectedItem - if not selectedItem then return {} end - return { selectedItem } -end - ---@param character Barotrauma.Character ---@return table, string local function tryBuildCharacterItemTree(character) @@ -310,7 +280,7 @@ local function quickStackItems(character) end end - local openContainers = getOpenContainers() + local openContainers = utils.getOpenContainers() for _, container in ipairs(openContainers) do local inventories = container.OwnInventories MyModGlobal.debugPrint(string.format("Found %d inventories in the open container", #inventories)) @@ -329,6 +299,78 @@ local function quickStackItems(character) end end +local function stackToCursor() + local slots, err = utils.getSlotsUnderCursor() + if err then + MyModGlobal.debugPrint(string.format("Error getting slots under cursor: %s", err)) + return + end + + for _, slot in ipairs(slots) do + local item + if not slot.slot.items or #slot.slot.items == 0 then + MyModGlobal.debugPrint("No items in slot") + goto continue + end + + item = slot.slot.items[1] + utils.enqueueAllPlayerItems({}, function(ititem) + if ititem.Prefab.Identifier.Value == item.Prefab.Identifier.Value then + if item == ititem then return false end + -- We are moving items in the predicate because we expect to only + -- Select a small subset of all items + -- And it is much easier to let the game decide when we can not move + -- Any more items (via return value of TryPutItem) + -- And we then know that we can safely stop + local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, nil) + if not moved then + MyModGlobal.debugPrint(string.format("Failed to move item %s to slot %d", ititem.Name, slot + .slotIndex - 1)) + return false, true + end + end + end) + + ::continue:: + end +end + +local function stackAllToCursor() + local slots, err = utils.getSlotsUnderCursor() + if err then + MyModGlobal.debugPrint(string.format("Error getting slots under cursor: %s", err)) + return + end + + for _, slot in ipairs(slots) do + local item + if not slot.slot.items or #slot.slot.items == 0 then + MyModGlobal.debugPrint("No items in slot") + goto continue + end + + item = slot.slot.items[1] + utils.enqueueAllOwnedItems({}, function(ititem) + if ititem.Prefab.Identifier.Value == item.Prefab.Identifier.Value then + if item == ititem then return false end + -- We are moving items in the predicate because we expect to only + -- Select a small subset of all items + -- And it is much easier to let the game decide when we can not move + -- Any more items (via return value of TryPutItem) + -- And we then know that we can safely stop + local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, nil) + if not moved then + MyModGlobal.debugPrint(string.format("Failed to move item %s to slot %d", ititem.Name, slot + .slotIndex - 1)) + return false, true + end + end + end) + + ::continue:: + end +end + return { buildItemTree = buildItemTree, tryBuildCharacterItemTree = tryBuildCharacterItemTree, @@ -336,5 +378,6 @@ return { tryMoveItem = tryMoveItem, tryMoveItems = tryMoveItems, quickStackItems = quickStackItems, - getOpenContainers = getOpenContainers + stackToCursor = stackToCursor, + stackAllToCursor = stackAllToCursor, } diff --git a/QuickStackToBag/Lua/Cyka/quickunload.lua b/QuickStackToBag/Lua/Cyka/quickunload.lua index a8ab7f4..5019004 100644 --- a/QuickStackToBag/Lua/Cyka/quickunload.lua +++ b/QuickStackToBag/Lua/Cyka/quickunload.lua @@ -1,6 +1,5 @@ -- luacheck: globals Character MyModGlobal Timer -local cursormacroer = require("Cyka.cursormacroer") -local dump = require("Cyka.dump") +local utils = require("Cyka.utils") ---@param inventory Barotrauma.ItemInventory ---@param predicate fun(slot: InventorySlot): boolean diff --git a/QuickStackToBag/Lua/Cyka/utils.lua b/QuickStackToBag/Lua/Cyka/utils.lua index e2d597f..8d3bc01 100644 --- a/QuickStackToBag/Lua/Cyka/utils.lua +++ b/QuickStackToBag/Lua/Cyka/utils.lua @@ -333,7 +333,12 @@ 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 - return slots[1], nil + for _, slot in ipairs(slots) do + if #slot.items > 0 then + return slot + end + end + return slots[1] end return {