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
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
|
|
|