Fix up quickload to work with the reworked queues

This commit is contained in:
2025-04-01 19:48:56 +02:00
parent 856dc8b305
commit 50d84f5ba5
3 changed files with 126 additions and 126 deletions

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

@@ -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 = islot.slot.items and #islot.slot.items == 0
if isEmpty then return true end
for _, prefab in ipairs(toUnloadByPrefab) do
local canAccept = islot.inventory.CanBePutInSlot(prefab, islot.slotIndex)
if canAccept then return true end
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

@@ -25,6 +25,8 @@ MyModGlobal.InventorySlot = {
self.inventory = inventory
self.slotIndex1 = slotIndex1
self.slotIndex0 = slotIndex1 - 1
self.stackSize = 0
self.maxStackSize = 0
-- self:update()
if inventory and inventory.slots and #inventory.slots > 0 then
@@ -40,6 +42,24 @@ MyModGlobal.InventorySlot = {
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
if not self.slot then
MyModGlobal.debugPrint("Error pretending moved but it was moved to nil slot")
return
end
-- Slot was previously empty, we want to figure out its max stack for the new item
if not self.item then
self.maxStackSize = item.Prefab.GetMaxStackSize(self.inventory)
end
self.item = item
self.stackSize = self.stackSize + 1
end,
update = function(self)
-- self.lastUpdated = Timer.GetTime()
if not self.inventory then
@@ -71,7 +91,9 @@ MyModGlobal.InventorySlot = {
tostring(self.inventory), self.slotIndex1, self.slotIndex0, tostring(self.item), self.stackSize,
self.maxStackSize)
end,
---@param self InventorySlot
---@param predicate? fun(slot: InventorySlot): boolean
---@return InventorySlot[]
getNearbySlots = function(self, predicate)
predicate = predicate or function() return true end
@@ -114,14 +136,44 @@ MyModGlobal.InventorySlot = {
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
MyModGlobal.debugPrint(string.format(
"%s can fit 0 of %s because it already contains an item that is not stackable with %s (%s)",
tostring(self), tostring(itemPrefab), tostring(itemPrefab), tostring(self.item.Prefab)))
return 0
end
-- The slot is empty - we can fit as many as the game tells us
if not self.item then
MyModGlobal.debugPrint(string.format("%s can fit %d of %s because it is empty", tostring(self),
itemPrefab.GetMaxStackSize(self.inventory), tostring(itemPrefab)))
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
MyModGlobal.debugPrint(string.format("%s can fit %d of %s because it contains %d items", tostring(self),
self.maxStackSize - self.stackSize, tostring(itemPrefab), self.stackSize))
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
-- hash = function(self)
-- return string.format("%s:%d:%d", tostring(self.inventory), self.slotIndex1, self.slotIndex0)
-- end
}
---@class ItemMoveRequest
---@field A InventorySlot
---@field B InventorySlot
---@field what Barotrauma.Item
---@field where InventorySlot
---@field allowSwap boolean
---@field allowCombine boolean
@@ -131,39 +183,45 @@ do
local enabled = true
---@type ItemMoveRequest[]
local itemMoveQueue = {}
local rate = 10
local rate = 100
local perIteration = 6
local function processQueue()
MyModGlobal.debugPrint("Processing queue")
-- MyModGlobal.debugPrint("Processing queue")
Timer.Wait(processQueue, rate)
if not enabled then return end
if #itemMoveQueue == 0 then return end
---@type ItemMoveRequest
local moveRequest = table.remove(itemMoveQueue, 1)
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.B.inventory.TryPutItem(moveRequest.A.item, moveRequest.B.slotIndex0,
moveRequest.allowSwap, moveRequest.allowCombine, Character.Controlled, true)
if not success then
MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(moveRequest.A),
tostring(moveRequest.B)))
-- 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, Character.Controlled, true)
if not success then
MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(moveRequest.what),
tostring(moveRequest.where)))
end
end
end
processQueue()
---@param A InventorySlot
---@param B InventorySlot
---@param allowSwap boolean
---@param allowCombine boolean
enqueueMove = function(A, B, allowSwap, allowCombine)
MyModGlobal.debugPrint(string.format("Enqueuing move from %s to %s", tostring(A), tostring(B)))
---@param what Barotrauma.Item
---@param where InventorySlot
---@param allowSwap? boolean
---@param allowCombine? boolean
enqueueMove = function(what, where, allowSwap, allowCombine)
MyModGlobal.debugPrint(string.format("Enqueuing move from %s to %s", tostring(what), tostring(where)))
table.insert(itemMoveQueue, {
A = A,
B = B,
allowSwap = allowSwap,
allowCombine = allowCombine,
what = what,
where = where,
allowSwap = allowSwap or false,
allowCombine = allowCombine ~= false,
})
-- We will very optimistically pretend that this will 100% for sure work
where:pretendMoved(what)
end
end
@@ -202,15 +260,15 @@ end
---@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
---@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
@@ -222,9 +280,9 @@ local function ensureOptionsDefaults(options)
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 or false
options.loadRefs = options.loadRefs == true
options.itemRef = options.itemRef or nil
options.recurse = options.recurse or true
options.recurse = options.recurse == true
return options
end
@@ -242,13 +300,13 @@ do
---@return EnqueueOptions, string?
enqueueItem = function(item, options)
options = ensureOptionsDefaults(options)
if not item then return options.itemQueue, "No item" end
if not item then return options, "No item" end
local ok, stop = options.itemPredicate(item, options.itemRef)
if ok then
options.itemQueue[#options.itemQueue + 1] = item
end
if stop then return options.itemQueue, "Stop" end
if stop then return options, "Stop" end
local err
if item.OwnInventory then