local _, S = ... local pairs, ipairs, string, type, time = pairs, ipairs, string, type, time -- Use new APIs, keeping compatibility with Classic local useNewContainerAPI = false local ContainerIDToInventoryID, GetContainerItemInfo, PickupContainerItem = ContainerIDToInventoryID, GetContainerItemInfo, PickupContainerItem if C_Container then if C_Container.ContainerIDToInventoryID then ContainerIDToInventoryID = C_Container.ContainerIDToInventoryID end if C_Container.GetContainerItemInfo then useNewContainerAPI = true; GetContainerItemInfo = C_Container.GetContainerItemInfo end if C_Container.PickupContainerItem then PickupContainerItem = C_Container.PickupContainerItem end end local GetCurrencyListLink = GetCurrencyListLink if C_CurrencyInfo then if C_CurrencyInfo.GetCurrencyListLink then GetCurrencyListLink = C_CurrencyInfo.GetCurrencyListLink end end local GetItemInfoInstant, GetItemInfo, GetItemClassInfo, GetItemSubClassInfo, GetDetailedItemLevelInfo, IsEquippableItem = GetItemInfoInstant, GetItemInfo, GetItemClassInfo, GetItemSubClassInfo, GetDetailedItemLevelInfo, IsEquippableItem if C_Item then GetItemInfoInstant, GetItemInfo, GetItemClassInfo, GetItemSubClassInfo, GetDetailedItemLevelInfo, IsEquippableItem = C_Item.GetItemInfoInstant, C_Item.GetItemInfo, C_Item.GetItemClassInfo, C_Item.GetItemSubClassInfo, C_Item.GetDetailedItemLevelInfo, C_Item.IsEquippableItem end S.Data = {} S.DataTooltip = CreateFrame("GameTooltip", "SortedDataTooltip", nil, "GameTooltipTemplate") S.DataTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") local playerGUID = UnitGUID("player") local totalMoney = {} function S.Data.GetTotalMoney() return totalMoney end local INVENTORY_IDS_TO_UPDATE = {} local BANK_INVENTORY_IDS = {} for _, containerID in pairs(S.Utils.ContainersOfType("ALL")) do if containerID ~= BACKPACK_CONTAINER and containerID ~= BANK_CONTAINER and containerID ~= REAGENTBANK_CONTAINER and containerID ~= KEYRING_CONTAINER then table.insert(INVENTORY_IDS_TO_UPDATE, ContainerIDToInventoryID(containerID)) end end for _, containerID in pairs(S.Utils.ContainersOfType("BANK")) do if containerID ~= BANK_CONTAINER then BANK_INVENTORY_IDS[ContainerIDToInventoryID(containerID)] = true end end -- Converts item link into a key for the data table function S.Data.ItemKey(itemLink, speciesID) -- Battle pets if speciesID then return "BattlePet:"..speciesID else if string.find(itemLink, "|Hitem") then local a,b = string.match(itemLink, "|Hitem:(%d*:)[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:(.-)|h") return a..b else local a = string.match(itemLink, "|H(.-)|h") return a end end end local ItemKey = S.Data.ItemKey -- One data table to hold them all, one table to find them, one table to bring them all, and in the darkness bind them. Sorted_Data = {} local sortedData = Sorted_Data -- Make local for performance. This table gets used a lot -- Table for account-wide data Sorted_AccountData = { ["containers"] = {}, ["containerNumSlots"] = {}, ["bankTabData"] = {}, } -- Player selection local selectedCharacterGUID = UnitGUID("player") function S.GetSelectedCharacter() return selectedCharacterGUID end function S.SelectCharacter(guid) if selectedCharacterGUID ~= guid then selectedCharacterGUID = guid S.Utils.TriggerEvent("CharacterSelected") end end function S.IsPlayingCharacterSelected() return selectedCharacterGUID == UnitGUID("player") end -- Equipment set selection local selectedEquipmentSetID = -1 -- -1 for no selection, -2 to filter items not in any equipment set function S.GetSelectedEquipmentSet() return selectedEquipmentSetID end function S.SelectEquipmentSet(id) selectedEquipmentSetID = id S.Utils.TriggerEvent("EquipmentSetSelected") end -- Return the data table for a player local function GetData(guid) if not guid then return sortedData[selectedCharacterGUID] else return sortedData[guid] end end function S.GetData(guid) return GetData(guid) end function S.Data.DeleteCharacter(guid) if S.GetSelectedCharacter() == guid then S.SelectCharacter(UnitGUID("player")) end sortedData[guid] = nil end -- On login, creates the data table if the current player is new local function InitialiseData() if not sortedData[playerGUID] then sortedData[playerGUID] = {} sortedData[playerGUID].bankNotCached = true sortedData[playerGUID].containers = {} sortedData[playerGUID].favoritedItems = {} end -- Check these fields individually for characters which last loaded an older version of the addon if not sortedData[playerGUID].trashItems then sortedData[playerGUID].trashItems = {} end if not sortedData[playerGUID].currencies then sortedData[playerGUID].currencies = {} end if not sortedData[playerGUID].favoritedCurrencies then sortedData[playerGUID].favoritedCurrencies = {} end if not sortedData[playerGUID].timeItemsAdded then sortedData[playerGUID].timeItemsAdded = {} end if not sortedData[playerGUID].newItems then sortedData[playerGUID].newItems = {} end if not sortedData[playerGUID].equipSets then sortedData[playerGUID].equipSets = {} end if not sortedData[playerGUID].containerNumSlots then sortedData[playerGUID].containerNumSlots = {} end if not sortedData[playerGUID].inventoryItems then sortedData[playerGUID].inventoryItems = {} end end -- Updates any info available on login local function UpdatePlayerData() local playerData = sortedData[playerGUID] playerData.lastLogged = time() playerData.name = UnitName("player") playerData.level = UnitLevel("player") _, _, playerData.race = UnitRace("player") playerData.faction = UnitFactionGroup("player") playerData.realm = GetRealmName() _, playerData.class = UnitClass("player") if S.WoWVersion() >= 6 and not IsReagentBankUnlocked() then playerData.reagentNotUnlocked = true else playerData.reagentNotUnlocked = nil end end local updatedCurrencies = {} -- Keep track of which currencies have been updated so far this session -- Sorted updates each currency once per session (in case of changes) and then only updates the quantities after local function UpdateCurrencies() if S.WoWVersion() > 1 then local i = 1 local headingInfo local currenciesTable = GetData(playerGUID).currencies -- Classic doesn't use the new C_CurrencyInfo namespace if not C_CurrencyInfo.GetCurrencyListSize then -- Iterate through the currency list, updating the currency table in sortedData while i <= GetCurrencyListSize() do local name, isHeader, isExpanded, isUnused, isWatched, count, icon, maximum, hasWeeklyLimit, currentWeeklyAmount, unknown, currencyID = GetCurrencyListInfo(i) if isHeader then if not isExpanded then ExpandCurrencyList(i, true) end headingInfo = name else if not currenciesTable[currencyID] then currenciesTable[currencyID] = {} end local data = currenciesTable[currencyID] if not updatedCurrencies[currencyID] then -- Do a full update data.id = currencyID data.name = name data.link = GetCurrencyListLink(i) data.texture = icon data.quality = 1 data.maxQuantity = maximum data.canEarnPerWeek = hasWeeklyLimit data.maxWeeklyQuantity = maxWeeklyQuantity data.heading = headingInfo data.tooltip = S.Utils.GetCurrencyTooltipByID(currencyID) updatedCurrencies[currencyID] = true end data.count = count data.quantityEarnedThisWeek = currentWeeklyAmount end i = i + 1 end -- Retail else -- Iterate through the currency list, updating the currency table in sortedData while i <= C_CurrencyInfo.GetCurrencyListSize() do local currencyInfo = C_CurrencyInfo.GetCurrencyListInfo(i) if currencyInfo.isHeader then -- It's a header, so make sure it's expanded before continuing through the list if not currencyInfo.isHeaderExpanded then C_CurrencyInfo.ExpandCurrencyList(i, true) end headingInfo = currencyInfo else -- It's not a header, so it's actually a currency! Update the data table, using the currencyID as the table key local currencyID = currencyInfo.currencyID if not currenciesTable[currencyID] then currenciesTable[currencyID] = {} end local data = currenciesTable[currencyID] if not updatedCurrencies[currencyID] then -- Do a full update data.id = currencyID data.name = currencyInfo.name data.link = C_CurrencyInfo.GetCurrencyListLink(i) data.texture = currencyInfo.iconFileID data.quality = currencyInfo.quality data.maxQuantity = currencyInfo.maxQuantity data.canEarnPerWeek = currencyInfo.canEarnPerWeek data.maxWeeklyQuantity = currencyInfo.maxWeeklyQuantity data.heading = headingInfo.name data.tooltip = S.Utils.GetCurrencyTooltipByID(currencyID) updatedCurrencies[currencyID] = true end data.count = currencyInfo.quantity data.quantityEarnedThisWeek = currencyInfo.quantityEarnedThisWeek end i = i + 1 end end end S.Utils.TriggerEvent("CurrenciesUpdated") end function S.Data.GetCurrency(currencyID) local data = GetData() if data.currencies then if data.currencies[currencyID] then return data.currencies[currencyID] end end end local function UpdateTimeItemsAdded() local time = time() local data = GetData(playerGUID) -- Make a table of every item in the player's bags local items = {} for _, bag in pairs(S.Utils.ContainersOfType("ALL")) do for slot = 1, S.Utils.GetContainerMaxSlots(bag) do local itemData = S.Data.GetItem(bag, slot, playerGUID) if itemData.link then local key = itemData.key if not items[key] then items[key] = {} items[key].time = time items[key].count = itemData.count else items[key].count = items[key].count + itemData.count end end end end -- Include equipment slots for invID = 1, 23 do local link = GetInventoryItemLink("player", invID) if link then local key = S.Data.ItemKey(link) if not items[key] then items[key] = {} items[key].time = time items[key].count = 1 else items[key].count = items[key].count + 1 end end end -- Replace times with any times from the existing table if data.timeItemsAdded then for key, timeItemAdded in pairs(data.timeItemsAdded) do if items[key] and items[key].count <= timeItemAdded.count then items[key].time = timeItemAdded.time end end end -- Replace with new table data.timeItemsAdded = items end function S.Data.GetItemAge(itemData) local key = itemData.key local timeItemAdded = GetData().timeItemsAdded[key] if timeItemAdded then return time() - timeItemAdded.time end end local function UpdateEquipmentSets() if S.WoWVersion() >= 3 then local data = GetData(playerGUID) for id, _ in pairs(data.equipSets) do data.equipSets[id].deleted = true end for _, id in pairs(C_EquipmentSet.GetEquipmentSetIDs()) do if not data.equipSets[id] then data.equipSets[id] = {} data.equipSets[id].locations = {} end data.equipSets[id].deleted = nil data.equipSets[id].name, data.equipSets[id].icon = C_EquipmentSet.GetEquipmentSetInfo(id) -- Clear current locations table, ignoring the bank if the player doesn't have it opened local toClear = {} for k,v in pairs(data.equipSets[id].locations) do if S.IsBankOpened() or not v.bag or v.bag <= NUM_BAG_SLOTS then table.insert(toClear, k) end end for _,k in pairs(toClear) do data.equipSets[id].locations[k] = nil end -- Populate locations table local itemLocations = C_EquipmentSet.GetItemLocations(id) if itemLocations then for _, location in pairs(itemLocations) do local player, bank, bags, voidStorage, slot, bag if S.WoWVersion() == 3 then player, bank, bags, slot, bag = EquipmentManager_UnpackLocation(location) else player, bank, bags, voidStorage, slot, bag = EquipmentManager_UnpackLocation(location) end if (bags or bank) and bag ~= nil then table.insert(data.equipSets[id].locations, { ["bag"] = bag, ["slot"] = slot }) end end end end -- Clear deleted equipment sets local toClear = {} for id, equipSet in pairs(data.equipSets) do if equipSet.deleted then table.insert(toClear, id) end end for _, id in pairs(toClear) do data.equipSets[id] = nil end -- Toggle the equipment sets dropdown button --SortedEquipmentSetsDropdownButton_Update() end S.Utils.TriggerEvent("EquipmentSetsUpdated") end local function UpdateBagContents(container) -- Bags for k, bagID in pairs(S.Utils.ContainersOfType("BAGS")) do if not container or bagID == container then for slotID = 1, S.Utils.MaxBagSlots() do S.Data.UpdateItem(bagID, slotID) end end end -- Container buttons --[[for i = 1, NUM_BAG_SLOTS do local slotID = -NUM_BAG_SLOTS + i S.Data.UpdateItem(0, slotID) end]] -- Bank if S.IsBankOpened() then GetData(playerGUID).bankNotCached = nil for k, bagID in pairs(S.Utils.ContainersOfType("BANK")) do if not container or bagID == container then for slotID = 1, S.Utils.MaxBagSlots() do S.Data.UpdateItem(bagID, slotID) end end end -- Container buttons for slotID = 1, NUM_BANKBAGSLOTS do S.Data.UpdateItem(-4, slotID) end end -- Reagent bank if S.WoWVersion() >= 6 and S.IsBankOpened() and IsReagentBankUnlocked() then GetData(playerGUID).reagentNotUnlocked = nil if not container or REAGENTBANK_CONTAINER == container then for slotID = 1, 98 do S.Data.UpdateItem(REAGENTBANK_CONTAINER, slotID) end end end -- Account bank if S.WoWVersion() >= 11 and S.IsBankOpened() and C_PlayerInfo.HasAccountInventoryLock() then for k, bagID in pairs(S.Utils.ContainersOfType("ACCOUNT")) do if not container or bagID == container then for slotID = 1, 98 do S.Data.UpdateItem(bagID, slotID) end end end end -- Keyring if S.WoWVersion() <= 3 then if not container or KEYRING_CONTAINER == container then for slotID = 1,32 do S.Data.UpdateItem(KEYRING_CONTAINER, slotID) end end end -- Inventory items for _, inventoryID in pairs(INVENTORY_IDS_TO_UPDATE) do if not BANK_INVENTORY_IDS[inventoryID] or S.IsBankOpened() then S.Data.UpdateInventoryItem(inventoryID) end end -- Update number of slots for k, bagID in pairs(S.Utils.ContainersOfType("ALL")) do if S.IsBankOpened() or not (S.Utils.ContainerIsType(bagID, "BANK") or S.Utils.ContainerIsType(bagID, "REAGENT") or S.Utils.ContainerIsType(bagID, "ACCOUNT")) then if S.Utils.ContainerIsType(bagID, "ACCOUNT") then Sorted_AccountData.containerNumSlots[bagID] = { ["numSlots"] = S.Utils.GetContainerNumSlots(bagID), ["numFreeSlots"] = S.Utils.GetContainerNumFreeSlots(bagID) } else sortedData[playerGUID].containerNumSlots[bagID] = { ["numSlots"] = S.Utils.GetContainerNumSlots(bagID), ["numFreeSlots"] = S.Utils.GetContainerNumFreeSlots(bagID) } end end end UpdateEquipmentSets() UpdateTimeItemsAdded() S.Utils.TriggerEvent("BagsUpdated") end S.Data.UpdateBagContents = UpdateBagContents local itemUpdateMethods = {} local columnItemUpdateMethod = {} function S.Data.AddDataToItem(func, columnKey) table.insert(itemUpdateMethods, func) if columnKey then columnItemUpdateMethod[columnKey] = func end end local function UpdateItemColumn(bag, slot, columnKey) columnItemUpdateMethod[columnKey](S.Data.GetItem(bag, slot, playerGUID)) end function S.Data.UpdateColumn(columnKey) -- Bags for k, bagID in pairs(S.Utils.ContainersOfType("BAGS")) do if not container or bagID == container then for slotID = 1, S.Utils.MaxBagSlots() do UpdateItemColumn(bagID, slotID, columnKey) end end end -- Bank for k, bagID in pairs(S.Utils.ContainersOfType("BANK")) do if not container or bagID == container then for slotID = 1, S.Utils.MaxBagSlots() do UpdateItemColumn(bagID, slotID, columnKey) end end end -- Reagent bank if S.WoWVersion() >= 6 then if not container or REAGENTBANK_CONTAINER == container then for slotID = 1, 98 do UpdateItemColumn(REAGENTBANK_CONTAINER, slotID, columnKey) end end end -- Account bank if S.WoWVersion() >= 11 then for k, bagID in pairs(S.Utils.ContainersOfType("ACCOUNT")) do if not container or bagID == container then for slotID = 1, 98 do UpdateItemColumn(bagID, slotID, columnKey) end end end end -- Keyring if S.WoWVersion() <= 3 then if not container or KEYRING_CONTAINER == container then for slotID = 1,32 do UpdateItemColumn(KEYRING_CONTAINER, slotID, columnKey) end end end end local guildTooltipInfoFields = { ["id"] = {"itemID", "intVal"}, ["battlePetSpeciesID"] = {"speciesID", "intVal"}, ["battlePetLevel"] = {"effectiveILvl", "intVal"}, ["battlePetName"] = {"speciesName", "stringVal"}, ["battlePetBreedQuality"] = {"quality", "intVal"}, } local updatedItems = { ["INVENTORY"] = {}, ["GUILD"] = {}, ["VOID"] = {}, ["BAGS"] = {} } local function OnDataAvailable(bag, slot, inventory, guild, void) local self, newLink if inventory then self = S.Data.GetInventoryItem(bag, playerGUID) self.texture, self.count, self.quality, newLink, self.hasNoValue, self.itemID = GetInventoryItemTexture("player", bag), GetInventoryItemCount("player", bag), GetInventoryItemQuality("player", bag), GetInventoryItemLink("player", bag), false, GetInventoryItemID("player", bag) elseif guild then if not S.IsGuildBankAvailable() then return end self = S.Data.GetGuildItem(bag, slot, playerGUID) self.texture, self.count, _, _, self.quality = GetGuildBankItemInfo(bag, slot) newLink = GetGuildBankItemLink(bag, slot) self.itemID = GetItemInfoInstant(newLink) elseif void then if not S.IsVoidStorageAvailable() then return end self = S.Data.GetVoidItem(bag, slot, playerGUID) self.itemID, self.texture, _, _, _, self.quality = GetVoidItemInfo(bag, slot) newLink = GetVoidItemHyperlinkString((bag - 1) * 80 + slot) self.count = 1 else self = S.Data.GetItem(bag, slot, playerGUID) if S.WoWVersion() >= 4 or bag ~= KEYRING_CONTAINER then if useNewContainerAPI then local t = GetContainerItemInfo(bag, slot) if t then self.texture = t.iconFileID self.count = t.stackCount self.quality = t.quality newLink = t.hyperlink self.hasNoValue = t.hasNoValue self.itemID = t.itemID else self.texture, self.count, self.quality, newLink, self.hasNoValue, self.itemID = nil, nil, nil, nil, nil, nil end else self.texture, self.count, _, self.quality, _, _, newLink, _, self.hasNoValue, self.itemID = GetContainerItemInfo(bag, slot) end else local invSlotID = KeyRingButtonIDToInvSlotID(slot) self.texture, self.count, self.quality, newLink, self.hasNoValue, self.itemID = GetInventoryItemTexture("player", invSlotID), GetInventoryItemCount("player", invSlotID), GetInventoryItemQuality("player", invSlotID), GetInventoryItemLink("player", invSlotID), false, GetInventoryItemID("player", invSlotID) end end -- Make sure that, after reloading, each item updates at least once, even if the link hasn't changed local itemHasUpdated if inventory then itemHasUpdated = updatedItems["INVENTORY"][bag] updatedItems["INVENTORY"][bag] = true elseif guild then if not updatedItems["GUILD"][bag] then updatedItems["GUILD"][bag] = {} end itemHasUpdated = updatedItems["GUILD"][bag][slot] updatedItems["GUILD"][bag][slot] = true elseif void then if not updatedItems["VOID"][bag] then updatedItems["VOID"][bag] = {} end itemHasUpdated = false --updatedItems["VOID"][bag][slot] DISABLED WHILE VOID STORAGE TAB IS HAVING ISSUES updatedItems["VOID"][bag][slot] = true else if not updatedItems["BAGS"][bag] then updatedItems["BAGS"][bag] = {} end itemHasUpdated = updatedItems["BAGS"][bag][slot] updatedItems["BAGS"][bag][slot] = true end -- Only update when the item changes, or if this is the first time this item has been updated since the last reload if self.link ~= newLink or not itemHasUpdated then self.link = newLink if not self.categories then self.categories = {} end if self.link then -- Run any methods to add data provided by plugins for _, func in pairs(itemUpdateMethods) do func(self) end if guild then -- Get info out of the C_TooltipInfo api local args = C_TooltipInfo.GetGuildBankItem(bag, slot) if args then if args.args then -- Pre 10.1 version args = args.args local t = {} for _, arg in pairs(args) do local fields = guildTooltipInfoFields[arg.field] if fields then self[fields[1]] = arg[fields[2]] end end else -- Post 10.1 version for field, arg in pairs(args) do local fields = guildTooltipInfoFields[field] if fields then self[fields[1]] = arg end end self.tooltip = {{}, {}} for k,v in pairs(args.lines) do self.tooltip[1][k] = v.leftText self.tooltip[2][k] = v.rightText end end end end -- Replaced with self.invSlotID self.equipLoc = nil -- Pet cages if self.itemID == 82800 then _, _, _, _, self.minLevel, _, _, self.stackCount, _, _, self.value, self.classID, self.subClassID, self.bindType, self.expacID = GetItemInfo(82800) self.invSlotID = nil if not guild then -- Guild items already have this data extracted from C_TooltipInfo self.speciesID, self.level, self.quality, _, _, _, self.speciesName = BattlePetToolTip_UnpackBattlePetLink(self.link) self.speciesName = self.speciesName:gsub("[%[%]]", "") -- Remove square brackets around name self.effectiveILvl = self.level end self.name = string.format(S.Localize("ITEM_PET_CAGED"), self.speciesName) else self.speciesName, self.speciesID, self.cageName = nil, nil, nil if S.WoWVersion() >= 5 then _, _, _, _, _, _, _, _, _, _, _, _, self.speciesID = C_PetJournal.GetPetInfoByItemID(self.itemID) end -- Mythic Keystones if S.WoWVersion() >= 7 and self.itemID and C_Item.IsItemKeystoneByID(self.itemID) then -- Mythic Keystone self.minLevel = 60 self.value = 0 self.classID = Enum.ItemClass.Miscellaneous self.subClassID = Enum.ItemMiscellaneousSubclass.Reagent self.bindType = 1 self.expacID = S.WoWVersion() - 1 self.quality = 8 self.invSlotID = nil if not guid then self.level = C_MythicPlus.GetOwnedKeystoneLevel() local mapID = C_MythicPlus.GetOwnedKeystoneChallengeMapID() if (not mapID) then mapID = C_MythicPlus.GetOwnedKeystoneMapID() end if (mapID) then local name, id, timeLimit, texture, backgroundTexture = C_ChallengeMode.GetMapUIInfo(mapID) if (name) then self.name = string.format(S.Localize("ITEM_MYTHIC_KEYSTONE"), name.." ("..self.level..")") else self.name = S.Localize("ITEM_UNKNOWN_KEYSTONE") end else self.name = S.Localize("ITEM_UNKNOWN_KEYSTONE") end else self.level = 1 self.name = S.Localize("ITEM_UNKNOWN_KEYSTONE") end self.effectiveILvl = self.level else self.name = nil -- Get more info from the link using GetItemInfo self.name, _, self.quality, self.level, self.minLevel, _, _, self.stackCount, _, _, self.value, self.classID, self.subClassID, self.bindType, self.expacID = GetItemInfo(self.link) -- If that didn't work, try using the itemID instead if not self.name then self.name, _, self.quality, self.level, self.minLevel, _, _, self.stackCount, _, _, self.value, self.classID, self.subClassID, self.bindType, self.expacID = GetItemInfo(self.itemID) self.effectiveILvl = self.level else -- Get the actual item level, after any scaling is applied self.effectiveILvl = GetDetailedItemLevelInfo(self.link) end self.invSlotID = C_Item.GetItemInventoryTypeByID(self.link) if not self.expacID then self.expacID = 0 elseif self.expacID > 253 then self.expacID = self.expacID - 254 end --[[if S.WoWVersion() >= 9 and self.link then if C_Soulbinds.IsItemConduitByItemInfo(self.link) then self.subClassID = S.CONDUIT_SUBCLASSID end end]] end end self.type = nil self.isEquippable = IsEquippableItem(self.link) -- Get soulbinding if guild then self.bound = false else if void then self.bound = true else local item if inventory then item = ItemLocation:CreateFromEquipmentSlot(bag) else item = ItemLocation:CreateFromBagAndSlot(bag, slot) end self.bound = C_Item.IsBound(item) -- Warbinding if S.WoWVersion() >= 11 then if self.bindType == 2 then -- Bind on equip self.accountBound = C_Item.IsBoundToAccountUntilEquip(item) else self.accountBound = C_Bank.IsItemAllowedInBankType(Enum.BankType.Account, item) end end end end if self.speciesID then end -- Get tooltip text if not guild then -- Guild items already have their tooltip info if C_TooltipInfo then -- C_TooltipInfo currently isn't available in Classic self.tooltip = {{}, {}} local tooltipInfo if void then tooltipInfo = C_TooltipInfo.GetVoidItem(bag, slot) elseif not inventory then tooltipInfo = C_TooltipInfo.GetBagItem(bag, slot) else tooltipInfo = C_TooltipInfo.GetInventoryItem("player", bag) end if tooltipInfo then for k,v in pairs(tooltipInfo.lines) do self.tooltip[1][k] = v.leftText self.tooltip[2][k] = v.rightText end end elseif void then self.tooltip = S.Utils.GetVoidItemTooltip(bag, slot) elseif not inventory then self.tooltip = S.Utils.GetBagItemTooltip(bag, slot) else self.tooltip = S.Utils.GetInventoryItemTooltip(bag) end end end -- Item is gone (or something has gone wrong), clear the data if not self.link or not self.itemID or not self.quality or not self.subClassID or not self.bindType then for k, _ in pairs(self) do if k ~= "bag" and k ~= "slot" and k ~= "tab" then self[k] = nil end end else self.key = ItemKey(self.link, self.speciesID) -- Generate item key which ignores certain parts of the item link. Used to determine if two items are the same end end return self.itemID end function S.Data.UpdateItem(bag, slot) local item = Item:CreateFromBagAndSlot(bag, slot) if item:IsItemDataCached() then OnDataAvailable(bag, slot) else item:ContinueOnItemLoad(function() local itemID = OnDataAvailable(bag, slot) end) end end function S.Data.UpdateInventoryItem(inventoryID) local item = Item:CreateFromEquipmentSlot(inventoryID) if item:IsItemDataCached() then OnDataAvailable(inventoryID, nil, true) else item:ContinueOnItemLoad(function() local itemID = OnDataAvailable(inventoryID, nil, true) end) end end -- Returns a reference to a table with elements: texture, count, quality, link, hasNoValue, itemID, key, bag, slot function S.Data.GetItem(bag, slot, guid) local data if S.Utils.ContainerIsType(bag, S.CONTAINER_TYPES.ACCOUNT) then data = Sorted_AccountData else data = GetData(guid) end if not data.containers[bag] then data.containers[bag] = {} end if not data.containers[bag][slot] then data.containers[bag][slot] = {} end local itemData = data.containers[bag][slot] itemData.bag = bag itemData.slot = slot if not itemData.key and itemData.link then itemData.key = ItemKey(itemData.link) end return itemData end -- Returns a reference to a table with elements: texture, count, quality, link, hasNoValue, itemID, key, bag, slot function S.Data.GetInventoryItem(inventoryID, guid) local data = GetData(guid) if not data.inventoryItems then data.inventoryItems = {} end if not data.inventoryItems[inventoryID] then data.inventoryItems[inventoryID] = {} end local itemData = data.inventoryItems[inventoryID] if not itemData.key and itemData.link then itemData.key = ItemKey(itemData.link) end return itemData end -- Returns two numbers, number of filled slots of the given type, and total number available function S.Data.GetNumSlots(type, guid) local data = GetData(guid) if not data then return 0,0 end return data[type:lower().."UsedSlots"], data[type:lower().."NumSlots"] end -- ACCOUNT BANK FUNCTIONS function S.Data.UpdateAccountBankTabs() local bankTabData = C_Bank.FetchPurchasedBankTabData(2) S.Utils.CopyTable(bankTabData, Sorted_AccountData.bankTabData) S.Utils.TriggerEvent("BankTabsUpdated") end -- GUILD BANK FUNCTIONS function S.Data.UpdateGuildInfo() local data = GetData(playerGUID) if not IsInGuild() then data.guild = nil else -- Initialise guild table if not data.guild then data.guild = {} data.guild.tabs = {} for i = 1, 8 do data.guild.tabs[i] = {} data.guild.tabs[i].slots = {} for slot = 1, 98 do data.guild.tabs[i].slots[slot] = { ["isGuild"] = true, ["bag"] = i, ["slot"] = slot } end end end -- Update guild info data.guild.name, data.guild.rank, data.guild.rankID = GetGuildInfo("player") end end function S.Data.UpdateGuildMoney() local data = GetData(playerGUID) if data.guild then data.guild.money = GetGuildBankMoney() data.guild.withdrawLimit = GetGuildBankWithdrawMoney() end end local defaultTabIcon = "Interface\\Icons\\INV_Misc_QuestionMark" local defaultTabNames = {"Tab 1","Tab 2","Tab 3","Tab 4","Tab 5","Tab 6","Tab 7","Tab 8"} function S.Data.UpdateGuildBankTabs() local data = GetData(playerGUID) if data.guild then local guild = data.guild for i = 1, 8 do local name, icon, canView, canDeposit, numWithdrawals, remainingWithdrawals, filtered = GetGuildBankTabInfo(i) local tab = guild.tabs[i] -- Only update name and icon if the info is available if name == defaultTabNames[i] and icon == defaultTabIcon then if not tab.name then -- Tab doesn't have a name or icon set anyway, so set them to the default values tab.name, tab.icon = name, icon end tab.canView, tab.canDeposit, tab.numWithdrawals, tab.remainingWithdrawals, tab.filtered = canView, canDeposit, numWithdrawals, remainingWithdrawals, filtered else -- All info is available, update all of the tab data tab.name, tab.icon, tab.canView, tab.canDeposit, tab.numWithdrawals, tab.remainingWithdrawals, tab.filtered = name, icon, canView, canDeposit, numWithdrawals, remainingWithdrawals, filtered end end end S.Utils.TriggerEvent("GuildBankTabsUpdated") end function S.Data.UpdateGuildItem(tab, slot, link) if link then local item = Item:CreateFromItemLink(link) if item:IsItemDataCached() then OnDataAvailable(tab, slot, false, true) else item:ContinueOnItemLoad(function() OnDataAvailable(tab, slot, false, true) end) end else -- There's no item in this slot, so clear anything existing local data = S.Data.GetGuildItem(tab, slot, playerGUID) if data.link then GetData(playerGUID).guild.tabs[tab].slots[slot] = { ["isGuild"] = true, ["bag"] = tab, ["slot"] = slot } end end end -- Updating the guild bank completely requires querying the server. Don't do this too often. function S.Data.UpdateGuildBank(tab) local data = GetData(playerGUID) if data.guild then local guild = data.guild -- Update one specific tab if tab then if guild.tabs[tab].canView then QueryGuildBankTab(tab) end local count = 0 if guild.tabs[tab].canView then -- Figure out if tab is empty, or if the data isn't available yet local dataUnavailable = true local slot = 1 while dataUnavailable and slot <= 98 do if GetGuildBankItemLink(tab, slot) then dataUnavailable = false end slot = slot + 1 end if dataUnavailable then -- No items found -- Count the number of times the tab has updated and been empty if not guild.tabs[tab].emptyUpdates then guild.tabs[tab].emptyUpdates = 1 else guild.tabs[tab].emptyUpdates = guild.tabs[tab].emptyUpdates + 1 end -- If it's still empty after several (3) updates, give in, let it update already if guild.tabs[tab].emptyUpdates > 3 then guild.tabs[tab].emptyUpdates = nil dataUnavailable = false end end -- Update data if not dataUnavailable then for slot = 1, 98 do local link = GetGuildBankItemLink(tab, slot) S.Data.UpdateGuildItem(tab, slot, link) if link then count = count + 1 end end guild.tabs[tab].count = count end else -- Can't be viewed guild.tabs[tab].count = 0 end -- Update everything else for tab = 1, 8 do if guild.tabs[tab].canView then QueryGuildBankTab(tab) end local count = 0 if guild.tabs[tab].canView then for slot = 1, 98 do local link = GetGuildBankItemLink(tab, slot) S.Data.UpdateGuildItem(tab, slot, link) if link then count = count + 1 end end end guild.tabs[tab].count = count end end end end function S.Data.GetGuildItem(tab, slot, guid) local guild = GetData(guid).guild if not guild then return {} else return guild.tabs[tab].slots[slot] end end -- VOID STORAGE FUNCTIONS function S.Data.UpdateVoidItem(tab, slot) OnDataAvailable(tab, slot, nil, nil, true) end function S.Data.GetVoidItem(tab, slot, guid) local void = GetData(guid).voidStorage if not void then return {} else return void.tabs[tab].slots[slot] end end local function UpdateVoidContents() for tab = 1, 2 do for slot = 1, 80 do S.Data.UpdateVoidItem(tab, slot) end end S.Utils.TriggerEvent("VoidStorageUpdated") end function S.Data.UpdateVoidContents() if S.IsVoidStorageAvailable() then local data = GetData(playerGUID) if not data.voidStorage then data.voidStorage = {} data.voidStorage.tabs = {} for tab = 1, 2 do data.voidStorage.tabs[tab] = {} data.voidStorage.tabs[tab].slots = {} for slot = 1, 80 do data.voidStorage.tabs[tab].slots[slot] = { ["isVoid"] = true, ["bag"] = tab, ["slot"] = slot } end end end -- Just make sure that the void storage item info is available for tab = 1,2 do for slot = 1,80 do local itemID = GetVoidItemInfo(tab, slot) if itemID then C_Item.RequestLoadItemDataByID(itemID) end end end C_Timer.After(0.01, UpdateVoidContents) end end -- ITEM DATA FUNCTIONS -- Takes itemData and return a number, 0 for grey, 1 for manually marked trash, 2 for not-grey or marked not-trash function S.Data.GetTrash(itemData) local data = GetData() if not data.trashItems then data.trashItems = {} end if data.trashItems[itemData.itemID] then return data.trashItems[itemData.itemID] elseif itemData.quality == 0 then return 0 else return 2 end end function S.Data.ToggleTrash(itemData) local data = GetData() if not data.trashItems then data.trashItems = {} end if data.trashItems[itemData.itemID] then if data.trashItems[itemData.itemID] == 1 then data.trashItems[itemData.itemID] = 2 else data.trashItems[itemData.itemID] = 1 end else if itemData.quality == 0 then data.trashItems[itemData.itemID] = 2 else data.trashItems[itemData.itemID] = 1 end end end -- Takes itemData and returns a number 1-8 for the raid marker icon, or nil if none function S.Data.GetFavorited(itemData) if itemData.key then local data = GetData() return data.favoritedItems[itemData.key] end return nil end function S.Data.ToggleFavorited(itemData, value) local key = itemData.key local data = GetData() if data.favoritedItems[key] then if value then data.favoritedItems[key] = value PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) else if data.favoritedItems[key] and data.favoritedItems[key] > 0 then data.favoritedItems[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) else data.favoritedItems[key] = 1 PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) end end else if value then data.favoritedItems[key] = value else data.favoritedItems[key] = 1 end PlaySound(SOUNDKIT.IG_CHAT_SCROLL_UP) end end function S.Data.Unfavorite(itemData) local key = itemData.key GetData().favoritedItems[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) end -- CURRENCY FUNCTIONS -- Takes currency data and returns true if it's tracked function S.Data.GetCurrencyTracked(currency) local data = GetData() if data.trackedCurrencies then for k,v in pairs(data.trackedCurrencies) do if v == currency.id then return true end end end end -- Creates or removes an entry for the currency's id -- Table is ordered in the order that the currencies were tracked in function S.Data.ToggleCurrencyTracked(currency) local data = GetData() -- Remove it if it's already tracked if data.trackedCurrencies then for k,v in pairs(data.trackedCurrencies) do if v == currency.id then table.remove(data.trackedCurrencies, k) return end end end -- Otherwise add it if not data.trackedCurrencies then data.trackedCurrencies = { currency.id } else table.insert(data.trackedCurrencies, currency.id) end end function S.Data.GetTrackedCurrencies() local data = GetData() return data.trackedCurrencies end -- Takes currency and returns a number 1-8 for the raid marker icon, or nil if none function S.Data.GetCurrencyFavorited(currency) local data = GetData() if data.favoritedCurrencies then return data.favoritedCurrencies[currency.id] end end function S.Data.ToggleCurrencyFavorited(currency, value) local key = currency.id local data = GetData() -- If viewing another character which was last played before Sorted 2.0, create a table for them if not data.favoritedCurrencies then data.favoritedCurrencies = {} end if data.favoritedCurrencies[key] then if value then data.favoritedCurrencies[key] = value PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) else if data.favoritedCurrencies[key] and data.favoritedCurrencies[key] > 0 then data.favoritedCurrencies[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) else data.favoritedCurrencies[key] = 1 PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) end end else if value then data.favoritedCurrencies[key] = value else data.favoritedCurrencies[key] = 1 end PlaySound(SOUNDKIT.IG_CHAT_SCROLL_UP) end end function S.Data.UnfavoriteCurrency(currency) local key = currency.id GetData().favoritedCurrencies[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) end -- S.IsBankOpened() returns true when the player is currently using the bank and the bank contents are available local bankOpen = false function S.IsBankOpened() return bankOpen end local guildOpen = false function S.IsGuildBankOpened() return guildOpen end local voidOpen = false function S.IsVoidStorageOpened() return voidOpen end -- EVENT HANDLING -- Make BAG_UPDATE work like BAG_UPDATE_DELAYED local bagUpdateHandlerFrame = CreateFrame("FRAME") bagUpdateHandlerFrame:RegisterEvent("BAG_UPDATE") bagUpdateHandlerFrame:SetScript("OnEvent", function(self) self.doUpdate = true end) bagUpdateHandlerFrame:SetScript("OnUpdate", function(self) if self.doUpdate and (not self.lastUpdateTime or self.lastUpdateTime < GetTime() - 0.2) then self.lastUpdateTime = GetTime() self.doUpdate = false UpdateBagContents() end end) local function BankFrameOpenedDelayed() if bankOpen then UpdateBagContents() if S.WoWVersion() >= 11 then S.Data.UpdateAccountBankTabs() end S.Utils.TriggerEvent("BankOpened") end end -- Creates a frame to perform initialisation at appropriate points in WoW's loading process and handle other events local eventHandlerFrame = CreateFrame("FRAME") eventHandlerFrame:RegisterEvent("ADDON_LOADED") eventHandlerFrame:RegisterEvent("PLAYER_LOGIN") eventHandlerFrame:RegisterEvent("PLAYER_ENTERING_WORLD") --eventHandlerFrame:RegisterEvent("BAG_UPDATE_DELAYED") - Blizz broke this in WotLK. Good job Blizz. eventHandlerFrame:RegisterEvent("BANKFRAME_OPENED") eventHandlerFrame:RegisterEvent("BANKFRAME_CLOSED") if S.WoWVersion() >= 11 then eventHandlerFrame:RegisterEvent("BANK_TAB_SETTINGS_UPDATED") end eventHandlerFrame:RegisterEvent("GUILDBANK_UPDATE_TABS") eventHandlerFrame:RegisterEvent("GUILDBANKBAGSLOTS_CHANGED") if S.WoWVersion() >= 3 then eventHandlerFrame:RegisterEvent("EQUIPMENT_SETS_CHANGED") end if S.WoWVersion() >= 6 then eventHandlerFrame:RegisterEvent("REAGENTBANK_PURCHASED") end eventHandlerFrame:RegisterEvent("PLAYER_GUILD_UPDATE") eventHandlerFrame:RegisterEvent("CURRENCY_DISPLAY_UPDATE") eventHandlerFrame:RegisterEvent("VOID_STORAGE_CONTENTS_UPDATE") eventHandlerFrame:RegisterEvent("VOID_TRANSFER_DONE") eventHandlerFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_SHOW") eventHandlerFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_HIDE") eventHandlerFrame:SetScript("OnEvent", function(self, event, param1, param2, param3) if event == "ADDON_LOADED" then if param1 == "Sorted" then sortedData = Sorted_Data -- Update the local sortedData variable once Sorted_Data gets loaded from SavedVariables InitialiseData() end elseif event == "PLAYER_LOGIN" then UpdatePlayerData() -- Clear characters after 1 month of inactivity, or if they have the same name as the current character. The alt is probably deleted local toDelete = {} for GUID, v in pairs(sortedData) do if GUID ~= playerGUID then if ((v.lastLogged and time() - v.lastLogged > 2678400) or (v.name == UnitName("player") and v.realm == GetRealmName())) then table.insert(toDelete, GUID) end end end for k, GUID in pairs(toDelete) do S.Data.DeleteCharacter(GUID) end -- Calculate total Alliance and Horde money on each realm, excluding the currently logged character since its money can change totalMoney = {} for GUID, v in pairs(sortedData) do if v.realm then if not totalMoney[v.realm] then totalMoney[v.realm] = {} totalMoney[v.realm].alliance = 0 totalMoney[v.realm].horde = 0 end if GUID ~= playerGUID then if v.money then if v.faction == "Alliance" then totalMoney[v.realm].alliance = totalMoney[v.realm].alliance + v.money else totalMoney[v.realm].horde = totalMoney[v.realm].horde + v.money end end end end end elseif event == "PLAYER_ENTERING_WORLD" then S.SelectCharacter(UnitGUID("player")) S.Settings.ReloadAll() S.Data.UpdateGuildInfo() UpdateCurrencies() UpdateBagContents() S.Utils.TriggerEvent("EnteredWorld") S.Utils.TriggerEvent("Resized") --[[elseif event == "BAG_UPDATE_DELAYED" then UpdateBagContents()]] elseif event == "BANKFRAME_OPENED" then bankOpen = true C_Timer.After(0.1, BankFrameOpenedDelayed) elseif event == "BANKFRAME_CLOSED" then bankOpen = false S.Utils.TriggerEvent("BankClosed") elseif event == "BANK_TAB_SETTINGS_UPDATED" then S.Data.UpdateAccountBankTabs() elseif event == "EQUIPMENT_SETS_CHANGED" then UpdateEquipmentSets() elseif event == "REAGENTBANK_PURCHASED" then UpdateBagContents() S.Utils.TriggerEvent("ReagentsPurchased") elseif event == "CURRENCY_DISPLAY_UPDATE" then UpdateCurrencies() elseif event == "PLAYER_GUILD_UPDATE" then S.Data.UpdateGuildInfo() elseif event == "GUILDBANK_UPDATE_TABS" then S.Data.UpdateGuildBankTabs() elseif event == "VOID_STORAGE_CONTENTS_UPDATE" then S.Data.UpdateVoidContents() elseif event == "VOID_TRANSFER_DONE" then S.Data.UpdateVoidContents() elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_SHOW" then if param1 == Enum.PlayerInteractionType.VoidStorageBanker then C_Timer.After(0.4, S.Data.UpdateVoidContents) voidOpen = true S.Utils.TriggerEvent("VoidStorageOpened") S.OpenBag() S.SelectCharacter(UnitGUID("player")) elseif param1 == Enum.PlayerInteractionType.GuildBanker then guildOpen = true S.OpenBag() S.SelectCharacter(UnitGUID("player")) end elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_HIDE" then if param1 == Enum.PlayerInteractionType.VoidStorageBanker then voidOpen = false S.Utils.TriggerEvent("VoidStorageClosed") S.CloseBag() elseif param1 == Enum.PlayerInteractionType.GuildBanker then guildOpen = false S.CloseBag() end end end) -- Check for when the GuildBankFrame is loaded and estimate when the items will be available local guildBankFrameLoadTime, guildBankLoaded = nil eventHandlerFrame:SetScript("OnUpdate", function(self) if GuildBankFrame then if not guildBankFrameLoadTime then guildBankFrameLoadTime = GetTime() else -- Give it 2 seconds after the GuildBankFrame is loaded if GetTime() > guildBankFrameLoadTime + 2 then guildBankLoaded = true S.Utils.TriggerEvent("GuildBankAvailable") eventHandlerFrame:SetScript("OnUpdate", nil) end end end end) function S.IsGuildBankAvailable() return guildBankLoaded end function S.IsVoidStorageAvailable() return CanUseVoidStorage and CanUseVoidStorage() and VoidStorageFrame end