5b7a73a9c2
* fix(db): 🐛 Fixes an issue where personal and realm bank information wouldn't get updated Introduce the "Blizzard_GuildBankUI" addon in constructor, ensuring that the `GuildBankFrame` is loaded for bank type detection. Hide the Blizzard Guildbank to avoid both Bagnon and Blizzard banks being shown at the same time. This breaks the red color from the bank tabs * refactor(db): ♻️ Use old implementation for personal and guild bank detection * fix: 🐛 Fix issue where personal and realm bank wasn't identified on guild bank change event * fix(db): 🐛 Fixes an issue where only current guild bank tab would be added to db Use `QueryGuildBankTab` to preload items in guild bank tabs and identify trigger in GUILDBANKBAGSLOTS_CHANGED event to update bags when items are ready * fix(db): 🐛 Correctly index realm and personal banks on initial load
622 lines
15 KiB
Lua
622 lines
15 KiB
Lua
--[[
|
|
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
|
|
if 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
|
|
|
|
self.guildBankUpdateCalls = 0
|
|
self.availableTabs = {} -- table of available tabs
|
|
|
|
-- Query all tabs for personal and realm bank to preload data
|
|
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
|
|
local currentTab = GetCurrentGuildBankTab()
|
|
|
|
-- Special operation: After 2 initial calls, the QueryGuildBankTab calls above
|
|
-- trigger the GUILDBANKBAGSLOTS_CHANGED event at which the queried items are available.
|
|
|
|
-- Only update the bank if we are within 1-6 range of the initial calls as those
|
|
-- are likely triggered by the QueryGuildBankTab calls above. Which makes items
|
|
-- in the tabs available for GetGuildBankItemInfo calls.
|
|
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
|
|
-- Ignore current tab, and only update the tab that is next in the sequence
|
|
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)
|
|
else
|
|
print("[BagnonForever] Error: Unknown bank type")
|
|
end
|
|
end
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Normal operation: Update current tab
|
|
if self.IsPersonalBank then
|
|
local avail = GetGuildBankTabInfo(currentTab)
|
|
if type(avail) == "string" then
|
|
self:UpdateBag(currentTab + ASC_PERSONAL_BANK_OFFSET)
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Update all tabs for realm bank on any change
|
|
if self.IsRealmBank then
|
|
local avail = GetGuildBankTabInfo(currentTab)
|
|
if type(avail) == "string" then
|
|
self:UpdateBag(currentTab + ASC_REALM_BANK_OFFSET)
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
function BagnonDB:GUILDBANKFRAME_CLOSED()
|
|
self.IsPersonalBank = nil
|
|
self.IsRealmBank = nil
|
|
self.guildBankUpdateCalls = 0
|
|
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
|