--[[ kRestack 1.1 : Katae @ Anvilmar US Usage: /restack [bags, bank, guild] - manual run /restack auto [bags, bank, guild] - toggles auto mode ]] local AS, atBank, atVault, isViewable, canDeposit, restacker, tabswap SLASH_RESTACK1 = "/restack" function SlashCmdList.RESTACK(s) kRestack(s) end -- pretty chat colors! local white, green, red = "|cffffffff", "|cff55ff55", "|cffff5555" local kR = "|cff44ccffk|cffffffaaRestack"..white -- set user's container identifers local container = { bags = { 0 }, bank = { -1 }, guild = { 42 } } for i = 1, NUM_BAG_SLOTS do table.insert(container.bags, i) end for i = NUM_BAG_SLOTS + 1, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do table.insert(container.bank, i) end -- ldb plugin local function ldb() local drop = CreateFrame("Frame", "kRestack", nil, "UIDropDownMenuTemplate") LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject("kRestack", { type = "launcher", label = "kRestack", icon = "Interface\\Icons\\INV_Crate_09", OnClick = function(self, button) if button == "RightButton" then GameTooltip:Hide() UIDropDownMenu_Initialize(drop, function() for _, opt in ipairs{ { text = "= Restack =", isTitle = true }, { text = "Backpack", func = function() kRestack("bags") end }, { text = "Bank", disabled = not atBank, func = function() kRestack("bank") end }, { text = "Guild Vault", disabled = not atVault, func = function() kRestack("guild", false, GetCurrentGuildBankTab()) end }, { disabled = true }, { text = "= Set Auto =", isTitle = true }, { text = "Backpack", checked = AS.bags, func = function() kRestack("auto bags") end }, { text = "Bank", checked = AS.bank, func = function() kRestack("auto bank") end }, { text = "Guild Vault", checked = AS.guild, func = function() kRestack("auto guild") end }, } do UIDropDownMenu_AddButton(opt, 1) end end, "MENU") return ToggleDropDownMenu(1, nil, drop, self, 0, 0) elseif button == "LeftButton" then kRestack(atBank and "bank" or atVault and "guild" or "bags") end end, }) end -- watching some events local f = CreateFrame("Frame") f:RegisterEvent'ADDON_LOADED' f:RegisterEvent'BANKFRAME_OPENED' f:RegisterEvent'BANKFRAME_CLOSED' f:RegisterEvent'LOOT_OPENED' f:RegisterEvent'GUILDBANKFRAME_OPENED' f:RegisterEvent'GUILDBANKFRAME_CLOSED' f:SetScript("OnEvent", function(_, e, addon) -- more ldb stuff if e == "BANKFRAME_OPENED" then atBank = true elseif e == "BANKFRAME_CLOSED" then atBank = false elseif e == "GUILDBANKFRAME_OPENED" then isViewable, canDeposit = select(3,GetGuildBankTabInfo(GetCurrentGuildBankTab())) atVault = (IsGuildLeader() or (isViewable and canDeposit)) elseif e == "GUILDBANKFRAME_CLOSED" then atVault = false end if e == "ADDON_LOADED" then -- initialize SavedVariables if addon == "kRestack" then if type(AutoStack) ~= "table" then AutoStack = {} end local c = UnitName("player").." - "..GetRealmName() if type(AutoStack[c]) ~= "table" then AutoStack[c] = {} end AS = AutoStack[c] if AS.bags == nil then AS.bags = true end if AS.bank == nil then AS.bank = true end if AS.guild == nil then AS.guild = false end -- loading ldb plugin elseif LibStub and ldb then if LibStub:GetLibrary("LibDataBroker-1.1", true) then ldb(); ldb = nil end end elseif AS.bank and e == "BANKFRAME_OPENED" then kRestack("bank", true) elseif AS.bags and e == "LOOT_OPENED" then kRestack("bags", true) elseif AS.guild and e == "GUILDBANKFRAME_OPENED" then isViewable, canDeposit = select(3,GetGuildBankTabInfo(GetCurrentGuildBankTab())) if (IsGuildLeader() or (isViewable and canDeposit)) then kRestack("guild", true, GetCurrentGuildBankTab()) end end end) -- hooking auto-stacking for _, func in ipairs{"CloseAllBags","ToggleBackpack","ToggleBag","OpenBackpack","OpenAllBags","OpenBackpack","CloseBackpack"} do hooksecurefunc(func, function() if AS.bags then kRestack("bags", true) end end) end local oSetCurrentGuildBankTab = SetCurrentGuildBankTab SetCurrentGuildBankTab = function(tab) if type(restacker) == "thread" then tabswap = tabswap + 1 if tabswap > 1 then restacker = nil end end -- going to lock tab switching while stacking is in progress if type(restacker) ~= "thread" or coroutine.status(restacker) == "dead" then oSetCurrentGuildBankTab(tab) isViewable, canDeposit = select(3,GetGuildBankTabInfo(tab)) if AS.guild and (IsGuildLeader() or (isViewable and canDeposit)) then kRestack("guild", false, tab) end end end -- yielding function; items will get locked and we have to wait local function coYield(loc, bag, slot, count) local elapsed = 0 f:SetScript("OnUpdate", function(_, update) elapsed = elapsed + update if type(restacker) == "thread" and coroutine.status(restacker) == "suspended" and elapsed > 0.1 then local locked = true locked = loc == "guild" and (select(2, GetGuildBankItemInfo(bag, slot)) == count and true or false) or select(3, GetContainerItemInfo(bag, slot)) if not locked then coroutine.resume(restacker) end elapsed = 0 end end) coroutine.yield() end -- main function function kRestack(loc, silent, tab) if loc ~= "bags" and loc ~= "bank" and loc ~= "guild" then if loc == "resume" then -- manual resume if stuck if coroutine.status(restacker) == "suspended" then print(kR, "Resuming suspended thread.") coroutine.resume(restacker) else print(kR, "No suspended threads to resume.") end else loc = loc:match("auto (%a+)") if loc == "bags" or loc == "bank" or loc == "guild" then -- toggle auto-stack AS[loc] = not AS[loc] print(kR, "Auto-stacking for", loc:gsub("bags","your backpack"):gsub("bank","your bank"):gsub("guild","the guild vault"), "toggled", AS[loc] and green.."ON" or red.."OFF") else -- usage message local bags = (AS.bags and green or red).."bags"..white local bank = (AS.bank and green or red).."bank"..white local guild = (AS.guild and green or red).."guild"..white print(kR, "Usage:") print(white.."- /restack [bags, bank, guild] - Run restacker manually") print(white.."- /restack auto ["..bags..",", bank..",", guild.."] - Toggle auto-stacking") end end return end if type(restacker) ~= "thread" or coroutine.status(restacker) == "dead" then restacker = coroutine.create(function() tabswap = 0 local changed = true while changed do changed = false local vault = loc == "guild" for _, bag in ipairs(container[loc]) do if changed then break end bag = vault and tab or bag for slot = 1, (vault and MAX_GUILDBANK_SLOTS_PER_TAB or GetContainerNumSlots(bag)) do while true and not vault do local locked = select(3, GetContainerItemInfo(bag, slot)) if locked then coYield(loc, bag, slot) else break end end local item = vault and GetGuildBankItemLink(bag, slot) or GetContainerItemLink(bag, slot) if item then local itemid = tonumber(item:match("item:(%d+)")) local stack = select(8, GetItemInfo(itemid)) local count = vault and select(2, GetGuildBankItemInfo(bag, slot)) or select(2, GetContainerItemInfo(bag, slot)) -- do "special" things with "special" items by moving them into "special" bags if not vault and select(9, GetItemInfo(itemid)) ~= "INVTYPE_BAG" then local bagType = (bag ~= 0 and bag ~= -1) and GetItemFamily(GetInventoryItemLink("player", ContainerIDToInventoryID(bag))) or 0 for _, sbag in ipairs(container[loc]) do if sbag > 0 and GetContainerNumFreeSlots(sbag) > 0 then local sbagID = ContainerIDToInventoryID(sbag) local sbagType = GetItemFamily(GetInventoryItemLink("player", sbagID)) local itemType = GetItemFamily(item) if sbagType > 0 and sbagType == itemType and bagType == 0 then PickupContainerItem(bag, slot) PutItemInBag(sbagID) coYield(loc, bag, slot) break end end end end if count < stack then -- found a partial stack local locked, found, done, pbag, pslot while true do -- search through bags backwards for another partial stack with a matching itemid for i = #container[loc], 1, -1 do local _bag = container[loc][i] if found or done then break end local _slots = vault and MAX_GUILDBANK_SLOTS_PER_TAB or GetContainerNumSlots(_bag) for _slot = _slots, 1, -1 do if vault then _bag = bag end if not (_bag == bag and _slot == slot) then local _item = vault and GetGuildBankItemLink(_bag, _slot) or GetContainerItemLink(_bag, _slot) if _item then local _itemid = tonumber(_item:match("item:(%d+):")) if _itemid == itemid then local _stack = select(8, GetItemInfo(_itemid)) local _count = vault and select(2, GetGuildBankItemInfo(_bag, _slot)) or select(2, GetContainerItemInfo(_bag, _slot)) if _count < _stack then found, pbag, pslot = true, _bag, _slot; break end end end else done = true; break end end end if vault then break end locked = found and select(3, GetContainerItemInfo(pbag, pslot)) or false if locked then coYield(loc, pbag, pslot) else break end end if found then ClearCursor() if vault then PickupGuildBankItem(pbag, pslot) PickupGuildBankItem(bag, slot) ClearCursor() coYield(loc, bag, slot, count) else -- if second partial stack is inside a special bag, move the original stack into it local bagType = (bag ~= 0 and bag ~= -1) and GetItemFamily(GetInventoryItemLink("player", ContainerIDToInventoryID(bag))) or 0 local pbagType = (pbag ~= 0 and pbag ~= -1) and GetItemFamily(GetInventoryItemLink("player", ContainerIDToInventoryID(pbag))) or 0 if pbagType > 0 and bagType == 0 then PickupContainerItem(bag, slot) PickupContainerItem(pbag, pslot) else PickupContainerItem(pbag, pslot) PickupContainerItem(bag, slot) end ClearCursor() end changed = true break end end end end end end -- turn off yielding function f:SetScript("OnUpdate", nil) end) coroutine.resume(restacker) else if not silent then -- user is impatient or there is a stall, tell them so print(kR..red, "Restacking in progress. (use '/restack resume' if stuck)") end end end