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") require("Cyka.xpticker")
else else
---@class MyModGlobal ---@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_NAME string
---@field MOD_VERSION string ---@field MOD_VERSION string
---@field DumpTable fun(table: table, depth?: number) ---@field DumpTable fun(table: table, depth?: number)

View File

@@ -2,106 +2,49 @@
if not CLIENT then return end if not CLIENT then return end
local utils = require("Cyka.utils") local utils = require("Cyka.utils")
local dump = require("Cyka.dump")
---@param inventory Barotrauma.ItemInventory ---@param invSlot InventorySlot
---@param predicate fun(slot: InventorySlot): boolean local function tryUnloadSlot(invSlot)
---@return InventorySlot[], string? ---@type table<Barotrauma.ItemPrefab, boolean>
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 = {}
local toUnloadByPrefab = {} local toUnloadByPrefab = {}
local inventorySlots = inventory.slots
for _, inventorySlot in ipairs(inventorySlots) do local itemInventory = invSlot.item.OwnInventory
for _, inventoryItem in ipairs(inventorySlot.items) do if not itemInventory then
toUnload[#toUnload + 1] = inventoryItem MyModGlobal.debugPrint("No inventory for item")
-- This will only serve as O(1) lookup return
toUnloadByPrefab[inventoryItem.Prefab] = true
end
end 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? -- Where can we put our toUnload items?
local nearbySlots = findSlotsThat(slot.inventory, function(islot) local nearbySlots = invSlot:getNearbySlots(function(islot)
local isEmpty = #islot.slot.items == 0 local isEmpty = islot.slot.items and #islot.slot.items == 0
if isEmpty then return true end if isEmpty then return true end
for _, prefab in ipairs(toUnloadByPrefab) do for prefab, _ in pairs(toUnloadByPrefab) do
local canAccept = islot.inventory.CanBePutInSlot(prefab, islot.slotIndex) local canFit = islot:canFit(prefab)
if canAccept then return true end if canFit then return true end
end end
return false return false
end) end)
-- print("Before sorting:") MyModGlobal.debugPrint(string.format("Into %d nearby slots", #nearbySlots))
-- dump(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) for _, iitem in ipairs(toUnload.itemQueue) do
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 _, nearbySlot in ipairs(nearbySlots) 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 if canAccept then
local moved = nearbySlot.inventory.TryPutItem(iitem, nearbySlot.slotIndex, true, false, utils.enqueueMove(iitem, nearbySlot)
Character.Controlled, true) break
-- print(string.format("Moved item %s to slot %d", iitem.Name, nearbySlot.slotIndex))
if moved then break end
end end
end end
end end
@@ -110,12 +53,12 @@ end
local function tryUnloadCursorItem() local function tryUnloadCursorItem()
local slots, err = utils.getSlotsUnderCursor() local slots, err = utils.getSlotsUnderCursor()
if err then if err then
-- MyModGlobal.debugPrint(string.format("Error getting inventory slot: %s", err)) MyModGlobal.debugPrint(string.format("Error getting inventory slot: %s", err))
return return
end end
if not slots or #slots == 0 then if not slots or #slots == 0 then
-- MyModGlobal.debugPrint("No items in slot") MyModGlobal.debugPrint("No items in slot")
return return
end end

View File

@@ -25,6 +25,8 @@ MyModGlobal.InventorySlot = {
self.inventory = inventory self.inventory = inventory
self.slotIndex1 = slotIndex1 self.slotIndex1 = slotIndex1
self.slotIndex0 = slotIndex1 - 1 self.slotIndex0 = slotIndex1 - 1
self.stackSize = 0
self.maxStackSize = 0
-- self:update() -- self:update()
if inventory and inventory.slots and #inventory.slots > 0 then if inventory and inventory.slots and #inventory.slots > 0 then
@@ -40,6 +42,24 @@ MyModGlobal.InventorySlot = {
return self return self
end, 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) update = function(self)
-- self.lastUpdated = Timer.GetTime() -- self.lastUpdated = Timer.GetTime()
if not self.inventory then if not self.inventory then
@@ -71,7 +91,9 @@ MyModGlobal.InventorySlot = {
tostring(self.inventory), self.slotIndex1, self.slotIndex0, tostring(self.item), self.stackSize, tostring(self.inventory), self.slotIndex1, self.slotIndex0, tostring(self.item), self.stackSize,
self.maxStackSize) self.maxStackSize)
end, end,
---@param self InventorySlot
---@param predicate? fun(slot: InventorySlot): boolean ---@param predicate? fun(slot: InventorySlot): boolean
---@return InventorySlot[]
getNearbySlots = function(self, predicate) getNearbySlots = function(self, predicate)
predicate = predicate or function() return true end predicate = predicate or function() return true end
@@ -114,14 +136,44 @@ MyModGlobal.InventorySlot = {
return slots return slots
end, 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) -- hash = function(self)
-- return string.format("%s:%d:%d", tostring(self.inventory), self.slotIndex1, self.slotIndex0) -- return string.format("%s:%d:%d", tostring(self.inventory), self.slotIndex1, self.slotIndex0)
-- end -- end
} }
---@class ItemMoveRequest ---@class ItemMoveRequest
---@field A InventorySlot ---@field what Barotrauma.Item
---@field B InventorySlot ---@field where InventorySlot
---@field allowSwap boolean ---@field allowSwap boolean
---@field allowCombine boolean ---@field allowCombine boolean
@@ -131,39 +183,45 @@ do
local enabled = true local enabled = true
---@type ItemMoveRequest[] ---@type ItemMoveRequest[]
local itemMoveQueue = {} local itemMoveQueue = {}
local rate = 10 local rate = 100
local perIteration = 6
local function processQueue() local function processQueue()
MyModGlobal.debugPrint("Processing queue") -- MyModGlobal.debugPrint("Processing queue")
Timer.Wait(processQueue, rate) Timer.Wait(processQueue, rate)
if not enabled then return end if not enabled then return end
if #itemMoveQueue == 0 then return end if #itemMoveQueue == 0 then return end
local iterations = math.min(perIteration, #itemMoveQueue)
for _ = 1, iterations do
---@type ItemMoveRequest ---@type ItemMoveRequest
local moveRequest = table.remove(itemMoveQueue, 1) local moveRequest = table.remove(itemMoveQueue, 1)
-- TODO: Maybe try and figure out if we CAN put A into B -- TODO: Maybe try and figure out if we CAN put A into B
moveRequest.allowCombine = moveRequest.allowCombine or false moveRequest.allowCombine = moveRequest.allowCombine or false
moveRequest.allowSwap = moveRequest.allowSwap or false moveRequest.allowSwap = moveRequest.allowSwap or false
local success = moveRequest.B.inventory.TryPutItem(moveRequest.A.item, moveRequest.B.slotIndex0, local success = moveRequest.where.inventory.TryPutItem(moveRequest.what, moveRequest.where.slotIndex0,
moveRequest.allowSwap, moveRequest.allowCombine, Character.Controlled, true) moveRequest.allowSwap, moveRequest.allowCombine, Character.Controlled, true)
if not success then if not success then
MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(moveRequest.A), MyModGlobal.debugPrint(string.format("Failed moving item from %s to %s", tostring(moveRequest.what),
tostring(moveRequest.B))) tostring(moveRequest.where)))
end
end end
end end
processQueue() processQueue()
---@param A InventorySlot ---@param what Barotrauma.Item
---@param B InventorySlot ---@param where InventorySlot
---@param allowSwap boolean ---@param allowSwap? boolean
---@param allowCombine boolean ---@param allowCombine? boolean
enqueueMove = function(A, B, allowSwap, allowCombine) enqueueMove = function(what, where, allowSwap, allowCombine)
MyModGlobal.debugPrint(string.format("Enqueuing move from %s to %s", tostring(A), tostring(B))) MyModGlobal.debugPrint(string.format("Enqueuing move from %s to %s", tostring(what), tostring(where)))
table.insert(itemMoveQueue, { table.insert(itemMoveQueue, {
A = A, what = what,
B = B, where = where,
allowSwap = allowSwap, allowSwap = allowSwap or false,
allowCombine = allowCombine, allowCombine = allowCombine ~= false,
}) })
-- We will very optimistically pretend that this will 100% for sure work
where:pretendMoved(what)
end end
end end
@@ -202,15 +260,15 @@ end
---@field slotIndex1 number ---@field slotIndex1 number
---@class EnqueueOptions ---@class EnqueueOptions
---@field itemQueue Barotrauma.Item[] ---@field itemQueue? Barotrauma.Item[]
---@field slotQueue Barotrauma.Inventory.ItemSlot[] ---@field slotQueue? Barotrauma.Inventory.ItemSlot[]
---@field inventoryQueue Barotrauma.Inventory[] ---@field inventoryQueue? Barotrauma.Inventory[]
---@field itemPredicate fun(item: Barotrauma.Item, itemRef: ItemRefs): boolean ---@field itemPredicate? fun(item: Barotrauma.Item, itemRef: ItemRefs): boolean
---@field slotPredicate fun(slot: Barotrauma.Inventory.ItemSlot, itemRef: ItemRefs): boolean ---@field slotPredicate? fun(slot: Barotrauma.Inventory.ItemSlot, itemRef: ItemRefs): boolean
---@field inventoryPredicate fun(inventory: Barotrauma.Inventory, itemRef: ItemRefs): boolean ---@field inventoryPredicate? fun(inventory: Barotrauma.Inventory, itemRef: ItemRefs): boolean
---@field loadRefs boolean ---@field loadRefs? boolean
---@field itemRef ItemRefs ---@field itemRef? ItemRefs
---@field recurse boolean ---@field recurse? boolean
---@param options EnqueueOptions ---@param options EnqueueOptions
---@return EnqueueOptions ---@return EnqueueOptions
@@ -222,9 +280,9 @@ local function ensureOptionsDefaults(options)
options.itemPredicate = options.itemPredicate or function() return true end options.itemPredicate = options.itemPredicate or function() return true end
options.slotPredicate = options.slotPredicate or function() return true end options.slotPredicate = options.slotPredicate or function() return true end
options.inventoryPredicate = options.inventoryPredicate 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.itemRef = options.itemRef or nil
options.recurse = options.recurse or true options.recurse = options.recurse == true
return options return options
end end
@@ -242,13 +300,13 @@ do
---@return EnqueueOptions, string? ---@return EnqueueOptions, string?
enqueueItem = function(item, options) enqueueItem = function(item, options)
options = ensureOptionsDefaults(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) local ok, stop = options.itemPredicate(item, options.itemRef)
if ok then if ok then
options.itemQueue[#options.itemQueue + 1] = item options.itemQueue[#options.itemQueue + 1] = item
end end
if stop then return options.itemQueue, "Stop" end if stop then return options, "Stop" end
local err local err
if item.OwnInventory then if item.OwnInventory then