Implement building item tree from player containers
This commit is contained in:
@@ -10,16 +10,16 @@ LuaUserData.MakeFieldAccessible(Descriptors["Barotrauma.CharacterInventory"], "s
|
||||
|
||||
-- Simple configuration
|
||||
local CONFIG = {
|
||||
TRIGGER_KEY = Keys.F, -- Key to press for quick stacking
|
||||
NESTED_CONTAINERS = true, -- Whether to include nested containers
|
||||
DEBUG_MODE = true, -- Print debug messages
|
||||
HAND_SLOTS = { 6, 7 }, -- Slot numbers for hands (typically slots 6 and 7)
|
||||
TRIGGER_KEY = Keys.F, -- Key to press for quick stacking
|
||||
NESTED_CONTAINERS = true, -- Whether to include nested containers
|
||||
DEBUG_MODE = true, -- Print debug messages
|
||||
HAND_SLOTS = { 6, 7 }, -- Slot numbers for hands (typically slots 6 and 7)
|
||||
PRIORITY_CONTAINERS = { "toolbelt", "backpack", "pouch" }, -- Priority order for containers
|
||||
EXCLUDE_TOOLS = true, -- Exclude tools from container list
|
||||
EXCLUDE_TOOLS = true, -- Exclude tools from container list
|
||||
TOOL_IDENTIFIERS = { "welding", "gun", "weapon", "revolver", "smg", "rifle", "shotgun",
|
||||
"diving", "oxygen", "scanner", "card", "id", "fuel", "rod", "battery",
|
||||
"fabricator", "deconstructor" }, -- Common tool identifiers to exclude
|
||||
ALWAYS_INCLUDE = { "toolbelt", "backpack", "pouch" } -- Always include these items even if they match tool criteria
|
||||
"diving", "oxygen", "scanner", "card", "id", "fuel", "rod", "battery",
|
||||
"fabricator", "deconstructor" }, -- Common tool identifiers to exclude
|
||||
ALWAYS_INCLUDE = { "toolbelt", "backpack", "pouch" } -- Always include these items even if they match tool criteria
|
||||
}
|
||||
|
||||
-- MOD INFO
|
||||
@@ -35,6 +35,26 @@ local function debugPrint(message)
|
||||
end
|
||||
end
|
||||
|
||||
---@param table table
|
||||
---@param depth number?
|
||||
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
|
||||
|
||||
-- Show notification to player
|
||||
-- Helper function to check if an item is a tool rather than a container
|
||||
local function isTool(item)
|
||||
@@ -120,7 +140,7 @@ local function moveItemToContainer(item, containerInv)
|
||||
-- Get max stack size for this item (default to 1 if we can't determine)
|
||||
local maxStackSize = 60
|
||||
pcall(function()
|
||||
print(item.Prefab.MaxStackSize)
|
||||
print(item.Prefab.MaxStackSize)
|
||||
if item.Prefab and item.Prefab.MaxStackSize then
|
||||
maxStackSize = item.Prefab.MaxStackSize
|
||||
end
|
||||
@@ -178,7 +198,8 @@ local function moveItemToContainer(item, containerInv)
|
||||
for _, matchingSlot in ipairs(matchingSlots) do
|
||||
-- Only try to put in slots that have space
|
||||
if matchingSlot.remainingSpace > 0 then
|
||||
debugPrint("Trying to stack " .. item.Name .. " with existing items (slot has " .. matchingSlot.remainingSpace .. " space)")
|
||||
debugPrint("Trying to stack " ..
|
||||
item.Name .. " with existing items (slot has " .. matchingSlot.remainingSpace .. " space)")
|
||||
|
||||
-- Try to move the full item
|
||||
if containerInv.TryPutItem(item, matchingSlot.slotIndex, true, false, nil) then
|
||||
@@ -313,6 +334,63 @@ local function findAllContainers(inventory, containers)
|
||||
return containers
|
||||
end
|
||||
|
||||
---@class ItemLocation
|
||||
---@field inventory Barotrauma.ItemInventory
|
||||
---@field slotIndex 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[]>
|
||||
---@return table
|
||||
local function buildItemTree(inventory, itemTree, iter)
|
||||
iter = iter or 0
|
||||
itemTree = itemTree or {}
|
||||
if not inventory or not inventory.slots then 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
|
||||
-- This iteration shit is done only to skip the player inventory as a potential destination
|
||||
-- Since it will always be 0th iteration and there's no point stacking
|
||||
-- Items from the player inventory to itself
|
||||
if iter > 0 then
|
||||
if #slot.items == 0 then
|
||||
itemTree['empty'] = itemTree['empty'] or {}
|
||||
itemTree['empty'][#itemTree['empty'] + 1] = {
|
||||
inventory = inventory,
|
||||
slotIndex = slotIndex,
|
||||
maxFits = 60
|
||||
}
|
||||
else
|
||||
---@type Barotrauma.Item
|
||||
local item = slot.items[1]
|
||||
local maxStackSize = item.Prefab.MaxStackSize or 60
|
||||
local identifier = item.Prefab.Identifier.Value
|
||||
itemTree[identifier] = itemTree[identifier] or {}
|
||||
itemTree[identifier][#itemTree[identifier] + 1] = {
|
||||
inventory = inventory,
|
||||
slotIndex = slotIndex,
|
||||
maxFits = maxStackSize - #slot.items
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if #slot.items > 1 then
|
||||
local item = slot.items[1]
|
||||
if item.OwnInventory then
|
||||
print("Searching inside " .. item.Name .. " for nested containers")
|
||||
buildItemTree(item.OwnInventory, itemTree, iter + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return itemTree
|
||||
end
|
||||
|
||||
-- Find the currently open container - improved version
|
||||
local function getOpenContainer()
|
||||
debugPrint("Attempting to find open container...")
|
||||
@@ -354,7 +432,7 @@ local function getOpenContainer()
|
||||
local selectedItem = Character.Controlled.SelectedItem
|
||||
-- Check if selected item is interacting with something
|
||||
if selectedItem.InteractingWith and selectedItem.InteractingWith.OwnInventory and
|
||||
selectedItem.InteractingWith ~= Character.Controlled then
|
||||
selectedItem.InteractingWith ~= Character.Controlled then
|
||||
openContainer = selectedItem.InteractingWith
|
||||
hasVisibleInventory = true
|
||||
debugPrint("Found open container via current interaction: " .. openContainer.Name)
|
||||
@@ -372,7 +450,7 @@ local function getOpenContainer()
|
||||
pcall(function()
|
||||
-- Common method to check if inventory is visible - look for visual components
|
||||
if openContainer.OwnInventory and openContainer.OwnInventory.visualSlots and
|
||||
#openContainer.OwnInventory.visualSlots > 0 then
|
||||
#openContainer.OwnInventory.visualSlots > 0 then
|
||||
isVisible = true
|
||||
end
|
||||
end)
|
||||
@@ -468,146 +546,149 @@ local function quickStackItems(character)
|
||||
return
|
||||
end
|
||||
|
||||
local itemTree = buildItemTree(inventory, {})
|
||||
DumpTable(itemTree)
|
||||
|
||||
-- Find all containers in player inventory, including nested ones
|
||||
local containers = findAllContainers(inventory, {})
|
||||
-- local containers = findAllContainers(inventory, {})
|
||||
|
||||
-- Fallback: If no containers found, try a direct search for known container types
|
||||
if #containers == 0 then
|
||||
debugPrint("No containers found with standard method, trying fallback method...")
|
||||
-- -- Fallback: If no containers found, try a direct search for known container types
|
||||
-- if #containers == 0 then
|
||||
-- debugPrint("No containers found with standard method, trying fallback method...")
|
||||
|
||||
-- Direct slot search for common container types
|
||||
for _, slot in ipairs(inventory.slots) do
|
||||
for _, item in ipairs(slot.items) do
|
||||
if item.OwnInventory then
|
||||
local identifier = item.Prefab.Identifier.Value:lower()
|
||||
-- Force include specific items that we know should be containers
|
||||
if identifier:find("toolbelt") or identifier:find("backpack") or
|
||||
identifier:find("pouch") or identifier:find("container") or
|
||||
identifier:find("box") or identifier:find("crate") or
|
||||
item.Name:find("Container") or item.Name:find("Box") then
|
||||
-- -- Direct slot search for common container types
|
||||
-- for _, slot in ipairs(inventory.slots) do
|
||||
-- for _, item in ipairs(slot.items) do
|
||||
-- if item.OwnInventory then
|
||||
-- local identifier = item.Prefab.Identifier.Value:lower()
|
||||
-- -- Force include specific items that we know should be containers
|
||||
-- if identifier:find("toolbelt") or identifier:find("backpack") or
|
||||
-- identifier:find("pouch") or identifier:find("container") or
|
||||
-- identifier:find("box") or identifier:find("crate") or
|
||||
-- item.Name:find("Container") or item.Name:find("Box") then
|
||||
|
||||
debugPrint("Fallback: Force including known container: " .. item.Name)
|
||||
table.insert(containers, item)
|
||||
-- debugPrint("Fallback: Force including known container: " .. item.Name)
|
||||
-- table.insert(containers, item)
|
||||
|
||||
-- Also check container slots
|
||||
if CONFIG.NESTED_CONTAINERS and #item.OwnInventory.slots > 2 then
|
||||
for _, containerSlot in ipairs(item.OwnInventory.slots) do
|
||||
for _, containerItem in ipairs(containerSlot.items) do
|
||||
if containerItem.OwnInventory and
|
||||
(containerItem.Prefab.Identifier.Value:lower():find("container") or
|
||||
containerItem.Name:find("Container")) then
|
||||
-- -- Also check container slots
|
||||
-- if CONFIG.NESTED_CONTAINERS and #item.OwnInventory.slots > 2 then
|
||||
-- for _, containerSlot in ipairs(item.OwnInventory.slots) do
|
||||
-- for _, containerItem in ipairs(containerSlot.items) do
|
||||
-- if containerItem.OwnInventory and
|
||||
-- (containerItem.Prefab.Identifier.Value:lower():find("container") or
|
||||
-- containerItem.Name:find("Container")) then
|
||||
|
||||
debugPrint("Fallback: Found nested container: " .. containerItem.Name)
|
||||
table.insert(containers, containerItem)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- debugPrint("Fallback: Found nested container: " .. containerItem.Name)
|
||||
-- table.insert(containers, containerItem)
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
debugPrint("Found " .. #containers .. " containers" .. (CONFIG.NESTED_CONTAINERS and " (including nested)" or ""))
|
||||
if #containers == 0 then
|
||||
debugPrint("No containers with inventory found!")
|
||||
showNotification("No containers found! Make sure you have a backpack, toolbelt, or storage box.")
|
||||
return
|
||||
end
|
||||
-- debugPrint("Found " .. #containers .. " containers" .. (CONFIG.NESTED_CONTAINERS and " (including nested)" or ""))
|
||||
-- if #containers == 0 then
|
||||
-- debugPrint("No containers with inventory found!")
|
||||
-- showNotification("No containers found! Make sure you have a backpack, toolbelt, or storage box.")
|
||||
-- return
|
||||
-- end
|
||||
|
||||
-- Sort containers by priority
|
||||
containers = sortContainersByPriority(containers)
|
||||
for i, container in ipairs(containers) do
|
||||
debugPrint(i .. ": " .. container.Name .. " - priority container: " ..
|
||||
tostring(container.Prefab.Identifier.Value:find(CONFIG.PRIORITY_CONTAINERS[1]) ~= nil))
|
||||
end
|
||||
-- -- Sort containers by priority
|
||||
-- containers = sortContainersByPriority(containers)
|
||||
-- for i, container in ipairs(containers) do
|
||||
-- debugPrint(i .. ": " .. container.Name .. " - priority container: " ..
|
||||
-- tostring(container.Prefab.Identifier.Value:find(CONFIG.PRIORITY_CONTAINERS[1]) ~= nil))
|
||||
-- end
|
||||
|
||||
local itemsMoved = 0
|
||||
-- local itemsMoved = 0
|
||||
|
||||
-- Create a cache of processable items to avoid redundant checks
|
||||
local itemsToProcess = {}
|
||||
local processedIdentifiers = {}
|
||||
-- -- Create a cache of processable items to avoid redundant checks
|
||||
-- local itemsToProcess = {}
|
||||
-- local processedIdentifiers = {}
|
||||
|
||||
-- First collect all items we might want to stack
|
||||
for slotIndex, slot in ipairs(inventory.slots) do
|
||||
-- Skip hand slots
|
||||
local isHandSlot = false
|
||||
for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do
|
||||
if slotIndex == handSlot then
|
||||
isHandSlot = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if isHandSlot then
|
||||
debugPrint("Skipping hand slot: " .. slotIndex)
|
||||
goto continueSlot
|
||||
end
|
||||
-- -- First collect all items we might want to stack
|
||||
-- for slotIndex, slot in ipairs(inventory.slots) do
|
||||
-- -- Skip hand slots
|
||||
-- local isHandSlot = false
|
||||
-- for _, handSlot in ipairs(CONFIG.HAND_SLOTS) do
|
||||
-- if slotIndex == handSlot then
|
||||
-- isHandSlot = true
|
||||
-- break
|
||||
-- end
|
||||
-- end
|
||||
-- if isHandSlot then
|
||||
-- debugPrint("Skipping hand slot: " .. slotIndex)
|
||||
-- goto continueSlot
|
||||
-- end
|
||||
|
||||
-- Process items in the slot
|
||||
for i = #slot.items, 1, -1 do
|
||||
local item = slot.items[i]
|
||||
local identifierValue = item.Prefab.Identifier.Value
|
||||
-- -- Process items in the slot
|
||||
-- for i = #slot.items, 1, -1 do
|
||||
-- local item = slot.items[i]
|
||||
-- local identifierValue = item.Prefab.Identifier.Value
|
||||
|
||||
-- Skip container items
|
||||
if item.OwnInventory then
|
||||
debugPrint("Skipping container item: " .. item.Name)
|
||||
goto nextItem
|
||||
end
|
||||
-- -- Skip container items
|
||||
-- if item.OwnInventory then
|
||||
-- debugPrint("Skipping container item: " .. item.Name)
|
||||
-- goto nextItem
|
||||
-- end
|
||||
|
||||
-- Skip if we've already processed an item of this type
|
||||
if processedIdentifiers[identifierValue] then
|
||||
debugPrint("Already processed items of type: " .. identifierValue)
|
||||
goto nextItem
|
||||
end
|
||||
-- -- Skip if we've already processed an item of this type
|
||||
-- if processedIdentifiers[identifierValue] then
|
||||
-- debugPrint("Already processed items of type: " .. identifierValue)
|
||||
-- goto nextItem
|
||||
-- end
|
||||
|
||||
debugPrint("Adding to process list: " .. item.Name)
|
||||
table.insert(itemsToProcess, {
|
||||
item = item,
|
||||
slotIndex = slotIndex
|
||||
})
|
||||
-- debugPrint("Adding to process list: " .. item.Name)
|
||||
-- table.insert(itemsToProcess, {
|
||||
-- item = item,
|
||||
-- slotIndex = slotIndex
|
||||
-- })
|
||||
|
||||
-- Mark identifier as scheduled for processing
|
||||
processedIdentifiers[identifierValue] = true
|
||||
-- -- Mark identifier as scheduled for processing
|
||||
-- processedIdentifiers[identifierValue] = true
|
||||
|
||||
::nextItem::
|
||||
end
|
||||
-- ::nextItem::
|
||||
-- end
|
||||
|
||||
::continueSlot::
|
||||
end
|
||||
-- ::continueSlot::
|
||||
-- end
|
||||
|
||||
-- Now process the collected items - this greatly reduces redundant container checks
|
||||
for _, itemData in ipairs(itemsToProcess) do
|
||||
local item = itemData.item
|
||||
-- -- Now process the collected items - this greatly reduces redundant container checks
|
||||
-- for _, itemData in ipairs(itemsToProcess) do
|
||||
-- local item = itemData.item
|
||||
|
||||
debugPrint("Processing inventory item: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")")
|
||||
-- debugPrint("Processing inventory item: " .. item.Name .. " (" .. item.Prefab.Identifier.Value .. ")")
|
||||
|
||||
-- Try to move the item to each container
|
||||
for _, container in ipairs(containers) do
|
||||
debugPrint("Trying container: " .. container.Name)
|
||||
if moveItemToContainer(item, container.OwnInventory) then
|
||||
debugPrint("Stacked " .. item.Name .. " into " .. container.Name)
|
||||
itemsMoved = itemsMoved + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
-- -- Try to move the item to each container
|
||||
-- for _, container in ipairs(containers) do
|
||||
-- debugPrint("Trying container: " .. container.Name)
|
||||
-- if moveItemToContainer(item, container.OwnInventory) then
|
||||
-- debugPrint("Stacked " .. item.Name .. " into " .. container.Name)
|
||||
-- itemsMoved = itemsMoved + 1
|
||||
-- break
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
-- Check if there's an open container to stack from
|
||||
local openContainer = getOpenContainer()
|
||||
if openContainer then
|
||||
debugPrint("Found open container to stack from: " .. openContainer.Name)
|
||||
local openContainerItemsMoved = stackFromOpenContainer(character, containers, openContainer)
|
||||
itemsMoved = itemsMoved + openContainerItemsMoved
|
||||
else
|
||||
debugPrint("No open container found to stack from")
|
||||
end
|
||||
-- -- Check if there's an open container to stack from
|
||||
-- local openContainer = getOpenContainer()
|
||||
-- if openContainer then
|
||||
-- debugPrint("Found open container to stack from: " .. openContainer.Name)
|
||||
-- local openContainerItemsMoved = stackFromOpenContainer(character, containers, openContainer)
|
||||
-- itemsMoved = itemsMoved + openContainerItemsMoved
|
||||
-- else
|
||||
-- debugPrint("No open container found to stack from")
|
||||
-- end
|
||||
|
||||
if itemsMoved > 0 then
|
||||
debugPrint("Stacked " .. itemsMoved .. " items into containers")
|
||||
else
|
||||
debugPrint("No matching items to stack")
|
||||
end
|
||||
-- if itemsMoved > 0 then
|
||||
-- debugPrint("Stacked " .. itemsMoved .. " items into containers")
|
||||
-- else
|
||||
-- debugPrint("No matching items to stack")
|
||||
-- end
|
||||
end
|
||||
|
||||
-- Hook into player control to listen for key press
|
||||
|
||||
Reference in New Issue
Block a user