--[[ BagEvents A library of functions for accessing and updating bag slot information Based on SpecialEvents-Bags by Tekkub Stoutwrithe (tekkub@gmail.com) ITEM_SLOT_ADD args: bag, slot, link, count, locked, coolingDown called when a new slot becomes available to the player ITEM_SLOT_REMOVE args: bag, slot called when an item slot is removed from being in use ITEM_SLOT_UPDATE args: bag, slot, link, count, locked, coolingDown called when an item slot's item or item count changes ITEM_SLOT_UPDATE_COOLDOWN args: bag, slot, coolingDown called when an item's cooldown starts/ends BANK_OPENED args: none called when the bank has opened and all of the bagnon events have SendMessaged BANK_CLOSED args: none called when the bank is closed and all of the bagnon events have SendMessaged BAG_UPDATE_TYPE args: bag, type called when the type of a bag changes (aka, what items you can put in it changes) --]] local Bagnon = LibStub('AceAddon-3.0'):GetAddon('Bagnon') local Bucket = LibStub("AceBucket-3.0") local BagEvents = Bagnon.Ears:New() Bucket:Embed(BagEvents) Bagnon.BagEvents = BagEvents --[[ privates? ]]-- local slots = {} local bagTypes = {} local pendingBags = {} local function ToIndex(bag, slot) return (bag < 0 and bag*100 - slot) or bag*100 + slot end local function GetBagSize(bag) return (bag == KEYRING_CONTAINER and GetKeyRingSize()) or GetContainerNumSlots(bag) end -- Defers a re-check of a bag by ~0.25s; used when a previously occupied slot -- reads back nil (lag race between BAG_UPDATE and item data arriving). local retryFrame = CreateFrame('Frame') retryFrame.elapsed = 0 retryFrame:Hide() retryFrame:SetScript('OnUpdate', function(self, elapsed) self.elapsed = self.elapsed + elapsed if self.elapsed < 0.25 then return end self.elapsed = 0 self:Hide() local bags = pendingBags pendingBags = {} for bag in pairs(bags) do Bagnon.BagEvents:UpdateItems(bag, true) end end) local function scheduleRetry(bag) pendingBags[bag] = true retryFrame.elapsed = 0 retryFrame:Show() end --[[ Startup ]]-- function BagEvents:Load() self.atBank = false self.firstVisit = true self.frame = CreateFrame('Frame') self.RegisterEvent = function(self, event) self.frame:RegisterEvent(event) end self.OnEvent = function(f, event, ...) if self[event] then self[event](self, event, ...) end end self.frame:SetScript('OnEvent', self.OnEvent) self:RegisterEvent('PLAYER_LOGIN') end --[[ Update Functions ]]-- --all info function BagEvents:AddItem(bag, slot) local index = ToIndex(bag,slot) if not slots[index] then slots[index] = {} end local data = slots[index] local texture, count, locked, quality, readable, lootable, link = GetContainerItemInfo(bag, slot) local start, duration, enable = GetContainerItemCooldown(bag, slot) local onCooldown = (start > 0 and duration > 0 and enable > 0) data[1] = link data[2] = count data[3] = locked data[4] = onCooldown self:SendMessage('ITEM_SLOT_ADD', bag, slot, link, count, locked, onCooldown) end function BagEvents:RemoveItem(bag, slot) local data = slots[ToIndex(bag, slot)] if data and next(data) then local prevLink = data[1] for i in pairs(data) do data[i] = nil end self:SendMessage('ITEM_SLOT_REMOVE', bag, slot, prevLink) end end function BagEvents:UpdateItem(bag, slot, isRetry) local data = slots[ToIndex(bag, slot)] if data then local prevLink = data[1] local prevCount = data[2] local texture, count, locked, quality, readable, lootable, link = GetContainerItemInfo(bag, slot) local start, duration, enable = GetContainerItemCooldown(bag, slot) local onCooldown = (start > 0 and duration > 0 and enable > 0) -- A previously occupied slot reading back nil is usually a lag race between -- BAG_UPDATE and item data arriving from the server. Defer once before drawing -- the slot as empty; on the second pass we trust whatever we read. if prevLink and (not link) and (not isRetry) then scheduleRetry(bag) return end if not(prevLink == link and prevCount == count) then data[1] = link data[2] = count data[3] = locked data[4] = onCooldown self:SendMessage('ITEM_SLOT_UPDATE', bag, slot, link, count, locked, onCooldown) end end end function BagEvents:UpdateItems(bag, isRetry) for slot = 1, GetBagSize(bag) do self:UpdateItem(bag, slot, isRetry) end end --cooldowns function BagEvents:UpdateCooldown(bag, slot) local data = slots[ToIndex(bag,slot)] if data and data[1] then local start, duration, enable = GetContainerItemCooldown(bag, slot) local onCooldown = (start > 0 and duration > 0 and enable > 0) if data[4] ~= onCooldown then data[4] = onCooldown self:SendMessage('ITEM_SLOT_UPDATE_COOLDOWN', bag, slot, onCooldown) end end end function BagEvents:UpdateCooldowns(bag) for slot = 1, GetBagSize(bag) do self:UpdateCooldown(bag, slot) end end --bag sizes function BagEvents:UpdateBagSize(bag) local prevSize = slots[bag*100] or 0 local newSize = GetBagSize(bag) or 0 slots[bag*100] = newSize if prevSize > newSize then for slot = newSize+1, prevSize do self:RemoveItem(bag, slot) end elseif prevSize < newSize then for slot = prevSize+1, newSize do self:AddItem(bag, slot) end end end function BagEvents:UpdateBagType(bag) local _, newType = GetContainerNumFreeSlots(bag) local prevType = bagTypes[bag] if newType ~= prevType then bagTypes[bag] = newType self:SendMessage('BAG_UPDATE_TYPE', bag, newType) end end function BagEvents:UpdateBagSizes() if self:AtBank() then for bag = 1, NUM_BAG_SLOTS + GetNumBankSlots() do self:UpdateBagSize(bag) end else for bag = 1, NUM_BAG_SLOTS do self:UpdateBagSize(bag) end end self:UpdateBagSize(KEYRING_CONTAINER) end function BagEvents:UpdateBagTypes() if self:AtBank() then for bag = 1, NUM_BAG_SLOTS + GetNumBankSlots() do self:UpdateBagType(bag) end else for bag = 1, NUM_BAG_SLOTS do self:UpdateBagType(bag) end end end --[[ Events ]]-- function BagEvents:PLAYER_LOGIN(...) self:RegisterBucketEvent('BAG_UPDATE', 0.2, 'BAG_UPDATE') self:RegisterEvent('BAG_UPDATE_COOLDOWN') self:RegisterEvent('PLAYERBANKSLOTS_CHANGED') self:RegisterEvent('BANKFRAME_OPENED') self:RegisterEvent('BANKFRAME_CLOSED') self:UpdateBagSize(KEYRING_CONTAINER) self:UpdateItems(KEYRING_CONTAINER) self:UpdateBagSize(BACKPACK_CONTAINER) self:UpdateItems(BACKPACK_CONTAINER) end function BagEvents:BAG_UPDATE(bagIDs) self:UpdateBagTypes() self:UpdateBagSizes() for bagID in pairs(bagIDs) do self:UpdateItems(bagID) end end function BagEvents:PLAYERBANKSLOTS_CHANGED(...) self:UpdateBagTypes() self:UpdateBagSizes() self:UpdateItems(BANK_CONTAINER) end function BagEvents:BANKFRAME_OPENED(...) self.atBank = true if self.firstVisit then self.firstVisit = nil self:UpdateBagSize(BANK_CONTAINER) self:UpdateBagTypes() self:UpdateBagSizes() end self:SendMessage('BANK_OPENED') end function BagEvents:BANKFRAME_CLOSED(...) self.atBank = false self:SendMessage('BANK_CLOSED') end function BagEvents:BAG_UPDATE_COOLDOWN(...) self:UpdateCooldowns(BACKPACK_CONTAINER) for bag = 1, NUM_BAG_SLOTS do self:UpdateCooldowns(bag) end end --[[ Accessor Methods ]]-- function BagEvents:AtBank() return self.atBank end --load the thing BagEvents:Load()