--[[ Database.lua BagnonForever's implementation of BagnonDB --]] BagnonDB = CreateFrame('GameTooltip', 'BagnonDB', nil, 'BasicTooltipTemplate') LibStub("AceBucket-3.0"):Embed(BagnonDB) BagnonDB:SetScript('OnEvent', function(self, event, arg1) if arg1 == 'Bagnon_Forever' then self:UnregisterEvent('ADDON_LOADED') self:Initialize() end end) BagnonDB:RegisterEvent('ADDON_LOADED') ASC_PERSONAL_BANK_OFFSET = 1000; ASC_REALM_BANK_OFFSET = 2000; GUILDBANKBAGSLOTS_CHANGED_INIT_OFFSET = 2; -- Offset used to identify when guild bank tabs are loaded --constants local L = BAGNON_FOREVER_LOCALS local CURRENT_VERSION = GetAddOnMetadata('Bagnon_Forever', 'Version') local NUM_EQUIPMENT_SLOTS = 19 --locals local currentPlayer = UnitName('player') --the name of the current player that's logged on local currentRealm = GetRealmName() --what currentRealm we're on local playerList --a sorted list of players --[[ Local Functions ]]-- local function ToIndex(bag, slot) if tonumber(bag) then return (bag < 0 and bag*100 - slot) or bag*100 + slot end return bag .. slot end local function ToBagIndex(bag) return (tonumber(bag) and bag*100) or bag end --returns the full item link only for items that have enchants/suffixes, otherwise returns the item's ID local function ToShortLink(link) if link then local a,b,c,d,e,f,g,h = link:match('(%-?%d+):(%-?%d+):(%-?%d+):(%-?%d+):(%-?%d+):(%-?%d+):(%-?%d+):(%-?%d+)') --ASC sets this to unique id in the personal bank, clear it c = 0; if(b == '0' and b == c and c == d and d == e and e == f and f == g) then return a end return format('item:%s:%s:%s:%s:%s:%s:%s:%s', a, b, c, d, e, f, g, h) end end local function GetBagSize(bag) if bag == KEYRING_CONTAINER then return GetKeyRingSize() end if (bag >= ASC_PERSONAL_BANK_OFFSET) then return 98 end if bag == 'e' then return NUM_EQUIPMENT_SLOTS end return GetContainerNumSlots(bag) end --[[ Addon Loading ]]-- function BagnonDB:Initialize() self:LoadSettings() self:SetScript('OnEvent', function(self, event, ...) if self[event] then self[event](self, event, ...) end end) if IsLoggedIn() then self:PLAYER_LOGIN() else self:RegisterEvent('PLAYER_LOGIN') end end function BagnonDB:LoadSettings() if not(BagnonForeverDB and BagnonForeverDB.version) then BagnonForeverDB = {version = CURRENT_VERSION} else local cMajor, cMinor = CURRENT_VERSION:match('(%d+)%.(%d+)') local major, minor = BagnonForeverDB.version:match('(%d+)%.(%d+)') if major ~= cMajor then BagnonForeverDB = {version = cVersion} elseif minor ~= cMinor then self:UpdateSettings() end if BagnonForeverDB.version ~= CURRENT_VERSION then self:UpdateVersion() end end self.db = BagnonForeverDB if not self.db[currentRealm] then self.db[currentRealm] = {} end self.rdb = self.db[currentRealm] if not self.rdb[currentPlayer] then self.rdb[currentPlayer] = {} end self.pdb = self.rdb[currentPlayer] if not self.rdb[currentRealm] then self.rdb[currentRealm] = {} end self.realmdb = self.rdb[currentRealm] end function BagnonDB:UpdateSettings() end function BagnonDB:UpdateVersion() BagnonForeverDB.version = CURRENT_VERSION print(format('BagnonForever: Updated to v%s', BagnonForeverDB.version)) end --[[ Events ]]-- function BagnonDB:PLAYER_LOGIN() self:SaveMoney() self:UpdateBag(BACKPACK_CONTAINER) self:UpdateBag(KEYRING_CONTAINER) self:SaveEquipment() self:SaveNumBankSlots() self:RegisterEvent('GUILDBANKFRAME_OPENED') self:RegisterEvent('GUILDBANKBAGSLOTS_CHANGED') self:RegisterEvent('GUILDBANKFRAME_CLOSED') self:RegisterEvent('BANKFRAME_OPENED') self:RegisterEvent('BANKFRAME_CLOSED') self:RegisterEvent('PLAYER_MONEY') self:RegisterBucketEvent('BAG_UPDATE', 0.2, 'BAG_UPDATE') self:RegisterEvent('PLAYERBANKSLOTS_CHANGED') self:RegisterEvent('UNIT_INVENTORY_CHANGED') self:RegisterEvent('PLAYERBANKBAGSLOTS_CHANGED') end function BagnonDB:PLAYER_MONEY() self:SaveMoney() end function BagnonDB:BAG_UPDATE(bagIDs) for bagID in pairs(bagIDs) do if not(bagID == BANK_CONTAINER or bagID > NUM_BAG_SLOTS) or self.atBank then self:OnBagUpdate(bagID) end end end function BagnonDB:PLAYERBANKSLOTS_CHANGED() self:UpdateBag(BANK_CONTAINER) end function BagnonDB:PLAYERBANKBAGSLOTS_CHANGED() self:SaveNumBankSlots() end function BagnonDB:BANKFRAME_OPENED() self.atBank = true self:UpdateBag(BANK_CONTAINER) for i = 1, GetNumBankSlots() do self:UpdateBag(i + 4) end end function BagnonDB:BANKFRAME_CLOSED() self.atBank = nil end function BagnonDB:GUILDBANKFRAME_OPENED() -- Identify bank type from permissions payload self.IsPersonalBank = nil self.IsRealmBank = nil if HasJsonCacheData and HasJsonCacheData("BANK_PERMISSIONS_PAYLOAD", 0) then local json = GetJsonCacheData("BANK_PERMISSIONS_PAYLOAD", 0) if json then local jsonObject = C_Serialize:FromJSON(json) if jsonObject then self.IsPersonalBank = jsonObject.IsPersonalBank self.IsRealmBank = jsonObject.IsRealmBank end end end -- Mirror onto GuildBankFrame so other modules (sortBtn, etc) can detect the bank type if GuildBankFrame then GuildBankFrame.IsPersonalBank = self.IsPersonalBank GuildBankFrame.IsRealmBank = self.IsRealmBank end -- Notify Bagnon listeners (TitleFrame, etc.) that bank type has been detected, -- in case GUILDBANKFRAME_OPENED already fired in another handler first. local bagnon = LibStub and LibStub('AceAddon-3.0', true) and LibStub('AceAddon-3.0'):GetAddon('Bagnon', true) if bagnon and bagnon.Callbacks and bagnon.Callbacks.SendMessage then bagnon.Callbacks:SendMessage('GUILDBANK_TYPE_DETECTED') end self.guildBankUpdateCalls = 0 self.availableTabs = {} -- table of available tabs -- Only pre-query tabs for personal/realm bank (these snapshot into the per-character DB). -- Regular guild bank content belongs to the guild and is handled by Bagnon_GuildBank. if not (self.IsPersonalBank or self.IsRealmBank) then return end local currentTab = GetCurrentGuildBankTab and GetCurrentGuildBankTab() or 0 for i = 1, 6 do local avail = GetGuildBankTabInfo(i) if type(avail) == "string" and i ~= currentTab then QueryGuildBankTab(i) self.availableTabs[i] = avail end end end function BagnonDB:GUILDBANKBAGSLOTS_CHANGED() self.guildBankUpdateCalls = self.guildBankUpdateCalls + 1 -- Regular guild bank: nothing to snapshot per-character; let Bagnon_GuildBank handle it. if not (self.IsPersonalBank or self.IsRealmBank) then return end local currentTab = GetCurrentGuildBankTab() -- Initial pre-query window: after the first 2 calls, each queued QueryGuildBankTab -- triggers one GUILDBANKBAGSLOTS_CHANGED that makes that tab's items available. if ((self.guildBankUpdateCalls > GUILDBANKBAGSLOTS_CHANGED_INIT_OFFSET) and (self.guildBankUpdateCalls <= GUILDBANKBAGSLOTS_CHANGED_INIT_OFFSET + #self.availableTabs)) then for i, avail in pairs(self.availableTabs) do if (i ~= currentTab and self.guildBankUpdateCalls == GUILDBANKBAGSLOTS_CHANGED_INIT_OFFSET + i) then if self.IsPersonalBank then self:UpdateBag(i + ASC_PERSONAL_BANK_OFFSET) elseif self.IsRealmBank then self:UpdateBag(i + ASC_REALM_BANK_OFFSET) end end end return end -- Normal operation: update current tab into per-character DB (personal) or realm DB local avail = GetGuildBankTabInfo(currentTab) if type(avail) ~= "string" then return end if self.IsPersonalBank then self:UpdateBag(currentTab + ASC_PERSONAL_BANK_OFFSET) elseif self.IsRealmBank then self:UpdateBag(currentTab + ASC_REALM_BANK_OFFSET) end end function BagnonDB:GUILDBANKFRAME_CLOSED() self.IsPersonalBank = nil self.IsRealmBank = nil self.guildBankUpdateCalls = 0 if GuildBankFrame then GuildBankFrame.IsPersonalBank = nil GuildBankFrame.IsRealmBank = nil end end function BagnonDB:UNIT_INVENTORY_CHANGED(event, unit) if unit == 'player' then self:SaveEquipment() end end --[[ Access Functions Bagnon requires all of these functions to be present when attempting to view cached data --]] --[[ BagnonDB:GetPlayerList() returns: iterator of all players on this realm with data usage: for playerName, data in BagnonDB:GetPlayers() --]] function BagnonDB:GetPlayerList() if(not playerList) then playerList = {} for player in self:GetPlayers() do table.insert(playerList, player) end --sort by currentPlayer first, then alphabetically table.sort(playerList, function(a, b) if(a == currentPlayer) then return true elseif(b == currentPlayer) then return false end return a < b end) end return playerList end function BagnonDB:GetPlayers() return pairs(self.rdb) end --[[ BagnonDB:GetMoney(player) args: player (string) the name of the player we're looking at. This is specific to the current realm we're on returns: (number) How much money, in copper, the given player has --]] function BagnonDB:GetMoney(player) local playerData = self.rdb[player] if playerData then return playerData.g or 0 end return 0 end --[[ BagnonDB:GetNumBankSlots(player) args: player (string) the name of the player we're looking at. This is specific to the current realm we're on returns: (number or nil) How many bank slots the current player has purchased --]] function BagnonDB:GetNumBankSlots(player) local playerData = self.rdb[player] if playerData then return playerData.numBankSlots end end --[[ BagnonDB:GetBagData(bag, player) args: player (string) the name of the player we're looking at. This is specific to the current realm we're on bag (number) the number of the bag we're looking at. returns: size (number) How many items the bag can hold (number) hyperlink (string) The hyperlink of the bag count (number) How many items are in the bag. This is used by ammo and soul shard bags --]] function BagnonDB:GetBagData(bag, player) local playerDB = self.rdb[player] if playerDB then local bagInfo = playerDB[ToBagIndex(bag)] if bagInfo then local size, link, count = strsplit(',', bagInfo) local hyperLink = (link and select(2, GetItemInfo(link))) or nil return tonumber(size), hyperLink, tonumber(count) or 1, GetItemIcon(link) end end end --[[ BagnonDB:GetItemData(bag, slot, player) args: player (string) the name of the player we're looking at. This is specific to the current realm we're on bag (number) the number of the bag we're looking at. itemSlot (number) the specific item slot we're looking at returns: hyperLink (string) The hyperLink of the item count (number) How many of there are of the specific item texture (string) The filepath of the item's texture quality (number) The numeric representaiton of the item's quality: from 0 (poor) to 7 (artifcat) --]] function BagnonDB:GetItemData(bag, slot, player) local playerDB = self.rdb[player] if playerDB then local itemInfo = playerDB[ToIndex(bag, slot)] if itemInfo then local link, count = strsplit(',', itemInfo) if link then local hyperLink, quality = select(2, GetItemInfo(link)) return hyperLink, tonumber(count) or 1, GetItemIcon(link), tonumber(quality) end end end end --[[ Returns how many of the specific item id the given player has in the given bag --]] function BagnonDB:GetItemCount(itemLink, bag, player) local total = 0 local itemLink = select(2, GetItemInfo(ToShortLink(itemLink))) local size = (self:GetBagData(bag, player)) or 0 if (bag == "e") then size = NUM_EQUIPMENT_SLOTS end for slot = 1, size do local link, count = self:GetItemData(bag, slot, player) if link == itemLink then total = total + (count or 1) end end return total end --[[ Storage Functions How we store the data (duh) --]] --[[ Storage Functions ]]-- function BagnonDB:SaveMoney() self.pdb.g = GetMoney() end function BagnonDB:SaveNumBankSlots() self.pdb.numBankSlots = GetNumBankSlots() end --saves all the player's equipment data information function BagnonDB:SaveEquipment() for slot = 0, NUM_EQUIPMENT_SLOTS do local link = GetInventoryItemLink('player', slot) local index = ToIndex('e', slot) if link then local link = ToShortLink(link) local count = GetInventoryItemCount('player', slot) count = count > 1 and count or nil if(link and count) then self.pdb[index] = format('%s,%d', link, count) else self.pdb[index] = link end else self.pdb[index] = nil end end end --saves data about a specific item the current player has function BagnonDB:SaveItem(bag, slot) if (bag > ASC_REALM_BANK_OFFSET) then local texture, count = GetGuildBankItemInfo(bag - ASC_REALM_BANK_OFFSET, slot) local index = ToIndex(bag, slot) if texture then local link = ToShortLink(GetGuildBankItemLink(bag - ASC_REALM_BANK_OFFSET, slot)) count = count > 1 and count or nil if(link and count) then self.realmdb[index] = format('%s,%d', link, count) else self.realmdb[index] = link end else self.realmdb[index] = nil end elseif (bag > ASC_PERSONAL_BANK_OFFSET) then local texture, count = GetGuildBankItemInfo(bag - ASC_PERSONAL_BANK_OFFSET, slot) local index = ToIndex(bag, slot) if texture then local link = ToShortLink(GetGuildBankItemLink(bag - ASC_PERSONAL_BANK_OFFSET, slot)) count = count > 1 and count or nil if(link and count) then self.pdb[index] = format('%s,%d', link, count) else self.pdb[index] = link end else self.pdb[index] = nil end else local texture, count = GetContainerItemInfo(bag, slot) local index = ToIndex(bag, slot) if texture then local link = ToShortLink(GetContainerItemLink(bag, slot)) count = count > 1 and count or nil if(link and count) then self.pdb[index] = format('%s,%d', link, count) else self.pdb[index] = link end else self.pdb[index] = nil end end end --saves all information about the given bag, EXCEPT the bag's contents function BagnonDB:SaveBag(bag) local data = self.pdb if (bag >= ASC_REALM_BANK_OFFSET) then local size = GetBagSize(bag) local index = ToBagIndex(bag) self.realmdb[index] = size elseif (bag >= ASC_PERSONAL_BANK_OFFSET) then local size = GetBagSize(bag) local index = ToBagIndex(bag) self.pdb[index] = size else local size = GetBagSize(bag) local index = ToBagIndex(bag) if size > 0 then local equipSlot = bag > 0 and ContainerIDToInventoryID(bag) local link = ToShortLink(GetInventoryItemLink('player', equipSlot)) local count = GetInventoryItemCount('player', equipSlot) if count < 1 then count = nil end if(size and link and count) then self.pdb[index] = format('%d,%s,%d', size, link, count) elseif(size and link) then self.pdb[index] = format('%d,%s', size, link) else self.pdb[index] = size end else self.pdb[index] = nil end end end --saves both relevant information about the given bag, and all information about items in the given bag function BagnonDB:UpdateBag(bag) self:SaveBag(bag) for slot = 1, GetBagSize(bag) do self:SaveItem(bag, slot) end end function BagnonDB:OnBagUpdate(bag) if self.atBank then for i = 1, (NUM_BAG_SLOTS + GetNumBankSlots()) do self:SaveBag(i) end else for i = 1, NUM_BAG_SLOTS do self:SaveBag(i) end end for slot = 1, GetBagSize(bag) do self:SaveItem(bag, slot) end end --[[ Removal Functions ]]-- --removes all saved data about the given player function BagnonDB:RemovePlayer(player, realm) local realm = realm or currentRealm local rdb = self.db[realm] if rdb then rdb[player] = nil end if realm == currentRealm and playerList then for i,character in pairs(playerList) do if(character == player) then table.remove(playerList, i) break end end end end