--[[ -- Roomba - (Thanks to BalkiTheWise for the name) - ]] Roomba = {} LibStub("AceTimer-3.0"):Embed(Roomba) local LAM = LibStub:GetLibrary("LibAddonMenu-1.0") local BACKPACK = ZO_PlayerInventoryBackpack.data local GUILDBANK = ZO_GuildBankBackpack.data local DELAY = 150 local containerHooks = { INVENTORY_BACKPACK, INVENTORY_BANK, INVENTORY_GUILD_BANK } Roomba.currentInventory = {} Roomba.guildInfo = {} local function GetItemID(link) return tonumber(string.match(string.match(link, "%d+:"), "%d+")) end function GetInstanceId(target, slotId) for i,v in ipairs(target) do if v.data.slotIndex == slotId then return v.data.itemInstanceId end end return nil end function FindSlot(target, slotId) for i,v in ipairs(target) do if v.data.slotIndex == slotId then return i,v.data end end return nil end local function ClearGuildDetails(guildId) return { name = GetGuildName(guildId), lookUp = {}, duplicates = {}, index = {} } end -- Who is your Guild and what do they do local function GetGuildDetails() for guildIndex = 1, GetNumGuilds() do local guildId = GetGuildId(guildIndex) if DoesGuildHavePrivilege(guildId, GUILD_PRIVILEGE_BANK_DEPOSIT) then Roomba.guildInfo[guildId] = true end end end -- Okay, let's see. -- First we'll take current bag inventory. local function CheckWeHaveEnoughRoom() return CheckInventorySpaceAndWarn(2) end -- Now take stock of everything so we don't accidentally move stuff out -- of our inventory into the bank local function AuditCurrentInventory() local _, totalSlots = GetBagInfo(INVENTORY_BACKPACK) Roomba.currentInventory = {} for slot=1, totalSlots do local id = GetItemInstanceId(INVENTORY_BACKPACK, slot) local count = GetItemTotalCount(INVENTORY_BACKPACK, slot) Roomba.currentInventory[slot] = id end end local checkingBank = false local currentBank = 1 -- Bank is ready! Find those duplicates! function Roomba.RoombaReady() if not checkingBank then checkingBank = true else return end local bank = Roomba.guildInfo[GetSelectedGuildBankId()] if not bank then return end Roomba.guildInfo[GetSelectedGuildBankId()] = ClearGuildDetails(GetSelectedGuildBankId()) bank = Roomba.guildInfo[GetSelectedGuildBankId()] d("Bank is ready: " ..#GUILDBANK) -- We only need to store A) slots with items for index, slot in ipairs(GUILDBANK) do if slot.data.equipType == 0 then -- Equipped gear cannot be stacked local id = slot.data.itemInstanceId local count, stack = GetSlotStackSize(slot.data.bagId, slot.data.slotIndex) -- and B) slots with items that aren't full stacks or single unstackables if count ~= stack then if bank.lookUp[id] then -- If we've run across this item before if not bank.duplicates[id] then -- store the table reference as a duplicate bank.duplicates[id] = bank.lookUp[id] end else bank.lookUp[id] = {} -- It's a new item! end -- Now group all items by id table.insert(bank.lookUp[id], {slot = slot.data.slotIndex, count = count}) end end end d("Number of duplicates: " .. #bank.duplicates) end --[[ Right, so this got complicated. Simply calling TransferFromGuildBank did not seem reliable, it would perhaps transfer one item in the loop, and disregard the rest. So instead we're going event based responses. This has the problem of breaking up the flow in a way that might be a little complicated to follow, but I'll try to make it simple, if for the basic reason that I have to read this crap afterwards. Let's hope this works. ]]-- local cInstanceId = nil local cDuplicateList = nil local cSlotIdx = nil local cSlot = nil local baseSlot = nil local inBagCollection = {} local function ResetAll() cSlotIdx = nil cSlot = nil inBagCollection = {} baseSlot = nil end function Roomba.SelectGuildBank(...) local eventId, guildBankId = ... currentBank = guildBankId checkingBank = nil end transitBag = {} local currentReturnIndex function Roomba.ReturnItemsToBank(...) local error = ... local slot -- Now that we've stacked it all lets move it back d("Getting next to move back to bank") currentReturnIndex, slot = next(inBagCollection) if slot then if error then d("trying again") return zo_callLater(Roomba.ReturnItemsToBank, 2000) end if FindSlot(BACKPACK, slot.bagSlot) then d("Moving stuff back from ".. slot.bagSlot) return TransferToGuildBank(INVENTORY_BACKPACK, slot.bagSlot) else -- we have a space, move to next table.remove(inBagCollection, currentReturnIndex) return zo_callLater(Roomba.ReturnItemsToBank, DELAY) end else -- No more to move, we're complete d("unregistering") EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR) EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED) -- Kick off the next transaction d("Finished processing item") ResetAll() return zo_callLater(Roomba.BeginProcess, DELAY) end end function Roomba.BankItemsReceived(...) local _, gslot = ... local id = GetInstanceId(GUILDBANK, gslot) if id ~= cInstanceId then return end d("Moved successfully to slot " ..gslot) -- We've moved one back! Remove it from contention local slot = table.remove(inBagCollection, currentReturnIndex) currentReturnIndex = nil d("Clearing slot " .. slot.bagSlot) return zo_callLater(Roomba.ReturnItemsToBank, DELAY) end function Roomba.StartStacking() d("Found an item to stack") baseSlot = nil EVENT_MANAGER:UnregisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE) for _,slot in pairs(inBagCollection) do if not baseSlot then -- We want to stack everything on this d("Setting ".. slot.bagSlot .." as base slot") baseSlot = slot else baseSlot.count, baseSlot.stack = GetSlotStackSize(INVENTORY_BACKPACK, slot.bagSlot) -- Find out how much we can fit -- If it's too much, move all we can and target this slot as the next to fill if (baseSlot.stack - baseSlot.count) < slot.count then result = CallSecureProtected("PickupInventoryItem", INVENTORY_BACKPACK, slot.bagSlot, baseSlot.stack - baseSlot.count) if (result) then result = CallSecureProtected("PlaceInInventory", INVENTORY_BACKPACK, baseSlot.bagSlot) end baseSlot = slot else -- Just move it all over result = CallSecureProtected("PickupInventoryItem", INVENTORY_BACKPACK, slot.bagSlot, slot.count) if (result) then result = CallSecureProtected("PlaceInInventory", INVENTORY_BACKPACK, baseSlot.bagSlot) end end ClearCursor() d("Stacked item in backpack") end end baseSlot = nil -- These events will loop the move back to the guild bank EVENT_MANAGER:RegisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR, Roomba.ReturnItemsToBank) EVENT_MANAGER:RegisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED, Roomba.BankItemsReceived) return zo_callLater(Roomba.ReturnItemsToBank, 1000) end -- Event called that item has arrived function Roomba.ReceiveItems(...) if baseSlot then return end -- We're still stacking local _, bagId, slotId = ... if bagId ~= INVENTORY_BACKPACK then return end if not cInstanceId then return end if not GetInstanceId(BACKPACK, slotId) then return end -- are we empty if GetInstanceId(BACKPACK, slotId) ~= cInstanceId then return end d("Checking " .. bagId .. " inventory slot " .. slotId .. " to match ".. cInstanceId .. " with " .. (GetInstanceId(BACKPACK, slotId) or "nothing")) SetItemIsJunk(INVENTORY_BACKPACK, slotId, false) cSlot.bagSlot = slotId table.insert(inBagCollection, cSlot) -- If we have another slot to move if next(cDuplicateList, cSlotIdx) then cSlotIdx, cSlot = next(cDuplicateList, cSlotIdx) TransferFromGuildBank(cSlot.slot) else -- No more slots to move, let's stack them return zo_callLater(Roomba.StartStacking, DELAY) end end function Roomba.BeginProcess() local bank = Roomba.guildInfo[currentBank] if not bank then return end cSlotIdx = nil -- Pull the next job off the stack cInstanceId, cDuplicateList = next(bank.duplicates, cInstanceId) if cDuplicateList then -- First off the rank cSlotIdx, cSlot = next(cDuplicateList, cSlotIdx) -- Clear this batch inBagCollection = {} -- And start off the job d("Stacking up ".. zo_strformat(SI_TOOLTIP_ITEM_NAME, GetItemLink(3, cSlot.slot, LINK_STYLE_DEFAULT))) EVENT_MANAGER:RegisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, Roomba.ReceiveItems) SetItemIsJunk(3, cSlot.slot, false) -- unjunk everything TransferFromGuildBank(cSlot.slot) else d("Nothing to restack") return false end return true end local function RoombaLoaded(eventCode, addOnName) if(addOnName ~= "Roomba") then return end local defaults = { } GetGuildDetails() SLASH_COMMANDS["/roomba"] = Roomba.BeginProcess settings = ZO_SavedVars:New("Roomba_Settings", 3, nil, defaults) -- Guild bank is evented to be ready, but wait a short while before processing. EVENT_MANAGER:RegisterForEvent("RoombaReady", EVENT_GUILD_BANK_ITEMS_READY, function() zo_callLater(Roomba.RoombaReady, 1000) end) -- Clear the flag when swapping banks EVENT_MANAGER:RegisterForEvent("RoombaSelected", EVENT_GUILD_BANK_SELECTED, Roomba.SelectGuildBank) end EVENT_MANAGER:RegisterForEvent("RoombaLoaded", EVENT_ADD_ON_LOADED, RoombaLoaded)