local _, S = ... S.DataTooltip = CreateFrame("GameTooltip", "SortedDataTooltip", nil, "GameTooltipTemplate") S.DataTooltip:SetOwner(WorldFrame, "ANCHOR_NONE") local playerGUID = UnitGUID("player") -- Custom item subclass IDs to add additional categorisation over Blizzard's S.CONDUIT_SUBCLASSID = 100 S.ANIMA_SUBCLASSID = 101 -- 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 = {} function Sorted_GetBankCached() if not Sorted_Data[Sorted_SelectedPlayerGUID] then return false end return not Sorted_Data[Sorted_SelectedPlayerGUID].bankNotCached end function Sorted_SetBankCached() Sorted_Data[playerGUID].bankNotCached = nil end function Sorted_GetReagentUnlocked() return not Sorted_Data[Sorted_SelectedPlayerGUID].reagentNotUnlocked end function Sorted_SetReagentUnlocked() Sorted_Data[playerGUID].reagentNotUnlocked = nil end function Sorted_GetData(guid) if not guid then return Sorted_Data[Sorted_SelectedPlayerGUID] else return Sorted_Data[guid] end end function Sorted_DeleteData(guid) Sorted_Data[guid] = nil end function Sorted_InitialiseData() if not Sorted_Data[playerGUID] then Sorted_Data[playerGUID] = {} Sorted_Data[playerGUID].bankNotCached = true Sorted_Data[playerGUID].containers = {} for bag = BANK_CONTAINER, NUM_BAG_SLOTS + NUM_BANKBAGSLOTS do if not Sorted_Data[playerGUID].containers[bag] then Sorted_Data[playerGUID].containers[bag] = {} for slot = 1, 36 do Sorted_Data[playerGUID].containers[bag][slot] = {} end end end if not Sorted_IsClassic() then Sorted_Data[playerGUID].containers[REAGENTBANK_CONTAINER] = {} for slot = 1, 98 do Sorted_Data[playerGUID].containers[REAGENTBANK_CONTAINER][slot] = {} end end Sorted_Data[playerGUID].containers[-4] = {} for slot = 1, NUM_BANKBAGSLOTS do Sorted_Data[playerGUID].containers[-4][slot] = {} end Sorted_Data[playerGUID].favoritedItems = {} end -- Check these fields individually for characters which last loaded an older version of the addon if not Sorted_Data[playerGUID].timeItemsAdded then Sorted_Data[playerGUID].timeItemsAdded = {} end if not Sorted_Data[playerGUID].newItems then Sorted_Data[playerGUID].newItems = {} end if not Sorted_Data[playerGUID].equipSets and not Sorted_IsClassic() then Sorted_Data[playerGUID].equipSets = {} end if Sorted_Data[playerGUID].categories then Sorted_Data[playerGUID].categories = nil end if not Sorted_Data[playerGUID].settingsProfile then --Sorted_Data[playerGUID].settingsProfile = Sorted_CreateNewSettingsProfile() end end function Sorted_LoadData() Sorted_Data[playerGUID].lastLogged = time() Sorted_Data[playerGUID].name = UnitName("player") Sorted_Data[playerGUID].level = UnitLevel("player") _, _, Sorted_Data[playerGUID].race = UnitRace("player") Sorted_Data[playerGUID].faction = UnitFactionGroup("player") Sorted_Data[playerGUID].realm = GetRealmName() _, Sorted_Data[playerGUID].class = UnitClass("player") if not Sorted_IsClassic() and not IsReagentBankUnlocked() then Sorted_Data[playerGUID].reagentNotUnlocked = true else Sorted_Data[playerGUID].reagentNotUnlocked = nil end -- 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(Sorted_Data) 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 Sorted_DeleteData(GUID) end -- Calculate total Alliance and Horde money on each realm, excluding the currently logged character since its money can change Sorted_totalMoney = {} for GUID, v in pairs(Sorted_Data) do if v.realm then if not Sorted_totalMoney[v.realm] then Sorted_totalMoney[v.realm] = {} Sorted_totalMoney[v.realm].alliance = 0 Sorted_totalMoney[v.realm].horde = 0 end if GUID ~= playerGUID then if v.faction == "Alliance" then Sorted_totalMoney[v.realm].alliance = Sorted_totalMoney[v.realm].alliance + v.money else Sorted_totalMoney[v.realm].horde = Sorted_totalMoney[v.realm].horde + v.money end end end end -- Load sorting buttons if not Sorted_enabledColumns.version then Sorted_enabledColumns.version = 0 end if Sorted_enabledColumns.version < 102023 then Sorted_enabledColumns[SORTED_SORTBUTTON_UPGRADE] = true Sorted_enabledColumns.version = 102023 end local count = 0 for i,v in ipairs(Sorted_sortButtons) do if Sorted_enabledColumns[v.uniqueID] and v:Enabled() then Sorted_EnableSortButton(v.uniqueID) else Sorted_DisableSortButton(v.uniqueID) end if Sorted_sortButtonOrder[v.uniqueID] then count = count + 1 end end for i,v in ipairs(Sorted_sortButtons) do if not Sorted_sortButtonOrder[v.uniqueID] then count = count + 1 Sorted_sortButtonOrder[v.uniqueID] = count end end table.sort(Sorted_sortButtons, function(a,b) return Sorted_sortButtonOrder[a.uniqueID] < Sorted_sortButtonOrder[b.uniqueID] end) Sorted_GetSortButton(SORTED_SORTBUTTON_ICON).width = Sorted_GetSetting("iconSize") * 1.1 Sorted_GetSortButton(SORTED_SORTBUTTON_FAVORITES).width = Sorted_GetSetting("iconSize") * 1.1 Sorted_UpdateSortButtons() -- Load settings Sorted_ReloadAllSettings() end function Sorted_UpdateBagContents(container, dontUpdateTimes) -- Bags for k, bagID in pairs(Sorted_ContainersOfType("BAGS")) do if not container or bagID == container then for slotID = 1, 36 do SortedContainerItemInfo_Update(bagID, slotID) end end end -- Container buttons for i = 1, NUM_BAG_SLOTS do local slotID = -NUM_BAG_SLOTS + i SortedContainerItemInfo_Update(0, slotID) end -- Bank if Sorted_bankIsOpened then for k, bagID in pairs(Sorted_ContainersOfType("BANK")) do if not container or bagID == container then for slotID = 1, 36 do SortedContainerItemInfo_Update(bagID, slotID) end end end -- Container buttons for slotID = 1, NUM_BANKBAGSLOTS do SortedContainerItemInfo_Update(-4, slotID) end end -- Reagent bank if not Sorted_IsClassic() then if not container or REAGENTBANK_CONTAINER == container then for slotID = 1, 98 do SortedContainerItemInfo_Update(REAGENTBANK_CONTAINER, slotID) end end -- Keyring else if not container or KEYRING_CONTAINER == container then for slotID = 1,32 do SortedContainerItemInfo_Update(KEYRING_CONTAINER, slotID) end end end -- Update number of slots local slots, freeSlots = 0, 0 for k, bagID in pairs(Sorted_ContainersOfType("BAGS")) do slots = slots + GetContainerNumSlots(bagID) freeSlots = freeSlots + GetContainerNumFreeSlots(bagID) end Sorted_Data[playerGUID].bagsUsedSlots = slots - freeSlots Sorted_Data[playerGUID].bagsNumSlots = slots if Sorted_bankIsOpened then slots, freeSlots = 0, 0 for k, bagID in pairs(Sorted_ContainersOfType("BANK")) do slots = slots + GetContainerNumSlots(bagID) freeSlots = freeSlots + GetContainerNumFreeSlots(bagID) end Sorted_Data[playerGUID].bankUsedSlots = slots - freeSlots Sorted_Data[playerGUID].bankNumSlots = slots if not Sorted_IsClassic() then Sorted_Data[playerGUID].reagentUsedSlots = GetContainerNumSlots(REAGENTBANK_CONTAINER) - GetContainerNumFreeSlots(REAGENTBANK_CONTAINER) Sorted_Data[playerGUID].reagentNumSlots = GetContainerNumSlots(REAGENTBANK_CONTAINER) end end -- Update time items added if not (dontUpdateTimes) then C_Timer.After(0.05, function() Sorted_UpdateTimeItemsAdded() Sorted_SortItems() end) end -- Update equipment sets Sorted_UpdateEquipmentSets() end function Sorted_UpdateTimeItemsAdded() -- Populate a new table local currentItems = {} local function UpdateItemCount(key, count) if currentItems[key] then currentItems[key].count = currentItems[key].count + count else currentItems[key] = { ["count"] = count } end end for k, bag in pairs(Sorted_ContainersOfType("ALL")) do local numSlots = 36 if bag == REAGENTBANK_CONTAINER then numSlots = 98 end for slot = 1, numSlots do local info = Sorted_GetContainerItemInfo(bag, slot, playerGUID) if info.link then UpdateItemCount(Sorted_ItemKey(info.link), info.count) end end end for invID = 1, 23 do local link = GetInventoryItemLink("player", invID) --local link = GetInventoryItemID("player", invID) if link then UpdateItemCount(Sorted_ItemKey(link), 1) end end -- Compare item counts with the older table in player data. If an item count has increased, update the time local previousItems = Sorted_GetData(playerGUID).timeItemsAdded local newItems = Sorted_GetData(playerGUID).newItems Sorted_newItemsPerCategory = {} local markedForDelete = {} for link, item in pairs(previousItems) do if currentItems[link] then if currentItems[link].count > item.count then item.time = time() if not newItems[link] then newItems[link] = currentItems[link].count - item.count else newItems[link] = newItems[link] + currentItems[link].count - item.count end end item.count = currentItems[link].count else table.insert(markedForDelete, link) end end for k,v in pairs(markedForDelete) do previousItems[v] = nil end -- Add any new items to the player data for link, item in pairs(currentItems) do if not previousItems[link] then previousItems[link] = { ["count"] = item.count, ["time"] = time() } newItems[link] = item.count end end -- Update slot frames. Usually slot frames are updated in SortedItemButton_Update(), but in this case, -- all of the timeAddedStrings need to be updated simultaneously to keep the order correct local tabEnabled = Sorted_enabledColumns[SORTED_SORTBUTTON_ADDED] if not S.IsCurrentPlayerSelected() then local data = Sorted_GetData() if not data.timeItemsAdded then data.timeItemsAdded = {} end previousItems = data.timeItemsAdded end for _, itemList in pairs(Sorted_itemLists) do for l, itemButton in pairs(itemList.itemButtons) do local itemData = itemButton:GetData() if itemData.link then local key = Sorted_ItemKey(itemData.link) if previousItems[key] then itemData.timeAdded = previousItems[key].time local timeDif = time() - previousItems[key].time itemButton.timeAddedString:SetText(Sorted_FormatTime(timeDif)) local alpha = 1 / (1 + timeDif / 10000) alpha = 0.4 + alpha * 0.6 itemButton.timeAddedString:SetAlpha(alpha) if tabEnabled then itemButton.timeAddedString:Show() else itemButton.timeAddedString:Hide() end else itemButton.timeAddedString:Hide() end end end end Sorted_UpdateItemButtons() Sorted_UpdateNewItemsPerCategory() end local function OnDataAvailable(bag, slot, guid) local self if not guid then self = Sorted_GetContainerItemInfo(bag, slot, playerGUID) if bag ~= KEYRING_CONTAINER then self.texture, self.count, _, self.quality, _, _, self.link, _, self.hasNoValue, self.itemID = GetContainerItemInfo(bag, slot) else local invSlotID = KeyRingButtonIDToInvSlotID(slot) self.texture, self.count, self.quality, self.link, self.hasNoValue, self.itemID = GetInventoryItemTexture("player", invSlotID), GetInventoryItemCount("player", invSlotID), GetInventoryItemQuality("player", invSlotID), GetInventoryItemLink("player", invSlotID), false, GetInventoryItemID("player", invSlotID) end else self = Sorted_GetContainerItemInfo(bag, slot, guid) end if not self.categories then self.categories = {} end if self.itemID then if not Sorted_IsClassic() and C_Item.IsItemKeystoneByID(self.itemID) then -- Mythic Keystone self.minLevel = 60 self.equipLoc = nil self.value = 0 self.classID = LE_ITEM_CLASS_MISCELLANEOUS self.subClassID = LE_ITEM_MISCELLANEOUS_REAGENT self.bindType = 1 self.expacID = 8 self.quality = 8 if not guid then self.level = C_MythicPlus.GetOwnedKeystoneLevel() self.name = string.format(Sorted.Localize("ITEM_MYTHIC_KEYSTONE"), C_ChallengeMode.GetMapUIInfo(C_MythicPlus.GetOwnedKeystoneChallengeMapID()).." ("..self.level..")") else self.level = 1 self.name = Sorted.Localize("ITEM_UNKNOWN_KEYSTONE") end self.effectiveILvl = self.level else self.name = nil if self.link then self.name, _, _, self.level, self.minLevel, _, _, _, self.equipLoc, _, self.value, self.classID, self.subClassID, self.bindType, self.expacID = GetItemInfo(self.link) end if not self.name then self.name, _, _, self.level, self.minLevel, _, _, _, self.equipLoc, _, self.value, self.classID, self.subClassID, self.bindType, self.expacID = GetItemInfo(self.itemID) self.effectiveILvl = self.level else self.effectiveILvl = GetDetailedItemLevelInfo(self.link) end if not Sorted_IsClassic() then if C_Soulbinds.IsItemConduitByItemInfo(self.link) then self.subClassID = S.CONDUIT_SUBCLASSID end end end self.type = nil if not guid then local item = ItemLocation:CreateFromBagAndSlot(bag, slot) self.bound = C_Item.IsBound(item) if self.itemID == 82800 then -- Pet cages if bag == BANK_CONTAINER then _, _, _, self.speciesID, self.level, _, _, _, _, self.name = GameTooltip:SetInventoryItem("player", BankButtonIDToInvSlotID(slot)) elseif (not Sorted_IsClassic() and bag == REAGENTBANK_CONTAINER) then _, _, _, self.speciesID, self.level, _, _, _, _, self.name = GameTooltip:SetInventoryItem("player", ReagentBankButtonIDToInvSlotID(slot)) else _, _, self.speciesID, self.level, _, _, _, _, self.name = GameTooltip:SetBagItem(bag, slot); end _, self.texture, self.subClassID = C_PetJournal.GetPetInfoBySpeciesID(self.speciesID) self.name = string.format(Sorted.Localize("ITEM_PET_CAGED"), self.name) self.effectiveILvl = self.level else self.speciesID, self.cageName = nil, nil end end --Sorted Categories local categories = Sorted_GetSetting("categories") for categoryID, category in ipairs(categories) do if Sorted_CategoryFilter(categoryID, self, true) then self.categories[categoryID] = 1 else self.categories[categoryID] = 0 end end end return self.itemID end function SortedContainerItemInfo_Update(bag, slot, guid) local item = Item:CreateFromBagAndSlot(bag, slot) if item:IsItemDataCached() then OnDataAvailable(bag, slot, guid) else item:ContinueOnItemLoad(function() local itemID = OnDataAvailable(bag, slot, guid) Sorted_UpdateItemButtons(nil, itemID) end) end end -- Returns a reference to a table with elements: texture, count, quality, link, hasNoValue, itemID function Sorted_GetContainerItemInfo(bag, slot, guid) local data = Sorted_GetData(guid) if not data.containers[bag] then data.containers[bag] = {} end if not data.containers[bag][slot] then data.containers[bag][slot] = {} end data.containers[bag][slot].bag = bag data.containers[bag][slot].slot = slot return data.containers[bag][slot] end function Sorted_GetNumSlots(type, guid) local data = Sorted_GetData(guid) if not data then return 0,0 end return data[type:lower().."UsedSlots"], data[type:lower().."NumSlots"] end function Sorted_UpdateEquipmentSets() if not Sorted_IsClassic() then local data = Sorted_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 Sorted_bankIsOpened 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 = EquipmentManager_UnpackLocation(location) 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 end -- Takes itemData and returns a number 1-8 for the raid marker icon, or nil if none function Sorted_GetFavorited(itemData) local data = Sorted_GetData() -- Convert to new table key format if data.favoritedItems[itemData.itemID] then data.favoritedItems[Sorted_ItemKey(itemData.link)] = data.favoritedItems[itemData.itemID] data.favoritedItems[itemData.itemID] = nil end if itemData.link then return data.favoritedItems[Sorted_ItemKey(itemData.link)] end return nil end function Sorted_ToggleFavorite(itemData, value) local key = Sorted_ItemKey(itemData.link) local data = Sorted_GetData() if data.favoritedItems[key] then if value then data.favoritedItems[key] = value PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON) else data.favoritedItems[key] = data.favoritedItems[key] + 1 if data.favoritedItems[key] > 8 then data.favoritedItems[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) else 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 Sorted_Unfavorite(itemData) local key = Sorted_ItemKey(itemData.link) Sorted_GetData().favoritedItems[key] = nil PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF) end