You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

884 lines
29 KiB

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