Factor quickstack out

This commit is contained in:
2025-03-30 14:36:16 +02:00
parent 89257d1cf2
commit ba01a65a1a
3 changed files with 471 additions and 419 deletions

View File

@@ -1,6 +1,56 @@
if SERVER then return end if SERVER then return end
-- Docs: https://evilfactory.github.io/LuaCsForBarotrauma/lua-docs/manual/common-questions/ -- Docs: https://evilfactory.github.io/LuaCsForBarotrauma/lua-docs/manual/common-questions/
---@class MyModGlobal
---@field CONFIG {QUICKSTACK_KEYS: Keys, FABRICATOR_KEY: Keys, MAX_BUY: Keys, NESTED_CONTAINERS: boolean, DEBUG_MODE: boolean}
---@field MOD_NAME string
---@field MOD_VERSION string
---@field DumpTable fun(table: table, depth?: number)
---@field debugPrint fun(message: string)
MyModGlobal = {
CONFIG = {
QUICKSTACK_KEYS = Keys.F,
FABRICATOR_KEY = Keys.V,
MAX_BUY = Keys.B,
NESTED_CONTAINERS = true,
DEBUG_MODE = true,
},
MOD_NAME = "Quick Stack To Containers",
MOD_VERSION = "1.1.0",
BAG_SLOT = 8,
}
---@param table table
---@param depth number?
MyModGlobal.DumpTable = function(table, depth)
if depth == nil then
depth = 0
end
if (depth > 200) then
print("Error: Depth > 200 in dumpTable()")
return
end
for k, v in pairs(table) do
if (type(v) == "table") then
print(string.rep(" ", depth) .. k .. ":")
MyModGlobal.DumpTable(v, depth + 1)
else
print(string.rep(" ", depth) .. k .. ": ", v)
end
end
end
-- Debugging helper function
MyModGlobal.debugPrint = function(message)
if MyModGlobal.CONFIG.DEBUG_MODE then
print("[" .. MyModGlobal.MOD_NAME .. "] " .. message)
end
end
require("Cyka.quickstack")
print(MyModGlobal.MOD_NAME .. " v" .. MyModGlobal.MOD_VERSION .. " loaded!")
-- Register necessary types and make fields accessible -- Register necessary types and make fields accessible
LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions") LuaUserData.RegisterType("Barotrauma.Items.Components.ItemContainer+SlotRestrictions")
LuaUserData.RegisterType( LuaUserData.RegisterType(
@@ -15,333 +65,6 @@ LuaUserData.RegisterType("Barotrauma.ItemPrefab")
LuaUserData.RegisterType("Barotrauma.Location+StoreInfo") LuaUserData.RegisterType("Barotrauma.Location+StoreInfo")
LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.CargoManager"], "GetConfirmedSoldEntities") LuaUserData.MakeMethodAccessible(Descriptors["Barotrauma.CargoManager"], "GetConfirmedSoldEntities")
-- TODO: Implement quick stacking from existing contasiners to existing containers
-- So we can have 1 or 2 full containers instead of 14 almost empty ones
-- Simple configuration
local CONFIG = {
QUICKSTACK_KEYS = Keys.F,
FABRICATOR_KEY = Keys.V,
MAX_BUY = Keys.B,
NESTED_CONTAINERS = true,
DEBUG_MODE = true,
}
-- MOD INFO
local MOD_NAME = "Quick Stack To Containers"
local MOD_VERSION = "1.1.0"
print(MOD_NAME .. " v" .. MOD_VERSION .. " loaded!")
---@param table table
---@param depth number?
local function DumpTable(table, depth)
if depth == nil then
depth = 0
end
if (depth > 200) then
print("Error: Depth > 200 in dumpTable()")
return
end
for k, v in pairs(table) do
if (type(v) == "table") then
print(string.rep(" ", depth) .. k .. ":")
DumpTable(v, depth + 1)
else
print(string.rep(" ", depth) .. k .. ": ", v)
end
end
end
-- Debugging helper function
local function debugPrint(message)
if CONFIG.DEBUG_MODE then
print("[" .. MOD_NAME .. "] " .. message)
end
end
---@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<string, ItemLocation[]>
---@param depth number
---@return table
local function buildItemTree(inventory, itemTree, depth)
itemTree = itemTree or {}
depth = depth or 0
if not inventory or not inventory.slots then
-- debugPrint(string.format("Inventory is nil or has no slots, returning empty itemTree"))
return itemTree
end
-- 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
-- debugPrint(string.format("Building item tree for inventory at slot index: %d", slotIndex))
-- debugPrint(string.format("Slot %d has %d items", slotIndex, #slot.items))
if #slot.items == 0 then
-- 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
}
-- 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
-- debugPrint(string.format("Found item: %s with identifier: %s", item.Name, identifier))
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
}
-- debugPrint(string.format("Added item to itemTree under identifier: %s", identifier))
local tags = item.Prefab.Tags
local shouldSuss = false
for tag in tags do
if tag.value:find("container") then
shouldSuss = true
break
end
end
if shouldSuss then
-- debugPrint(string.format("Searching inside %s for nested containers", item.Name))
buildItemTree(item.OwnInventory, itemTree, depth + 1)
end
end
end
-- debugPrint("Completed building item tree")
debugPrint("Completed building item tree")
return itemTree
end
-- We would like to fill larger stacks first
---@param itemTree table<string, ItemLocation[]>
---@return table<string, ItemLocation[]>
local function sortItemtreeBySlots(itemTree)
for _, item in pairs(itemTree) do
table.sort(item, function(a, b)
---@cast a ItemLocation
---@cast b ItemLocation
if a.depth ~= b.depth then
return a.depth < b.depth
elseif a.maxFits ~= b.maxFits then
return a.maxFits > b.maxFits
else
return a.slotIndex < b.slotIndex
end
end)
end
return itemTree
end
---@param item Barotrauma.Item
---@param itemTree table<string, ItemLocation[]>
---@return string
local function tryMoveItem(item, itemTree)
local location = itemTree[item.Prefab.Identifier.Value]
if not location then return nil, "No locations for item, not stacking" end
local moved = false
-- First try to move to existing stacks
for _, itemLocation in ipairs(location) do
if itemLocation.maxFits > 0 then
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, nil)
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
end
-- If we can not find an existing stack
-- Then move to any of the empty slots
if not moved then
for _, itemLocation in ipairs(itemTree['empty']) do
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, nil)
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
end
-- If we still can not move the item give up
if not moved then return "Failed to find valid location for item" end
return nil
end
---@param items Barotrauma.Item[]
---@param itemTree table<string, ItemLocation[]>
---@return string[]
local function tryMoveItems(items, itemTree)
local errs = {}
for _, item in ipairs(items) do
local err = tryMoveItem(item, itemTree)
-- oops, this one failed, continue...
if err then
errs[#errs + 1] = string.format("Failed to move item: %s", item.Prefab.Identifier.Value)
end
end
return errs
end
-- We got to do this shit because enqueueInventory calls enqueueItem
-- And enqueueItem calls enqueueInventory
-- So unless we define them both before using them
-- We will get an error saying either is undefined
local enqueueItem
local enqueueSlot
local enqueueInventory
local _
---@param item Barotrauma.Item
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueItem = function(item, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing item: %s", item.Prefab.Identifier.Value))
-- local err
-- This should make it breadth first, right...?
-- No, not yet...
local ok, stop = predicate(item)
if ok then
queue[#queue + 1] = item
end
if stop then return queue, "Stop" end
if item.OwnInventory then
-- As far as I know every item has only one inventory
-- Only machines have multiple
-- So inventrorY should be fine here
-- debugPrint("Item has its own inventory, enqueuing inventory...")
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate)
-- if err then
-- debugPrint(string.format("Error enqueuing inventory: %s", err))
-- end
end
-- debugPrint(string.format("Item enqueued. Current queue size: %d", #queue))
return queue, nil
end
---@param slot Barotrauma.ItemInventory.Slot
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueSlot = function(slot, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing slot with %d items.", #slot.items))
-- We don't want to shadow queue
local err
-- If the slot is empty there's nothing to iterate
-- And we will naturally return queue as is
for _, item in ipairs(slot.items) do
-- Only the final leaf nodes decide upon the predicate
queue, err = enqueueItem(item, queue, predicate)
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing slot. Current queue size: %d", #queue))
return queue
end
---@param inventory Barotrauma.ItemInventory
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueInventory = function(inventory, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing inventory with %d slots.", #inventory.slots))
local err
for _, slot in ipairs(inventory.slots) do
-- Only the final leaf nodes decide upon the predicate
queue, err = enqueueSlot(slot, queue, predicate)
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing inventory. Current queue size: %d", #queue))
return queue
end
-- This is a bit fucking sucky.....
-- But I really don't know better
-- Maybe it will be fine...
---@return Barotrauma.Item[]
local function getOpenContainers()
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 inventory Barotrauma.ItemInventory
---@return table<string, ItemLocation[]>, string
local function tryBuildItemTree(inventory)
local itemTree = {}
-- debugPrint(string.format("Preparing to stack items into the bag..."))
local bagSlot = inventory.slots[8]
if bagSlot then
-- debugPrint(string.format("Bag slot found at index 8 with %d items.", #bagSlot.items))
if #bagSlot.items > 0 then
local item = bagSlot.items[1]
-- debugPrint(string.format("Found item in bag slot: %s", item.Name))
if item and item.OwnInventory then
-- debugPrint(string.format("Item has its own inventory, building item tree for it..."))
itemTree = buildItemTree(item.OwnInventory, itemTree)
else
return itemTree, "Bag does not have its own inventory."
end
else
return itemTree, "Bag slot is empty."
end
else
return itemTree, "No bag slot found at index 8."
end
return itemTree, nil
end
-- -- Register necessary type to access VisualSlot -- -- Register necessary type to access VisualSlot
-- LuaUserData.RegisterType("Barotrauma.VisualSlot") -- LuaUserData.RegisterType("Barotrauma.VisualSlot")
-- --
@@ -403,94 +126,44 @@ end
-- return nil, nil, nil -- return nil, nil, nil
-- end -- end
-- Function to quickly stack items from inventory to containers -- -- Register necessary types for detecting repairable objects
-- 6 and 7 are hands -- LuaUserData.RegisterType("Barotrauma.Items.Components.Repairable")
-- 9..18 are main slots -- LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.Items.Components.Repairable"], "RepairButton")
local inventorySlotsToStack = { 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 } --
local BAG_SLOT = 8 -- ---@return Barotrauma.Item|nil, Barotrauma.Items.Components.Repairable|nil
local function quickStackItems(character) -- local function getRepairableObjectInFocus()
if not character then -- -- Make sure we have a controlled character
debugPrint("No character found") -- local controlledCharacter = Character.Controlled
return -- if not controlledCharacter then return nil, nil end
end --
-- -- Check if we have a selected item - this is necessary for repair interfaces to show
-- local selectedItem = controlledCharacter.SelectedItem
-- if not selectedItem then return nil, nil end
--
-- -- Check if the selected item is in fact the repairable object itself
-- for _, component in pairs(selectedItem.Components) do
-- if component.name == "Repairable" then
-- -- Check if repair interface should be shown
-- if component:ShouldDrawHUD(controlledCharacter) then
-- return selectedItem, component
-- end
-- end
-- end
--
-- -- Nothing found
-- return nil, nil
-- end
--
-- ---@return boolean
-- local function isRepairButtonVisible()
-- local _, repairableComponent = getRepairableObjectInFocus()
-- if not repairableComponent then return false end
--
-- -- Check if the repair button exists and is visible
-- local repairButton = repairableComponent.RepairButton
-- return repairButton ~= nil and repairButton.Visible
-- end
debugPrint("Quick stack function called")
local inventory = character.Inventory
if not inventory or not inventory.slots then
debugPrint("Character has no inventory")
return
end
local itemTree, err = tryBuildItemTree(inventory)
if err then
debugPrint(string.format("Error building item tree: %s", err))
return
end
itemTree = sortItemtreeBySlots(itemTree)
--DumpTable(itemTree)
local toMove = {}
-- for i, slot in ipairs(inventory.slots) do
-- if #slot.items > 0 then
-- local item = slot.items[1]
-- local identifier = item.Prefab.Identifier.Value
-- print(string.format("Item at slot %d is %s", i, identifier))
-- end
-- end
for _, slotid in ipairs(inventorySlotsToStack) do
debugPrint(string.format("Processing inventory slot: %d", slotid))
local slot = inventory.slots[slotid]
if #slot.items > 0 then
local item = slot.items[1]
local tags = item.Prefab.Tags
local shouldSuss = true
for tag in tags do
if tag.value:find("tool") or tag.value:find("weapon") then
debugPrint(string.format("Item '%s' is a tool or weapon, skipping", item.Name))
shouldSuss = false
break
end
end
if shouldSuss then
local before = #toMove
toMove = enqueueSlot(slot, toMove)
local after = #toMove
debugPrint(string.format("Enqueued %d items from the inventory slot %d", after - before, slotid))
end
end
end
local openContainers = getOpenContainers()
for _, container in ipairs(openContainers) do
local inventories = container.OwnInventories
debugPrint(string.format("Found %d inventories in the open container", #inventories))
for containerInventory in inventories do
debugPrint(string.format("Enqueuing inventory with %d slots", #containerInventory.slots))
local before = #toMove
toMove = enqueueInventory(containerInventory, toMove)
local after = #toMove
debugPrint(string.format("Enqueued %d items from the open container", after - before))
end
end
local errors = tryMoveItems(toMove, itemTree)
for _, error in ipairs(errors) do
print(string.format("Error stacking item: %s", error))
end
end
-- Hook into player control to listen for key press
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if not PlayerInput.KeyHit(CONFIG.QUICKSTACK_KEYS) then return end
local character = instance
if not character then return end
quickStackItems(character)
end, Hook.HookMethodType.After)
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------

View File

@@ -0,0 +1,292 @@
local utils = require("Cyka.utils")
---@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<string, ItemLocation[]>
---@param depth number
---@return table
local function buildItemTree(inventory, itemTree, depth)
itemTree = itemTree or {}
depth = depth or 0
if not inventory or not inventory.slots then
-- MyModGlobal.debugPrint(string.format("Inventory is nil or has no slots, returning empty itemTree"))
return itemTree
end
-- 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
-- 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
}
-- 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))
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
}
-- MyModGlobal.debugPrint(string.format("Added item to itemTree under identifier: %s", identifier))
local tags = item.Prefab.Tags
local shouldSuss = false
for tag in tags do
if tag.value:find("container") then
shouldSuss = true
break
end
end
if shouldSuss then
-- MyModGlobal.debugPrint(string.format("Searching inside %s for nested containers", item.Name))
buildItemTree(item.OwnInventory, itemTree, depth + 1)
end
end
end
-- MyModGlobal.debugPrint("Completed building item tree")
MyModGlobal.debugPrint("Completed building item tree")
return itemTree
end
-- We would like to fill larger stacks first
---@param itemTree table<string, ItemLocation[]>
---@return table<string, ItemLocation[]>
local function sortItemtreeBySlots(itemTree)
for _, item in pairs(itemTree) do
table.sort(item, function(a, b)
---@cast a ItemLocation
---@cast b ItemLocation
if a.depth ~= b.depth then
return a.depth < b.depth
elseif a.maxFits ~= b.maxFits then
return a.maxFits > b.maxFits
else
return a.slotIndex < b.slotIndex
end
end)
end
return itemTree
end
---@param item Barotrauma.Item
---@param itemTree table<string, ItemLocation[]>
---@return string
local function tryMoveItem(item, itemTree)
local location = itemTree[item.Prefab.Identifier.Value]
if not location then return nil, "No locations for item, not stacking" end
local moved = false
-- First try to move to existing stacks
for _, itemLocation in ipairs(location) do
if itemLocation.maxFits > 0 then
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, nil)
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
end
-- If we can not find an existing stack
-- Then move to any of the empty slots
if not moved then
for _, itemLocation in ipairs(itemTree['empty']) do
moved = moved or itemLocation.inventory.TryPutItem(item, itemLocation.slotIndex, false, true, nil)
itemLocation.maxFits = itemLocation.inventory.HowManyCanBePut(item.Prefab, itemLocation.slotIndex)
end
end
-- If we still can not move the item give up
if not moved then return "Failed to find valid location for item" end
return nil
end
---@param items Barotrauma.Item[]
---@param itemTree table<string, ItemLocation[]>
---@return string[]
local function tryMoveItems(items, itemTree)
local errs = {}
for _, item in ipairs(items) do
local err = tryMoveItem(item, itemTree)
-- oops, this one failed, continue...
if err then
errs[#errs + 1] = string.format("Failed to move item: %s", item.Prefab.Identifier.Value)
end
end
return errs
end
-- This is a bit fucking sucky.....
-- But I really don't know better
-- Maybe it will be fine...
---@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 inventory Barotrauma.ItemInventory
---@return table<string, ItemLocation[]>, string
local function tryBuildItemTree(inventory)
local itemTree = {}
-- MyModGlobal.debugPrint(string.format("Preparing to stack items into the bag..."))
local bagSlot = inventory.slots[8]
if bagSlot then
-- MyModGlobal.debugPrint(string.format("Bag slot found at index 8 with %d items.", #bagSlot.items))
if #bagSlot.items > 0 then
local item = bagSlot.items[1]
-- MyModGlobal.debugPrint(string.format("Found item in bag slot: %s", item.Name))
if item and item.OwnInventory then
-- MyModGlobal.debugPrint(string.format("Item has its own inventory, building item tree for it..."))
itemTree = buildItemTree(item.OwnInventory, itemTree)
else
return itemTree, "Bag does not have its own inventory."
end
else
return itemTree, "Bag slot is empty."
end
else
return itemTree, "No bag slot found at index 8."
end
return itemTree, nil
end
-- Function to quickly stack items from inventory to containers
-- 6 and 7 are hands
-- 9..18 are main slots
local inventorySlotsToStack = { 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }
local function quickStackItems(character)
if not character then
MyModGlobal.debugPrint("No character found")
return
end
MyModGlobal.debugPrint("Quick stack function called")
local inventory = character.Inventory
if not inventory or not inventory.slots then
MyModGlobal.debugPrint("Character has no inventory")
return
end
local itemTree, err = tryBuildItemTree(inventory)
if err then
MyModGlobal.debugPrint(string.format("Error building item tree: %s", err))
return
end
itemTree = sortItemtreeBySlots(itemTree)
--DumpTable(itemTree)
local toMove = {}
-- for i, slot in ipairs(inventory.slots) do
-- if #slot.items > 0 then
-- local item = slot.items[1]
-- local identifier = item.Prefab.Identifier.Value
-- print(string.format("Item at slot %d is %s", i, identifier))
-- end
-- end
for _, slotid in ipairs(inventorySlotsToStack) do
MyModGlobal.debugPrint(string.format("Processing inventory slot: %d", slotid))
local slot = inventory.slots[slotid]
if #slot.items > 0 then
local item = slot.items[1]
local tags = item.Prefab.Tags
local shouldSuss = true
for tag in tags do
if tag.value:find("tool") or tag.value:find("weapon") then
MyModGlobal.debugPrint(string.format("Item '%s' is a tool or weapon, skipping", item.Name))
shouldSuss = false
break
end
end
if shouldSuss then
local before = #toMove
toMove = utils.enqueueSlot(slot, toMove)
local after = #toMove
MyModGlobal.debugPrint(string.format("Enqueued %d items from the inventory slot %d", after - before,
slotid))
end
end
end
local openContainers = getOpenContainers()
for _, container in ipairs(openContainers) do
local inventories = container.OwnInventories
MyModGlobal.debugPrint(string.format("Found %d inventories in the open container", #inventories))
for containerInventory in inventories do
MyModGlobal.debugPrint(string.format("Enqueuing inventory with %d slots", #containerInventory.slots))
local before = #toMove
toMove = utils.enqueueInventory(containerInventory, toMove)
local after = #toMove
MyModGlobal.debugPrint(string.format("Enqueued %d items from the open container", after - before))
end
end
local errors = tryMoveItems(toMove, itemTree)
for _, error in ipairs(errors) do
print(string.format("Error stacking item: %s", error))
end
end
-- Hook into player control to listen for key press
Hook.Patch("Barotrauma.Character", "ControlLocalPlayer", function(instance, ptable)
if not PlayerInput.KeyHit(MyModGlobal.CONFIG.QUICKSTACK_KEYS) then return end
local character = instance
if not character then return end
quickStackItems(character)
end, Hook.HookMethodType.After)

View File

@@ -0,0 +1,87 @@
-- We got to do this shit because enqueueInventory calls enqueueItem
-- And enqueueItem calls enqueueInventory
-- So unless we define them both before using them
-- We will get an error saying either is undefined
local enqueueItem
local enqueueSlot
local enqueueInventory
local _
---@param item Barotrauma.Item
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueItem = function(item, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing item: %s", item.Prefab.Identifier.Value))
-- local err
-- This should make it breadth first, right...?
-- No, not yet...
local ok, stop = predicate(item)
if ok then
queue[#queue + 1] = item
end
if stop then return queue, "Stop" end
if item.OwnInventory then
-- As far as I know every item has only one inventory
-- Only machines have multiple
-- So inventrorY should be fine here
-- debugPrint("Item has its own inventory, enqueuing inventory...")
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate)
-- if err then
-- debugPrint(string.format("Error enqueuing inventory: %s", err))
-- end
end
-- debugPrint(string.format("Item enqueued. Current queue size: %d", #queue))
return queue, nil
end
---@param slot Barotrauma.ItemInventory.Slot
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueSlot = function(slot, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing slot with %d items.", #slot.items))
-- We don't want to shadow queue
local err
-- If the slot is empty there's nothing to iterate
-- And we will naturally return queue as is
for _, item in ipairs(slot.items) do
-- Only the final leaf nodes decide upon the predicate
queue, err = enqueueItem(item, queue, predicate)
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing slot. Current queue size: %d", #queue))
return queue
end
---@param inventory Barotrauma.ItemInventory
---@param queue Barotrauma.Item[]
---@param predicate? fun(item: Barotrauma.Item): boolean
---@return Barotrauma.Item[], string?
enqueueInventory = function(inventory, queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- debugPrint(string.format("Enqueuing inventory with %d slots.", #inventory.slots))
local err
for _, slot in ipairs(inventory.slots) do
-- Only the final leaf nodes decide upon the predicate
queue, err = enqueueSlot(slot, queue, predicate)
if err then
return queue, err
end
end
-- debugPrint(string.format("Finished enqueuing inventory. Current queue size: %d", #queue))
return queue
end
return {
enqueueItem = enqueueItem,
enqueueSlot = enqueueSlot,
enqueueInventory = enqueueInventory
}