local _, addonTable = ... local L = LibStub("AceLocale-3.0"):GetLocale("Rarity") local R = Rarity local lbz = LibStub("LibBabble-Zone-3.0"):GetUnstrictLookupTable() local lbsz = LibStub("LibBabble-SubZone-3.0"):GetUnstrictLookupTable() local lbb = LibStub("LibBabble-Boss-3.0"):GetUnstrictLookupTable() local hbd = LibStub("HereBeDragons-2.0") --- --[[ VARIABLES ---------------------------------------------------------------------------------------------------------------- ]] R.modulesEnabled = {} local npcs = {} Rarity.fishzones = {} Rarity.mount_sources = {} Rarity.pet_sources = {} Rarity.lockouts = {} Rarity.lockouts_detailed = {} Rarity.lockouts_holiday = {} Rarity.holiday_textures = {} Rarity.ach_npcs_isKilled = {} Rarity.ach_npcs_achId = {} Rarity.stats_to_scan = {} Rarity.items_with_stats = {} Rarity.collection_items = {} Rarity.itemInfoCache = {} Rarity.isBankOpen = false Rarity.isGuildBankOpen = false Rarity.isAuctionHouseOpen = false Rarity.isTradeWindowOpen = false Rarity.isTradeskillOpen = false Rarity.isMailboxOpen = false Rarity.isFishing = false Rarity.isOpening = false Rarity.isPool = false local itemCacheDebug = false Rarity.itemsToPrime = {} Rarity.itemsMasterList = {} local initTimer local numPrimeAttempts = 0 --[[ CONSTANTS ---------------------------------------------------------------------------------------------------------------- ]] local CONSTANTS = addonTable.constants -- Methods of obtaining local NPC = "NPC" local BOSS = "BOSS" local ZONE = "ZONE" local USE = "USE" local FISHING = "FISHING" local ARCH = "ARCH" local SPECIAL = "SPECIAL" local COLLECTION = "COLLECTION" -- Sort modes local SORT_NAME = "SORT_NAME" local SORT_DIFFICULTY = "SORT_DIFFICULTY" local SORT_PROGRESS = "SORT_PROGRESS" local SORT_CATEGORY = "SORT_CATEGORY" local SORT_ZONE = "SORT_ZONE" Rarity.CONSTANTS = addonTable.constants --[[ UPVALUES ----------------------------------------------------------------------------------------------------------------- ]] -- Lua APIs local _G = getfenv(0) local pairs = _G.pairs local strlower = _G.strlower local format = _G.format local min = min local tostring = tostring -- WOW APIs local UnitGUID = _G.UnitGUID local UnitName = _G.UnitName local UnitCanAttack = _G.UnitCanAttack local UnitIsPlayer = _G.UnitIsPlayer local UnitIsDead = _G.UnitIsDead local GetNumLootItems = _G.GetNumLootItems local GetLootSlotInfo = _G.GetLootSlotInfo local GetLootSlotLink = _G.GetLootSlotLink local GetItemInfo_Blizzard = _G.GetItemInfo local GetItemInfo = function(id) return R:GetItemInfo(id) end local GetRealZoneText = _G.GetRealZoneText local GetContainerNumSlots = _G.C_Container.GetContainerNumSlots local GetContainerItemID = _G.C_Container.GetContainerItemID local GetContainerItemInfo = _G.C_Container.GetContainerItemInfo local GetNumArchaeologyRaces = _G.GetNumArchaeologyRaces local GetArchaeologyRaceInfo = _G.GetArchaeologyRaceInfo local GetStatistic = _G.GetStatistic local GetLootSourceInfo = _G.GetLootSourceInfo local GetBestMapForUnit = _G.C_Map.GetBestMapForUnit local GetMapInfo = _G.C_Map.GetMapInfo local C_Timer = _G.C_Timer local IsSpellKnown = _G.IsSpellKnown local CombatLogGetCurrentEventInfo = _G.CombatLogGetCurrentEventInfo local IsQuestFlaggedCompleted = _G.C_QuestLog.IsQuestFlaggedCompleted local C_Covenants = _G.C_Covenants local COMBATLOG_OBJECT_AFFILIATION_MINE = _G.COMBATLOG_OBJECT_AFFILIATION_MINE local COMBATLOG_OBJECT_AFFILIATION_PARTY = _G.COMBATLOG_OBJECT_AFFILIATION_PARTY local COMBATLOG_OBJECT_AFFILIATION_RAID = _G.COMBATLOG_OBJECT_AFFILIATION_RAID -- Addon APIs local DebugCache = Rarity.Utils.DebugCache local GetRealDropPercentage = Rarity.Statistics.GetRealDropPercentage local FormatTime = Rarity.Utils.PrettyPrint.FormatTime local GetDate = Rarity.Utils.Time.GetDate do -- Set up the debug cache (TODO: Move to initialisation routine after the refactoring is complete) Rarity.Utils.DebugCache:SetOutputHandler(Rarity.Utils.PrettyPrint.DebugMsg) function Rarity:Error(message, ...) if R.db.profile.disableCustomErrors then return end Rarity.Utils.PrettyPrint.Error(message, ...) end end local GetMapNameByID = Rarity.MapInfo.GetMapNameByID --[[ LIFECYCLE ---------------------------------------------------------------------------------------------------------------- ]] function R:OnInitialize() end local Output = Rarity.Output do local isInitialized = false function R:OnEnable() self:DoEnable() end function R:DoEnable() if isInitialized then return end isInitialized = true self:PrepareDefaults() -- Loads in any new items self.db = LibStub("AceDB-3.0"):New("RarityDB", self.defaults, true) Output:Setup() self:RegisterChatCommand("rarity", "OnChatCommand") self:RegisterChatCommand("rare", "OnChatCommand") -- Register keybind(s): These must match the info from Bindings.xml (and use localized descriptions) _G.BINDING_HEADER_Rarity = "Rarity" _G.BINDING_NAME_RARITY_DEBUGWINDOWTOGGLE = L["Toggle Debug Window"] Rarity.GUI:RegisterDataBroker() -- Expose private objects R.npcs = npcs Rarity.GUI:InitialiseBar() Rarity.Collections:ScanExistingItems("INITIALIZING") -- Checks for items you already have self:ScanBags() -- Initialize our inventory list, as well as checking if you've obtained an item self:OnBagUpdate() -- This will update counters for collection items self:OnCurrencyUpdate("INITIALIZING") -- Prepare our currency list self:UpdateInterestingThings() Rarity.Tracking:FindTrackedItem() Rarity.Caching:SetReadyState(false) Rarity.GUI:UpdateText() Rarity.Caching:SetReadyState(true) Rarity.GUI:UpdateText() Rarity.GUI:UpdateBar() Rarity.Serialization:ImportFromBunnyHunter() Rarity.EventHandlers:Register() if R.Options_DoEnable then R:Options_DoEnable() end self.db.profile.lastRevision = R.MINOR_VERSION self.db.RegisterCallback(self, "OnProfileChanged", "OnProfileChanged") self.db.RegisterCallback(self, "OnProfileCopied", "OnProfileChanged") self.db.RegisterCallback(self, "OnProfileReset", "OnProfileChanged") self.db.RegisterCallback(self, "OnProfileDeleted", "OnProfileChanged") self:ScanAllArch("DoEnable") RequestRaidInfo() -- Request raid lock info from the server RequestLFDPlayerLockInfo() -- Request LFD data from the server; this is used for holiday boss detection C_Calendar.OpenCalendar() -- Request calendar info from the server -- Prepare a master list of all the items Rarity may need info for table.wipe(R.itemsMasterList) Rarity.Caching:SetPrimedItems(0) Rarity.Caching:SetItemsToPrime(0) for k, v in pairs(self.db.profile.groups) do if type(v) == "table" then for kk, vv in pairs(v) do if type(vv) == "table" then if vv.itemId then R.itemsMasterList[vv.itemId] = true end if vv.collectedItemId then if type(vv.collectedItemId) == "table" then for kkk, vvv in pairs(vv.collectedItemId) do R.itemsMasterList[vvv] = true end else R.itemsMasterList[vv.collectedItemId] = true end end if vv.items and type(vv.items) == "table" then for kkk, vvv in pairs(vv.items) do R.itemsMasterList[vvv] = true end end end end end end for k, v in pairs(self.db.profile.oneTimeItems) do if type(v) == "table" and v.itemId then R.itemsMasterList[v.itemId] = true end end for k, v in pairs(self.db.profile.extraTooltips.inventoryItems) do for kk, vv in pairs(v) do R.itemsMasterList[vv] = true end end local temp = {} for k, v in pairs(R.itemsMasterList) do Rarity.Caching:SetItemsToPrime(Rarity.Caching:GetItemsToPrime() + 1) temp[Rarity.Caching:GetItemsToPrime()] = k end R.itemsMasterList = temp -- Progressively prime our item cache over time instead of hitting Blizzard's API all at once Rarity.Caching:SetItemsToPrime(100) -- Just setting this temporarily to avoid a divide by zero self:ScheduleTimer(function() self:PrimeItemCache() end, 2) -- Scan instance locks 5 seconds after init self:ScheduleTimer(function() R:ScanInstanceLocks("DELAYED INIT") end, 5) -- Scan bags, currency, and instance locks 10 seconds after init self:ScheduleTimer(function() R:ScanBags() R:OnCurrencyUpdate("DELAYED INIT") R:OnBagUpdate() R:ScanInstanceLocks("DELAYED INIT 2") end, 10) -- Clean up session info for k, v in pairs(self.db.profile.groups) do if type(v) == "table" then for kk, vv in pairs(v) do if type(vv) == "table" then vv.session = nil end end end end -- Delayed calendar init a few times self:ScheduleTimer(function() if type(CalendarFrame) ~= "table" or not CalendarFrame:IsShown() then local CalendarTime = C_DateAndTime.GetCurrentCalendarTime() local month, year = CalendarTime.month, CalendarTime.year C_Calendar.SetAbsMonth(month, year) end end, 7) self:ScheduleTimer(function() if type(CalendarFrame) ~= "table" or not CalendarFrame:IsShown() then local CalendarTime = C_DateAndTime.GetCurrentCalendarTime() local month, year = CalendarTime.month, CalendarTime.year C_Calendar.SetAbsMonth(month, year) end end, 20) -- Update text again several times later - this helps get the icon right after login self:ScheduleTimer(function() R:DelayedInit() end, 10) self:ScheduleTimer(function() R:DelayedInit() end, 20) self:ScheduleTimer(function() R:DelayedInit() end, 30) self:ScheduleTimer(function() R:DelayedInit() end, 60) self:ScheduleTimer(function() R:DelayedInit() end, 120) self:ScheduleTimer(function() R:DelayedInit() end, 180) self:ScheduleTimer(function() self:ScanCalendar("FINAL INIT") Rarity.Collections:ScanExistingItems("FINAL INIT") Rarity.GUI:UpdateText() end, 240) self:Debug(L["Loaded (running in debug mode)"]) if self.db.profile.verifyDatabaseOnLogin then self.Validation:ValidateItemDB() end end end function R:DelayedInit() self:ScanStatistics("DELAYED INIT") self:ScanCalendar("DELAYED INIT") Rarity.Collections:ScanToys("DELAYED INIT") Rarity.Collections:ScanTransmog("DELAYED INIT") Rarity.GUI:UpdateText() Rarity.GUI:UpdateBar() end function R:PrimeItemCache() numPrimeAttempts = numPrimeAttempts + 1 if numPrimeAttempts >= 20 then self:Debug("Maximum number of cache prime attempts reached") return end -- This doesn't always fully work, so first build a list of which items we still need to obtain Rarity.Caching:SetItemsToPrime(1) Rarity.Caching:SetPrimedItems(0) table.wipe(R.itemsToPrime) for k, v in pairs(R.itemsMasterList) do if R.itemInfoCache[v] == nil then R.itemsToPrime[Rarity.Caching:GetItemsToPrime()] = v Rarity.Caching:SetItemsToPrime(Rarity.Caching:GetItemsToPrime() + 1) end end Rarity.Caching:SetItemsToPrime(Rarity.Caching:GetItemsToPrime() - 1) if Rarity.Caching:GetItemsToPrime() <= 0 then return end -- Prime the items self:Debug("Loading " .. Rarity.Caching:GetItemsToPrime() .. " item(s) from server...") initTimer = self:ScheduleRepeatingTimer(function() if Rarity.Caching:GetPrimedItems() <= 0 then Rarity.Caching:SetPrimedItems(1) end for i = 1, 10 do GetItemInfo(R.itemsToPrime[Rarity.Caching:GetPrimedItems()]) Rarity.Caching:SetPrimedItems(Rarity.Caching:GetPrimedItems() + 1) if Rarity.Caching:GetPrimedItems() > Rarity.Caching:GetItemsToPrime() then break end end if Rarity.Caching:GetPrimedItems() >= Rarity.Caching:GetItemsToPrime() then self:CancelTimer(initTimer) -- First-time initialization finished if not Rarity.Caching:IsReady() then Rarity.Caching:SetReadyState(false) -- Trigger holiday reminders self:ScheduleTimer(function() Rarity:ShowTooltip(true) end, 5) end -- Check how many items were not processed, rescanning if necessary local got = 0 local totalNeeded = 0 for k, v in pairs(R.itemInfoCache) do got = got + 1 end for k, v in pairs(R.itemsMasterList) do totalNeeded = totalNeeded + 1 end if got < totalNeeded then self:Debug("Initialization failed to retrieve " .. (totalNeeded - got) .. " item(s)") self:ScheduleTimer(function() self:PrimeItemCache() end, 5) else self:Debug("Finished loading " .. Rarity.Caching:GetItemsToPrime() .. " item(s) from server") end end Rarity.GUI:UpdateText() end, 0.1) end --[[ UTILITIES ---------------------------------------------------------------------------------------------------------------- ]] -- Item cache function R:GetItemInfo(id) if id == nil then return end if R.itemInfoCache[id] ~= nil then return unpack(R.itemInfoCache[id]) end if itemCacheDebug and Rarity.Caching:IsReady() == false then R:Debug("ItemInfo not cached for " .. id) end local info = { GetItemInfo_Blizzard(id) } if #info > 0 then R.itemInfoCache[id] = info end if R.itemInfoCache[id] == nil then return nil end return unpack(R.itemInfoCache[id]) end -- Miscellaneous function R:tcopy(to, from) for k, v in pairs(from) do if type(v) == "table" then to[k] = {} R:tcopy(to[k], v) else to[k] = v end end end -- Location/Distance/Zone function R:GetDistanceToItem(item) local distance = 999999999 if item and type(item) == "table" and item.coords and type(item.coords) == "table" then local playerWorldX, playerWorldY, instance = hbd:GetPlayerWorldPosition() for k, v in pairs(item.coords) do if v and type(v) == "table" and v.m and v.i ~= true then local map = v.m local x = (v.x or 50) / 100 local y = (v.y or 50) / 100 local itemWorldX, itemWorldY = hbd:GetWorldCoordinatesFromZone(x, y, map, v.f or 1) if itemWorldX ~= nil then -- Library returns nil for instances local thisDistance = hbd:GetWorldDistance(instance, itemWorldX, itemWorldY, playerWorldX, playerWorldY) -- R:Print("map: "..map..", x: "..x..", y: "..y..", itemWorldX: "..itemWorldX..", itemWorldY: "..itemWorldY..", playerWorldX: "..playerWorldX..", playerWorldY: "..playerWorldY..", thisDistance: "..thisDistance) if thisDistance < distance then distance = thisDistance end end end end end if distance ~= 999999999 then return distance end return nil end -- Prepares a set of lookup tables to let us quickly determine if we're interested in various things. -- Many of the events we handle fire quite frequently, so speed is of the essence. -- Any item that is not enabled for tracking won't show up in these lists. function R:UpdateInterestingThings() self:Debug("Updating interesting things tables") -- Store an internal table listing every MapID if self.db.profile.mapIds == nil then self.db.profile.mapIds = {} else table.wipe(self.db.profile.mapIds) end for map_id = 1, 5000 do -- 5000 seems arbitrarily high; right now (8.0.1) there are barely 100 uiMapIDs... but it shouldn't matter if the misses are skipped if GetMapNameByID(map_id) ~= nil then self.db.profile.mapIds[map_id] = GetMapNameByID(map_id) end end table.wipe(npcs) table.wipe(Rarity.bosses) table.wipe(Rarity.zones) table.wipe(Rarity.items) table.wipe(Rarity.guids) table.wipe(Rarity.npcs_to_items) table.wipe(Rarity.items_to_items) table.wipe(Rarity.used) table.wipe(Rarity.fishzones) table.wipe(Rarity.architems) table.wipe(Rarity.stats_to_scan) table.wipe(Rarity.items_with_stats) table.wipe(Rarity.collection_items) for k, v in pairs(self.db.profile.groups) do if type(v) == "table" then for kk, vv in pairs(v) do if type(vv) == "table" then if vv.enabled ~= false and vv.statisticId and type(vv.statisticId) == "table" then local numStats = 0 for kkk, vvv in pairs(vv.statisticId) do Rarity.stats_to_scan[vvv] = vv numStats = numStats + 1 end if numStats > 0 then Rarity.items_with_stats[kk] = vv end end if vv.method == NPC and vv.npcs ~= nil and type(vv.npcs) == "table" then for kkk, vvv in pairs(vv.npcs) do npcs[vvv] = vv if Rarity.npcs_to_items[vvv] == nil then Rarity.npcs_to_items[vvv] = {} end table.insert(Rarity.npcs_to_items[vvv], vv) end elseif vv.method == BOSS and vv.npcs ~= nil and type(vv.npcs) == "table" then for kkk, vvv in pairs(vv.npcs) do Rarity.bosses[vvv] = vv if Rarity.npcs_to_items[vvv] == nil then Rarity.npcs_to_items[vvv] = {} end table.insert(Rarity.npcs_to_items[vvv], vv) end elseif vv.method == ZONE and vv.zones ~= nil and type(vv.zones) == "table" then for kkk, vvv in pairs(vv.zones) do if lbz[vvv] then Rarity.zones[lbz[vvv]] = vv end if lbsz[vvv] then Rarity.zones[lbsz[vvv]] = vv end Rarity.zones[vvv] = vv end elseif vv.method == USE and vv.items ~= nil and type(vv.items) == "table" then for kkk, vvv in pairs(vv.items) do Rarity.used[vvv] = vv if Rarity.items_to_items[vvv] == nil then Rarity.items_to_items[vvv] = {} end table.insert(Rarity.items_to_items[vvv], vv) end elseif vv.method == FISHING and vv.zones ~= nil and type(vv.zones) == "table" then for kkk, vvv in pairs(vv.zones) do if lbz[vvv] then Rarity.fishzones[lbz[vvv]] = vv end if lbsz[vvv] then Rarity.fishzones[lbsz[vvv]] = vv end Rarity.fishzones[vvv] = vv end elseif vv.method == ARCH and vv.itemId ~= nil then local itemName = GetItemInfo(vv.itemId) if itemName then Rarity.architems[itemName] = vv end end if vv.itemId ~= nil and vv.method ~= COLLECTION then Rarity.items[vv.itemId] = vv end if vv.itemId2 ~= nil and vv.method ~= COLLECTION then Rarity.items[vv.itemId2] = vv end if vv.method == COLLECTION and vv.collectedItemId ~= nil then if type(vv.collectedItemId) == "table" then for kkk, vvv in pairs(vv.collectedItemId) do local itemID = tonumber(vvv) -- It's stored as a list of strings, but we use numbers for indices Rarity.items[itemID] = vv end else Rarity.items[vv.collectedItemId] = vv end table.insert(Rarity.collection_items, vv) end if vv.tooltipNpcs and type(vv.tooltipNpcs) == "table" then -- Item has tooltipNpcs -> Check if they should be displayed local showTooltipNpcs = true -- If no filters exist, always show the tooltip for relevant NPCs if vv.showTooltipCondition and type(vv.showTooltipCondition) == "table" -- This item has filter conditions to help decide when the tooltipNpcs should be added and vv.showTooltipCondition.filter and type(vv.showTooltipCondition.filter) == "function" and vv.showTooltipCondition.value then -- Filter has the correct format and can be applied -- Check filter conditions to see if tooltipNpcs should be added showTooltipNpcs = false -- Hide the additional tooltip info by default (filters will overwrite this if they can find a match, below) -- Each filter requires separate handling here if vv.showTooltipCondition.filter == IsSpellKnown then -- Filter if a (relevant) spell with the given name is not known for spellID, spellName in pairs(Rarity.relevantSpells) do -- Try to find any match for the given spell (a single one will do) if spellName == vv.showTooltipCondition.value then -- The value is a relevant spell -> Check if filter condition is true -- Player hasn't learned the required spell -> Stop trying to find other matches and turn off the filter showTooltipNpcs = IsSpellKnown(spellID) if showTooltipNpcs then break end -- No point in checking the other spells; A single match is enough to decide to not filter them end end end -- There aren't any other Filter types at the moment... but there could be! end -- Check for post-processing via tooltip modifiers (additional logic contained in a database entry that requires special handling) -- This has to run last, as it is intended to update things on the fly where a filter isn't sufficient local tooltipModifier = vv.tooltipModifier if tooltipModifier ~= nil and type(tooltipModifier) == "table" and tooltipModifier.condition ~= nil and tooltipModifier.value ~= nil then -- Apply modifications where necessary local shouldApplyModification = type(tooltipModifier.condition) == "function" and tooltipModifier.condition() if shouldApplyModification and tooltipModifier.action and type(tooltipModifier.action) == "function" then -- Apply this action to the entry vv = tooltipModifier.action(vv, tooltipModifier.value) -- A tooltip modifier always returns the (modified) database entry to keep processing separate end end -- Add entries to the list of relevant NPCs for this item if showTooltipNpcs then for kkk, vvv in pairs(vv.tooltipNpcs) do if Rarity.npcs_to_items[vvv] == nil then Rarity.npcs_to_items[vvv] = {} end table.insert(Rarity.npcs_to_items[vvv], vv) end end end end end end end end function R:GetNPCIDFromGUID(guid) if guid then local unit_type, _, _, _, _, mob_id = strsplit("-", guid) if unit_type == "Pet" or unit_type == "Player" then return 0 end return (guid and mob_id and tonumber(mob_id)) or 0 end return 0 end function R:IsAttemptAllowed(item) -- No item supplied; assume it's okay if item == nil then return true end -- Check disabled classes local playerClass = select(2, UnitClass("player")) if item.disableForClass and item.disableForClass[playerClass] then Rarity:Debug(format("Attempts for item %s are disallowed (disabled for class %s)", item.name, playerClass)) return false end local dungeonID = select(10, GetInstanceInfo()) if dungeonID and item.requiredDungeons and not item.requiredDungeons[dungeonID] then Rarity:Debug(format("Attempts for item %s are disallowed (not a required dungeon: %d)", item.name, dungeonID)) return false end local activeCovenantID = C_Covenants.GetActiveCovenantID() if item.requiresCovenant and item.requiredCovenantID and activeCovenantID ~= item.requiredCovenantID then local activeCovenantData = C_Covenants.GetCovenantData(activeCovenantID) local requiredCovenantData = C_Covenants.GetCovenantData(item.requiredCovenantID) if not activeCovenantData then Rarity:Debug( format( "Attempts for item %s are disallowed (Covenant %d/%s is required, but none is currently active)", item.name, item.requiredCovenantID, requiredCovenantData.name ) ) return false end Rarity:Debug( format( "Attempts for item %s are disallowed (Covenant %d/%s is required, but active covenant is %d/%s)", item.name, item.requiredCovenantID, requiredCovenantData.name, activeCovenantID, activeCovenantData.name ) ) return false end -- If any prerequisite quests exist, check if they are all completed if item.requiresCompletedQuestId and type(item.requiresCompletedQuestId) == "table" then for key, questId in pairs(item.requiresCompletedQuestId) do if not IsQuestFlaggedCompleted(questId) then return false end end end if item.requiredAreaPOIs and not Rarity.AreaPOIs.HasActiveAreaPOIs(item.requiredAreaPOIs) then Rarity:Debug(format("Attempts for item %s are disallowed (requires active area POIs)", item.name)) return false end -- No valid instance difficulty configuration; allow (this needs to be the second-to-last check) if item.instanceDifficulties == nil or type(item.instanceDifficulties) ~= "table" or next(item.instanceDifficulties) == nil then return true end -- Check instance difficulty (this needs to be the last check) local foundTrue = false for k, v in pairs(item.instanceDifficulties) do if v == true then foundTrue = true end end if foundTrue == false then return true end local name, type, difficulty, difficultyName, maxPlayers, playerDifficulty, isDynamicInstance, mapID, instanceGroupSize = GetInstanceInfo() if item.instanceDifficulties[difficulty] and item.instanceDifficulties[difficulty] == true then return true end return false end function R:CheckNpcInterest(guid, zone, subzone, zone_t, subzone_t, curSpell, requiresPickpocket) if guid == nil then return end if type(guid) ~= "string" then return end if Rarity.guids[guid] ~= nil then return end -- Already seen this NPC local npcid = self:GetNPCIDFromGUID(guid) if npcs[npcid] == nil then -- Not an NPC we need, abort self:Debug("NPC ID not on the list of needed NPCs: " .. (npcid or "nil")) if Rarity.zones[tostring(GetBestMapForUnit("player"))] == nil and Rarity.zones[zone] == nil and Rarity.zones[lbz[zone] or "."] == nil and Rarity.zones[lbsz[subzone] or "."] == nil and Rarity.zones[zone_t] == nil and Rarity.zones[subzone_t] == nil and Rarity.zones[lbz[zone_t] or "."] == nil and Rarity.zones[lbsz[subzone_t] or "."] == nil then -- Not a zone we need, abort self:Debug("Map ID not on the list of needed zones: " .. tostring(GetBestMapForUnit("player"))) return end else self:Debug("NPC ID is one we need: " .. (npcid or "nil")) end -- If the loot is the result of certain spell casts (mining, herbing, opening, pick lock, archaeology, disenchanting, etc), stop here -> This is to avoid multiple attempts, since those methods are handled separately! if Rarity.relevantSpells[curSpell] then self:Debug("Aborting because we were casting a disallowed spell: " .. curSpell) return end -- If the loot is not from an NPC (could be from yourself or a world object), we don't want to process this local unitType, _, _, _, _, mob_id = strsplit("-", guid) if unitType ~= "Creature" and unitType ~= "Vehicle" then self:Debug( "This loot isn't from an NPC; disregarding. Loot source identified as unit type: " .. (unitType or "nil") ) return end Rarity.guids[guid] = true -- Increment attempt counter(s). One NPC might drop multiple things we want, so scan for them all. if Rarity.npcs_to_items[npcid] and type(Rarity.npcs_to_items[npcid]) == "table" then for k, v in pairs(Rarity.npcs_to_items[npcid]) do if v.enabled ~= false and (v.method == NPC or v.method == ZONE) then if self:IsAttemptAllowed(v) then -- Don't increment attempts if this NPC also has a statistic defined. This would result in two attempts counting instead of one. if not v.statisticId or type(v.statisticId) ~= "table" or #v.statisticId <= 0 then -- Don't increment attempts for unique items if you already have the item in your bags if not (v.unique == true and (Rarity.bagitems[v.itemId] or 0) > 0) then -- Don't increment attempts for non-pickpocketed items if this item isn't being pickpocketed if (requiresPickpocket and v.pickpocket) or (requiresPickpocket == false and not v.pickpocket) then if v.attempts == nil then v.attempts = 1 else v.attempts = v.attempts + 1 end self:OutputAttempts(v) end end end end end end end -- Check for zone-wide items and increment them if needed for k, v in pairs(self.db.profile.groups) do if type(v) == "table" then for kk, vv in pairs(v) do if type(vv) == "table" then if vv.enabled ~= false then local found = false if vv.method == ZONE and vv.zones ~= nil and type(vv.zones) == "table" then for kkk, vvv in pairs(vv.zones) do if tonumber(vvv) ~= nil and tonumber(vvv) == GetBestMapForUnit("player") then found = true end if vvv == zone or vvv == lbz[zone] or vvv == subzone or vvv == lbsz[subzone] or vvv == zone_t or vvv == subzone_t or vvv == lbz[zone_t] or vvv == subzone or vvv == lbsz[subzone_t] then found = true end end end if found then if self:IsAttemptAllowed(vv) then if vv.attempts == nil then vv.attempts = 1 else vv.attempts = vv.attempts + 1 end self:OutputAttempts(vv) end end end end end end end end --[[ CORE FUNCTIONALITY ------------------------------------------------------------------------------------------------------- ]] function R:Update(reason) Rarity.Collections:ScanExistingItems(reason) self:UpdateInterestingThings(reason) Rarity.Tracking:FindTrackedItem() Rarity.GUI:UpdateText() -- if self:InTooltip() then self:ShowTooltip() end end