diff --git a/QuickStackToBag/Lua/Autorun/init.lua b/QuickStackToBag/Lua/Autorun/init.lua index 4556fe1..cdb3e5d 100644 --- a/QuickStackToBag/Lua/Autorun/init.lua +++ b/QuickStackToBag/Lua/Autorun/init.lua @@ -16,6 +16,7 @@ MyModGlobal = { FABRICATOR_KEY = Keys.V, MAX_BUY = Keys.B, FIX = Keys.R, + UNLOAD = Keys.E, NESTED_CONTAINERS = true, DEBUG_MODE = true, }, @@ -56,6 +57,7 @@ local fabricatorstack = require("Cyka.fabricatorstack") local quickbuy = require("Cyka.quickbuy") local hotkeyrepair = require("Cyka.hotkeyrepair") local cursormacroer = require("Cyka.cursormacroer") +local quickunload = require("Cyka.quickunload") require("Cyka.xpticker") print(MyModGlobal.MOD_NAME .. " v" .. MyModGlobal.MOD_VERSION .. " loaded!") @@ -111,3 +113,8 @@ Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptab -- if not PlayerInput.PrimaryMouseButtonClicked() then return end cursormacroer.tryStackCursorItem() end, Hook.HookMethodType.After) + +Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable) + if not PlayerInput.KeyHit(MyModGlobal.CONFIG.UNLOAD) then return end + quickunload.tryUnloadCursorItem() +end, Hook.HookMethodType.After) \ No newline at end of file diff --git a/QuickStackToBag/Lua/Cyka/cursormacroer.lua b/QuickStackToBag/Lua/Cyka/cursormacroer.lua index cb9d6fc..7729779 100644 --- a/QuickStackToBag/Lua/Cyka/cursormacroer.lua +++ b/QuickStackToBag/Lua/Cyka/cursormacroer.lua @@ -228,5 +228,6 @@ end return { tryStackCursorItem = tryStackCursorItem, - setTargetInventory = setTargetInventory + setTargetInventory = setTargetInventory, + getInventorySlotsUnderCursor = getInventorySlotsUnderCursor } diff --git a/QuickStackToBag/Lua/Cyka/dump.lua b/QuickStackToBag/Lua/Cyka/dump.lua new file mode 100644 index 0000000..3b860f2 --- /dev/null +++ b/QuickStackToBag/Lua/Cyka/dump.lua @@ -0,0 +1,24 @@ +---@param table table +---@param depth number? +local function dump(table, depth) + if depth == nil then + depth = 0 + end + if (depth > 200) then + print("Error: Depth > 200 in dumpTable()") + return + end + if (type(table) ~= "table") then + return tostring(table) + end + for k, v in pairs(table) do + if (type(v) == "table") then + print(string.rep(" ", depth) .. tostring(k) .. ":") + dump(v, depth + 1) + else + print(string.rep(" ", depth) .. tostring(k) .. ": " .. tostring(v)) + end + end +end + +return dump diff --git a/QuickStackToBag/Lua/Cyka/quickunload.lua b/QuickStackToBag/Lua/Cyka/quickunload.lua new file mode 100644 index 0000000..93f3e2f --- /dev/null +++ b/QuickStackToBag/Lua/Cyka/quickunload.lua @@ -0,0 +1,181 @@ +-- luacheck: globals Character MyModGlobal Timer +local cursormacroer = require("Cyka.cursormacroer") +local dump = require("Cyka.dump") + +---@class InventorySlot +---@field slot Barotrauma.ItemSlot +---@field inventory Barotrauma.ItemInventory +---@field slotIndex number + +---@param inventory Barotrauma.ItemInventory +---@param predicate fun(slot: InventorySlot): boolean +---@return InventorySlot[], string? +local function findSlotsThat(inventory, predicate) + local slots = {} + for i, slot in ipairs(inventory.slots) do + local inventorySlot = { + slot = slot, + inventory = inventory, + slotIndex = i - 1 + } + if predicate(inventorySlot) then + slots[#slots + 1] = inventorySlot + end + end + return slots +end + +---@param slot InventorySlot +local function tryUnloadSlot(slot) + ---@type Barotrauma.Item + local item = slot.slot.items[1] + if not item then + MyModGlobal.debugPrint("No item in slot") + return + end + local inventory = item.OwnInventory + if not inventory then + MyModGlobal.debugPrint("Item has no own inventory") + return + end + + local toUnload = {} + local toUnloadByPrefab = {} + local inventorySlots = inventory.slots + for _, inventorySlot in ipairs(inventorySlots) do + for _, inventoryItem in ipairs(inventorySlot.items) do + toUnload[#toUnload + 1] = inventoryItem + -- This will only serve as O(1) lookup + toUnloadByPrefab[inventoryItem.Prefab] = true + end + end + + -- Where can we put our toUnload items? + local nearbySlots = findSlotsThat(slot.inventory, function(islot) + local isEmpty = #islot.slot.items == 0 + if isEmpty then return true end + + for _, prefab in ipairs(toUnloadByPrefab) do + local canAccept = islot.inventory.CanBePutInSlot(prefab, islot.slotIndex) + if canAccept then return true end + end + return false + end) + -- print("Before sorting:") + -- dump(nearbySlots) + + table.sort(nearbySlots, function(a, b) + local distanceA = math.abs(a.slotIndex - slot.slotIndex) + local distanceB = math.abs(b.slotIndex - slot.slotIndex) + return distanceA < distanceB + end) + -- print("After sorting:") + -- dump(nearbySlots) + + for _, iitem in ipairs(toUnload) do + for _, nearbySlot in ipairs(nearbySlots) do + local canAccept = nearbySlot.inventory.CanBePutInSlot(iitem.Prefab, nearbySlot.slotIndex) + if canAccept then + local moved = nearbySlot.inventory.TryPutItem(iitem, nearbySlot.slotIndex, true, false, nil) + if moved then break end + end + end + end +end + +local function tryUnloadCursorItem() + local slots, err = cursormacroer.getInventorySlotsUnderCursor() + if err then + -- MyModGlobal.debugPrint(string.format("Error getting inventory slot: %s", err)) + return + end + + if not slots or #slots == 0 then + -- MyModGlobal.debugPrint("No items in slot") + return + end + + for _, slot in ipairs(slots) do + tryUnloadSlot(slot) + end + + -- local canAccept = inventorySlot.CanBePutInSlot(item, inventorySlot.slotIndex, item.Condition) + -- if canAccept then + -- toUnload[#toUnload + 1] = inventoryItem + -- end + -- local slots = findSlotsThat(inventory, function(slot) + -- local canAccept + -- end) + + -- local inventory = targetInventory + -- -- MyModGlobal.debugPrint(string.format("Target inventory: %s", tostring(inventory))) + -- if not inventory then + -- local controlledCharacter = Character.Controlled + -- if not controlledCharacter then + -- -- MyModGlobal.debugPrint("No controlled character found") + -- return + -- end + -- local cinventory = controlledCharacter.Inventory + -- if not cinventory or not cinventory.slots then + -- -- MyModGlobal.debugPrint("No inventory found") + -- return + -- end + -- local bagSlot = cinventory.slots[MyModGlobal.BAG_SLOT] + -- if not bagSlot or not bagSlot.items or not bagSlot.items[1] then + -- -- MyModGlobal.debugPrint("No bag slot found") + -- return + -- end + -- local bagItem = bagSlot.items[1] + -- if not bagItem or not bagItem.OwnInventory then + -- -- MyModGlobal.debugPrint("Bag item has no own inventory") + -- return + -- end + -- local bagInventory = bagItem.OwnInventory + -- if not bagInventory or not bagInventory.slots then + -- -- MyModGlobal.debugPrint("Bag inventory has no slots") + -- return + -- end + -- inventory = bagInventory + -- end + -- if not inventory then + -- -- MyModGlobal.debugPrint("No inventory found") + -- return + -- end + + -- local itemTree + -- itemTree, err = quickstack.buildItemTree(inventory) + -- if err then + -- -- MyModGlobal.debugPrint(string.format("Error building item tree: %s", err)) + -- return + -- end + -- itemTree = quickstack.sortItemTree(itemTree) + + -- local itemsToMove = {} + -- local now = Timer.GetTime() + -- for _, slot in ipairs(slots) do + -- local runAfter = slotThrottle[slot] or 0 + -- if now < runAfter then + -- goto continue + -- end + -- -- MyModGlobal.debugPrint(string.format("Enqueuing slot: %s, before: %d", tostring(slot), #itemsToMove)) + -- utils.enqueueSlot(slot.slot, itemsToMove) + -- -- MyModGlobal.debugPrint(string.format("Enqueuing slot: %s, after: %d", tostring(slot), #itemsToMove)) + -- slotThrottle[slot] = now + 1 + -- ::continue:: + -- end + -- -- for _, item in ipairs(itemsToMove) do + -- -- MyModGlobal.debugPrint(string.format("Enqueued item: %s", tostring(item))) + -- -- end + -- -- -- MyModGlobal.debugPrint(string.format("Enqueued %d items from the inventory slot", #itemsToMove)) + -- -- MyModGlobal.DumpTable(itemTree) + + -- quickstack.tryMoveItems(itemsToMove, itemTree, true) + -- -- local errors = quickstack.tryMoveItems(itemsToMove, itemTree, true) + -- -- for _, error in ipairs(errors) do + -- -- MyModGlobal.debugPrint(string.format("Error moving item: %s", error)) + -- -- end +end + +return { + tryUnloadCursorItem = tryUnloadCursorItem, +} diff --git a/QuickStackToBag/Lua/Cyka/scratch.lua b/QuickStackToBag/Lua/Cyka/scratch.lua new file mode 100644 index 0000000..56b3c14 --- /dev/null +++ b/QuickStackToBag/Lua/Cyka/scratch.lua @@ -0,0 +1,71 @@ +function prettyPrint(value) + local function helper(val, indent, visited) + -- Handle non-tables and non-userdata + if type(val) ~= 'table' and type(val) ~= 'userdata' then + return tostring(val) + end + + -- Detect cycles (tables or userdata) + if visited[val] then + return "{...}" + end + visited[val] = true + + -- Check if it's iterable (table or userdata with __pairs) + local is_iterable = false + local iterator_func = nil + + if type(val) == 'table' then + is_iterable = true + iterator_func = pairs + elseif type(val) == 'userdata' then + -- Check for __pairs metamethod (Lua 5.2+) + local mt = debug.getmetatable(val) + if mt and mt.__pairs then + is_iterable = true + iterator_func = mt.__pairs(val) + end + end + + -- If not iterable, just return tostring + if not is_iterable then + visited[val] = nil -- Clean up visited + return tostring(val) + end + + -- Build key-value pairs + local entries = {} + local nextIndent = indent + 1 + for k, v in iterator_func(val) do + local keyStr = helper(k, nextIndent, visited) + local valStr = helper(v, nextIndent, visited) + local entry = string.rep(" ", nextIndent) .. keyStr .. ": " .. valStr + table.insert(entries, entry) + end + + -- Format output + local result = "{\n" + if #entries > 0 then + result = result .. table.concat(entries, ",\n") .. "\n" + end + result = result .. string.rep(" ", indent) .. "}" + + visited[val] = nil -- Allow reuse in different branches + return result + end + + print(helper(value, 0, {})) +end + +local test = { + numbers = { 1, 2, 3 }, + nested = { + a = "apple", + b = { c = "cherry" } + }, + func = function() end, + self = nil +} +test.self = test + +prettyPrint(test)