local DEBUG = -- function() end d local function _tr(str) return str end local ST_OK = 0 local ST_TGTFULL = 1 local ST_SPAM = 2 local InvCache = nil local function ScanInventory(bagType) local _empties = { } local _stackCount = { } local inv = SHARED_INVENTORY:GetOrCreateBagCache(bagType) for i = 0, GetBagSize(bagType)-1, 1 do if not inv[i] then _empties[#_empties + 1] = i else local curStack, maxStack = GetSlotStackSize(bagType, i) _stackCount[i] = { ["id"] = inv[i].itemInstanceId, ["current"] = curStack, ["max"] = maxStack } end end -- Empties is a list of empty slots, items an overview over stack counts in specific slots return { ["empties"] = _empties, ["items"] = _stackCount } end local function RebuildStackCache(tgtBagType) InvCache["tgtStackCache"][tgtBagType] = { } local tgtInv = InvCache[tgtBagType] local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] for k,v in pairs(tgtInv["items"]) do local missing = v["max"] - v["current"] if missing > 0 then tgtStackCache[v["id"]] = { missing, k } end end end local function FindTargetSlot(srcBagType, srcSlotId, tgtBagType) local srcInv = InvCache[srcBagType] local tgtInv = InvCache[tgtBagType] local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] local iId = srcInv["items"][srcSlotId]["id"] local count = srcInv["items"][srcSlotId]["current"] if tgtStackCache[iId] then local missing = tgtStackCache[iId][1] if missing > 0 then local k = tgtStackCache[iId][2] local empties = missing >= count return empties, false, k, (empties and count) or missing end end -- No incomplete stack found, return an empty slot or a failure local emptyslots = tgtInv["empties"] if #emptyslots == 0 then return false, false, -1, 0 end -- We'd start another stack, but we're sure it'll be a complete transfer return true, true, emptyslots[#emptyslots], count end local function CollectSingleDirection(action) local bagType = (action == InventoryManager.ACTION_STASH and BAG_BACKPACK) or BAG_BANK local _moveSlots = { } InventoryManager:SetCurrentInventory(bagType) for i,_ in pairs(InventoryManager.currentInventory) do local data = InventoryManager:GetItemData(i) if action == InventoryManager.currentRuleset:Match(data) then _moveSlots[#_moveSlots + 1] = i end end return _moveSlots end local function PrepareMoveCaches() InvCache = { [BAG_BACKPACK] = ScanInventory(BAG_BACKPACK), [BAG_BANK] = ScanInventory(BAG_BANK), ["tgtStackCache"] = { [BAG_BACKPACK] = { }, [BAG_BANK] = { } } } RebuildStackCache(BAG_BACKPACK) RebuildStackCache(BAG_BANK) Moves = { ["stash"] = CollectSingleDirection(InventoryManager.ACTION_STASH), ["retrieve"] = CollectSingleDirection(InventoryManager.ACTION_RETRIEVE) } end local function CalculateSingleMove(direction) local IMR = InventoryManager.IM_Ruleset local srcBagType = (direction == 1 and BAG_BACKPACK) or BAG_BANK local tgtBagType = (direction == 1 and BAG_BANK) or BAG_BACKPACK local tgtStackCache = InvCache["tgtStackCache"][tgtBagType] local srcSlotRepo = Moves[(direction == 1 and "stash") or "retrieve"] if #srcSlotRepo == 0 then return "src_empty" end local srcSlotId = srcSlotRepo[#srcSlotRepo] local empties, newSlot, tgtSlotId, count = FindTargetSlot(srcBagType, srcSlotId, tgtBagType) if count == 0 then return "tgt_full" end InventoryManager:SetCurrentInventory(srcBagType) local data = InventoryManager:GetItemData(srcSlotId) if count > 0 then end if empties then -- Empties source slot, remove from pending moves InvCache[srcBagType]["items"][srcSlotId] = nil local emptyslots = InvCache[srcBagType]["empties"] emptyslots[#emptyslots + 1] = srcSlotId srcSlotRepo[#srcSlotRepo] = nil else -- Incomplete move, deduce count in source cache local srcslot = InvCache[srcBagType]["items"][srcSlotId] srcslot["current"] = srcslot["current"] - count end if newSlot then -- Filled up a new slot in the target InvCache[tgtBagType]["items"][tgtSlotId] = { ["id"] = data.itemInstanceId, ["current"] = count, ["max"] = data.maxCount } local emptyslots = InvCache[tgtBagType]["empties"] emptyslots[#emptyslots] = nil local tgtslot = InvCache[tgtBagType]["items"][tgtSlotId] tgtStackCache[data.itemInstanceId] = { tgtslot["max"] - tgtslot["current"], tgtSlotId } else -- Stashed onto existing stack, increase count local tgtslot = InvCache[tgtBagType]["items"][tgtSlotId] tgtslot["current"] = tgtslot["current"] + count tgtStackCache[data.itemInstanceId] = { tgtslot["max"] - tgtslot["current"], tgtSlotId } -- If we filled a stack, rescan, maybe there's another incomplete stack. if tgtStackCache[data.itemInstanceId][1] == 0 then RebuildStackCache(tgtBagType) end end return "ok", { ["srcbag"] = srcBagType, ["srcslot"] = srcSlotId, ["tgtbag"] = tgtBagType, ["tgtslot"] = tgtSlotId, ["count"] = count } end local function CalculateMoves() -- Prepare an overview of the inventories and the pending transfers in both directions PrepareMoveCaches() local continue = true local _moveStack = { } -- We alternate between stashing and retrieving to minimize the chance of one -- of the inventories running full. while continue do local leftres, leftentry = CalculateSingleMove(1) local rightres, rightentry = CalculateSingleMove(-1) -- ZOS Spam limitation if #_moveStack > 95 then return "limited", _moveStack end -- Both inventories full, can't move anything if leftres == "tgt_full" and rightres == "tgt_full" then return "deadlock", _moveStack end -- We completed all we were set out to do if leftres == "src_empty" and rightres == "src_empty" then return "ok", _moveStack end -- We filled up one side, but we can't empty it out if leftres ~= "ok" and rightres ~= "ok" then return "partial", _moveStack end if leftres == "ok" then _moveStack[#_moveStack + 1] = leftentry end if rightres == "ok" then _moveStack[#_moveStack + 1] = rightentry end end -- NOTREACHED end InventoryManager.moveStatus = nil function ProcessMove(move) local IMR = InventoryManager.IM_Ruleset local bagIdFrom = move["srcbag"] local slotIdFrom = move["srcslot"] local bagIdTo = move["tgtbag"] local slotIdTo = move["tgtslot"] local qtyToMove = move["count"] local action = (bagIdFrom == BAG_BACKPACK and InventoryManager.ACTION_STASH) or InventoryManager.ACTION_RETRIEVE local data = InventoryManager:GetItemData(slotIdFrom, SHARED_INVENTORY:GetOrCreateBagCache(bagIdFrom)) InventoryManager:ReportAction(data, false, action) if IsProtectedFunction("RequestMoveItem") then CallSecureProtected("RequestMoveItem", bagIdFrom, slotIdFrom, bagIdTo, slotIdTo, qtyToMove) else RequestMoveItem(bagIdFrom, slotIdFrom, bagIdTo, slotIdTo, qtyToMove) end end function InventoryManager:FinishMoves() local result if self.moveStatus == "limited" then result = GetString(IM_BANK_LIMITED) elseif self.moveStatus == "deadlock" then result = GetString(IM_BANK_DEADLOCK) elseif self.moveStatus == "partial" then result = GetString(IM_BANK_PARTIAL) elseif self.moveStatus == "ok" then result = GetString(IM_BANK_OK) end if result ~= "" then CHAT_SYSTEM:AddMessage(result) end end function InventoryManager:BalanceCurrency(currencyType, minCur, maxCur, curName) local carried = GetCarriedCurrencyAmount(currencyType) local banked = GetBankedCurrencyAmount(currencyType) local move = 0 if(carried < minCur) then move = carried - minCur elseif(carried > maxCur) then move = carried - maxCur end if move == 0 then return elseif move > 0 then CHAT_SYSTEM:AddMessage( zo_strformat(GetString(IM_CUR_DEPOSIT), move, curName)) DepositCurrencyIntoBank(currencyType, move) else move = -move if move > banked then move = banked end if move == 0 then return end CHAT_SYSTEM:AddMessage( zo_strformat(GetString(IM_CUR_WITHDRAW), move, curName)) WithdrawCurrencyFromBank(currencyType, move) end end function InventoryManager:OnBankOpened() local moves self.moveStatus, moves = CalculateMoves() self:BalanceCurrency(CURT_MONEY, self.settings.minGold, self.settings.maxGold, GetString(IM_CUR_GOLD)) self:BalanceCurrency(CURT_TELVAR_STONES, self.settings.minTV, self.settings.maxTV, GetString(IM_CUR_TVSTONES)) self:DoDelayedProcessing(moves, ProcessMove, function() InventoryManager:FinishMoves() end, InventoryManager.settings.bankMoveDelay, InventoryManager.settings.bankInitDelay) end local function OnBankOpened(eventCode) InventoryManager:OnBankOpened() end EVENT_MANAGER:RegisterForEvent(InventoryManager.name, EVENT_OPEN_BANK, OnBankOpened)