Compare commits

8 Commits
old ... master

10 changed files with 758 additions and 548 deletions

23
CykaQuick/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,23 @@
{
"highlight.regex.workspace.regexes": [
{
"name": "namedGroups",
"regexes": [
{
"regex": "(?:(\\?<[^>]+>)[^)]+)",
"decorations": [
{
"backgroundColor": "#000000",
"color": "#1CAFC4"
}
]
}
]
}
],
"Lua.diagnostics.libraryFiles": "Enable",
"Lua.workspace.library": [
"../Meta/Types/client",
"../Meta/Types/shared"
],
}

View File

@@ -7,7 +7,6 @@ if SERVER then
require("Cyka.xpticker")
else
---@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)

View File

@@ -4,7 +4,7 @@ if not CLIENT then return end
local dump = require("Cyka.dump")
local quickstack = require("Cyka.quickstack")
---@class ItemLocation
---@class ItemLocationDistance
---@field item Barotrauma.Item
---@field distance number

View File

@@ -60,7 +60,7 @@ local function tryStackCursorItem()
end
itemTree = quickstack.sortItemTree(itemTree)
local itemsToMove = {}
local options = {}
local now = Timer.GetTime()
for _, slot in ipairs(slots) do
local runAfter = slotThrottle[slot] or 0
@@ -68,7 +68,7 @@ local function tryStackCursorItem()
goto continue
end
-- MyModGlobal.debugPrint(string.format("Enqueuing slot: %s, before: %d", tostring(slot), #itemsToMove))
utils.enqueueSlot(slot.slot, itemsToMove)
options = utils.enqueueSlot(slot.inventory.slots[slot.slotIndex1], options)
-- MyModGlobal.debugPrint(string.format("Enqueuing slot: %s, after: %d", tostring(slot), #itemsToMove))
slotThrottle[slot] = now + 1
::continue::
@@ -79,7 +79,7 @@ local function tryStackCursorItem()
-- -- MyModGlobal.debugPrint(string.format("Enqueued %d items from the inventory slot", #itemsToMove))
-- MyModGlobal.DumpTable(itemTree)
quickstack.tryMoveItems(itemsToMove, itemTree, true)
quickstack.tryMoveItems(options.itemQueue, itemTree, true)
-- local errors = quickstack.tryMoveItems(itemsToMove, itemTree, true)
-- for _, error in ipairs(errors) do
-- MyModGlobal.debugPrint(string.format("Error moving item: %s", error))
@@ -101,14 +101,7 @@ local function setTargetInventory()
-- If we get multiple we'll use the first valid one
-- Although everything is valid to us...
for _, slot in ipairs(slots) do
local item
local items = slot.slot.items
if not items or #items == 0 then
print(string.format("Slot is empty, setting target inventory to %s", tostring(slot.inventory)))
targetInventory = slot.inventory
goto continue
end
item = items[1]
local item = slot.item
if not item then
print(string.format("Item in slot is nil, setting target inventory to %s", tostring(slot.inventory)))
targetInventory = slot.inventory

View File

@@ -3,7 +3,7 @@ if not CLIENT then return end
local utils = require("Cyka.utils")
local dump = require("Cyka.dump")
---@return {item: Barotrauma.Item, fabricator: Barotrauma.FabricatorComponent}, string?
---@return {item: Barotrauma.Item, fabricator: Barotrauma.Items.Components.Fabricator}?, string?
local function getOpenFabricator()
-- Get the controlled character
local controlledCharacter = Character.Controlled
@@ -29,8 +29,8 @@ end
---@field targetItem {identifier: string, name: string, amount: number}
---@field requiredItems {amount: number, minCondition: number, maxCondition: number, prefabs: string[]}[]
---@param fabricator Barotrauma.FabricatorComponent
---@return RecipeInfo, string?
---@param fabricator Barotrauma.Items.Components.Fabricator
---@return RecipeInfo?, string?
local function getSelectedRecipeRequirements(fabricator)
-- local openFabricator, err = getOpenFabricator()
-- if err then return nil, err end
@@ -73,7 +73,7 @@ local function tryStackFabricator(character)
MyModGlobal.debugPrint("Character inventory is nil.")
return
end
---@type Barotrauma.ItemInventory.Slot
---@type Barotrauma.Inventory.ItemSlot
local bagSlot = inventory.slots[MyModGlobal.BAG_SLOT]
if not bagSlot then
MyModGlobal.debugPrint("Bag slot not found.")
@@ -91,14 +91,14 @@ local function tryStackFabricator(character)
end
local fabricator, err = getOpenFabricator()
if err then
if err or not fabricator then
print(string.format("Error getting open fabricator: %s", err))
return
end
local recipe
recipe, err = getSelectedRecipeRequirements(fabricator.fabricator)
if err then
if err or not recipe then
print(string.format("Error getting selected recipe requirements: %s", err))
return
end
@@ -147,7 +147,10 @@ local function tryStackFabricator(character)
-- dump(itemsOnSubmarine)
-- MyModGlobal.DumpTable(toGet)
local items, _ = utils.enqueueAllOwnedItems({}, filter)
local items, _ = utils.enqueueAllOwnedItems({
recurse = true,
itemPredicate = filter
})
-- if err then
-- print(string.format("Error enqueueing all owned items: %s", err))
-- return

View File

@@ -4,12 +4,12 @@ if not CLIENT then return end
---@return Barotrauma.Location.StoreInfo[], string?
local function getCurrentStore()
if not Game or not Game.GameSession or not Game.GameSession.Campaign then
return nil, "No game session found"
return {}, "No game session found"
end
local map = Game.GameSession.Campaign.Map
if not map or not map.CurrentLocation or not map.CurrentLocation.Stores then
return nil, "No map found"
return {}, "No map found"
end
local location = map.CurrentLocation
@@ -17,13 +17,13 @@ local function getCurrentStore()
-- Otherwise, determine which store is active by checking the cargo manager
local cargoManager = Game.GameSession.Campaign.CargoManager
if not cargoManager then
return nil, "No cargo manager found"
return {}, "No cargo manager found"
end
-- Find which store has items in the cart
local stores = {}
for _, store in pairs(location.Stores) do
if #cargoManager:GetBuyCrateItems(store) > 0 then
if #cargoManager:GetBuyCrateItems() > 0 then
stores[#stores + 1] = store
end
end
@@ -47,7 +47,7 @@ local function tryBuy()
for _, store in ipairs(stores) do
local toAdd = {}
-- Get items available at the store
local items = cargoManager:GetBuyCrateItems(store)
local items = cargoManager:GetBuyCrateItems()
for item in items do
-- We have already added this many of item
toAdd[item.ItemPrefab.Identifier.Value] = {
@@ -55,6 +55,7 @@ local function tryBuy()
prefab = item.ItemPrefab -- Store the ItemPrefab object
}
end
---@diagnostic disable-next-line: undefined-field
for item in store.Stock do
-- So if we add the total amount available
-- We get the amount we have to add to buy entire stock
@@ -68,6 +69,7 @@ local function tryBuy()
if info.quantity > 0 then
MyModGlobal.debugPrint(string.format("Adding %d of %s to the buy crate", info.quantity, idValue))
-- Use the stored ItemPrefab object, not the string identifier
---@diagnostic disable-next-line: undefined-field
cargoManager:ModifyItemQuantityInBuyCrate(store.Identifier, info.prefab, info.quantity)
end
end

View File

@@ -16,14 +16,13 @@ local LOAD_MAP = require("Cyka.quickreload_loadmap")
---@param inventory Barotrauma.ItemInventory
---@return InventorySlot[]
local function getSlots(inventory)
---@type InventorySlot[]
local slots = {}
---@type Barotrauma.Inventory.ItemSlot[]
local inventorySlots = inventory.slots
for i, inventorySlot in ipairs(inventorySlots) do
slots[#slots + 1] = {
inventory = inventory,
slotIndex = i - 1,
slot = inventorySlot
}
for i, _ in ipairs(inventorySlots) do
local invSlot = MyModGlobal.InventorySlot.new(inventory, i)
slots[#slots + 1] = invSlot
end
return slots
end
@@ -36,7 +35,11 @@ local function getItemsPerSlot(slots)
---@type table<InventorySlot, Barotrauma.Item[]>
local movableBySlot = {}
-- Get all the items and then we will sort them by condition and shit
utils.enqueueAllPlayerItems({}, function(ititem, itemRef)
utils.enqueuePlayerItems({
recurse = true,
loadRefs = true,
itemPredicate = function(ititem, itemRef)
MyModGlobal.debugPrint(string.format("Checking item: %s", tostring(ititem)))
-- We don't want to take oxygen out of our diving suit to load our plasma cutter
-- Most loadable items have 1 capacity
-- But some have 2 or 3 (coil speargun)
@@ -48,7 +51,7 @@ local function getItemsPerSlot(slots)
-- dump(slots)
-- MyModGlobal.debugPrint(ititem.Prefab.Identifier.Value)
for _, inventorySlot in ipairs(slots) do
local canMove = inventorySlot.inventory.CanBePutInSlot(ititem, inventorySlot.slotIndex)
local canMove = inventorySlot.inventory.CanBePutInSlot(ititem, inventorySlot.slotIndex1 - 1)
-- MyModGlobal.debugPrint(string.format("Can move to slot %d: %s", inventorySlot.slotIndex, tostring(canMove)))
if canMove then
movableBySlot[inventorySlot] = movableBySlot[inventorySlot] or {}
@@ -57,7 +60,8 @@ local function getItemsPerSlot(slots)
end
end
return false
end, true)
end
})
return movableBySlot
end
@@ -93,11 +97,11 @@ local function printPermissibleItems(movableBySlot)
end
---@param slot InventorySlot
---@param invSlot InventorySlot
---@param preferMinCondition boolean
local function tryReloadSlot(slot, preferMinCondition)
local function tryReloadSlot(invSlot, preferMinCondition)
---@type Barotrauma.Item
local item = slot.slot.items[1]
local item = invSlot.item
if not item then
MyModGlobal.debugPrint("No item in slot")
return
@@ -115,13 +119,10 @@ local function tryReloadSlot(slot, preferMinCondition)
MyModGlobal.debugPrint("No slots")
return
end
-- MyModGlobal.debugPrint("Slots:")
-- dump(slots)
---@type table<InventorySlot, Barotrauma.Item[]>
local movableBySlot = getItemsPerSlot(slots)
-- MyModGlobal.debugPrint("Movable by slot:")
-- dump(movableBySlot)
local permissibleItems = LOAD_MAP[tostring(item.Prefab.Identifier.Value)]
if not permissibleItems then
@@ -156,22 +157,19 @@ local function tryReloadSlot(slot, preferMinCondition)
-- We loaded as many as we have been allowed to
-- And we do this check up front because an item may already
-- Be partially loaded
local nowHave = #inventorySlot.slot.items
local nowHave = inventorySlot.stackSize
if nowHave >= permissible then
-- MyModGlobal.debugPrint(string.format(
-- "Finished processing item: %s. Current slot has reached the permissible limit of %d items.",
-- tostring(ititem.Prefab.Identifier.Value), permissible))
MyModGlobal.debugPrint(string.format(
"Finished processing item: %s. Current slot has reached the permissible limit of %d items.",
tostring(ititem.Prefab.Identifier.Value), permissible))
break
end
local moved = inventorySlot.inventory.TryPutItem(ititem, inventorySlot.slotIndex, false, true, Character.Controlled, true)
-- When the slot is full no more will be able to be moved
-- And tat that point we're done with that slot
if not moved then break end
if not inventorySlot:canFit(ititem.Prefab) then
break
end
utils.enqueueMove(ititem, inventorySlot)
numMoved = numMoved + 1
-- else
-- MyModGlobal.debugPrint(string.format("Not permissible: %s", tostring(ititem.Prefab.Identifier.Value)))
end
end
end

View File

@@ -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<string, ItemLocation[]>
---@param depth number
---@return table<string, ItemLocation[]>
---@param inventory Barotrauma.Inventory
---@param itemTree? table<string, InventorySlot[]>
---@param depth? number
---@return table<string, InventorySlot[]>
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<string, ItemLocation[]>
---@return table<string, ItemLocation[]>
---@param itemTree table<string, InventorySlot[]>
---@return table<string, InventorySlot[]>
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<string, ItemLocation[]>
---@param itemTree table<string, InventorySlot[]>
---@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,23 +102,13 @@ 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)
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
-- 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
end
end
end
@@ -149,29 +120,17 @@ 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)
local canFit = itemLocation:canFit(item.Prefab)
if canFit then
utils.enqueueMove(item, itemLocation)
moved = true
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
end
end
end
@@ -185,8 +144,8 @@ local function tryMoveItem(item, itemTree, force)
end
---@param items Barotrauma.Item[]
---@param itemTree table<string, ItemLocation[]>
---@param force boolean
---@param itemTree table<string, InventorySlot[]>
---@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, ItemLocation[]>, string
---@return table<string, InventorySlot[]>, 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

View File

@@ -2,106 +2,49 @@
if not CLIENT then return end
local utils = require("Cyka.utils")
local dump = require("Cyka.dump")
---@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 = {}
---@param invSlot InventorySlot
local function tryUnloadSlot(invSlot)
---@type table<Barotrauma.ItemPrefab, boolean>
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
local itemInventory = invSlot.item.OwnInventory
if not itemInventory then
MyModGlobal.debugPrint("No inventory for item")
return
end
MyModGlobal.debugPrint(string.format("Enqueuing inventory %s", tostring(itemInventory)))
local toUnload = utils.enqueueInventory(itemInventory, {
itemPredicate = function(item)
toUnloadByPrefab[item.Prefab] = true
return true
end,
recurse = false,
})
MyModGlobal.debugPrint(string.format("Moving %d items to unload %s", #toUnload.itemQueue, tostring(invSlot.item)))
-- Where can we put our toUnload items?
local nearbySlots = findSlotsThat(slot.inventory, function(islot)
local isEmpty = #islot.slot.items == 0
local nearbySlots = invSlot:getNearbySlots(function(islot)
local isEmpty = not islot.item
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
for prefab, _ in pairs(toUnloadByPrefab) do
local canFit = islot:canFit(prefab)
if canFit then return true end
end
return false
end)
-- print("Before sorting:")
-- dump(nearbySlots)
MyModGlobal.debugPrint(string.format("Into %d nearby slots", #nearbySlots))
-- Some inventories don't have slots per row, like the player inventory
local slotsPerRow = 900
local ok, err = pcall(function()
slotsPerRow = slot.inventory.slotsPerRow
end)
if not ok then
MyModGlobal.debugPrint(string.format("Error getting slots per row: %s", err))
end
local getGridPos = function(slotIndex)
local x = slotIndex % slotsPerRow
local y = math.floor(slotIndex / slotsPerRow)
return x, y
end
-- We are offsetting here by 1 because the backend uses 0-indexed slots
-- And the lua uses 1-indexed slots
-- We are trying to match the backend behavior for sorting
local slotx, sloty = getGridPos(slot.slotIndex - 1)
-- print(string.format("Slot position %d: %d, %d", slot.slotIndex, slotx, sloty))
table.sort(nearbySlots, function(a, b)
local ax, ay = getGridPos(a.slotIndex)
local bx, by = getGridPos(b.slotIndex)
local distA = math.max(math.abs(ax - slotx), math.abs(ay - sloty))
local distB = math.max(math.abs(bx - slotx), math.abs(by - sloty))
if distA == distB then
return a.slotIndex < b.slotIndex
end
return distA < distB
end)
-- print(string.format("Current slot: %d at (%d, %d)", slot.slotIndex, slotx, sloty))
for _, iitem in ipairs(toUnload) do
for _, iitem in ipairs(toUnload.itemQueue) do
for _, nearbySlot in ipairs(nearbySlots) do
local canAccept = nearbySlot.inventory.CanBePutInSlot(iitem.Prefab, nearbySlot.slotIndex)
local canAccept = nearbySlot:canFit(iitem.Prefab)
if canAccept then
local moved = nearbySlot.inventory.TryPutItem(iitem, nearbySlot.slotIndex, true, false,
Character.Controlled, true)
-- print(string.format("Moved item %s to slot %d", iitem.Name, nearbySlot.slotIndex))
if moved then break end
utils.enqueueMove(iitem, nearbySlot)
break
end
end
end
@@ -110,12 +53,12 @@ end
local function tryUnloadCursorItem()
local slots, err = utils.getSlotsUnderCursor()
if err then
-- MyModGlobal.debugPrint(string.format("Error getting inventory slot: %s", err))
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")
MyModGlobal.debugPrint("No items in slot")
return
end

View File

@@ -1,15 +1,290 @@
-- luacheck: globals Character MyModGlobal
-- luacheck: globals Character MyModGlobal Timer _
-- luacheck: max line length 420
---@class ItemRefs
---@field item Barotrauma.Item
---@field inventory Barotrauma.ItemInventory
---@field slot Barotrauma.ItemInventory.Slot
---@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
---@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
---@param slotIndex1 number
---@return InventorySlot
new = function(inventory, slotIndex1)
local self = setmetatable({}, {
__index = MyModGlobal.InventorySlot
})
self.inventory = inventory
self.slotIndex1 = slotIndex1
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
self.slot = inventory.slots[slotIndex1]
end
if self.slot and self.slot.items and #self.slot.items > 0 then
self.item = self.slot.items[1]
self.stackSize = #self.slot.items
-- At this point inventory has to exist
-- If it didn't slot wouldn't either and then this wouldn't either
self.maxStackSize = self.item.Prefab.GetMaxStackSize(inventory)
end
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)
if not self.inventory then
MyModGlobal.debugPrint("Error pretending moved but it was moved to nil inventory")
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)
end
self.item = item
self.stackSize = self.stackSize + 1
end,
update = function(self)
-- self.lastUpdated = Timer.GetTime()
if not self.inventory then
MyModGlobal.debugPrint("Error updating inventory slot, inventory not found")
return
end
if not self.inventory.slots then
MyModGlobal.debugPrint("Error updating inventory slot, inventory has no slots")
return
end
local slot = self.inventory.slots[self.slotIndex1]
if not slot then
MyModGlobal.debugPrint("Error updating inventory slot, slot not found")
return
end
self.slot = slot
if not slot.items or #slot.items == 0 then
-- MyModGlobal.debugPrint("Error updating inventory slot, slot is empty")
return
end
self.item = slot.items[1]
self.stackSize = #slot.items
self.maxStackSize = self.item.Prefab.GetMaxStackSize(self.inventory)
end,
__tostring = function(self)
return string.format(
"InventorySlot(inventory=%s, item=%s, stackSize=%d, maxStackSize=%d, slotIndex1=%d, slotIndex0=%d)",
tostring(self.inventory), tostring(self.item), self.stackSize, self.maxStackSize, self.slotIndex1,
self.slotIndex0)
end,
---@param self InventorySlot
---@param predicate? fun(slot: InventorySlot): boolean
---@return InventorySlot[]
getNearbySlots = function(self, predicate)
predicate = predicate or function() return true end
local slotsPerRow = 900
local ok, err = pcall(function()
slotsPerRow = self.inventory.slotsPerRow
end)
if not ok then
MyModGlobal.debugPrint(string.format("Error getting slots per row: %s", err))
end
local getGridPos = function(slotIndex)
local x = slotIndex % slotsPerRow
local y = math.floor(slotIndex / slotsPerRow)
return x, y
end
local slots = {}
for slotIndex, _ in ipairs(self.inventory.slots) do
local inventorySlot = MyModGlobal.InventorySlot.new(self.inventory, slotIndex)
if predicate(inventorySlot) then
slots[#slots + 1] = inventorySlot
end
end
local slotx, sloty = getGridPos(self.slotIndex0)
table.sort(slots, function(a, b)
local ax, ay = getGridPos(a.slotIndex0)
local bx, by = getGridPos(b.slotIndex0)
-- Chebyshev distance
local distA = math.max(math.abs(ax - slotx), math.abs(ay - sloty))
local distB = math.max(math.abs(bx - slotx), math.abs(by - sloty))
if distA == distB then
return a.slotIndex0 < b.slotIndex0
end
return distA < distB
end)
return slots
end,
--- TODO: What about item condition?
---@param self InventorySlot
---@param itemPrefab Barotrauma.ItemPrefab
---@return number
howManyCanFit = function(self, itemPrefab)
-- There is an item in the slot and it's not stackable with itemPrefab
if self.item and not self.item.Prefab.Equals(itemPrefab) then
return 0
end
-- The slot is empty - we can fit as many as the game tells us
if not self.item then
return itemPrefab.GetMaxStackSize(self.inventory)
end
-- The slot has an item that is stackable with itemPrefab
-- We can fit as many as to fill the stack
return self.maxStackSize - self.stackSize
end,
---@param self InventorySlot
---@param itemPrefab Barotrauma.ItemPrefab
---@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)
-- end
}
---@class ItemMoveRequest
---@field what Barotrauma.Item
---@field where InventorySlot
---@field allowSwap boolean
---@field allowCombine boolean
local enqueueMove
do
-- A bit of cheeky scoping
local enabled = true
---@type ItemMoveRequest[]
local itemMoveQueue = {}
---@type table<Barotrauma.Item, boolean>
local itemLookup = {}
local rate = 500
local perIteration = 30
local noQueue = true
-- rate / 1000 is ms to seconds and *perIteraion is number of items per second
local maxQueueSize = 10 * (1000 / rate * perIteration)
local function processQueue()
if noQueue then return end
-- MyModGlobal.debugPrint("Processing queue")
Timer.Wait(processQueue, rate)
if not enabled then return end
if #itemMoveQueue == 0 then return end
local iterations = math.min(perIteration, #itemMoveQueue)
for _ = 1, iterations do
---@type ItemMoveRequest
local moveRequest = table.remove(itemMoveQueue, 1)
-- TODO: Maybe try and figure out if we CAN put A into B
moveRequest.allowCombine = moveRequest.allowCombine or false
moveRequest.allowSwap = moveRequest.allowSwap or false
local success = moveRequest.where.inventory.TryPutItem(moveRequest.what, moveRequest.where.slotIndex0,
moveRequest.allowSwap, moveRequest.allowCombine, nil)
if not success then
MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(moveRequest.what),
tostring(moveRequest.where:__tostring())))
end
itemLookup[moveRequest.what] = nil
end
end
processQueue()
---@param what Barotrauma.Item
---@param where InventorySlot
---@param allowSwap? boolean
---@param allowCombine? boolean
enqueueMove = function(what, where, allowSwap, allowCombine)
allowCombine = allowCombine == true
allowSwap = allowSwap == true
if noQueue then
local success = where.inventory.TryPutItem(what, where.slotIndex0,
allowSwap, allowCombine, nil)
if not success then
MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(what),
tostring(where:__tostring())))
end
where:pretendMoved(what)
else
if #itemMoveQueue >= maxQueueSize then
MyModGlobal.debugPrint("Queue is full, skipping move")
return
end
if itemLookup[what] then
MyModGlobal.debugPrint("Item is already in the queue, skipping move")
return
end
MyModGlobal.debugPrint(string.format("Enqueuing move from %s to %s, now in queue %d/%d", tostring(what),
tostring(where:__tostring()), #itemMoveQueue, maxQueueSize))
table.insert(itemMoveQueue, {
what = what,
where = where,
allowSwap = allowSwap or false,
allowCombine = allowCombine ~= false,
})
itemLookup[what] = true
-- We will very optimistically pretend that this will 100% for sure work
where:pretendMoved(what)
end
end
end
---@class InventorySlot
---@field slot Barotrauma.ItemSlot
---@field inventory Barotrauma.ItemInventory
---@field slotIndex number
---@return Barotrauma.Item[], string?
local function getOpenContainers()
@@ -23,7 +298,9 @@ end
---@return Barotrauma.Item, string?
local function getFirstOpenContainer()
local containers, err = getOpenContainers()
---@diagnostic disable-next-line: return-type-mismatch
if err then return nil, err end
---@diagnostic disable-next-line: return-type-mismatch
if #containers == 0 then return nil, "No open containers" end
return containers[1], nil
end
@@ -32,194 +309,232 @@ end
-- And enqueueItem calls enqueueInventory
-- So unless we define them both before using them
-- We will get an error saying either is undefined
-- TODO: Rework these enqueue functions to accept a params object
-- That will house all optional parameters
-- And in that include recurse boolean
---@class ItemRefs
---@field item Barotrauma.Item
---@field inventory Barotrauma.Inventory
---@field slot Barotrauma.Inventory.ItemSlot
---@field slotIndex1 number
---@class EnqueueOptions
---@field itemQueue? Barotrauma.Item[]
---@field slotQueue? Barotrauma.Inventory.ItemSlot[]
---@field inventoryQueue? Barotrauma.Inventory[]
---@field itemPredicate? fun(item: Barotrauma.Item, itemRef: ItemRefs): boolean
---@field slotPredicate? fun(slot: Barotrauma.Inventory.ItemSlot, itemRef: ItemRefs): boolean
---@field inventoryPredicate? fun(inventory: Barotrauma.Inventory, itemRef: ItemRefs): boolean
---@field loadRefs? boolean
---@field itemRef? ItemRefs
---@field recurse? boolean
---@param options EnqueueOptions
---@return EnqueueOptions
local function ensureOptionsDefaults(options)
options = options or {}
options.itemQueue = options.itemQueue or {}
options.slotQueue = options.slotQueue or {}
options.inventoryQueue = options.inventoryQueue or {}
options.itemPredicate = options.itemPredicate or function() return true end
options.slotPredicate = options.slotPredicate or function() return true end
options.inventoryPredicate = options.inventoryPredicate or function() return true end
options.loadRefs = options.loadRefs == true
options.itemRef = options.itemRef or {}
options.recurse = options.recurse == true
return options
end
local enqueueItem
local enqueueSlot
local enqueueInventory
local enqueuePlayerItems
local enqueueOpenContainers
local allPlayerItems
local allSubmarineItems
local allOwnedItems
local _
local enqueueSubmarineItems
local enqueueAllOwnedItems
---@alias FilterPredicate fun(item: Barotrauma.Item, inventoryRef?: Barotrauma.ItemInventory, slotRef: Barotrauma.ItemInventory.Slot): boolean
do
---@param item Barotrauma.Item
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueItem = function(item, options)
options = ensureOptionsDefaults(options)
if not item then return options, "No item" end
-- Loading refs is optional because it MAY have a performance impact
---@param item Barotrauma.Item
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueItem = function(item, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- debugPrint(string.format("Enqueuing item: %s", item.Prefab.Identifier.Value))
-- local err
-- This should make it breadth first, right...?
-- No, not yet...
if not item then return queue, "No item" end
local ok, stop = predicate(item, itemRef)
local ok, stop = options.itemPredicate(item, options.itemRef)
if ok then
queue[#queue + 1] = item
options.itemQueue[#options.itemQueue + 1] = item
end
if stop then return queue, "Stop" end
if stop then return options, "Stop" end
local err
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...")
if loadRefs then
itemRef.item = item
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate, loadRefs, itemRef)
if options.recurse then
if options.loadRefs then
options.itemRef.item = item
options.inventoryQueue, err = enqueueInventory(item.OwnInventory, options)
else
queue, _ = enqueueInventory(item.OwnInventory, queue, predicate, itemRef)
options.inventoryQueue, err = enqueueInventory(item.OwnInventory, options)
end
-- 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
end
return options, err
end
---@param slot Barotrauma.ItemInventory.Slot
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueSlot = function(slot, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- 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
if not slot then return queue, "No slot" end
if not slot.items then return queue, "No items" end
---@param slot Barotrauma.Inventory.ItemSlot
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueSlot = function(slot, options)
options = ensureOptionsDefaults(options)
if not slot then return options, "No slot" end
if not slot.items then return options, "No items" end
local ok, stop = options.slotPredicate(slot, options.itemRef)
if ok then
options.slotQueue[#options.slotQueue + 1] = slot
end
if stop then return options, "Stop" end
for _, item in ipairs(slot.items) do
-- Only the final leaf nodes decide upon the predicate
if loadRefs then
itemRef.slot = slot
queue, err = enqueueItem(item, queue, predicate, loadRefs, itemRef)
else
queue, err = enqueueItem(item, queue, predicate)
end
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? FilterPredicate
---@param loadRefs? boolean
---@param itemRef? ItemRefs
---@return Barotrauma.Item[], string?
enqueueInventory = function(inventory, queue, predicate, loadRefs, itemRef)
queue = queue or {}
predicate = predicate or function() return true end
itemRef = itemRef or {}
-- debugPrint(string.format("Enqueuing inventory with %d slots.", #inventory.slots))
-- We redeclare err every iteration so it doesn't spill over
local err
if not inventory then return queue, "No inventory" end
if not inventory.slots then return queue, "No slots" end
for _, slot in ipairs(inventory.slots) do
-- Only the final leaf nodes decide upon the predicate
if loadRefs then
itemRef.inventory = inventory
queue, err = enqueueSlot(slot, queue, predicate, loadRefs, itemRef)
if options.loadRefs then
options.itemRef.slot = slot
options, err = enqueueItem(item, options)
else
queue, err = enqueueSlot(slot, queue, predicate)
options, err = enqueueItem(item, options)
end
if err then
return queue, err
return options, err
end
end
-- debugPrint(string.format("Finished enqueuing inventory. Current queue size: %d", #queue))
return queue
end
return options
end
local relevantPlayerInventorySlots = { 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, MyModGlobal.BAG_SLOT }
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@return Barotrauma.Item[], string?
allPlayerItems = function(queue, predicate, loadRefs)
queue = queue or {}
predicate = predicate or function() return true end
---@param inventory Barotrauma.Inventory
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueInventory = function(inventory, options)
options = ensureOptionsDefaults(options)
if not inventory then return options, "No inventory" end
if not inventory.slots then return options, "No slots" end
local ok, stop = options.inventoryPredicate(inventory, options.itemRef)
if ok then
options.inventoryQueue[#options.inventoryQueue + 1] = inventory
end
if stop then return options, "Stop" end
for i, slot in ipairs(inventory.slots) do
local err
if options.loadRefs then
options.itemRef.inventory = inventory
options.itemRef.slot = slot
options.itemRef.slotIndex1 = i
options, err = enqueueSlot(slot, options)
else
options, err = enqueueSlot(slot, options)
end
if err then
return options, err
end
end
return options
end
local relevantPlayerInventorySlots = {
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
}
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueuePlayerItems = function(options)
options = ensureOptionsDefaults(options)
local character = Character.Controlled
if not character then return queue, "No character" end
if not character then return options, "No character" end
local inventory = character.Inventory
if not inventory then return queue, "No inventory" end
if not inventory then return options, "No inventory" end
for _, slotid in ipairs(relevantPlayerInventorySlots) do
local slot = inventory.slots[slotid]
local err
if not slot then goto continue end
if #slot.items == 0 then goto continue end
queue, err = enqueueSlot(slot, queue, predicate, loadRefs)
if err then return queue, err end
::continue::
options.loadRefs = true
local originalItemPredicate = options.itemPredicate or function() return true end
options.itemPredicate = function(item)
if not item then return false end
local parentInventory = item.ParentInventory
if not parentInventory then return false end
if not parentInventory.Equals(inventory) then return false end
return originalItemPredicate(item, options.itemRef)
end
local originalSlotPredicate = options.slotPredicate or function() return true end
options.slotPredicate = function(slot, itemRef)
if not slot then return false end
if itemRef.slotIndex1 and relevantPlayerInventorySlots[itemRef.slotIndex1] then
return originalSlotPredicate(slot, itemRef)
end
return false
end
return queue
end
local err
options, err = enqueueInventory(inventory, options)
if err then return options, err end
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@return Barotrauma.Item[], string?
enqueueOpenContainers = function(queue, predicate, loadRefs)
queue = queue or {}
predicate = predicate or function() return true end
return options
end
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueOpenContainers = function(options)
options = ensureOptionsDefaults(options)
local containers, err = getOpenContainers()
if err then return queue, err end
if err then return options, err end
for _, container in ipairs(containers) do
local inventories = container.OwnInventories
if not inventories then goto continue end
for containerInventory in inventories do
queue, err = enqueueInventory(containerInventory, queue, predicate, loadRefs)
if err then return queue, err end
options, err = enqueueInventory(containerInventory, options)
if err then return options, err end
end
::continue::
end
return queue
end
return options
end
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueSubmarineItems = function(options)
options = ensureOptionsDefaults(options)
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@return Barotrauma.Item[], string?
allSubmarineItems = function(queue, predicate)
queue = queue or {}
predicate = predicate or function() return true end
-- This only exists so predicate does not explode
-- Even if its empty
local itemRef = {}
local character = Character.Controlled
if not character then return queue, "No character" end
if not character then return options, "No character" end
local submarine = character.Submarine
if not submarine then return queue, "No submarine" end
if not submarine then return options, "No submarine" end
for item in submarine.GetItems(false) do
-- We do NOT want to call enqueueItem here because enqueueItem
@@ -227,32 +542,30 @@ allSubmarineItems = function(queue, predicate)
-- And this call (GetItems) already gets all items
-- So we would be doing double the work (at best case)
-- It also means we won't have refs here which sucks
local ok, stop = predicate(item, itemRef)
local ok, stop = options.itemPredicate(item, itemRef)
if ok then
queue[#queue + 1] = item
options.itemQueue[#options.itemQueue + 1] = item
end
if stop then return queue, "Stop" end
if stop then return options, "Stop" end
end
return queue
end
return options
end
---@param queue Barotrauma.Item[]
---@param predicate? FilterPredicate
---@param loadRefs? boolean
---@return Barotrauma.Item[], string?
allOwnedItems = function(queue, predicate, loadRefs)
queue = queue or {}
predicate = predicate or function() return true end
---@param options EnqueueOptions
---@return EnqueueOptions, string?
enqueueAllOwnedItems = function(options)
options = ensureOptionsDefaults(options)
local err
queue, err = allPlayerItems(queue, predicate, loadRefs)
if err then return queue, err end
options, err = enqueuePlayerItems(options)
if err then return options, err end
queue, err = allSubmarineItems(queue, predicate)
if err then return queue, err end
options, err = enqueueSubmarineItems(options)
if err then return options, err end
return queue
return options
end
end
-- There is actually no need to recurse deep
@@ -260,8 +573,8 @@ end
-- And not an item in an item in the inventory
-- So in theory we only need to recurse 1 deep
---@param inventory Barotrauma.Inventory
---@param slots InventorySlot[]
---@param depth number
---@param slots? InventorySlot[]
---@param depth? number
---@return InventorySlot[], string?
local function getMouseoverSlots(inventory, slots, depth)
slots = slots or {}
@@ -269,7 +582,7 @@ local function getMouseoverSlots(inventory, slots, depth)
if depth > 1 then return slots, nil end
local visualSlots = inventory.visualSlots
if not visualSlots then return nil, "Inventory has no visual slots" end
if not visualSlots then return slots, "Inventory has no visual slots" end
for i, visualSlot in ipairs(visualSlots) do
local item
@@ -305,11 +618,8 @@ local function getMouseoverSlots(inventory, slots, depth)
::mouseover::
if visualSlot:MouseOn() then
slots[#slots + 1] = {
inventory = inventory,
slotIndex = i,
slot = slot
}
local inventorySlot = MyModGlobal.InventorySlot.new(inventory, i)
slots[#slots + 1] = inventorySlot
end
::continue::
@@ -320,15 +630,17 @@ end
---@return InventorySlot[], string?
local function getSlotsUnderCursor()
local slots = {}
-- Make sure we have a controlled character
local controlledCharacter = Character.Controlled
if not controlledCharacter then return nil, "No controlled character" end
if not controlledCharacter then return slots, "No controlled character" end
local inventory = controlledCharacter.Inventory
if not inventory then return nil, "No inventory" end
if not inventory then return slots, "No inventory" end
local mouseoverSlots, err = getMouseoverSlots(inventory)
if err then return mouseoverSlots, err end
local err
slots, err = getMouseoverSlots(inventory, slots)
if err then return slots, err end
-- Even if we don't get them we're still fine
local openContainers, _ = getOpenContainers()
@@ -337,35 +649,30 @@ local function getSlotsUnderCursor()
for _, container in ipairs(openContainers) do
local containerInventories = container.OwnInventories
for containerInventory in containerInventories do
local slot
if not containerInventory or not containerInventory.visualSlots then
MyModGlobal.debugPrint("Container inventory has no visual slots")
goto continue
end
for i, visualSlot in ipairs(containerInventory.visualSlots) do
if visualSlot:MouseOn() then
slot = containerInventory.slots[i]
mouseoverSlots[#mouseoverSlots + 1] = {
inventory = containerInventory,
slotIndex = i,
slot = slot
}
local inventorySlot = MyModGlobal.InventorySlot.new(containerInventory, i)
slots[#slots + 1] = inventorySlot
end
end
::continue::
end
end
return mouseoverSlots, nil
return slots, nil
end
---@return InventorySlot, string?
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
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
@@ -376,12 +683,13 @@ return {
enqueueItem = enqueueItem,
enqueueSlot = enqueueSlot,
enqueueInventory = enqueueInventory,
enqueueAllPlayerItems = allPlayerItems,
enqueueAllSubmarineItems = allSubmarineItems,
enqueueAllOwnedItems = allOwnedItems,
enqueuePlayerItems = enqueuePlayerItems,
enqueueSubmarineItems = enqueueSubmarineItems,
enqueueAllOwnedItems = enqueueAllOwnedItems,
enqueueOpenContainers = enqueueOpenContainers,
getOpenContainers = getOpenContainers,
getFirstOpenContainer = getFirstOpenContainer,
getSlotsUnderCursor = getSlotsUnderCursor,
getFirstSlotUnderCursor = getFirstSlotUnderCursor,
enqueueMove = enqueueMove,
}