From 88187358e872af8681cf45b58dcde2628b92c963 Mon Sep 17 00:00:00 2001 From: PhatPhuckDave Date: Tue, 1 Apr 2025 20:40:19 +0200 Subject: [PATCH] Fix up quick stack --- CykaQuick/Lua/Cyka/quickstack.lua | 241 +++++++++++------------------ CykaQuick/Lua/Cyka/quickunload.lua | 2 +- CykaQuick/Lua/Cyka/utils.lua | 52 ++++++- 3 files changed, 137 insertions(+), 158 deletions(-) diff --git a/CykaQuick/Lua/Cyka/quickstack.lua b/CykaQuick/Lua/Cyka/quickstack.lua index c799e22..d0fec27 100644 --- a/CykaQuick/Lua/Cyka/quickstack.lua +++ b/CykaQuick/Lua/Cyka/quickstack.lua @@ -5,20 +5,14 @@ if not CLIENT then return end local utils = require("Cyka.utils") local dump = require("Cyka.dump") ----@class ItemLocation ----@field inventory Barotrauma.ItemInventory ----@field slotIndex number ----@field depth number ----@field maxFits number - -- The resulting item tree is a table where the key is an ID of an item -- And the value is an object that represents where that item is located -- In our inventory -- Special case are empty slots where any item fits ----@param inventory Barotrauma.ItemInventory ----@param itemTree table ----@param depth number ----@return table +---@param inventory Barotrauma.Inventory +---@param itemTree? table +---@param depth? number +---@return table local function buildItemTree(inventory, itemTree, depth) itemTree = itemTree or {} depth = depth or 0 @@ -29,38 +23,24 @@ local function buildItemTree(inventory, itemTree, depth) -- One slot can have one item but multiple of it -- The number of an item in a slot is #slot.items - for slotIndex, slot in ipairs(inventory.slots) do - -- MyModGlobal.debugPrint(string.format("Building item tree for inventory at slot index: %d", slotIndex)) - -- MyModGlobal.debugPrint(string.format("Slot %d has %d items", slotIndex, #slot.items)) - if #slot.items == 0 then + for slotIndex, _ in ipairs(inventory.slots) do + local invSlot = MyModGlobal.InventorySlot.new(inventory, slotIndex):with({ depth = depth }) + if not invSlot.item then -- MyModGlobal.debugPrint(string.format("Slot %d is empty, adding to itemTree as 'empty'", slotIndex)) itemTree['empty'] = itemTree['empty'] or {} - itemTree['empty'][#itemTree['empty'] + 1] = { - inventory = inventory, - slotIndex = slotIndex - 1, - maxFits = 60, - depth = depth - } + itemTree['empty'][#itemTree['empty'] + 1] = invSlot -- MyModGlobal.debugPrint(string.format("Added empty slot to itemTree at index: %d", slotIndex)) else - ---@type Barotrauma.Item - local item = slot.items[1] - local identifier = item.Prefab.Identifier.Value - -- MyModGlobal.debugPrint(string.format("Found item: %s with identifier: %s", item.Name, identifier)) + local identifier = invSlot.item.Prefab.Identifier.Value itemTree[identifier] = itemTree[identifier] or {} -- We DO want even slots with maxFits = 0 -- Because that indicates that we DO HAVE the item -- At all -- And based on that we decide to move it - itemTree[identifier][#itemTree[identifier] + 1] = { - inventory = inventory, - slotIndex = slotIndex - 1, - maxFits = slot.HowManyCanBePut(item.Prefab), - depth = depth - } + itemTree[identifier][#itemTree[identifier] + 1] = invSlot -- MyModGlobal.debugPrint(string.format("Added item to itemTree under identifier: %s", identifier)) - local tags = item.Prefab.Tags + local tags = invSlot.item.Prefab.Tags local shouldSuss = false for tag in tags do if tag.value:find("container") then @@ -71,7 +51,7 @@ local function buildItemTree(inventory, itemTree, depth) if shouldSuss then -- MyModGlobal.debugPrint(string.format("Searching inside %s for nested containers", item.Name)) - buildItemTree(item.OwnInventory, itemTree, depth + 1) + buildItemTree(invSlot.item.OwnInventory, itemTree, depth + 1) end end end @@ -81,19 +61,20 @@ local function buildItemTree(inventory, itemTree, depth) end -- We would like to fill larger stacks first ----@param itemTree table ----@return table +---@param itemTree table +---@return table local function sortItemTree(itemTree) for _, item in pairs(itemTree) do table.sort(item, function(a, b) - ---@cast a ItemLocation - ---@cast b ItemLocation + ---@cast a InventorySlot + ---@cast b InventorySlot + local maxfitsA, maxfitsB = a:maxFits(), b:maxFits() if a.depth ~= b.depth then return a.depth < b.depth - elseif a.maxFits ~= b.maxFits then - return a.maxFits > b.maxFits + elseif maxfitsA ~= maxfitsB then + return maxfitsA > maxfitsB else - return a.slotIndex < b.slotIndex + return a.slotIndex0 < b.slotIndex0 end end) end @@ -102,16 +83,16 @@ local function sortItemTree(itemTree) end ---@param item Barotrauma.Item ----@param itemTree table +---@param itemTree table ---@param force boolean ----@return string +---@return string? local function tryMoveItem(item, itemTree, force) -- MyModGlobal.debugPrint(string.format("Attempting to move item: %s", item.Prefab.Identifier.Value)) force = force or false local location = itemTree[item.Prefab.Identifier.Value] if not location and not force then -- MyModGlobal.debugPrint("No locations for item, not stacking (not forced)") - return nil, "No locations for item, not stacking (not forced)" + return "No locations for item, not stacking (not forced)" end -- MyModGlobal.debugPrint(string.format("Attempting to move item: %s", item.Prefab.Identifier.Value)) -- MyModGlobal.DumpTable(location) @@ -121,22 +102,12 @@ local function tryMoveItem(item, itemTree, force) -- First try to move to existing stacks for _, itemLocation in ipairs(location) do -- We cannot stack items with decreased condition - local canBePut = itemLocation.inventory.CanBePutInSlot(item.Prefab, itemLocation.slotIndex, item.Condition) - -- MyModGlobal.debugPrint(string.format("Can be put in slot %d: %s", itemLocation.slotIndex, tostring(canBePut))) - - if itemLocation.maxFits > 0 and canBePut then - moved = moved or - itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, Character.Controlled, - true) - if moved then - itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex) - break - end - -- if moved then - -- MyModGlobal.debugPrint(string.format("Moved item to existing stack at slot index: %d", itemLocation .slotIndex)) - -- else - -- MyModGlobal.debugPrint(string.format("Failed to move item to existing stack at slot index: %d", itemLocation .slotIndex)) - -- end + local canFit = itemLocation:canFit(item.Prefab) + if canFit then + -- There's no more guess work, if we call move then we must be sure we can move + utils.enqueueMove(item, itemLocation) + moved = true + break end end end @@ -149,28 +120,16 @@ local function tryMoveItem(item, itemTree, force) return "No empty slots found" end for _, itemLocation in ipairs(itemTree['empty']) do - local maxFits = itemLocation.maxFits -- These empty slots are not guranteed to be empty, ironically -- After we insert an item into one it's no longer empty -- But it still is in the empty table -- So we want to make sure we can insert our item -- Into the maybe empty slots - itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex) - - if maxFits > 0 then - -- MyModGlobal.debugPrint(string.format("Trying to move item to empty slot at index: %d", itemLocation.slotIndex)) - moved = moved or - itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, true, false, Character.Controlled, - true) - if moved then - itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex) - break - end - -- if moved then - -- MyModGlobal.debugPrint(string.format("Moved item to empty slot at index: %d", itemLocation.slotIndex)) - -- else - -- MyModGlobal.debugPrint(string.format("Failed to move item to empty slot at index: %d", itemLocation.slotIndex)) - -- end + local canFit = itemLocation:canFit(item.Prefab) + if canFit then + utils.enqueueMove(item, itemLocation) + moved = true + break end end end @@ -185,8 +144,8 @@ local function tryMoveItem(item, itemTree, force) end ---@param items Barotrauma.Item[] ----@param itemTree table ----@param force boolean +---@param itemTree table +---@param force? boolean ---@return string[] local function tryMoveItems(items, itemTree, force) force = force or false @@ -202,7 +161,7 @@ local function tryMoveItems(items, itemTree, force) end ---@param character Barotrauma.Character ----@return table, string +---@return table, string? local function tryBuildCharacterItemTree(character) local itemTree = {} -- MyModGlobal.debugPrint(string.format("Preparing to stack items into the bag...")) @@ -267,7 +226,7 @@ end -- Function to quickly stack items from inventory to containers -- 6 and 7 are hands -- 9..18 are main slots -local inventorySlotsToStack = { 6, 7, } +-- local inventorySlotsToStack = { 6, 7, } -- local inventorySlotsToStack = { 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 } ---@param character Barotrauma.Character local function quickStackItems(character) @@ -276,12 +235,10 @@ local function quickStackItems(character) -- Then stack all items from the parent inventory into the mouseover container local mouseover = utils.getFirstSlotUnderCursor() if mouseover then - ---@type Barotrauma.Item - local slotItem = mouseover.slot.items[1] - local itemInventory = slotItem.OwnInventory + local itemInventory = mouseover.item.OwnInventory if itemInventory then MyModGlobal.debugPrint(string.format("Item inventory found: %s", tostring(itemInventory))) - local err = stackToContainer(slotItem) + local err = stackToContainer(mouseover.item) if err then MyModGlobal.debugPrint(string.format("Error stacking items to container: %s", err)) end @@ -341,6 +298,7 @@ local function quickStackItems(character) -- end -- end + -- TODO: enqueueOpenContainers? local openContainers = utils.getOpenContainers() for _, container in ipairs(openContainers) do local inventories = container.OwnInventories @@ -367,48 +325,38 @@ local function stackToCursor() return end - local item, slot local function predicate(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 - -- UPDATE: OK well that was a stupid idea, it returns an error for other shit as well - -- What other shit? Wish I knew - -- So we'll use HowManyCanBePut instead... - local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, Character.Controlled, - true) - 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 - local maxFits = slot.inventory.HowManyCanBePut(ititem.Prefab, slot.slotIndex - 1) - if maxFits <= 0 then - MyModGlobal.debugPrint(string.format("Item %s has no more fits in slot %d", ititem.Name, slot - .slotIndex - 1)) - return false, true + for _, invSlot in ipairs(slots) do + if invSlot:canFit(ititem.Prefab) then + utils.enqueueMove(ititem, invSlot) end end + local haveSpace = false + for _, invSlot in ipairs(slots) do + -- Empty slot or has space for more items + if (invSlot.stackSize < invSlot.maxStackSize) or not invSlot.item then + haveSpace = true + break + end + end + if not haveSpace then return false, true end end + ---@type EnqueueOptions + local options = { + itemPredicate = predicate, + recurse = true, + } -- We gotta do a little juggling... - for _, sslot in ipairs(slots) do - slot = sslot - local items - if not slot.slot.items or #slot.slot.items == 0 then + for _, invSlot in ipairs(slots) do + if not invSlot.item then MyModGlobal.debugPrint("No items in slot") goto continue end - item = slot.slot.items[1] - MyModGlobal.debugPrint(string.format("Stacking all player items to %s", item.Prefab.Identifier.Value)) - items = {} - utils.enqueueAllPlayerItems(items, predicate) - utils.enqueueOpenContainers(items, predicate) + MyModGlobal.debugPrint(string.format("Stacking all player items to %s", invSlot.item.Prefab.Identifier.Value)) + utils.enqueuePlayerItems(options) + utils.enqueueOpenContainers(options) ::continue:: end end @@ -420,45 +368,38 @@ local function stackAllToCursor() return end - for _, slot in ipairs(slots) do - local item, predicate - if not slot.slot.items or #slot.slot.items == 0 then + local function predicate(ititem) + for _, invSlot in ipairs(slots) do + if invSlot:canFit(ititem.Prefab) then + utils.enqueueMove(ititem, invSlot) + end + end + local haveSpace = false + for _, invSlot in ipairs(slots) do + -- Empty slot or has space for more items + if (invSlot.stackSize < invSlot.maxStackSize) or not invSlot.item then + haveSpace = true + break + end + end + if not haveSpace then return false, true end + end + + ---@type EnqueueOptions + local options = { + itemPredicate = predicate, + recurse = true, + } + + for _, invSlot in ipairs(slots) do + if not invSlot.item then MyModGlobal.debugPrint("No items in slot") goto continue end - item = slot.slot.items[1] - MyModGlobal.debugPrint(string.format("Stacking all items to %s", item.Prefab.Identifier.Value)) - predicate = 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 - -- UPDATE: OK well that was a stupid idea, it returns an error for other shit as well - -- What other shit? Wish I knew - -- So we'll use HowManyCanBePut instead... - local moved = slot.inventory.TryPutItem(ititem, slot.slotIndex - 1, false, true, Character.Controlled, - true) - 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 - local maxFits = slot.inventory.HowManyCanBePut(ititem.Prefab, slot.slotIndex - 1) - if maxFits <= 0 then - MyModGlobal.debugPrint(string.format("Item %s has no more fits in slot %d", ititem.Name, slot - .slotIndex - 1)) - return false, true - end - end - end - - utils.enqueueAllSubmarineItems({}, predicate) - utils.enqueueAllPlayerItems({}, predicate) - + MyModGlobal.debugPrint(string.format("Stacking all items to %s", invSlot.item.Prefab.Identifier.Value)) + utils.enqueueSubmarineItems(options) + utils.enqueuePlayerItems(options) ::continue:: end end diff --git a/CykaQuick/Lua/Cyka/quickunload.lua b/CykaQuick/Lua/Cyka/quickunload.lua index 7929dc4..42d3813 100644 --- a/CykaQuick/Lua/Cyka/quickunload.lua +++ b/CykaQuick/Lua/Cyka/quickunload.lua @@ -27,7 +27,7 @@ local function tryUnloadSlot(invSlot) -- Where can we put our toUnload items? local nearbySlots = invSlot:getNearbySlots(function(islot) - local isEmpty = islot.slot.items and #islot.slot.items == 0 + local isEmpty = not islot.item if isEmpty then return true end for prefab, _ in pairs(toUnloadByPrefab) do diff --git a/CykaQuick/Lua/Cyka/utils.lua b/CykaQuick/Lua/Cyka/utils.lua index 3c6b106..a1d10ce 100644 --- a/CykaQuick/Lua/Cyka/utils.lua +++ b/CykaQuick/Lua/Cyka/utils.lua @@ -4,15 +4,24 @@ ---@class Barotrauma.Inventory.ItemSlot ---@field items Barotrauma.Item[] +---@class HollowInventorySlot +---@field inventory? Barotrauma.Inventory +---@field slotIndex1? number Lua based item slots +---@field slotIndex0? number Barotrauma API based item slots +---@field item? Barotrauma.Item +---@field stackSize? number +---@field maxStackSize? number +---@field depth? number Currently almost always 0 + -- local globalInventorySlotCache = {} ----@class InventorySlot ----@field slot Barotrauma.Inventory.ItemSlot +---@class InventorySlot ---@field slot Barotrauma.Inventory.ItemSlot ---@field inventory Barotrauma.Inventory ---@field slotIndex1 number Lua based item slots ---@field slotIndex0 number Barotrauma API based item slots ---@field item Barotrauma.Item ---@field stackSize number ---@field maxStackSize number +---@field depth number Currently almost always 0 -- ---@field lastUpdated number MyModGlobal.InventorySlot = { ---@param inventory Barotrauma.Inventory @@ -27,6 +36,7 @@ MyModGlobal.InventorySlot = { self.slotIndex0 = slotIndex1 - 1 self.stackSize = 0 self.maxStackSize = 0 + self.depth = 0 -- self:update() if inventory and inventory.slots and #inventory.slots > 0 then @@ -42,6 +52,33 @@ MyModGlobal.InventorySlot = { return self end, + --- A very weird builder indeed + ---@param self InventorySlot + ---@param other HollowInventorySlot + with = function(self, other) + if other.inventory ~= nil then + self.inventory = other.inventory + end + if other.slotIndex1 ~= nil then + self.slotIndex1 = other.slotIndex1 + end + if other.slotIndex0 ~= nil then + self.slotIndex0 = other.slotIndex0 + end + if other.item ~= nil then + self.item = other.item + end + if other.stackSize ~= nil then + self.stackSize = other.stackSize + end + if other.maxStackSize ~= nil then + self.maxStackSize = other.maxStackSize + end + if other.depth ~= nil then + self.depth = other.depth + end + return self + end, ---@param self InventorySlot ---@param item Barotrauma.Item pretendMoved = function(self, item) @@ -49,10 +86,6 @@ MyModGlobal.InventorySlot = { MyModGlobal.debugPrint("Error pretending moved but it was moved to nil inventory") return end - if not self.slot then - MyModGlobal.debugPrint("Error pretending moved but it was moved to nil slot") - return - end -- Slot was previously empty, we want to figure out its max stack for the new item if not self.item then self.maxStackSize = item.Prefab.GetMaxStackSize(self.inventory) @@ -158,6 +191,11 @@ MyModGlobal.InventorySlot = { ---@return boolean canFit = function(self, itemPrefab) return self:howManyCanFit(itemPrefab) > 0 + end, + ---@param self InventorySlot + ---@return number + maxFits = function(self) + return self.maxStackSize - self.stackSize end -- hash = function(self) -- return string.format("%s:%d:%d", tostring(self.inventory), self.slotIndex1, self.slotIndex0) @@ -605,7 +643,7 @@ local function getFirstSlotUnderCursor() if err then return slots, err end if #slots == 0 then return slots, "No slots found under cursor" end for _, slot in ipairs(slots) do - if slot.slot.items and #slot.slot.items > 0 then + if slot.item then return slot end end