--[[ -- Roomba - (Thanks to BalkiTheWise for the name) - ]] 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 Roomba.dmsg(msg) if Roomba.Debugging then d(msg) end end local dmsg = Roomba.dmsg 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 function HaveStuffToStack() local bank = Roomba.guildInfo[GetSelectedGuildBankId()] if type(bank) == "table" and next(bank.duplicates) then return true end return false 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()] dmsg("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 zo_callLater(function() checkingBank = nil end, 3000) if KEYBIND_STRIP:HasKeybindButtonGroup(Roomba.runDescriptor) then KEYBIND_STRIP:UpdateKeybindButtonGroup(Roomba.runDescriptor) end 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 dmsg("Getting next to move back to bank") currentReturnIndex, slot = next(inBagCollection) if slot then if error then dmsg("trying again") return zo_callLater(Roomba.ReturnItemsToBank, 2000) end if FindSlot(BACKPACK, slot.bagSlot) then dmsg("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 dmsg("unregistering") EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR) EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED) -- Kick off the next transaction dmsg("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 dmsg("Moved successfully to slot " ..gslot) -- We've moved one back! Remove it from contention local slot = table.remove(inBagCollection, currentReturnIndex) currentReturnIndex = nil dmsg("Clearing slot " .. slot.bagSlot) return zo_callLater(Roomba.ReturnItemsToBank, DELAY) end function Roomba.StartStacking() dmsg("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 dmsg("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() dmsg("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 IsItemJunk(INVENTORY_BACKPACK, slotId) then SetItemIsJunk(INVENTORY_BACKPACK, slotId, false) return zo_callLater(function(...) Roomba.ReceiveItems(...) end, 1000) end if not GetInstanceId(BACKPACK, slotId) then return end -- are we empty if GetInstanceId(BACKPACK, slotId) ~= cInstanceId then return end dmsg("Checking " .. bagId .. " inventory slot " .. slotId .. " to match ".. cInstanceId .. " with " .. (GetInstanceId(BACKPACK, slotId) or "nothing")) 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 dmsg("Stacking up ".. zo_strformat(SI_TOOLTIP_ITEM_NAME, GetItemLink(3, cSlot.slot, LINK_STYLE_DEFAULT))) -- If it suddenly doesn't exist, try the next in the list if not FindSlot(GUILDBANK, cSlot.slot) then return zo_callLater(Roomba.BeginProcess, DELAY) end EVENT_MANAGER:RegisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, Roomba.ReceiveItems) TransferFromGuildBank(cSlot.slot) else dmsg("Nothing to restack") -- Now rescan and show/hide roomba button Roomba.RoombaReady() return false end return true end local function IsUsePossible(name) return true end local function RoombaLoaded(eventCode, addOnName) if(addOnName ~= "Roomba") then return end local defaults = { } GetGuildDetails() SLASH_COMMANDS["/roombadebug"] = function() Roomba.Debugging = not Roomba.Debugging d("Turning debug ".. (Roomba.Debugging and "on" or "off")) end ZO_CreateStringId("SI_BINDING_NAME_RUN_ROOMBA", "Run Roomba") ZO_CreateStringId("SI_BINDING_NAME_RESCAN_ROOMBA", "Rescan Bank") Roomba.runDescriptor = { alignment = KEYBIND_STRIP_ALIGN_LEFT, { name = "Run Roomba", -- or function that returns a name keybind = "RUN_ROOMBA", callback = function(descriptor) Roomba.BeginProcess() end, -- First and only argument is this descriptor table visible = function(descriptor) return HaveStuffToStack() end, -- An optional predicate, if present returning true indicates that this descriptor is visible, otherwise it is not icon = [[Roomba\media\Roomba.dds]], -- or a function that returns an icon path, an optional icon to display to the right of the name }, { name = "Scan Bank", -- or function that returns a name keybind = "RESCAN_ROOMBA", callback = function(descriptor) Roomba.RoombaReady() end, -- First and only argument is this descriptor table visible = function(descriptor) return ZO_GuildBankBackpackLoading:IsHidden() end, -- An optional predicate, if present returning true indicates that this descriptor is visible, otherwise it is not icon = [[Roomba\media\RoombaSearch.dds]], -- or a function that returns an icon path, an optional icon to display to the right of the name }, } 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) EVENT_MANAGER:RegisterForEvent("RoombaGuildBankOpen", EVENT_OPEN_GUILD_BANK, function() if not KEYBIND_STRIP:HasKeybindButtonGroup(Roomba.runDescriptor) then KEYBIND_STRIP:AddKeybindButtonGroup(Roomba.runDescriptor) end end) EVENT_MANAGER:RegisterForEvent("RoombaGuildBankOpen", EVENT_CLOSE_GUILD_BANK, function() if KEYBIND_STRIP:HasKeybindButtonGroup(Roomba.runDescriptor) then KEYBIND_STRIP:RemoveKeybindButtonGroup(Roomba.runDescriptor) end end) end EVENT_MANAGER:RegisterForEvent("RoombaLoaded", EVENT_ADD_ON_LOADED, RoombaLoaded)