--[[ -- Roomba - (Thanks to BalkiTheWise for the name) - ]] Roomba = ZO_Object:Subclass() function Roomba:New(...) local roomba = ZO_Object.New(self) roomba:Initialise(...) return roomba end local LAM = LibStub:GetLibrary("LibAddonMenu-1.0") local BACKPACK = ZO_PlayerInventoryBackpack.data local GUILDBANK = ZO_GuildBankBackpack.data local WM = WINDOW_MANAGER local DELAY = 100 local settings = {} local format = " %3d" local containerHooks = { INVENTORY_BACKPACK, INVENTORY_BANK, INVENTORY_GUILD_BANK } local currentRun = {} local function GetItemID(link) return tonumber(string.match(string.match(link, "%d+:"), "%d+")) end local 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 local 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 local function TableEntryCount(t) local i = next(t) local c = 0 while i do c = c + 1 i = next(t, i) end return c end -- Who is your Guild and what do they do local function GetGuildDetails(self) for guildIndex = 1, GetNumGuilds() do local guildId = GetGuildId(guildIndex) if DoesGuildHavePrivilege(guildId, GUILD_PRIVILEGE_BANK_DEPOSIT) then self.guildInfo[guildId] = true end end end -- Okay, let's see. -- First we'll take current bag inventory. function Roomba:CheckWeHaveEnoughRoom() return CheckInventorySpaceAndWarn(5) end function Roomba:HaveStuffToStack() local bank = self.guildInfo[GetSelectedGuildBankId()] if type(bank) == "table" and next(bank.duplicates) then return true end return false end local checkingBank = false -- Bank is ready! Find those duplicates! function Roomba:RoombaReady() -- Are we in the process of checking the bank? if not checkingBank then checkingBank = true else return end local selectedBankId = GetSelectedGuildBankId() if not self.guildInfo[selectedBankId] then return end self.guildInfo[selectedBankId] = ClearGuildDetails(selectedBankId) local bank = self.guildInfo[selectedBankId] -- 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, texture = slot.data.iconFile, name = slot.data.name}) end end end zo_callLater(function() checkingBank = nil end, 3000) if KEYBIND_STRIP:HasKeybindButtonGroup(self.runDescriptor) then KEYBIND_STRIP:UpdateKeybindButtonGroup(self.runDescriptor) end currentRun = {} self.control:SetHidden(true) 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 = nil local transitBag = nil local currentReturnIndex = nil local function ResetAll() cSlotIdx = nil cSlot = nil inBagCollection = {} baseSlot = nil end function Roomba:SelectGuildBank(...) local eventId, guildBankId = ... self.currentBank = guildBankId checkingBank = nil end function Roomba:ReturnItemsToBank(...) local error = ... local slot -- Now that we've stacked it all lets move it back currentReturnIndex, slot = next(inBagCollection) if slot then if error then return zo_callLater(function(...) self:ReturnItemsToBank() end, 2000) end if FindSlot(BACKPACK, slot.bagSlot) then self.text:SetText("Returning restacked " .. cSlot.name .. " to the Guild Bank") return TransferToGuildBank(INVENTORY_BACKPACK, slot.bagSlot) else -- we have a space, move to next table.remove(inBagCollection, currentReturnIndex) return zo_callLater(function(...) self:ReturnItemsToBank(...) end, DELAY) end else -- No more to move, we're complete EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR) EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED) -- Kick off the next transaction ResetAll() return zo_callLater(function() self:BeginProcess() end, DELAY) end end function Roomba:BankItemsReceived(...) local eventId, gslot = ... local id = GetInstanceId(GUILDBANK, gslot) if id ~= cInstanceId then return end -- We've moved one back! Remove it from contention local slot = table.remove(inBagCollection, currentReturnIndex) currentReturnIndex = nil return zo_callLater(function(...) self:ReturnItemsToBank() end, DELAY) end function Roomba:StartStacking() baseSlot = nil EVENT_MANAGER:UnregisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE) self.text:SetText("Stacking " .. cSlot.name .. " in inventory") for _,slot in pairs(inBagCollection) do if not baseSlot then -- We want to stack everything on this 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() end end baseSlot = nil -- These events will loop the move back to the guild bank EVENT_MANAGER:RegisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR, function(...) self:ReturnItemsToBank() end) EVENT_MANAGER:RegisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED, function(...) self:BankItemsReceived(...) end) return zo_callLater(function(...) self:ReturnItemsToBank() end, DELAY) 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(...) self:ReceiveItems(...) end, 1000) end if not GetInstanceId(BACKPACK, slotId) then return end -- are we empty if GetInstanceId(BACKPACK, slotId) ~= cInstanceId then return end 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(function() self:StartStacking() end, DELAY) end end function Roomba:BeginProcess() local bank = self.guildInfo[self.currentBank] if not bank then return end if not self:CheckWeHaveEnoughRoom() then return end ZO_MenuBar_SelectLastVisibleButton(ZO_PlayerInventoryTabs) cSlotIdx = nil -- Pull the next job off the stack cInstanceId, cDuplicateList = next(bank.duplicates, cInstanceId) if cInstanceId then currentRun[cInstanceId] = true end self.control:SetHidden(false) local index = TableEntryCount(currentRun) local total = TableEntryCount(bank.duplicates) ZO_StatusBar_SmoothTransition(self.speedRow.bar, index , total, FORCE_VALUE) self.speedRow.name:SetText(string.format(format, (index/total)*100) .. "%") if cDuplicateList then -- First off the rank cSlotIdx, cSlot = next(cDuplicateList, cSlotIdx) self.icon:SetTexture(cSlot.texture) -- Clear this batch inBagCollection = {} -- And start off the job -- If it suddenly doesn't exist, try the next in the list if not FindSlot(GUILDBANK, cSlot.slot) then return zo_callLater(function() self:BeginProcess() end, DELAY) end EVENT_MANAGER:RegisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE, function(...) self:ReceiveItems(...) end) self.text:SetText("Retrieving " .. cSlot.name .. " from Guild Bank") TransferFromGuildBank(cSlot.slot) else -- Now rescan and show/hide roomba button self.control:SetHidden(true) self.text:SetText("Complete") self:RoombaReady() return false end return true end local function RoombaLoaded(eventCode, addOnName) if(addOnName ~= "Roomba") then return end ZO_CreateStringId("SI_BINDING_NAME_RUN_ROOMBA", "Run Roomba") ZO_CreateStringId("SI_BINDING_NAME_RESCAN_ROOMBA", "Rescan Bank") end function Roomba:InitialiseSettings() self.guildInfo = {} self.currentBank = 1 self.CurrentState = CurrentState GetGuildDetails(self) self.runDescriptor = { alignment = KEYBIND_STRIP_ALIGN_LEFT, { name = "Run Roomba", -- or function that returns a name keybind = "RUN_ROOMBA", control = self, callback = function(descriptor) self:BeginProcess() end, visible = function(descriptor) return self:HaveStuffToStack() end, icon = [[Roomba\media\Roomba.dds]], }, { name = "Scan Bank", keybind = "RESCAN_ROOMBA", control = self, callback = function(descriptor) self:RoombaReady() end, visible = function(descriptor) return ZO_GuildBankBackpackLoading:IsHidden() end, icon = [[Roomba\media\RoombaSearch.dds]], }, } end function Roomba:Initialise(control) self.control = control self:InitialiseFrame() self:InitialiseSettings() local bGroup = self.runDescriptor EVENT_MANAGER:RegisterForEvent("RoombaGuildBankOpen", EVENT_OPEN_GUILD_BANK, function() self:InitialiseEvents() if not KEYBIND_STRIP:HasKeybindButtonGroup(bGroup) then KEYBIND_STRIP:AddKeybindButtonGroup(bGroup) end end) EVENT_MANAGER:RegisterForEvent("RoombaGuildBankClose", EVENT_CLOSE_GUILD_BANK, function() if KEYBIND_STRIP:HasKeybindButtonGroup(bGroup) then KEYBIND_STRIP:RemoveKeybindButtonGroup(bGroup) end self.control:SetHidden(true) ResetAll() self:UninitialiseEvents() end) end function Roomba:InitialiseEvents() -- 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(function() self:RoombaReady() end, 1000) end) -- Clear the flag when swapping banks EVENT_MANAGER:RegisterForEvent("RoombaSelected", EVENT_GUILD_BANK_SELECTED, function(...) self:SelectGuildBank(...) end) end function Roomba:UninitialiseEvents() EVENT_MANAGER:UnregisterForEvent("RoombaInventoryAdded", EVENT_INVENTORY_SINGLE_SLOT_UPDATE) EVENT_MANAGER:UnregisterForEvent("RoombaSelected", EVENT_GUILD_BANK_SELECTED) EVENT_MANAGER:UnregisterForEvent("RoombaReady", EVENT_GUILD_BANK_ITEMS_READY) EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankError", EVENT_GUILD_BANK_TRANSFER_ERROR) EVENT_MANAGER:UnregisterForEvent("RoombaGuildBankSuccess", EVENT_GUILD_BANK_ITEM_ADDED) end function Roomba:InitialiseFrame() self.control:SetDrawLayer(DL_OVERLAY) self.speedRow = self.control:GetNamedChild("SpeedRow") self.speedRow.name:SetText(" 0%") self.icon = self.control:GetNamedChild("Icon") self.text = self.control:GetNamedChild("Description") self.text:SetText("...") ZO_StatusBar_SetGradientColor(self.speedRow.bar, ZO_XP_BAR_GRADIENT_COLORS) ZO_StatusBar_SmoothTransition(self.speedRow.bar, 0, 20, FORCE_VALUE) end function Roomba_Initialise(control) ROOMBA = Roomba:New(control) end EVENT_MANAGER:RegisterForEvent("RoombaLoaded", EVENT_ADD_ON_LOADED, RoombaLoaded)