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.

4370 lines
157 KiB

local SI, L = unpack((select(2, ...)))
local QTip = SI.Libs.QTip
local db
local maxdiff = 33 -- max number of instance difficulties
local maxcol = 4 -- max columns per player+instance
local maxid = 3000 -- highest possible value for an instanceID, current max (Battle of Dazar'alor) is 2070
local table, math, bit, string, pairs, ipairs, unpack, strsplit, time, type, wipe, tonumber, select, strsub =
table, math, bit, string, pairs, ipairs, unpack, strsplit, time, type, wipe, tonumber, select, strsub
local GetSavedInstanceInfo, GetNumSavedInstances, GetSavedInstanceChatLink, GetLFGDungeonNumEncounters, GetLFGDungeonEncounterInfo, GetNumRandomDungeons, GetLFGRandomDungeonInfo, GetLFGDungeonInfo, GetLFGDungeonRewards, GetTime, UnitIsUnit, GetInstanceInfo, IsInInstance, SecondsToTime, GetNumGroupMembers, UnitAura =
GetSavedInstanceInfo, GetNumSavedInstances, GetSavedInstanceChatLink, GetLFGDungeonNumEncounters, GetLFGDungeonEncounterInfo, GetNumRandomDungeons, GetLFGRandomDungeonInfo, GetLFGDungeonInfo, GetLFGDungeonRewards, GetTime, UnitIsUnit, GetInstanceInfo, IsInInstance, SecondsToTime, GetNumGroupMembers, UnitAura
local RAID_CLASS_COLORS = RAID_CLASS_COLORS
local FONTEND = FONT_COLOR_CODE_CLOSE
local GOLDFONT = NORMAL_FONT_COLOR_CODE
local YELLOWFONT = LIGHTYELLOW_FONT_COLOR_CODE
local REDFONT = RED_FONT_COLOR_CODE
local GREENFONT = GREEN_FONT_COLOR_CODE
local WHITEFONT = HIGHLIGHT_FONT_COLOR_CODE
local GRAYFONT = GRAY_FONT_COLOR_CODE
local GRAY_COLOR = { 0.5, 0.5, 0.5, 1 }
local INSTANCE_SAVED, TRANSFER_ABORT_TOO_MANY_INSTANCES, NO_RAID_INSTANCES_SAVED =
INSTANCE_SAVED, TRANSFER_ABORT_TOO_MANY_INSTANCES, NO_RAID_INSTANCES_SAVED
local ALREADY_LOOTED = ERR_LOOT_GONE:gsub("%(.*%)","")
ALREADY_LOOTED = ALREADY_LOOTED:gsub("(.*)","") -- fix on zhCN and zhTW
local currency = SI.currency
local QuestExceptions = SI.QuestExceptions
local TimewalkingItemQuest = SI.TimewalkingItemQuest
local Config = SI:GetModule('Config')
local Tooltip = SI:GetModule('Tooltip')
local Calling = SI:GetModule('Calling')
local Currency = SI:GetModule('Currency')
local MythicPlus = SI:GetModule('MythicPlus')
local Progress = SI:GetModule('Progress')
local TradeSkill = SI:GetModule('TradeSkill')
local Warfront = SI:GetModule('Warfront')
SI.Indicators = {
ICON_STAR = ICON_LIST[1] .. "16:16:0:0|t",
ICON_CIRCLE = ICON_LIST[2] .. "16:16:0:0|t",
ICON_DIAMOND = ICON_LIST[3] .. "16:16:0:0|t",
ICON_TRIANGLE = ICON_LIST[4] .. "16:16:0:0|t",
ICON_MOON = ICON_LIST[5] .. "16:16:0:0|t",
ICON_SQUARE = ICON_LIST[6] .. "16:16:0:0|t",
ICON_CROSS = ICON_LIST[7] .. "16:16:0:0|t",
ICON_SKULL = ICON_LIST[8] .. "16:16:0:0|t",
BLANK = "None",
}
SI.Categories = { }
local maxExpansion
for i = 0,10 do
local ename = _G["EXPANSION_NAME"..i]
if ename then
maxExpansion = i
SI.Categories["D"..i] = ename .. ": " .. LFG_TYPE_DUNGEON
SI.Categories["R"..i] = ename .. ": " .. LFG_TYPE_RAID
else
break
end
end
function SI:QuestInfo(questid)
if not questid or questid == 0 then return nil end
SI.ScanTooltip:SetOwner(UIParent, 'ANCHOR_NONE')
SI.ScanTooltip:SetHyperlink("\124cffffff00\124Hquest:"..questid..":90\124h[]\124h\124r")
SI.ScanTooltip:Show()
local l = _G[SI.ScanTooltip:GetName().."TextLeft1"]
l = l and l:GetText()
if not l or #l == 0 then return nil end -- cache miss
return l, "\124cffffff00\124Hquest:"..questid..":90\124h["..l.."]\124h\124r"
end
-- abbreviate expansion names (which apparently are not localized in any western character set)
local function abbreviate(iname)
iname = iname:gsub("Burning Crusade", "BC")
iname = iname:gsub("Wrath of the Lich King", "WotLK")
iname = iname:gsub("Cataclysm", "Cata")
iname = iname:gsub("Mists of Pandaria", "MoP")
iname = iname:gsub("Warlords of Draenor", "WoD")
iname = iname:gsub("Battle for Azeroth", "BfA")
return iname
end
function SI:formatNumber(num, ismoney)
num = tonumber(num)
if not num then return "" end
local post = ""
if ismoney then
if num < 1000*10000 then -- less than 1k, show it all
return GetMoneyString(num)
end
num = math.floor(num / 10000)
post = " \124TInterface\\MoneyFrame\\UI-GoldIcon:0:0:2:0\124t"
end
if SI.db.Tooltip.NumberFormat then
local str = ""
local neg = num < 0
num = math.abs(num)
local int = math.floor(num)
local dec = num - int
local t = tostring(int)
if #t > 4 then -- leave 4 digit numbers
while #t > 3 do
str = LARGE_NUMBER_SEPERATOR .. t:sub(-3) .. str
t = t:sub(1,-4)
end
end
str = t..str
if dec > 0 then
str = str..string.format("%15g",dec):match("(%..*)$")
end
if neg then
str = "-"..str
end
return str..post
else
return num..post
end
end
SI.defaultDB = {
DBVersion = 12,
History = { }, -- for tracking 5 instance per hour limit
-- key: instance string; value: time first entered
Toons = { }, -- table key: "Toon - Realm"; value:
-- Class: string
-- Level: integer
-- Race: string
-- LastSeen: integer
-- AlwaysShow: boolean REMOVED
-- Show: string "always", "never", "saved"
-- Daily1: expiry (normal) REMOVED
-- Daily2: expiry (heroic) REMOVED
-- LFG1: expiry (random dungeon)
-- LFG2: expiry (deserter)
-- WeeklyResetTime: expiry
-- DailyResetTime: expiry
-- DailyCount: integer REMOVED
-- PlayedLevel: integer
-- PlayedTotal: integer
-- Money: integer
-- Zone: string
-- Warmode: boolean
-- Artifact: string REMOVED
-- Cloak: string REMOVED
-- Covenant: number
-- MythicPlusScore: number
-- Paragon: table
-- oRace: string
-- isResting: boolean
-- MaxXP: integer
-- XP: integer
-- RestXP: integer
-- Arena2v2rating: integer
-- Arena3v3rating: integer
-- RBGrating: integer
-- SoloShuffleRating: table
-- SpecializationIDs: table
-- currency: key: currencyID value:
-- amount: integer
-- earnedThisWeek: integer
-- weeklyMax: integer
-- totalMax: integer
-- totalEarned: integer
-- relatedItemCount: integer
-- Quests: key: QuestID value:
-- Title: string
-- Link: hyperlink
-- Zone: string
-- isDaily: boolean
-- Expires: expiration (non-daily)
-- Skills: key: SpellID or CDID value:
-- Title: string
-- Link: hyperlink
-- Expires: expiration
-- BonusRoll: key: int value:
-- name: string
-- time: int
-- costCurrencyID: int
-- currencyID: int or nil
-- money: integer or nil
-- item: linkstring or nil
-- MythicKey
-- name: string
-- ResetTime: expiry
-- mapID: int
-- level: int
-- color: string
-- link: string
-- TimewornMythicKey
-- name: string
-- ResetTime: expiry
-- mapID: int
-- level: int
-- color: string
-- link: string
-- MythicKeyBest
-- ResetTime: expiry
-- [1-3]: number
-- lastCompletedIndex: number
-- threshold[1-3]: number
-- rewardWaiting: boolean
-- [runHistory]: [
-- completed,
-- thisWeek,
-- mapChallengeModeID,
-- level,
-- name,
-- rewardLevel,
-- }
-- REMOVED
-- DailyWorldQuest
-- days[0,1,2]
-- name
-- dayleft
-- questneed
-- questdone
-- Emissary
-- [expansionLevel] = {
-- unlocked = (boolean),
-- days = {
-- [Day] = {
-- isComplete = isComplete,
-- isFinish = isFinish,
-- questDone = questDone,
-- questReward = {
-- money = money,
-- itemName = itemName,
-- itemLvl = itemLvl,
-- quality = quality,
-- currencyID = currencyID,
-- quantity = quantity,
-- },
-- },
-- },
-- }
-- Progress
-- table<string, QuestStore|QuestListStore|table>
-- Warfront
-- [index] = {
-- scenario = (boolean),
-- boss = (boolean),
-- }
-- Calling
-- unlocked = (boolean),
-- [Day] = {
-- isCompleted = isCompleted,
-- expiredTime = expiredTime,
-- isOnQuest = isOnQuest,
-- questID = questID,
-- title = title,
-- text = text,
-- objectiveType = objectiveType,
-- isFinished = isFinished,
-- questDone = questDone,
-- questNeed = questNeed,
-- questReward = {
-- itemName = itemName,
-- quality = quality,
-- },
-- }
Indicators = {
D1Indicator = "BLANK", -- indicator: ICON_*, BLANK
D1Text = "KILLED/TOTAL",
D1Color = { 0, 0.6, 0 }, -- dark green
D1ClassColor = true,
D2Indicator = "BLANK",
D2Text = "KILLED/TOTALH",
D2Color = { 0, 1, 0 }, -- green
D2ClassColor = true,
D3Indicator = "BLANK",
D3Text = "KILLED/TOTALM",
D3Color = { 1, 0, 0 }, -- red
D3ClassColor = true,
R0Indicator = "BLANK",
R0Text = "KILLED/TOTAL",
R0Color = { 0.6, 0.6, 0 }, -- dark yellow
R0ClassColor = true,
R1Indicator = "BLANK",
R1Text = "KILLED/TOTAL",
R1Color = { 0.6, 0.6, 0 }, -- dark yellow
R1ClassColor = true,
R2Indicator = "BLANK",
R2Text = "KILLED/TOTAL",
R2Color = { 0.6, 0, 0 }, -- dark red
R2ClassColor = true,
R3Indicator = "BLANK",
R3Text = "KILLED/TOTALH",
R3Color = { 1, 1, 0 }, -- yellow
R3ClassColor = true,
R4Indicator = "BLANK",
R4Text = "KILLED/TOTALH",
R4Color = { 1, 0, 0 }, -- red
R4ClassColor = true,
R5Indicator = "BLANK",
R5Text = "KILLED/TOTALL",
R5Color = { 0, 0, 1 }, -- blue
R5ClassColor = true,
R6Indicator = "BLANK",
R6Text = "KILLED/TOTAL",
R6Color = { 0, 1, 0 }, -- green
R6ClassColor = true,
R7Indicator = "BLANK",
R7Text = "KILLED/TOTALH",
R7Color = { 1, 1, 0 }, -- yellow
R7ClassColor = true,
R8Indicator = "BLANK",
R8Text = "KILLED/TOTALM",
R8Color = { 1, 0, 0 }, -- red
R8ClassColor = true,
},
Tooltip = {
ReverseInstances = false,
ShowExpired = false,
ShowHoliday = true,
ShowRandom = true,
CombineWorldBosses = false,
CombineLFR = true,
TrackDailyQuests = true,
TrackWeeklyQuests = true,
ShowCategories = false,
CategorySpaces = false,
RowHighlight = 0.1,
Scale = 1,
FitToScreen = true,
NewFirst = true,
RaidsFirst = true,
NumberFormat = true,
CategorySort = "EXPANSION", -- "EXPANSION", "TYPE"
ShowSoloCategory = false,
ShowHints = true,
ReportResets = true,
LimitWarn = true,
HistoryText = false,
ShowServer = false,
ServerSort = true,
ServerOnly = false,
ConnectedRealms = "group",
SelfFirst = true,
SelfAlways = false,
TrackLFG = true,
TrackDeserter = true,
TrackSkills = true,
TrackBonus = false,
TrackPlayed = true,
AugmentBonus = true,
CurrencyValueColor = true,
Currency2003 = true, -- Dragon Isles Supplies
Currency2123 = true, -- Bloody Tokens
Currency2245 = true, -- Flightstones
Currency2533 = true, -- Renascent Shadowflame
CurrencyMax = false,
CurrencyEarned = true,
CurrencySortName = false,
MythicKey = true,
TimewornMythicKey = true,
MythicKeyBest = true,
Emissary6 = false, -- LEG Emissary
Emissary7 = false, -- BfA Emissary
EmissaryFullName = true,
EmissaryShowCompleted = true,
CombineEmissary = false,
AbbreviateKeystone = true,
TrackParagon = true,
Calling = true,
CallingShowCompleted = true,
CombineCalling = true,
Warfront1 = false, -- Arathi Highlands
Warfront2 = false, -- Darkshores
KeystoneReportTarget = "EXPORT",
},
Instances = { }, -- table key: "Instance name"; value:
-- Show: boolean
-- Raid: boolean
-- Holiday: boolean
-- Random: boolean
-- Expansion: integer
-- RecLevel: integer
-- LFDID: integer
-- LFDupdated: integer REMOVED
-- REMOVED Encounters[integer] = { GUID : integer, Name : string }
-- table key: "Toon - Realm"; value:
-- table key: "Difficulty"; value:
-- ID: integer, positive for a Blizzard Raid ID,
-- negative value for an LFR encounter count
-- Expires: integer
-- Locked: boolean, whether toon is locked to the save
-- Extended: boolean, whether this is an extended raid lockout
-- Link: string hyperlink to the save
-- 1..numEncounters: boolean LFR isLooted
MinimapIcon = { hide = false },
Quests = {}, -- Account-wide Quests: key: QuestID value: same as toon Quest database
QuestDB = { -- permanent repeatable quest DB: key: questid value: mapid
Daily = {},
Weekly = {},
Darkmoon = {},
AccountDaily = {},
AccountWeekly = {},
},
Warfront = {},
-- Track Warfronts
-- [index] = {
-- captureSide = ("Alliance" or "Horde"), -- Capture Side of Warfront
-- contributing = (boolean), -- if it is contributing
-- restTime = restTime, -- timeOfNextStateChange
-- }
Emissary = {
Cache = {},
Expansion = {},
},
-- Track emissaries
-- Cache: [questID] = questName
-- Expansion:
-- [expansionLevel] = {
-- [1, 2, 3] = {
-- questID = {
-- ["Alliance"] = questID,
-- ["Horde"] = questID,
-- },
-- questNeed = questNeed,
-- expiredTime = expiredTime,
-- }
-- }
RealmMap = {},
}
-- skinning support
-- skinning addons should hook this function, eg:
-- hooksecurefunc(SavedInstances,"SkinFrame",function(self,frame,name) frame:SetWhatever() end)
function SI:SkinFrame(frame, name)
-- default behavior (ticket 81)
if IsAddOnLoaded("ElvUI") or IsAddOnLoaded("Tukui") then
if frame.StripTextures then
frame:StripTextures()
end
if frame.CreateBackdrop then
frame:CreateBackdrop("Transparent")
end
local closeButton = _G[name .. "CloseButton"] or frame.CloseButton
if closeButton and closeButton.SetAlpha then
if ElvUI then
ElvUI[1]:GetModule('Skins'):HandleCloseButton(closeButton)
end
if Tukui and Tukui[1] and Tukui[1].SkinCloseButton then
Tukui[1].SkinCloseButton(closeButton)
end
closeButton:SetAlpha(1)
end
end
end
-- general helper functions below
local function ColorCodeOpenRGB(r,g,b,a)
return format("|c%02x%02x%02x%02x", math.floor(a * 255), math.floor(r * 255), math.floor(g * 255), math.floor(b * 255))
end
local function ColorCodeOpen(color)
return ColorCodeOpenRGB(color[1] or color.r,
color[2] or color.g,
color[3] or color.b,
color[4] or color.a or 1)
end
local function ClassColorise(class, targetstring)
local c = (CUSTOM_CLASS_COLORS and CUSTOM_CLASS_COLORS[class]) or RAID_CLASS_COLORS[class]
if c.colorStr then
c = "|c"..c.colorStr
else
c = ColorCodeOpen( c )
end
return c .. targetstring .. FONTEND
end
local function CurrencyColor(amt, max)
amt = amt or 0
local samt = SI:formatNumber(amt)
if max == nil or max == 0 then
return samt
end
if SI.db.Tooltip.CurrencyValueColor then
local pct = amt / max
local color = GREENFONT
if pct >= 1 then
color = REDFONT
elseif pct > 0.75 then
color = GOLDFONT
end
samt = color .. samt .. FONTEND
end
return samt
end
local function TableLen(table)
local i = 0
for _, _ in pairs(table) do
i = i + 1
end
return i
end
function SI:QuestIgnored(questID)
if (TimewalkingItemQuest[questID]) and SI.activeHolidays then
-- Timewalking Item Quests
if SI.activeHolidays[TimewalkingItemQuest[questID]] then
-- Timewalking Weedend Event ONGOING
return
end
return true
elseif Progress:QuestEnabled(questID) then
return true
end
end
function SI:QuestCount(toonname)
local t
if toonname then
t = SI and SI.db.Toons and SI.db.Toons[toonname]
else -- account-wide quests
t = db
end
if not t then return 0,0 end
local dailycount, weeklycount = 0,0
-- ticket 96: GetDailyQuestsCompleted() is unreliable, the response is laggy and it fails to count some quests
local id, info
for id, info in pairs(t.Quests) do
if not self:QuestIgnored(id) then
if info.isDaily then
dailycount = dailycount + 1
else
weeklycount = weeklycount + 1
end
end
end
return dailycount, weeklycount
end
-- local addon functions below
local function GetLastLockedInstance()
local numsaved = GetNumSavedInstances()
if numsaved > 0 then
for i = 1, numsaved do
local name, id, expires, diff, locked, extended, mostsig, raid, players, diffname = GetSavedInstanceInfo(i)
if locked then
return name, id, expires, diff, locked, extended, mostsig, raid, players, diffname
end
end
end
end
function SI:normalizeName(str)
return str:gsub("%p",""):gsub("%s"," "):gsub("%s%s"," "):gsub("^%s+",""):gsub("%s+$",""):upper()
end
SI.transInstance = {
-- lockout hyperlink id = LFDID
[543] = 188, -- Hellfire Citadel: Ramparts
[540] = 189, -- Hellfire Citadel: Shattered Halls : deDE
[542] = 187, -- Hellfire Citadel: Blood Furnace esES
[534] = 195, -- The Battle for Mount Hyjal
[509] = 160, -- Ruins of Ahn'Qiraj
[557] = 179, -- Auchindoun: Mana-Tombs : ticket 72 zhTW
[556] = 180, -- Auchindoun: Sethekk Halls : ticket 151 frFR
[568] = 340, -- Zul'Aman: frFR
[1004] = 474, -- Scarlet Monastary: deDE
[600] = 215, -- Drak'Tharon: ticket 105 deDE
[560] = 183, -- Escape from Durnholde Keep: ticket 124 deDE
[531] = 161, -- AQ temple: ticket 137 frFR
[1228] = 897, -- Highmaul: ticket 175 ruRU
[552] = 1011, -- Arcatraz: ticket 216 frFR
[1516] = 1190, -- Arcway: ticket 227/233 ptBR
[1651] = 1347, -- Return to Karazhan: ticket 237 (fake LFDID)
[545] = 185, -- The Steamvault: issue #143 esES
[1530] = 1353, -- The Nighthold: issue #186 frFR
[585] = 1154, -- Magisters' Terrace: issue #293 frFR
[2235] = 1911, -- Caverns of Time - Anniversary: issue #315 (fake LFDID used by Escape from Tol Dagor)
[725] = 320, -- The Stonecore: issue #328 frFR
[2515] = 2335, -- The Azure Vault: issue #630 deDE
[550] = 193, -- Tempest Keep: issue #612 ruRU
}
-- some instances (like sethekk halls) are named differently by GetSavedInstanceInfo() and LFGGetDungeonInfoByID()
-- we use the latter name to key our database, and this function to convert as needed
function SI:FindInstance(name, raid)
if not name or #name == 0 then return nil end
local nname = SI:normalizeName(name)
-- first pass, direct match
local info = SI.db.Instances[name]
if info then
return name, info.LFDID
end
-- hyperlink id lookup: must precede substring match for ticket 99
-- (so transInstance can override incorrect substring matches)
for i = 1, GetNumSavedInstances() do
local link = GetSavedInstanceChatLink(i) or ""
local lid,lname = link:match(":(%d+):%d+:%d+\124h%[(.+)%]\124h")
lname = lname and SI:normalizeName(lname)
lid = lid and tonumber(lid)
local lfdid = lid and SI.transInstance[lid]
if lname == nname and lfdid then
local truename = SI:UpdateInstance(lfdid)
if truename then
return truename, lfdid
end
end
end
-- normalized substring match
for truename, info in pairs(SI.db.Instances) do
local tname = SI:normalizeName(truename)
if (tname:find(nname, 1, true) or nname:find(tname, 1, true)) and
info.Raid == raid then -- Tempest Keep: The Botanica
-- SI:Debug("FindInstance("..name..") => "..truename)
return truename, info.LFDID
end
end
return nil
end
-- provide either id or name/raid to get the instance truename and db entry
function SI:LookupInstance(id, name, raid)
-- SI:Debug("LookupInstance("..(id or "nil")..","..(name or "nil")..","..(raid and "true" or "false")..")")
local truename, instance
if name then
truename, id = SI:FindInstance(name, raid)
end
if id then
truename = SI:UpdateInstance(id)
end
if truename then
instance = SI.db.Instances[truename]
end
if not instance then
SI:Debug("LookupInstance() failed to find instance: "..(name or "")..":"..(id or 0).." : "..GetLocale())
SI.warned = SI.warned or {}
if not SI.warned[name] then
SI.warned[name] = true
local lid
for i = 1, GetNumSavedInstances() do
local link = GetSavedInstanceChatLink(i) or ""
local tlid,tlname = link:match(":(%d+):%d+:%d+\124h%[(.+)%]\124h")
if tlname == name then lid = tlid end
end
SI:BugReport("SavedInstances: ERROR: Refresh() failed to find instance: "..name.." : "..GetLocale().." : "..(lid or "x"))
end
instance = {}
--SI.db.Instances[name] = instance
end
return truename, instance
end
function SI:InstanceCategory(instance)
if not instance then return nil end
instance = SI.db.Instances[instance]
if instance.Holiday then return "H" end
if instance.Random then return "N" end
return ((instance.Raid and "R") or ((not instance.Raid) and "D")) .. instance.Expansion
end
function SI:InstancesInCategory(targetcategory)
-- returns a table of the form { "instance1", "instance2", ... }
if (not targetcategory) then return { } end
local list = { }
for instance, _ in pairs(SI.db.Instances) do
if SI:InstanceCategory(instance) == targetcategory then
table.insert(list, instance)
end
end
return list
end
function SI:CategorySize(category)
if not category then return nil end
local i = 0
for instance, _ in pairs(SI.db.Instances) do
if category == SI:InstanceCategory(instance) then
i = i + 1
end
end
return i
end
local _instance_exceptions = {
-- workaround a Blizzard bug:
-- since 5.0, some old raid lockout tooltips are missing boss kill info
-- currently affects 25+ man BC/Vanilla raids (but not Kara or AQ Ruins, go figure)
-- starting in 6.1 we have the kill bitmap but no boss names
[48] = { -- Molten Core
12118, -- Lucifron
11982, -- Magmadar
12259, -- Gehennas
12057, -- Garr
12264, -- Shazzrah
12056, -- Baron Geddon
12098, -- Sulfuron Harbinger
11988, -- Golemagg the Incinerator
12018, -- Majordomo Executus
11502, -- Ragnaros
},
[50] = { -- Blackwing Lair
12435, -- Razorgore the Untamed
13020, -- Vaelastrasz the Corrupt
12017, -- Broodlord Lashlayer
11983, -- Firemaw
14601, -- Ebonroc
11981, -- Flamegor
14020, -- Chromaggus
11583, -- Nefarian
},
[161] = { -- Ahn'Qiraj Temple
15263, -- Prophet Skeram
15543, -- Princess Yauj (also Vem and Lord Kri)
15516, -- Bodyguard Sartura
15510, -- Fankriss the Unyielding
15299, -- Viscidus
15509, -- Princess Huhuran
15276, -- Emperor Vek'lor
15517, -- Ouro
15727, -- C'Thun
},
[176] = { -- Magtheridon's Lair
17257, -- Magtheridon
},
[177] = { -- Gruul's Lair
18831, -- High King Maulgar
19044, -- Gruul
},
[193] = { -- Tempest Keep
19514, -- A'lar
19516, -- Void Reaver
18805, -- High Astromancer Solarian
19622, -- Kael'thas Sunstrider
},
[194] = { -- Serpentshrine Cavern
21216, -- Hydross the Unstable
21217, -- The Lurker Below
21215, -- Leotheras the Blind
21214, -- Fathom-Lord Karathress
21213, -- Morogrim Tidewalker
21212, -- Lady Vashj
},
[195] = { -- Hyjal Past
17767, -- Rage Winterchill
17808, -- Anetheron
17888, -- Kaz'rogal
17842, -- Azgalor
17968, -- Archimonde
},
[196] = { -- Black Temple
22887, -- High Warlord Naj'entus
22898, -- Supremus
22841, -- Shade of Akama
22871, -- Teron Gorefiend
22948, -- Gurtogg Bloodboil
22856, -- Reliquary of Souls
22947, -- Mother Shahraz
23426, -- Illidari Council
22917, -- Illidan Stormrage
},
[199] = { -- Sunwell
24850, -- Kalecgos
24882, -- Brutallus
25038, -- Felmyst
25166, -- Grand Warlock Alythess
25741, -- M'uru
25315, -- Kil'jaeden
},
[1347] = { total=8 }, -- Return to Karazhan
[1701] = { total=4 }, -- Siege of Boralus
}
function SI:instanceException(LFDID)
if not LFDID then return nil end
local exc = _instance_exceptions[LFDID]
if exc then -- localize boss names
local total = 0
for idx, id in ipairs(exc) do
if type(id) == "number" then
SI.ScanTooltip:SetOwner(UIParent, 'ANCHOR_NONE')
SI.ScanTooltip:SetHyperlink(("unit:Creature-0-0-0-0-%d:0000000000"):format(id))
SI.ScanTooltip:Show()
local line = _G[SI.ScanTooltip:GetName().."TextLeft1"]
line = line and line:GetText()
if line and #line > 0 then
exc[idx] = line
end
end
total = total + 1
end
exc.total = exc.total or total
end
return exc
end
function SI:instanceBosses(instance,toon,diff)
local killed,total,base = 0,0,1
local remap, origin
local inst = SI.db.Instances[instance]
local save = inst and inst[toon] and inst[toon][diff]
if inst.WorldBoss then
return (save[1] and 1 or 0), 1, 1
end
if not inst or not inst.LFDID then return 0,0,1 end
local exc = SI:instanceException(inst.LFDID)
total = (exc and exc.total) or GetLFGDungeonNumEncounters(inst.LFDID)
local LFR = SI.LFRInstances[inst.LFDID]
if LFR then
total = LFR.total or total
base = LFR.base or base
remap = LFR.remap
origin = LFR.origin
end
if not save then
return killed, total, base, remap, origin
elseif save.Link then
local bits = save.Link:match(":(%d+)\124h")
bits = bits and tonumber(bits)
if bits then
if inst.LFDID == 1944 then
-- Battle of Dazar'alor
-- https://github.com/SavedInstances/SavedInstances/issues/233
if db.Toons[toon].Faction == "Alliance" then
bits = bit.band(bits, 0x3134D)
else
bits = bit.band(bits, 0x3135A)
end
end
while bits > 0 do
if bit.band(bits,1) > 0 then
killed = killed + 1
end
bits = bit.rshift(bits,1)
end
end
elseif save.ID < 0 then
for i=1,-1*save.ID do
killed = killed + (save[i] and 1 or 0)
end
end
return killed, total, base, remap, origin
end
local lfrkey = "^"..L["LFR"]..": "
local function instanceSort(i1, i2)
local instance1 = SI.db.Instances[i1]
local instance2 = SI.db.Instances[i2]
local level1 = instance1.RecLevel or 0
local level2 = instance2.RecLevel or 0
local id1 = instance1.LFDID or instance1.WorldBoss or 0
local id2 = instance2.LFDID or instance2.WorldBoss or 0
local key1 = level1*1000000+id1
local key2 = level2*1000000+id2
if i1:match(lfrkey) then key1 = key1 - 20000 end
if i2:match(lfrkey) then key2 = key2 - 20000 end
if instance1.WorldBoss then key1 = key1 - 30000 end
if instance2.WorldBoss then key2 = key2 - 30000 end
if SI.db.Tooltip.ReverseInstances then
return key1 < key2
else
return key2 < key1
end
end
SI.oi_cache = {}
function SI:OrderedInstances(category)
-- returns a table of the form { "instance1", "instance2", ... }
local instances = SI.oi_cache[category]
if not instances then
instances = SI:InstancesInCategory(category)
table.sort(instances, instanceSort)
if SI.instancesUpdated then
SI.oi_cache[category] = instances
end
end
return instances
end
function SI:OrderedCategories()
-- returns a table of the form { "category1", "category2", ... }
if SI.oc_cache then return SI.oc_cache end
local orderedlist = { }
local firstexpansion, lastexpansion, expansionstep, firsttype, lasttype
if SI.db.Tooltip.NewFirst then
firstexpansion = maxExpansion
lastexpansion = 0
expansionstep = -1
else
firstexpansion = 0
lastexpansion = maxExpansion
expansionstep = 1
end
if SI.db.Tooltip.RaidsFirst then
firsttype = "R"
lasttype = "D"
else
firsttype = "D"
lasttype = "R"
end
for i = firstexpansion, lastexpansion, expansionstep do
table.insert(orderedlist, firsttype .. i)
if SI.db.Tooltip.CategorySort == "EXPANSION" then
table.insert(orderedlist, lasttype .. i)
end
end
if SI.db.Tooltip.CategorySort == "TYPE" then
for i = firstexpansion, lastexpansion, expansionstep do
table.insert(orderedlist, lasttype .. i)
end
end
SI.oc_cache = orderedlist
return orderedlist
end
local function DifficultyString(instance, diff, toon, expired, killoverride, totoverride)
local setting,color
if not instance then
setting = "D1"
else
local inst = SI.db.Instances[instance]
if not inst or not inst.Raid then -- 5-man
if diff == 2 then -- heroic
setting = "D2"
elseif diff == 23 then -- mythic
setting = "D3"
else -- normal?
setting = "D1"
end
elseif inst.Expansion == 0 then -- classic raid
setting = "R0"
elseif diff >= 3 and diff <= 7 then -- pre-WoD raids
setting = "R"..(diff-2)
elseif diff >= 14 and diff <= 16 then -- WoD raids
setting = "R"..(diff-8)
elseif diff == 17 then -- Looking For Raid
setting = "R5"
else -- don't know
setting = "D1"
end
end
local prefs = SI.db.Indicators
local classcolor = prefs[setting .. "ClassColor"]
if classcolor == nil then
classcolor = SI.defaultDB.Indicators[setting .. "ClassColor"]
end
if expired then
color = GRAY_COLOR
elseif classcolor then
color = (CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS)[SI.db.Toons[toon].Class]
else
prefs[setting.."Color"] = prefs[setting.."Color"] or SI.defaultDB.Indicators[setting.."Color"]
color = prefs[setting.."Color"]
end
local text = prefs[setting.."Text"] or SI.defaultDB.Indicators[setting.."Text"]
local indicator = prefs[setting.."Indicator"] or SI.defaultDB.Indicators[setting.."Indicator"]
text = ColorCodeOpen(color) .. text .. FONTEND
if text:find("ICON", 1, true) and indicator ~= "BLANK" then
text = text:gsub("ICON", FONTEND .. SI.Indicators[indicator] .. ColorCodeOpen(color))
end
if text:find("KILLED", 1, true) or text:find("TOTAL", 1, true) then
local killed, total
if killoverride then
killed, total = killoverride, totoverride
else
killed, total = SI:instanceBosses(instance,toon,diff)
end
if killed == 0 and total == 0 then -- boss kill info missing
killed = "*"
total = "*"
elseif killed == 1 and total == 1 and not expired then
text = SI.questCheckMark
end
text = text:gsub("KILLED",killed)
text = text:gsub("TOTAL",total)
end
return text
end
-- run about once per session to update our database of instance info
function SI:UpdateInstanceData()
-- SI:Debug("UpdateInstanceData()")
if SI.instancesUpdated then return end -- nil before first use in UI
SI.instancesUpdated = true
local added = 0
local lfdid_to_name = {}
local wbid_to_name = {}
local id_blacklist = {}
local starttime = debugprofilestop()
-- previously we used GetFullRaidList() and LFDDungeonList to help populate the instance list
-- Unfortunately those are loaded lazily, and forcing them to load from here can lead to taint.
-- They are also somewhat incomplete, so instead we just brute force it, which is reasonably fast anyhow
for id=1,maxid do
local instname, newentry, blacklist = SI:UpdateInstance(id)
if newentry then
added = added + 1
end
if blacklist then
id_blacklist[id] = true
end
if instname then
if lfdid_to_name[id] then
SI:Debug("Duplicate entry in lfdid_to_name: "..id..":"..lfdid_to_name[id]..":"..instname)
end
lfdid_to_name[id] = instname
end
end
for eid,info in pairs(SI.WorldBosses) do
info.eid = eid
if not info.name then
info.name = select(2,EJ_GetCreatureInfo(1,eid))
end
info.name = info.name or ("UNKNOWN" .. eid)
local instance = SI.db.Instances[info.name]
if info.remove then -- cleanup hook
SI.db.Instances[info.name] = nil
SI.WorldBosses[eid] = nil
else
if not instance then
added = added + 1
instance = {}
SI.db.Instances[info.name] = instance
end
instance.Show = instance.Show or "saved"
instance.WorldBoss = eid
instance.Expansion = info.expansion
instance.RecLevel = info.level
instance.Raid = true
wbid_to_name[eid] = info.name
end
end
-- instance merging: this algorithm removes duplicate entries created by client locale changes using the same database
-- we really should re-key the database by ID, but this is sufficient for now
local renames = 0
local merges = 0
local conflicts = 0
for instname, inst in pairs(SI.db.Instances) do
local truename
if inst.WorldBoss then
truename = wbid_to_name[inst.WorldBoss]
elseif inst.LFDID then
truename = lfdid_to_name[inst.LFDID]
else
SI:Debug("Ignoring bogus entry in instance database: "..instname)
end
if not truename then
if inst.LFDID and id_blacklist[inst.LFDID] then
SI:Debug("Removing blacklisted entry in instance database: "..instname)
SI.db.Instances[instname] = nil
else
SI:Debug("Ignoring unmatched entry in instance database: "..instname)
end
elseif instname == truename then
-- this is the canonical entry, nothing to do
else -- this is a stale entry, merge data and remove it
local trueinst = SI.db.Instances[truename]
if not trueinst or trueinst == inst then
SI:Debug("Merge error in UpdateInstanceData: "..truename)
else
for key, info in pairs(inst) do
if key:find(" - ") then -- is a character key
if trueinst[key] then
-- merge conflict: keep the trueinst data
SI:Debug("Merge conflict on "..truename..":"..instname..":"..key)
conflicts = conflicts + 1
else
trueinst[key] = info
merges = merges + 1
end
end
end
-- copy config settings, favoring old entry
trueinst.Show = inst.Show or trueinst.Show
-- clear stale entry
SI.db.Instances[instname] = nil
renames = renames + 1
end
end
end
-- SI.lfdid_to_name = lfdid_to_name
-- SI.wbid_to_name = wbid_to_name
Config:BuildOptions() -- refresh config table
starttime = debugprofilestop()-starttime
SI:Debug("UpdateInstanceData(): completed in %.3f ms : %d added, %d renames, %d merges, %d conflicts.",
starttime, added, renames, merges, conflicts)
if SI.RefreshPending then
SI.RefreshPending = nil
SI:Refresh()
end
end
--if LFDParentFrame then hooksecurefunc(LFDParentFrame,"Show",function() SI:UpdateInstanceData() end) end
function SI:UpdateInstance(id)
-- returns: <instance_name>, <is_new_instance>, <blacklisted_id>
-- SI:Debug("UpdateInstance: "..id)
if not id or id <= 0 then return end
local name, typeID, subtypeID,
minLevel, maxLevel, recLevel, minRecLevel, maxRecLevel,
expansionLevel, groupID, textureFilename,
difficulty, maxPlayers, description, isHoliday = GetLFGDungeonInfo(id)
-- name is nil for non-existent ids
-- isHoliday is for single-boss holiday instances that don't generate raid saves
-- typeID 4 = outdoor area, typeID 6 = random
maxPlayers = tonumber(maxPlayers)
if not name or not expansionLevel or not recLevel or (typeID > 2 and typeID ~= TYPEID_RANDOM_DUNGEON) then return end
if name:find(PVP_RATED_BATTLEGROUND) then return nil, nil, true end -- ignore 10v10 rated bg
if id == 1347 then -- ticket 237: Return to Karazhan currently has no actual LFDID, so use this one (Kara Scenario)
name = SPLASH_LEGION_NEW_7_1_RIGHT_TITLE
expansionLevel = 6
recLevel = 110
maxPlayers = 5
isHoliday = false
typeID = TYPEID_DUNGEON
subtypeID = LFG_SUBTYPEID_HEROIC
elseif id == 1911 then -- Caverns of Time - Anniversary: issue #315 (fake LFDID used by Escape from Tol Dagor)
local _
_, typeID, subtypeID, _, _, recLevel, _, _, expansionLevel, _,
_, difficulty, maxPlayers, _, isHoliday, _, _, _, name = GetLFGDungeonInfo(2004)
elseif id == 842 then -- Downfall (#308) different name for origin and solo LFG in deDE
if SI.locale == 'deDE' then
name = "Niedergang"
end
end
if subtypeID == LFG_SUBTYPEID_SCENARIO and typeID ~= TYPEID_RANDOM_DUNGEON then -- ignore non-random scenarios
return nil, nil, true
end
if typeID == 2 and subtypeID == 0 and difficulty == 17 and maxPlayers == 0 then
--print("ignoring "..id, GetLFGDungeonInfo(id))
return nil, nil, true -- ignore bogus LFR entries
end
if typeID == 1 and subtypeID == 5 and difficulty == 14 and maxPlayers == 25 then
--print("ignoring "..id, GetLFGDungeonInfo(id))
return nil, nil, true -- ignore old Flex entries
end
if SI.LFRInstances[id] then -- ensure uniqueness (eg TeS LFR)
local lfrid = SI.db.Instances[name] and SI.db.Instances[name].LFDID
if lfrid and SI.LFRInstances[lfrid] then
SI.db.Instances[name] = nil -- clean old LFR entries
end
SI.db.Instances[L["Flex"]..": "..name] = nil -- clean old flex entries
name = L["LFR"]..": "..name
end
if id == 1966 then -- ignore Arathi Basin Comp Stomp
return nil, nil, true
end
if id == 1661 then -- ignore AI Test - Arathi Basin
return nil, nil, true
end
if id == 1508 then -- ignore AI Test - Warsong Gulch
return nil, nil, true
end
if id == 1428 then -- ignore Shado-Pan Showdown
return nil, nil, true
end
if id == 852 and expansionLevel == 5 then -- XXX: Molten Core hack
return nil, nil, true -- ignore Molten Core holiday version, which has no save
end
if id == 767 then -- ignore bogus Ordos entry
return nil, nil, true
end
if id == 768 then -- ignore bogus Celestials entry
return nil, nil, true
end
local instance = SI.db.Instances[name]
local newinst = false
if not instance then
SI:Debug("UpdateInstance: "..id.." "..(name or "nil").." "..(expansionLevel or "nil").." "..(recLevel or "nil").." "..(maxPlayers or "nil"))
instance = {}
newinst = true
end
SI.db.Instances[name] = instance
instance.Show = instance.Show or "saved"
instance.Encounters = nil -- deprecated
instance.LFDupdated = nil
instance.LFDID = id
instance.Holiday = isHoliday or nil
instance.Expansion = expansionLevel
if not instance.RecLevel or instance.RecLevel < 1 then instance.RecLevel = recLevel end
if recLevel > 0 and recLevel < instance.RecLevel then instance.RecLevel = recLevel end -- favor non-heroic RecLevel
instance.Raid = (maxPlayers > 5 or (maxPlayers == 0 and typeID == 2))
if typeID == TYPEID_RANDOM_DUNGEON then
instance.Random = true
end
if subtypeID == LFG_SUBTYPEID_SCENARIO then
instance.Scenario = true
end
return name, newinst
end
function SI:updateSpellTip(spellID)
local slot
SI.db.spelltip = SI.db.spelltip or {}
SI.db.spelltip[spellID] = SI.db.spelltip[spellID] or {}
for i = 1, 255 do
local id = select(10, UnitAura('player', i, 'HARMFUL'))
if id == spellID then
slot = i
break
end
end
if slot then
SI.ScanTooltip:SetOwner(UIParent, 'ANCHOR_NONE')
SI.ScanTooltip:SetUnitDebuff('player', slot)
SI.ScanTooltip:Show()
for i = 1, SI.ScanTooltip:NumLines() - 1 do
local textLeft = _G[SI.ScanTooltip:GetName() .. 'TextLeft' .. i]
SI.db.spelltip[spellID][i] = textLeft:GetText()
end
end
end
-- run regularly to update lockouts and cached data for this toon
function SI:UpdateToonData()
SI.activeHolidays = SI.activeHolidays or {}
wipe(SI.activeHolidays)
-- blizz internally conflates all the holiday flags, so we have to detect which is really active
for i=1, GetNumRandomDungeons() do
local id, name = GetLFGRandomDungeonInfo(i)
local d = SI.db.Instances[name]
if d and d.Holiday then
-- id used in timewalking item quest, name used later this function
SI.activeHolidays[id] = true
SI.activeHolidays[name] = true
end
end
local nextreset = SI:GetNextDailyResetTime()
for instance, i in pairs(SI.db.Instances) do
for toon, t in pairs(SI.db.Toons) do
if i[toon] then
for difficulty, d in pairs(i[toon]) do
if d.Expires and d.Expires < time() then
d.Locked = false
d.Expires = 0
if d.ID < 0 then
i[toon][difficulty] = nil
end
end
end
end
end
if (i.Holiday and SI.activeHolidays[instance]) or
(i.Random and not i.Holiday) then
local id = i.LFDID
GetLFGDungeonInfo(id) -- forces update
local donetoday, money = GetLFGDungeonRewards(id)
if donetoday and i.Random and (
id == 301 or -- Cata heroic
id == 434 -- Hour of Twilight
) then -- donetoday flag is falsely set for some level/dungeon combos where no daily incentive is available
donetoday = false
end
if nextreset and donetoday and (i.Holiday or (money and money > 0)) then
i[SI.thisToon] = i[SI.thisToon] or {}
i[SI.thisToon][1] = i[SI.thisToon][1] or {}
local d = i[SI.thisToon][1]
d.ID = -1
d.Locked = false
d.Expires = nextreset
end
end
end
-- update random toon info
local t = SI.db.Toons[SI.thisToon]
local now = time()
if SI.logout or SI.PlayedTime or SI.playedpending then
if SI.PlayedTime then
local more = now - SI.PlayedTime
t.PlayedTotal = t.PlayedTotal + more
t.PlayedLevel = t.PlayedLevel + more
SI.PlayedTime = now
end
else
SI.playedpending = true
SI.playedreg = SI.playedreg or {}
wipe(SI.playedreg)
for i=1,10 do
local c = _G["ChatFrame"..i]
if c and c:IsEventRegistered("TIME_PLAYED_MSG") then
c:UnregisterEvent("TIME_PLAYED_MSG") -- prevent spam
SI.playedreg[c] = true
end
end
RequestTimePlayed()
end
t.LFG1 = SI:GetTimeToTime(GetLFGRandomCooldownExpiration()) or t.LFG1
t.LFG2 = SI:GetTimeToTime(SI:GetPlayerAuraExpirationTime(71041)) or t.LFG2 -- GetLFGDeserterExpiration()
if t.LFG2 then SI:updateSpellTip(71041) end
t.pvpdesert = SI:GetTimeToTime(SI:GetPlayerAuraExpirationTime(26013)) or t.pvpdesert
if t.pvpdesert then SI:updateSpellTip(26013) end
for toon, ti in pairs(SI.db.Toons) do
if ti.LFG1 and (ti.LFG1 < now) then ti.LFG1 = nil end
if ti.LFG2 and (ti.LFG2 < now) then ti.LFG2 = nil end
if ti.pvpdesert and (ti.pvpdesert < now) then ti.pvpdesert = nil end
ti.Quests = ti.Quests or {}
end
local IL,ILe,ILPvp = GetAverageItemLevel()
if IL and tonumber(IL) and tonumber(IL) > 0 then -- can fail during logout
t.IL, t.ILe = tonumber(IL), tonumber(ILe)
end
if ILPvp and tonumber(ILPvp) > 0 then
t.ILPvp = tonumber(ILPvp)
end
t.Arena2v2rating = tonumber(GetPersonalRatedInfo(1), 10) or t.Arena2v2rating
t.Arena3v3rating = tonumber(GetPersonalRatedInfo(2), 10) or t.Arena3v3rating
t.RBGrating = tonumber(GetPersonalRatedInfo(4), 10) or t.RBGrating
t.SpecializationIDs = t.SpecializationIDs or {}
for i = 1, GetNumSpecializations() do
t.SpecializationIDs[i] = GetSpecializationInfo(i) or t.SpecializationIDs[i]
end
-- Solo Shuffle rating is unique to each specialization
t.SoloShuffleRating = t.SoloShuffleRating or {}
local currentSpecID = GetSpecialization()
if currentSpecID then
t.SoloShuffleRating[currentSpecID] = GetPersonalRatedInfo(7) or t.SoloShuffleRating[currentSpecID]
end
TradeSkill:ScanItemCDs()
-- Daily Reset
if nextreset and nextreset > time() then
for toon, ti in pairs(SI.db.Toons) do
if not ti.DailyResetTime or (ti.DailyResetTime < time()) then
for id,qi in pairs(ti.Quests) do
if qi.isDaily then
ti.Quests[id] = nil
end
end
Progress:OnDailyReset(toon)
ti.DailyResetTime = (ti.DailyResetTime and ti.DailyResetTime + 24*3600) or nextreset
end
end
Calling:OnDailyReset()
t.DailyResetTime = nextreset
if not db.DailyResetTime or (db.DailyResetTime < time()) then -- AccountDaily reset
for id,qi in pairs(db.Quests) do
if qi.isDaily then
db.Quests[id] = nil
end
end
-- Emissary Quest Reset
if SI.db.Emissary and SI.db.Emissary.Expansion then
local expansionLevel, tbl
for expansionLevel, tbl in pairs(SI.db.Emissary.Expansion) do
while tbl[1] and tbl[1].expiredTime < time() do
tbl[1] = tbl[2]
tbl[2] = tbl[3]
tbl[3] = nil
for toon, ti in pairs(SI.db.Toons) do
if ti.Emissary then
local t = ti.Emissary[expansionLevel]
if t and t.unlocked then
t.days[1] = t.days[2]
t.days[2] = t.days[3]
t.days[3] = {
isComplete = false,
isFinish = false,
questDone = 0,
}
end
end
end
end
end
end
db.DailyResetTime = nextreset
end
end
-- Skill Reset
for toon, ti in pairs(SI.db.Toons) do
if ti.Skills then
for spellid, sinfo in pairs(ti.Skills) do
if sinfo.Expires and sinfo.Expires < time() then
ti.Skills[spellid] = nil
end
end
end
end
-- Weekly Reset
nextreset = SI:GetNextWeeklyResetTime()
if nextreset and nextreset > time() then
for toon, ti in pairs(SI.db.Toons) do
if not ti.WeeklyResetTime or (ti.WeeklyResetTime < time()) then
ti.currency = ti.currency or {}
for _,idx in ipairs(currency) do
local ci = ti.currency[idx]
if ci and ci.earnedThisWeek then
ci.earnedThisWeek = 0
end
end
Progress:OnWeeklyReset(toon)
ti.WeeklyResetTime = (ti.WeeklyResetTime and ti.WeeklyResetTime + 7*24*3600) or nextreset
end
end
t.WeeklyResetTime = nextreset
end
for toon, ti in pairs(SI.db.Toons) do
for id,qi in pairs(ti.Quests) do
if not qi.isDaily and (qi.Expires or 0) < time() then
ti.Quests[id] = nil
end
if QuestExceptions[id] == "Regular" then -- adjust exceptions
ti.Quests[id] = nil
end
end
end
for toon, ti in pairs(SI.db.Toons) do
if ti.MythicKey and (ti.MythicKey.ResetTime or 0) < time() then
ti.MythicKey = {}
end
end
for toon, ti in pairs(SI.db.Toons) do
if ti.TimewornMythicKey and (ti.TimewornMythicKey.ResetTime or 0) < time() then
ti.TimewornMythicKey = {}
end
end
for toon, ti in pairs(SI.db.Toons) do
if ti.MythicKeyBest and (ti.MythicKeyBest.ResetTime or 0) < time() then
ti.MythicKeyBest.rewardWaiting = ti.MythicKeyBest.lastCompletedIndex and ti.MythicKeyBest.lastCompletedIndex > 0
ti.MythicKeyBest[1] = nil
ti.MythicKeyBest[2] = nil
ti.MythicKeyBest[3] = nil
ti.MythicKeyBest.lastCompletedIndex = nil
ti.MythicKeyBest.runHistory = nil
ti.MythicKeyBest.ResetTime = SI:GetNextWeeklyResetTime()
end
end
for id,qi in pairs(db.Quests) do -- AccountWeekly reset
if not qi.isDaily and (qi.Expires or 0) < time() then
db.Quests[id] = nil
end
end
Calling:PostRefresh()
Currency:UpdateCurrency()
local zone = GetRealZoneText()
if zone and #zone > 0 then
t.Zone = zone
end
t.Level = UnitLevel("player")
local lrace, race = UnitRace("player")
local faction, lfaction = UnitFactionGroup("player")
t.Faction = faction
t.oRace = race
if race == "Pandaren" then
t.Race = lrace.." ("..lfaction..")"
else
t.Race = lrace
end
if not SI.logout then
t.isResting = IsResting()
t.MaxXP = UnitXPMax("player")
if t.Level < SI.maxLevel then
t.XP = UnitXP("player")
t.RestXP = GetXPExhaustion()
else
t.XP = nil
t.RestXP = nil
end
t.Warmode = C_PvP.IsWarModeDesired()
t.Covenant = C_Covenants.GetActiveCovenantID()
t.MythicPlusScore = C_ChallengeMode.GetOverallDungeonScore()
end
t.LastSeen = time()
end
function SI:QuestIsDarkmoonMonthly()
if QuestIsDaily() then return false end
local id = GetQuestID()
local scope = id and QuestExceptions[id]
if scope and scope ~= "Darkmoon" then return false end -- one-time referral quests
for i=1,GetNumRewardCurrencies() do
local name,texture,amount = GetQuestCurrencyInfo("reward",i)
if texture == 134481 then
return true
end
end
return false
end
local function SI_GetQuestReward()
local t = SI and SI.db.Toons[SI.thisToon]
if not t then return end
local id = GetQuestID() or -1
local title = GetTitleText() or ""
local isMonthly = SI:QuestIsDarkmoonMonthly()
local isWeekly = QuestIsWeekly()
local isDaily = QuestIsDaily()
local isAccount = C_QuestLog.IsAccountQuest(id)
local link = GetQuestLink(id)
if id > 1 then -- try harder to fetch names
local t,l = SI:QuestInfo(id)
if not (link and #link > 0) then
link = l
end
if not (title and #title > 0) then
title = t or "<unknown>"
end
end
if QuestExceptions[id] then
local qe = QuestExceptions[id]
isAccount = qe:find("Account") and true
isDaily = qe:find("Daily") and true
isWeekly = qe:find("Weekly") and true
isMonthly = qe:find("Darkmoon") and true
end
local expires
local questDB
if isWeekly then
expires = SI:GetNextWeeklyResetTime()
questDB = (isAccount and db.QuestDB.AccountWeekly) or db.QuestDB.Weekly
elseif isMonthly then
expires = SI:GetNextDarkmoonResetTime()
questDB = db.QuestDB.Darkmoon
elseif isDaily then
questDB = (isAccount and db.QuestDB.AccountDaily) or db.QuestDB.Daily
end
SI:Debug("Quest Complete: "..(link or title).." "..id.." : "..title.." "..
(isAccount and "(Account) " or "")..
(isMonthly and "(Monthly)" or isWeekly and "(Weekly)" or isDaily and "(Daily)" or "(Regular)").." "..
(expires and date("%c",expires) or ""))
if not isMonthly and not isWeekly and not isDaily then return end
local mapid = SI:GetCurrentMapAreaID()
questDB[id] = mapid
local qinfo = { ["Title"] = title, ["Link"] = link,
["isDaily"] = isDaily,
["Expires"] = expires,
["Zone"] = C_Map.GetMapInfo(mapid) }
local scope = t
if isAccount then
scope = db
if t.Quests then t.Quests[id] = nil end -- make sure we promote account quests
end
scope.Quests = scope.Quests or {}
scope.Quests[id] = qinfo
local dc, wc = SI:QuestCount(SI.thisToon)
local adc, awc = SI:QuestCount(nil)
SI:Debug("DailyCount: "..dc.." WeeklyCount: "..wc.." AccountDailyCount: "..adc.." AccountWeeklyCount: "..awc)
end
hooksecurefunc("GetQuestReward", SI_GetQuestReward)
local function coloredText(fontstring)
if not fontstring then return nil end
local text = fontstring:GetText()
if not text then return nil end
local textR, textG, textB, textAlpha = fontstring:GetTextColor()
return string.format("|c%02x%02x%02x%02x"..text.."|r",
textAlpha*255, textR*255, textG*255, textB*255)
end
-- Hover Tooltips
local hoverTooltip = {}
SI.hoverTooltip = hoverTooltip
hoverTooltip.ShowToonTooltip = function (cell, arg, ...)
local toon = arg
if not toon then return end
local t = SI.db.Toons[toon]
if not t then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
local ftex = ""
if t.Faction == "Alliance" then
ftex = "\124TInterface\\TargetingFrame\\UI-PVP-Alliance:0:0:0:0:100:100:0:50:0:55\124t "
elseif t.Faction == "Horde" then
ftex = "\124TInterface\\TargetingFrame\\UI-PVP-Horde:0:0:0:0:100:100:10:70:0:55\124t"
end
indicatortip:SetCell(indicatortip:AddHeader(),1,ftex..ClassColorise(t.Class, toon))
indicatortip:SetCell(1,2,ClassColorise(t.Class, LEVEL.." "..t.Level.." "..(t.LClass or "")))
if t.Level < SI.maxLevel and t.XP then
local restXP = (t.RestXP or 0) + (t.MaxXP / 20) * ((time() - t.LastSeen) / (3600 * (t.isResting and 8 or 32)))
local percent = min(floor(restXP / t.MaxXP * 100), 150) * (t.oRace == "Pandaren" and 2 or 1)
indicatortip:AddLine(COMBAT_XP_GAIN, format("%.0f%% + %.0f%%", t.XP / t.MaxXP * 100, percent))
end
indicatortip:AddLine(STAT_AVERAGE_ITEM_LEVEL,("%d "):format(t.IL or 0)..STAT_AVERAGE_ITEM_LEVEL_EQUIPPED:format(t.ILe or 0))
indicatortip:AddLine(LFG_LIST_ITEM_LEVEL_INSTR_PVP_SHORT,("%d"):format(t.ILPvp or 0))
if t.Covenant and t.Covenant > 0 then
local data = C_Covenants.GetCovenantData(t.Covenant)
local name = data and data.name
if name then
indicatortip:AddLine(L["Covenant"], name)
end
end
if t.MythicPlusScore and t.MythicPlusScore > 0 then
indicatortip:AddLine(DUNGEON_SCORE, t.MythicPlusScore)
end
if t.Arena2v2rating and t.Arena2v2rating > 0 then
indicatortip:AddLine(ARENA_2V2 .. ARENA_RATING, t.Arena2v2rating)
end
if t.Arena3v3rating and t.Arena3v3rating > 0 then
indicatortip:AddLine(ARENA_3V3 .. ARENA_RATING, t.Arena3v3rating)
end
if t.RBGrating and t.RBGrating > 0 then
indicatortip:AddLine(BG_RATING_ABBR, t.RBGrating)
end
if t.SoloShuffleRating and t.SpecializationIDs then
for i, specID in ipairs(t.SpecializationIDs) do
if t.SoloShuffleRating[i] and t.SoloShuffleRating[i] > 0 then
local _, specName = GetSpecializationInfoForSpecID(specID)
indicatortip:AddLine(PVP_RATED_SOLO_SHUFFLE .. " " .. RATING .. ": " .. specName, t.SoloShuffleRating[i])
end
end
end
if t.Money then
indicatortip:AddLine(MONEY,SI:formatNumber(t.Money,true))
end
if t.Warmode and t.Warmode == true then
indicatortip:AddLine(PVP_LABEL_WAR_MODE, PVP_WAR_MODE_ENABLED)
end
if t.Zone then
indicatortip:AddLine(ZONE,t.Zone)
end
--[[
if t.Race then
indicatortip:AddLine(RACE,t.Race)
end
]]
if t.LastSeen then
local when = date("%c",t.LastSeen)
indicatortip:AddLine(L["Last updated"],when)
end
if SI.db.Tooltip.TrackPlayed and t.PlayedTotal and t.PlayedLevel and ChatFrame_TimeBreakDown then
--indicatortip:AddLine((TIME_PLAYED_TOTAL):format((TIME_DAYHOURMINUTESECOND):format(ChatFrame_TimeBreakDown(t.PlayedTotal))))
--indicatortip:AddLine((TIME_PLAYED_LEVEL):format((TIME_DAYHOURMINUTESECOND):format(ChatFrame_TimeBreakDown(t.PlayedLevel))))
indicatortip:AddLine((TIME_PLAYED_TOTAL):format(""),SecondsToTime(t.PlayedTotal))
indicatortip:AddLine((TIME_PLAYED_LEVEL):format(""),SecondsToTime(t.PlayedLevel))
end
indicatortip:Show()
end
hoverTooltip.ShowQuestTooltip = function (cell, arg, ...)
local toon,cnt,isDaily = unpack(arg)
local qstr = cnt.." "..(isDaily and L["Daily Quests"] or L["Weekly Quests"])
local t = db
local scopestr = L["Account"]
local reset
if toon then
t = SI.db.Toons[toon]
if not t then return end
scopestr = ClassColorise(t.Class, toon)
reset = (isDaily and t.DailyResetTime) or (not isDaily and t.WeeklyResetTime)
end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
indicatortip:AddHeader(scopestr, qstr)
if not reset then
reset = (isDaily and SI:GetNextDailyResetTime()) or (not isDaily and SI:GetNextWeeklyResetTime())
end
if reset then
indicatortip:AddLine(YELLOWFONT .. L["Time Left"] .. ":" .. FONTEND,
SecondsToTime(reset - time()))
end
local ql = {}
local zonename, id
for id,qi in pairs(t.Quests) do
if (not isDaily) == (not qi.isDaily) then
if not SI:QuestIgnored(id) then
zonename = qi.Zone and qi.Zone.name or ""
table.insert(ql,zonename.." # "..id)
end
end
end
table.sort(ql)
for _,e in ipairs(ql) do
zonename, id = e:match("(.*) # (%d+)")
id = tonumber(id)
local qi = t.Quests[id]
local line = indicatortip:AddLine()
local link = qi.Link
if not link then -- sometimes missing the actual link due to races, fake it for display to prevent confusion
if qi.Title and qi.Title:find("("..LOOT..")") then
link = qi.Title
else
link = "\124cffffff00["..(qi.Title or "???").."]\124r"
end
end
-- Exception: Some quests should not show zone name, such as Blingtron
if (id == 31752 or id == 34774 or id == 40753 or id == 56042) then
zonename = ""
end
indicatortip:SetCell(line,1,zonename,"LEFT")
indicatortip:SetCell(line,2,link,"RIGHT")
end
indicatortip:Show()
end
hoverTooltip.ShowSkillTooltip = function (cell, arg, ...)
local toon, cnt = unpack(arg)
local cstr = cnt.." "..L["Trade Skill Cooldowns"]
local t = SI.db.Toons[toon]
if not t then return end
local indicatortip = Tooltip:AcquireIndicatorTip(3, "LEFT","RIGHT","RIGHT")
local tname = ClassColorise(t.Class, toon)
indicatortip:AddHeader()
indicatortip:SetCell(1,1,tname,"LEFT")
indicatortip:SetCell(1,2,cstr,"RIGHT",2)
local tmp = {}
for _,sinfo in pairs(t.Skills) do
table.insert(tmp,sinfo)
end
table.sort(tmp, function (s1, s2)
if s1.Expires ~= s2.Expires then
return (s1.Expires or 0) < (s2.Expires or 0)
else
return (s1.Title or "") < (s2.Title or "")
end
end)
for _,sinfo in ipairs(tmp) do
local line = indicatortip:AddLine()
local title = sinfo.Link or sinfo.Title or "???"
local tstr = SecondsToTime((sinfo.Expires or 0) - time())
indicatortip:SetCell(line,1,title,"LEFT",2)
indicatortip:SetCell(line,3,tstr,"RIGHT")
end
indicatortip:Show()
end
hoverTooltip.ShowEmissarySummary = function (cell, arg, ...)
local expansionLevel, days = unpack(arg)
local day
local first = true
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
for _, day in pairs(days) do
if first == false then
indicatortip:AddSeparator(6,0,0,0,0)
end
first = false
indicatortip:AddHeader(L["Emissary quests"], "+" .. (day - 1) .. " " .. L["Day"])
local tbl = {}
local toon, t
for toon, t in pairs(SI.db.Toons) do
local info = (
t.Emissary and t.Emissary[expansionLevel] and
t.Emissary[expansionLevel].days and t.Emissary[expansionLevel].days[day]
)
if info then
tbl[t.Faction] = true
end
end
if (not tbl.Alliance and not tbl.Horde) or (not SI.db.Emissary.Expansion[expansionLevel][day]) then
indicatortip:AddLine(L["Emissary Missing"], "")
else
local globalInfo = SI.db.Emissary.Expansion[expansionLevel][day]
local merge = (globalInfo.questID.Alliance == globalInfo.questID.Horde) and true or false
local header = false
for fac, _ in pairs(tbl) do
if merge == false then header = false end
for toon, t in pairs(SI.db.Toons) do
if t.Faction == fac then
local info = (
t.Emissary and t.Emissary[expansionLevel] and
t.Emissary[expansionLevel].days and t.Emissary[expansionLevel].days[day]
)
if info then
if header == false then
local name = SI.db.Emissary.Cache[globalInfo.questID[fac]]
if not name then
name = L["Emissary Missing"]
end
indicatortip:AddLine(name)
header = true
end
local text
if info.isComplete == true then
text = SI.questCheckMark
elseif info.isFinish == true then
text = SI.questTurnin
else
text = info.questDone
if globalInfo.questNeed then
text = text .. "/" .. globalInfo.questNeed
end
end
indicatortip:AddLine(ClassColorise(t.Class, toon), text)
end
end
end
end
end
end
indicatortip:Show()
end
hoverTooltip.ShowEmissaryTooltip = function (cell, arg, ...)
local expansionLevel, day, toon = unpack(arg)
local info = db.Toons[toon].Emissary[expansionLevel].days[day]
if not info then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
local globalInfo = SI.db.Emissary.Expansion[expansionLevel][day] or {}
local text
if info.isComplete == true then
text = SI.questCheckMark
elseif info.isFinish == true then
text = SI.questTurnin
else
text = info.questDone
if globalInfo.questNeed then
text = text .. "/" .. globalInfo.questNeed
end
end
indicatortip:AddLine(ClassColorise(db.Toons[toon].Class, toon), text)
text = (
globalInfo.questID and db.Emissary.Cache[globalInfo.questID[db.Toons[toon].Faction]]
) or L["Emissary Missing"]
indicatortip:AddLine()
indicatortip:SetCell(2, 1, text, "LEFT", 2)
if info.questReward then
text = ""
if info.questReward.itemName then
text = "|c" .. select(4, GetItemQualityColor(info.questReward.quality)) ..
"[" .. info.questReward.itemName .. "(" .. info.questReward.itemLvl .. ")]" .. FONTEND
elseif info.questReward.money then
text = GetMoneyString(info.questReward.money)
elseif info.questReward.currencyID then
local data = C_CurrencyInfo.GetCurrencyInfo(info.questReward.currencyID)
local iconID = Currency.OverrideTexture[info.questReward.currencyID] or data.iconFileID
text = "\124T" .. iconID .. ":0\124t " .. info.questReward.quantity
end
indicatortip:AddLine()
indicatortip:SetCell(3, 1, text, "RIGHT", 2)
end
indicatortip:Show()
end
hoverTooltip.ShowCallingTooltip = function (cell, arg, ...)
local day, toon = unpack(arg)
local info = db.Toons[toon].Calling[day]
if not info then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
local text
if info.isCompleted == true then
text = SI.questCheckMark
elseif not info.isOnQuest then
text = SI.questNormal
elseif info.isFinished == true then
text = SI.questTurnin
else
if info.objectiveType == 'progressbar' then
text = floor(info.questDone / info.questNeed * 100) .. "%"
else
text = info.questDone .. '/' .. info.questNeed
end
end
indicatortip:AddLine(ClassColorise(db.Toons[toon].Class, toon), text)
indicatortip:AddLine()
text = info.title
if not text then
for _, t in pairs(SI.db.Toons) do
if t.Calling and t.Calling[day] and t.Calling[day].title then
text = t.Calling[day].title
break
end
end
end
indicatortip:SetCell(2, 1, text or L["Calling Missing"], "LEFT", 2)
if info.questReward and info.questReward.itemName then
text = "|c" .. select(4, GetItemQualityColor(info.questReward.quality)) ..
"[" .. info.questReward.itemName .. "]" .. FONTEND
indicatortip:AddLine()
indicatortip:SetCell(3, 1, text, "RIGHT", 2)
end
indicatortip:Show()
end
hoverTooltip.ShowParagonTooltip = function (cell, arg, ...)
local toon = arg
local t = SI.db.Toons[toon]
if not t or not t.Paragon then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
indicatortip:AddHeader(ClassColorise(t.Class, toon), #t.Paragon)
for k, v in pairs(t.Paragon) do
local name = GetFactionInfoByID(v)
indicatortip:AddLine()
indicatortip:SetCell(k + 1, 1, name, "RIGHT", 2)
end
indicatortip:Show()
end
hoverTooltip.ShowMythicPlusTooltip = function (cell, arg, ...)
local toon, keydesc = unpack(arg)
local t = SI.db.Toons[toon]
if not t or not t.MythicKeyBest then
return
end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
local text = keydesc or ""
indicatortip:AddHeader(ClassColorise(t.Class, toon), text)
if t.MythicKeyBest.runHistory and #t.MythicKeyBest.runHistory > 0 then
local maxThreshold = t.MythicKeyBest.threshold and t.MythicKeyBest.threshold[#t.MythicKeyBest.threshold]
local displayNumber = min(#t.MythicKeyBest.runHistory, maxThreshold or 8)
indicatortip:AddLine()
indicatortip:SetCell(2, 1, format(WEEKLY_REWARDS_MYTHIC_TOP_RUNS, displayNumber), "LEFT", 2)
indicatortip:AddLine()
indicatortip:SetCell(3, 1, format(TOTAL_STACKS, #t.MythicKeyBest.runHistory), "LEFT", 2)
for i = 1, displayNumber do
local runInfo = t.MythicKeyBest.runHistory[i]
if runInfo.level and runInfo.name and runInfo.rewardLevel then
indicatortip:AddLine()
text = string.format("(%3$d) %1$d - %2$s", runInfo.level, runInfo.name, runInfo.rewardLevel)
-- these are the thresholds that will populate the great vault
if t.MythicKeyBest.threshold and tContains(t.MythicKeyBest.threshold, i) then
text = GREENFONT..text..FONTEND
end
indicatortip:SetCell(2 + i, 1, text, "LEFT", 2)
end
end
end
indicatortip:Show()
end
hoverTooltip.ShowBonusTooltip = function (cell, arg, ...)
local toon = arg
local parent
if type(toon) == "table" then
toon, parent = unpack(toon)
end
local t = SI.db.Toons[toon]
if not t or not t.BonusRoll then return end
local indicatortip = Tooltip:AcquireIndicatorTip(4, "LEFT","LEFT","LEFT","LEFT")
if parent then
indicatortip:SetAutoHideDelay(0.1, parent)
indicatortip:SmartAnchorTo(parent)
end
local tname = ClassColorise(t.Class, toon)
indicatortip:AddHeader()
indicatortip:SetCell(1,1,tname,"LEFT",2)
indicatortip:SetCell(1,3,L["Recent Bonus Rolls"],"RIGHT",2)
local line = indicatortip:AddLine()
for i,roll in ipairs(t.BonusRoll) do
if i > 10 then break end
local line = indicatortip:AddLine()
local icon = roll.costCurrencyID and (Currency.OverrideTexture[roll.costCurrencyID] or C_CurrencyInfo.GetCurrencyInfo(roll.costCurrencyID).iconFileID)
if icon then
indicatortip:SetCell(line,1, " \124T"..icon..":0\124t ")
end
if roll.name then
indicatortip:SetCell(line,2,roll.name)
end
if roll.item then
indicatortip:SetCell(line,3,roll.item)
elseif roll.currencyID then
local data = C_CurrencyInfo.GetCurrencyInfo(roll.currencyID)
local currencyIcon = Currency.OverrideTexture[roll.currencyID] or data.iconFileID
local str = "\124T" .. currencyIcon .. ":0\124t "
if roll.money then
str = str .. roll.money
else
str = str .. data.name
end
indicatortip:SetCell(line,3,str)
elseif roll.money then
indicatortip:SetCell(line,3,GetMoneyString(roll.money))
end
if roll.time then
indicatortip:SetCell(line,4,date("%b %d %H:%M",roll.time))
end
end
indicatortip:Show()
end
hoverTooltip.ShowAccountSummary = function (cell, arg, ...)
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
indicatortip:SetCell(indicatortip:AddHeader(),1,GOLDFONT..L["Account Summary"]..FONTEND,"LEFT",2)
local tmoney = 0
local ttime = 0
local ttoons = 0
local tmaxtoons = 0
local r = {}
for toon, t in pairs(SI.db.Toons) do -- deliberately include ALL toons
local realm = toon:match(" %- (.+)$")
local money = t.Money or 0
tmoney = tmoney + money
local ri = r[realm] or { ["realm"] = realm, ["money"] = 0, ["cnt"] = 0 }
ri.money = ri.money + money
ri.cnt = ri.cnt + 1
r[realm] = ri
ttime = ttime + (t.PlayedTotal or 0)
ttoons = ttoons + 1
if t.Level == SI.maxLevel then
tmaxtoons = tmaxtoons + 1
end
end
indicatortip:AddLine(L["Characters"], ttoons)
indicatortip:AddLine(string.format(L["Level %d Characters"], SI.maxLevel), tmaxtoons)
if SI.db.Tooltip.TrackPlayed then
indicatortip:AddLine((TIME_PLAYED_TOTAL):format(""),SecondsToTime(ttime))
end
indicatortip:AddLine(TOTAL.." "..MONEY,SI:formatNumber(tmoney,true))
local rmoney = {}
for _,ri in pairs(r) do table.insert(rmoney,ri) end
table.sort(rmoney,function(a,b) return a.money > b.money end)
for _,ri in ipairs(rmoney) do
if ri.money > 10000*10000 then -- show servers with over 10k wealth
indicatortip:AddLine(ri.realm.." "..MONEY,SI:formatNumber(ri.money,true))
end
end
-- history information
indicatortip:AddLine("")
SI:HistoryUpdate()
local tmp = {}
local cnt = 0
for _,ii in pairs(db.History) do
table.insert(tmp,ii)
end
cnt = #tmp
table.sort(tmp, function(i1,i2) return i1.last < i2.last end)
indicatortip:SetCell(indicatortip:AddHeader(),1,GOLDFONT..cnt.." "..L["Recent Instances"]..": "..FONTEND,"LEFT",2)
for _,ii in ipairs(tmp) do
local tstr = REDFONT..SecondsToTime(ii.last+SI.histReapTime - time(),false,false,1)..FONTEND
indicatortip:AddLine(tstr, ii.desc)
end
indicatortip:AddLine("")
indicatortip:SetCell(indicatortip:AddLine(),1,
string.format(L["These are the instances that count towards the %i instances per hour account limit, and the time until they expire."],
SI.histLimit),"LEFT",2,nil,nil,nil,250)
indicatortip:AddLine("")
indicatortip:SetCell(indicatortip:AddLine(), 1, L["|cffffff00Click|r to open weekly rewards"], "LEFT", indicatortip:GetColumnCount())
indicatortip:Show()
end
hoverTooltip.ShowWorldBossTooltip = function (cell, arg, ...)
local worldbosses = arg[1]
local toon = arg[2]
local saved = arg[3]
if not worldbosses or not toon then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
local line = indicatortip:AddHeader()
local toonstr = (db.Tooltip.ShowServer and toon) or strsplit(' ', toon)
local t = SI.db.Toons[toon]
local reset = t.WeeklyResetTime or SI:GetNextWeeklyResetTime()
indicatortip:SetCell(line, 1, ClassColorise(SI.db.Toons[toon].Class, toonstr), indicatortip:GetHeaderFont(), "LEFT")
indicatortip:SetCell(line, 2, GOLDFONT .. L["World Bosses"] .. FONTEND, indicatortip:GetHeaderFont(), "RIGHT")
indicatortip:AddLine(YELLOWFONT .. L["Time Left"] .. ":" .. FONTEND, SecondsToTime(reset - time()))
for _, instance in ipairs(worldbosses) do
local thisinstance = SI.db.Instances[instance]
if thisinstance then
local info = thisinstance[toon] and thisinstance[toon][2]
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, instance, "LEFT")
if info and info[1] then
indicatortip:SetCell(n, 2, REDFONT..ALREADY_LOOTED..FONTEND, "RIGHT")
else
indicatortip:SetCell(n, 2, GREENFONT..AVAILABLE..FONTEND, "RIGHT")
end
end
end
indicatortip:Show()
end
hoverTooltip.ShowLFRTooltip = function (cell, arg, ...)
local boxname, toon, tbl = unpack(arg)
local t = SI.db.Toons[toon]
if not boxname or not t or not tbl then return end
local indicatortip = Tooltip:AcquireIndicatorTip(3, "LEFT", "LEFT","RIGHT")
local line = indicatortip:AddHeader()
local toonstr = (db.Tooltip.ShowServer and toon) or strsplit(' ', toon)
local reset = t.WeeklyResetTime or SI:GetNextWeeklyResetTime()
indicatortip:SetCell(line, 1, ClassColorise(SI.db.Toons[toon].Class, toonstr), indicatortip:GetHeaderFont(), "LEFT", 1)
indicatortip:SetCell(line, 2, GOLDFONT .. boxname .. FONTEND, indicatortip:GetHeaderFont(), "RIGHT", 2)
indicatortip:AddLine(YELLOWFONT .. L["Time Left"] .. ":" .. FONTEND, nil, SecondsToTime(reset - time()))
for i=1,20 do
local instance = tbl[i]
local diff = 2
if instance then
indicatortip:SetCell(indicatortip:AddLine(), 1, YELLOWFONT .. instance .. FONTEND, "CENTER",3)
local thisinstance = SI.db.Instances[instance]
local info = thisinstance[toon] and thisinstance[toon][diff]
local killed, total, base, remap, origin = SI:instanceBosses(instance,toon,diff)
for i=base,base+total-1 do
local bossid = i
if remap then
bossid = remap[i-base+1]
end
local bossname = GetLFGDungeonEncounterInfo(thisinstance.LFDID, bossid)
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, bossname, "LEFT", 2)
-- for LFRs that are different between two factions
-- https://github.com/SavedInstances/SavedInstances/pull/238
if info and info[origin and origin[i-base+1] or bossid] then
indicatortip:SetCell(n, 3, REDFONT..ALREADY_LOOTED..FONTEND, "RIGHT", 1)
else
indicatortip:SetCell(n, 3, GREENFONT..AVAILABLE..FONTEND, "RIGHT", 1)
end
end
end
end
indicatortip:Show()
end
hoverTooltip.ShowIndicatorTooltip = function (cell, arg, ...)
local instance = arg[1]
local toon = arg[2]
local diff = arg[3]
if not instance or not toon or not diff then return end
local indicatortip = Tooltip:AcquireIndicatorTip(3, "LEFT", "LEFT","RIGHT")
local thisinstance = SI.db.Instances[instance]
local worldboss = thisinstance and thisinstance.WorldBoss
local info = thisinstance[toon][diff]
if not info then return end
local id = info.ID or 0
local nameline = indicatortip:AddHeader()
indicatortip:SetCell(nameline, 1, DifficultyString(instance, diff, toon), indicatortip:GetHeaderFont(), "LEFT", 1)
indicatortip:SetCell(nameline, 2, GOLDFONT .. instance .. FONTEND, indicatortip:GetHeaderFont(), "RIGHT", 2)
local toonline = indicatortip:AddHeader()
local toonstr = (db.Tooltip.ShowServer and toon) or strsplit(' ', toon)
indicatortip:SetCell(toonline, 1, ClassColorise(SI.db.Toons[toon].Class, toonstr), indicatortip:GetHeaderFont(), "LEFT", 1)
indicatortip:SetCell(toonline, 2, SI:idtext(thisinstance,diff,info), "RIGHT", 2)
local EMPH = " !!! "
if info.Extended then
indicatortip:SetCell(indicatortip:AddLine(),1,WHITEFONT .. EMPH .. L["Extended Lockout - Not yet saved"] .. EMPH .. FONTEND,"CENTER",3)
elseif info.Locked == false and id > 0 then
indicatortip:SetCell(indicatortip:AddLine(),1,WHITEFONT .. EMPH .. L["Expired Lockout - Can be extended"] .. EMPH .. FONTEND,"CENTER",3)
end
if info.Expires > 0 then
indicatortip:AddLine(YELLOWFONT .. L["Time Left"] .. ":" .. FONTEND, nil, SecondsToTime(thisinstance[toon][diff].Expires - time()))
end
if id > 0 and (
(thisinstance.Raid and (diff == 5 or diff == 6 or diff == 16)) -- raid: 10 heroic, 25 heroic or mythic
or
(diff == 23) -- mythic 5-man
) then
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, YELLOWFONT .. ID .. ":" .. FONTEND, "LEFT", 1)
indicatortip:SetCell(n, 2, id, "RIGHT", 2)
end
if info.Link then
local link = info.Link
if thisinstance.LFDID == 1944 then
-- Battle of Dazar'alor
-- https://github.com/SavedInstances/SavedInstances/issues/233
local locFaction = UnitFactionGroup("player")
if db.Toons[toon].Faction ~= locFaction then
local bits = tonumber(link:match(":(%d+)\124h"))
if db.Toons[toon].Faction == "Alliance" then
bits = bit.band(bits, 0x3134D)
if bit.band(bits, 0x1) > 0 then -- Grong the Revenant (Alliance)
bits = bit.bor(bits, 0x2)
end
if bit.band(bits, 0x4) > 0 then -- Jadefire Masters (Alliance)
bits = bit.bor(bits, 0x10)
end
else
bits = bit.band(bits, 0x3135A)
if bit.band(bits, 0x2) > 0 then -- Grong, the Jungle Lord (Horde)
bits = bit.bor(bits, 0x1)
end
if bit.band(bits, 0x10) > 0 then -- Jadefire Masters (Horde)
bits = bit.bor(bits, 0x4)
end
end
link = "\124cffff8000\124Hinstancelock:Player-0000-00000000:2070:"
.. diff .. ":" .. bits .. "\124h[Battle of Dazar'alor]\124h\124r"
end
end
SI.ScanTooltip:SetOwner(UIParent, 'ANCHOR_NONE')
SI.ScanTooltip:SetHyperlink(link)
SI.ScanTooltip:Show()
local name = SI.ScanTooltip:GetName()
local gotbossinfo
for i=2,SI.ScanTooltip:NumLines() do
local left,right = _G[name.."TextLeft"..i], _G[name.."TextRight"..i]
if right and right:GetText() then
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, coloredText(left), "LEFT", 2)
indicatortip:SetCell(n, 3, coloredText(right), "RIGHT", 1)
gotbossinfo = true
else
indicatortip:SetCell(indicatortip:AddLine(),1,coloredText(left),"CENTER",3)
end
end
if not gotbossinfo then
local exc = SI:instanceException(thisinstance.LFDID)
local bits = tonumber(link:match(":(%d+)\124h"))
if exc and bits then
for i=1,exc.total do
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, exc[i], "LEFT", 2)
local text = "\124cff00ff00"..BOSS_ALIVE.."\124r"
if bit.band(bits,1) > 0 then
text = "\124cffff1f1f"..BOSS_DEAD.."\124r"
end
indicatortip:SetCell(n, 3, text, "RIGHT", 1)
bits = bit.rshift(bits,1)
end
else
indicatortip:SetCell(indicatortip:AddLine(),1,WHITEFONT ..
L["Boss kill information is missing for this lockout.\nThis is a Blizzard bug affecting certain old raids."] ..
FONTEND,"CENTER",3)
end
end
end
if id < 0 then
local killed, total, base, remap = SI:instanceBosses(instance,toon,diff)
for i=base,base+total-1 do
local bossid = i
if remap then
bossid = remap[i-base+1]
end
local bossname
if worldboss then
bossname = SI.WorldBosses[worldboss].name or "UNKNOWN"
else
bossname = GetLFGDungeonEncounterInfo(thisinstance.LFDID, bossid)
end
local n = indicatortip:AddLine()
indicatortip:SetCell(n, 1, bossname, "LEFT", 2)
if info[bossid] then
indicatortip:SetCell(n, 3, REDFONT..ALREADY_LOOTED..FONTEND, "RIGHT", 1)
else
indicatortip:SetCell(n, 3, GREENFONT..AVAILABLE..FONTEND, "RIGHT", 1)
end
end
end
indicatortip:Show()
end
hoverTooltip.ShowSpellIDTooltip = function (cell, arg, ...)
local toon, spellid, timestr = unpack(arg)
if not toon or not spellid or not timestr then return end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
indicatortip:AddHeader(ClassColorise(SI.db.Toons[toon].Class, strsplit(' ', toon)), timestr)
if spellid > 0 then
local tip = SI.db.spelltip and SI.db.spelltip[spellid]
for i=1,#tip do
indicatortip:AddLine("")
indicatortip:SetCell(indicatortip:GetLineCount(),1,tip[i], nil, "LEFT",2, nil, nil, nil, 250)
end
else
local queuestr = LFG_RANDOM_COOLDOWN_YOU:match("^(.+)\n")
indicatortip:AddLine(LFG_TYPE_RANDOM_DUNGEON)
indicatortip:AddLine("")
indicatortip:SetCell(indicatortip:GetLineCount(),1,queuestr, nil, "LEFT",2, nil, nil, nil, 250)
end
indicatortip:Show()
end
hoverTooltip.ShowCurrencyTooltip = function (cell, arg, ...)
local toon, idx, ci = unpack(arg)
if not toon or not idx or not ci then return end
local info = C_CurrencyInfo.GetBasicCurrencyInfo(idx)
local tex = " \124T" .. info.icon .. ":0\124t"
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
indicatortip:AddHeader(ClassColorise(SI.db.Toons[toon].Class, strsplit(' ', toon)), CurrencyColor(ci.amount or 0,ci.totalMax)..tex)
indicatortip:AddLine('')
indicatortip:SetCell(indicatortip:GetLineCount(), 1, GOLDFONT .. info.description .. FONTEND, nil, 'LEFT', 2, nil, nil, nil, 220)
local spacer = nil
if ci.weeklyMax and ci.weeklyMax > 0 then
if not spacer then
indicatortip:AddLine(" ")
spacer = true
end
indicatortip:AddLine(format(CURRENCY_WEEKLY_CAP, "", CurrencyColor(ci.earnedThisWeek or 0, ci.weeklyMax), SI:formatNumber(ci.weeklyMax)))
end
if ci.totalEarned and ci.totalEarned > 0 and ci.totalMax and ci.totalMax > 0 then
if not spacer then
indicatortip:AddLine(" ")
spacer = true
end
indicatortip:AddLine(format(CURRENCY_TOTAL, "", CurrencyColor(ci.amount or 0, ci.totalMax)))
-- currently, only season currency use totalEarned
indicatortip:AddLine(format(CURRENCY_SEASON_TOTAL_MAXIMUM, "", CurrencyColor(ci.totalEarned or 0, ci.totalMax), SI:formatNumber(ci.totalMax)))
elseif ci.totalMax and ci.totalMax > 0 then
if not spacer then
indicatortip:AddLine(" ")
spacer = true
end
indicatortip:AddLine(format(CURRENCY_TOTAL_CAP, "", CurrencyColor(ci.amount or 0, ci.totalMax), SI:formatNumber(ci.totalMax)))
end
if ci.covenant then
if not spacer then
indicatortip:AddLine(" ")
spacer = true
end
for covenantID = 1, 4 do
if ci.covenant[covenantID] then
local data = C_Covenants.GetCovenantData(covenantID)
local name = data and data.name or UNKNOWN
indicatortip:AddLine(name .. ": " .. CurrencyColor(ci.covenant[covenantID] or 0, ci.totalMax))
end
end
end
if SI.specialCurrency[idx] and SI.specialCurrency[idx].relatedItem then
if not spacer then
indicatortip:AddLine(" ")
spacer = true
end
local itemName = GetItemInfo(SI.specialCurrency[idx].relatedItem.id) or ""
if SI.specialCurrency[idx].relatedItem.holdingMax then
local holdingMax = SI.specialCurrency[idx].relatedItem.holdingMax
indicatortip:AddLine(itemName .. ": " .. CurrencyColor(ci.relatedItemCount or 0, holdingMax) .. "/" .. holdingMax)
else
indicatortip:AddLine(itemName .. ": " .. (ci.relatedItemCount or 0))
end
end
indicatortip:Show()
end
hoverTooltip.ShowCurrencySummary = function (cell, arg, ...)
local idx = arg
if not idx then return end
local data = C_CurrencyInfo.GetCurrencyInfo(idx)
local name = Currency.OverrideName[idx] or data.name
local tex = Currency.OverrideTexture[idx] or data.iconFileID
tex = " \124T"..tex..":0\124t"
local itemFlag, itemIcon
if SI.specialCurrency[idx] and SI.specialCurrency[idx].relatedItem then
itemFlag = true
itemIcon = select(10, GetItemInfo(SI.specialCurrency[idx].relatedItem.id))
itemIcon = itemIcon and (" \124T" .. itemIcon .. ":0\124t") or ""
end
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT","RIGHT")
indicatortip:AddHeader(name, "")
local total = 0
local tmax
local temp = {}
for toon, t in pairs(SI.db.Toons) do -- deliberately include ALL toons
local ci = t.currency and t.currency[idx]
if ci and ci.amount then
tmax = tmax or ci.totalMax
local str2 = CurrencyColor(ci.amount or 0, tmax) .. tex
if itemFlag then
if SI.specialCurrency[idx].relatedItem.holdingMax then
str2 = str2 .. " + " .. CurrencyColor(ci.relatedItemCount or 0, SI.specialCurrency[idx].relatedItem.holdingMax) .. itemIcon
else
str2 = str2 .. " + " .. (ci.relatedItemCount or 0) .. itemIcon
end
end
tinsert(temp, {
toon = toon, amount = ci.amount, itemCount = ci.relatedItemCount or 0,
str1 = ClassColorise(t.Class, toon), str2 = str2,
})
total = total + ci.amount
end
end
indicatortip:SetCell(1,2,CurrencyColor(total,0)..tex)
--indicatortip:AddLine(TOTAL, CurrencyColor(total,tmax)..tex)
--indicatortip:AddLine(" ")
SI.currency_sort = SI.currency_sort or function(a,b)
if a.amount ~= b.amount then
return a.amount > b.amount
elseif a.itemCount ~= b.itemCount then
return a.itemCount > b.itemCount
end
local an, as = a.toon:match('^(.*) [-] (.*)$')
local bn, bs = b.toon:match('^(.*) [-] (.*)$')
if db.Tooltip.ServerSort and as ~= bs then
return as < bs
else
return a.toon < b.toon
end
end
table.sort(temp, SI.currency_sort)
for _,t in ipairs(temp) do
indicatortip:AddLine(t.str1, t.str2)
end
indicatortip:Show()
end
hoverTooltip.ShowKeyReportTarget = function (cell, arg, ...)
local indicatortip = Tooltip:AcquireIndicatorTip(2, "LEFT", "RIGHT")
indicatortip:AddHeader(GOLDFONT..L["Keystone report target"]..FONTEND, SI.db.Tooltip.KeystoneReportTarget)
indicatortip:Show()
end
-- global addon code below
function SI:toonInit()
local ti = db.Toons[SI.thisToon] or { }
db.Toons[SI.thisToon] = ti
ti.LClass, ti.Class = UnitClass("player")
ti.Level = UnitLevel("player")
ti.Show = ti.Show or "saved"
ti.Order = ti.Order or 50
ti.Quests = ti.Quests or {}
ti.Skills = ti.Skills or {}
ti.Progress = ti.Progress or {}
ti.DailyWorldQuest = nil -- REMOVED
ti.Artifact = nil -- REMOVED
ti.Cloak = nil -- REMOVED
-- try to get a reset time, but don't overwrite existing, which could break quest list
-- real update comes later in UpdateToonData
ti.DailyResetTime = ti.DailyResetTime or SI:GetNextDailyResetTime()
ti.WeeklyResetTime = ti.WeeklyResetTime or SI:GetNextWeeklyResetTime()
end
function SI:OnInitialize()
local versionString = GetAddOnMetadata("SavedInstances", "version")
--[==[@debug@
if versionString == "10.1.5" then
versionString = "Dev"
end
--@end-debug@]==]
SI.version = versionString
SavedInstancesDB = SavedInstancesDB or SI.defaultDB
-- begin backwards compatibility
if not SavedInstancesDB.DBVersion or SavedInstancesDB.DBVersion < 10 then
SavedInstancesDB = SI.defaultDB
elseif SavedInstancesDB.DBVersion < 12 then
SavedInstancesDB.Indicators = SI.defaultDB.Indicators
SavedInstancesDB.DBVersion = 12
end
-- end backwards compatibilty
db = db or SavedInstancesDB
SI.db = db
SI:toonInit()
db.Lockouts = nil -- deprecated
db.History = db.History or {}
db.Emissary = db.Emissary or SI.defaultDB.Emissary
db.Quests = db.Quests or SI.defaultDB.Quests
db.QuestDB = db.QuestDB or SI.defaultDB.QuestDB
db.Warfront = db.Warfront or SI.defaultDB.Warfront
for name,default in pairs(SI.defaultDB.Tooltip) do
db.Tooltip[name] = (db.Tooltip[name]==nil and default) or db.Tooltip[name]
end
for _, id in ipairs(SI.currency) do
local name = "Currency"..id
db.Tooltip[name] = (db.Tooltip[name]==nil and SI.defaultDB.Tooltip[name]) or db.Tooltip[name]
end
local currtmp = {}
for _,idx in ipairs(currency) do currtmp[idx] = true end
for toon, t in pairs(SI.db.Toons) do
t.Order = t.Order or 50
if t.currency then -- clean old undiscovered currency entries
for idx, ci in pairs(t.currency) do
-- detect outdated entries because new version doesn't explicitly store max zeros
if (ci.amount == 0 and (ci.weeklyMax == 0 or ci.totalMax == 0))
or ci.amount == nil -- another outdated entry type created by old weekly reset logic
or not currtmp[idx] -- removed currency
then
t.currency[idx] = nil
end
end
end
end
for qid, _ in pairs(db.QuestDB.Daily) do
if db.QuestDB.AccountDaily[qid] then
SI:Debug("Removing duplicate questDB entry: "..qid)
db.QuestDB.Daily[qid] = nil
end
end
for qid, escope in pairs(QuestExceptions) do -- upgrade QuestDB with new exceptions
local val = -1 -- default to a blank zone
for scope, qdb in pairs(db.QuestDB) do
val = qdb[qid] or val
qdb[qid] = nil
end
if db.QuestDB[escope] then
db.QuestDB[escope][qid] = val
end
end
RequestRatedInfo()
RequestRaidInfo() -- get lockout data
RequestLFDPlayerLockInfo()
SI.dataobject = SI.Libs.LDB and SI.Libs.LDB:NewDataObject("SavedInstances", {
text = "SI",
type = "launcher",
icon = "Interface\\Addons\\SavedInstances\\Media\\Icon.tga",
OnEnter = function(frame)
if not Tooltip:IsDetached() and not db.Tooltip.DisableMouseover then
SI:ShowTooltip(frame)
end
end,
OnLeave = function(frame) end,
OnClick = function(frame, button)
if button == "MiddleButton" then
if InCombatLockdown() then return end
ToggleFriendsFrame(4) -- open Blizzard Raid window
RaidInfoFrame:Show()
elseif button == "LeftButton" then
Tooltip:ToggleDetached()
else
Config:ShowConfig()
end
end
})
if SI.Libs.LDBI then
SI.Libs.LDBI:Register("SavedInstances", SI.dataobject, db.MinimapIcon)
SI.Libs.LDBI:AddButtonToCompartment("SavedInstances")
SI.Libs.LDBI:Refresh("SavedInstances")
end
end
function SI:OnEnable()
self:RegisterBucketEvent("UPDATE_INSTANCE_INFO", 2, function() SI:Refresh(nil) end)
self:RegisterBucketEvent("LOOT_CLOSED", 1, function() SI:QuestRefresh(nil) end)
self:RegisterBucketEvent("LFG_UPDATE_RANDOM_INFO", 1, function() SI:UpdateInstanceData(); SI:UpdateToonData() end)
self:RegisterBucketEvent("RAID_INSTANCE_WELCOME", 1, RequestRaidInfo)
self:RegisterEvent("CHAT_MSG_SYSTEM", "CheckSystemMessage")
self:RegisterEvent("CHAT_MSG_CURRENCY", "CheckSystemMessage")
self:RegisterEvent("CHAT_MSG_LOOT", "CheckSystemMessage")
self:RegisterEvent("CHAT_MSG_COMBAT_XP_GAIN", "UpdateToonData")
self:RegisterEvent("PLAYER_UPDATE_RESTING", "UpdateToonData")
self:RegisterEvent("PVP_RATED_STATS_UPDATE", "UpdateToonData")
self:RegisterEvent("COVENANT_CHOSEN", "UpdateToonData")
self:RegisterEvent("MYTHIC_PLUS_NEW_WEEKLY_RECORD", "UpdateToonData")
self:RegisterEvent("ZONE_CHANGED_NEW_AREA", RequestRatedInfo)
self:RegisterEvent("PLAYER_ENTERING_WORLD", function()
C_Timer.After(1, function()
RequestRatedInfo()
RequestRaidInfo()
end)
SI:UpdateToonData()
end)
-- Update rating on spec change because Solo Shuffle is unique to each spec
self:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED", function()
C_Timer.After(1, function()
RequestRatedInfo()
RequestRaidInfo()
end)
SI:UpdateToonData()
end)
-- self:RegisterBucketEvent("PLAYER_ENTERING_WORLD", 1, RequestRaidInfo)
self:RegisterBucketEvent("LFG_LOCK_INFO_RECEIVED", 1, RequestRaidInfo)
self:RegisterEvent("PLAYER_LOGOUT", function() SI.logout = true ; SI:UpdateToonData() end) -- update currency spent
self:RegisterEvent("LFG_COMPLETION_REWARD", "RefreshLockInfo") -- for random daily dungeon tracking
self:RegisterEvent("BOSS_KILL")
self:RegisterEvent("ENCOUNTER_END")
self:RegisterEvent("TIME_PLAYED_MSG", function(_,total,level)
local t = SI.thisToon and SI and SI.db and SI.db.Toons[SI.thisToon]
if total > 0 and t then
t.PlayedTotal = total
t.PlayedLevel = level
end
SI.PlayedTime = time()
if SI.playedpending then
for c,_ in pairs(SI.playedreg) do
c:RegisterEvent("TIME_PLAYED_MSG") -- Restore default
end
SI.playedpending = false
end
end)
self:RegisterEvent("ADDON_LOADED")
SI:ADDON_LOADED()
if not SI.resetDetect then
SI.resetDetect = CreateFrame("Button", "SavedInstancesResetDetectHiddenFrame", UIParent)
for _,e in pairs({
"RAID_INSTANCE_WELCOME",
"PLAYER_ENTERING_WORLD", "CHAT_MSG_SYSTEM", "CHAT_MSG_ADDON",
"ZONE_CHANGED_NEW_AREA",
"INSTANCE_BOOT_START", "INSTANCE_BOOT_STOP", "GROUP_ROSTER_UPDATE",
}) do
SI.resetDetect:RegisterEvent(e)
end
end
SI.resetDetect:SetScript("OnEvent", SI.HistoryEvent)
C_ChatInfo.RegisterAddonMessagePrefix("SavedInstances")
SI:HistoryEvent("PLAYER_ENTERING_WORLD") -- update after initial load
SI:specialQuests()
SI:updateRealmMap()
end
function SI:ADDON_LOADED()
if DBM and DBM.EndCombat and not SI.dbmhook then
SI.dbmhook = true
hooksecurefunc(DBM, "EndCombat", function(self, mod, wipe)
SI:BossModEncounterEnd("DBM:EndCombat", mod and mod.combatInfo and mod.combatInfo.name)
end)
end
if BigWigsLoader and not SI.bigwigshook then
SI.bigwigshook = true
BigWigsLoader.RegisterMessage(self, "BigWigs_OnBossWin", function(self, event, mod)
SI:BossModEncounterEnd("BigWigs_OnBossWin", mod and mod.displayName)
end)
end
end
function SI:OnDisable()
self:UnregisterAllEvents()
SI.resetDetect:SetScript("OnEvent", nil)
end
function SI:RequestLockInfo() -- request lock info from the server immediately
RequestRaidInfo()
RequestLFDPlayerLockInfo()
end
function SI:RefreshLockInfo() -- throttled lock update with retry
local now = GetTime()
if now > (SI.lastrefreshlock or 0) + 1 then
SI.lastrefreshlock = now
SI:RequestLockInfo()
end
if now > (SI.lastrefreshlocksched or 0) + 120 then
-- make sure we update any lockout info (sometimes there's server-side delay)
SI.lastrefreshlockshed = now
SI:ScheduleTimer("RequestLockInfo",5)
SI:ScheduleTimer("RequestLockInfo",30)
SI:ScheduleTimer("RequestLockInfo",60)
SI:ScheduleTimer("RequestLockInfo",90)
SI:ScheduleTimer("RequestLockInfo",120)
end
end
local currency_msg = CURRENCY_GAINED:gsub(":.*$","")
function SI:CheckSystemMessage(event, msg)
local inst, t = IsInInstance()
-- note: currency is already updated in TooltipShow,
-- here we just hook JP/VP currency messages to capture lockout changes
if inst and (t == "party" or t == "raid") and -- dont update on bg honor
(msg:find(INSTANCE_SAVED) or -- first boss kill
msg:find(currency_msg)) -- subsequent boss kills (unless capped or over level)
then
SI:RefreshLockInfo()
end
end
function SI:updateRealmMap()
local realm = GetRealmName():gsub("%s+","")
local lmap = GetAutoCompleteRealms()
local rmap = SI.db.RealmMap or {}
SI.db.RealmMap = rmap
if lmap and next(lmap) then -- connected realms detected
table.sort(lmap)
local mapid = rmap[realm] -- find existing map
if not mapid then
for _,r in ipairs(lmap) do
mapid = mapid or rmap[r]
end
end
if mapid then -- check for possible expansion
local oldmap = rmap[mapid]
if oldmap and #lmap > #oldmap then
rmap[mapid] = lmap
end
else -- new map
mapid = #rmap + 1
rmap[mapid] = lmap
end
for _,r in ipairs(rmap[mapid]) do -- maintain inverse mapping
rmap[r] = mapid
end
end
end
function SI:getRealmGroup(realm)
-- returns realm-group-id, { realm1, realm2, ...} for connected realm, or nil,nil for unconnected
realm = realm:gsub("%s+","")
local rmap = SI.db.RealmMap
local gid = rmap and rmap[realm]
return gid, gid and rmap[gid]
end
function SI:BossModEncounterEnd(modname, bossname)
SI:Debug("%s refresh: %s", (modname or "BossMod"), tostring(bossname))
SI:BossRecord(SI.thisToon, bossname, select(3, GetInstanceInfo()), true)
self:RefreshLockInfo()
end
function SI:ENCOUNTER_END(event, encounterID, encounterName, difficultyID, raidSize, endStatus)
SI:Debug("ENCOUNTER_END:%s:%s:%s:%s:%s", tostring(encounterID), tostring(encounterName), tostring(difficultyID), tostring(raidSize), tostring(endStatus))
if endStatus ~= 1 then return end -- wipe
self:RefreshLockInfo()
SI:BossRecord(SI.thisToon, encounterName, difficultyID)
end
function SI:BOSS_KILL(event, encounterID, encounterName, ...)
SI:Debug("BOSS_KILL:%s:%s",tostring(encounterID),tostring(encounterName)) -- ..":"..strjoin(":",...))
local name = encounterName
if name and type(name) == "string" then
name = name:gsub(",.*$","") -- remove extraneous trailing boss titles
name = strtrim(name)
self:BossModEncounterEnd("BOSS_KILL", name)
end
end
function SI:InGroup()
if IsInRaid() then return "RAID"
elseif GetNumGroupMembers() > 0 then return "PARTY"
else return nil end
end
local function doExplicitReset(instancemsg, failed)
if HasLFGRestrictions() or IsInInstance() or
(SI:InGroup() and not UnitIsGroupLeader("player")) then return end
if not failed then
SI:HistoryUpdate(true)
end
local reportchan = SI:InGroup()
if reportchan then
if not failed then
C_ChatInfo.SendAddonMessage("SavedInstances", "GENERATION_ADVANCE", reportchan)
end
if SI.db.Tooltip.ReportResets then
local msg = instancemsg or RESET_INSTANCES
msg = msg:gsub("\1241.+;.+;","") -- ticket 76, remove |1;; escapes on koKR
SendChatMessage("<".."SavedInstances".."> "..msg, reportchan)
end
end
end
hooksecurefunc("ResetInstances", doExplicitReset)
local resetmsg = INSTANCE_RESET_SUCCESS:gsub("%%s",".+")
local resetfails = { INSTANCE_RESET_FAILED, INSTANCE_RESET_FAILED_OFFLINE, INSTANCE_RESET_FAILED_ZONING }
for k,v in pairs(resetfails) do
resetfails[k] = v:gsub("%%s",".+")
end
local raiddiffmsg = ERR_RAID_DIFFICULTY_CHANGED_S:gsub("%%s",".+")
local dungdiffmsg = ERR_DUNGEON_DIFFICULTY_CHANGED_S:gsub("%%s",".+")
local delaytime = 3 -- seconds to wait on zone change for settings to stabilize
function SI.HistoryEvent(f, evt, ...)
-- SI:Debug("HistoryEvent: "..evt, ...)
if evt == "CHAT_MSG_ADDON" then
local prefix, message, channel, sender = ...
if prefix ~= "SavedInstances" then return end
if message:match("^GENERATION_ADVANCE$") and not UnitIsUnit(sender,"player") then
SI:HistoryUpdate(true)
end
elseif evt == "CHAT_MSG_SYSTEM" then
local msg = ...
if msg:match("^"..resetmsg.."$") then -- I performed expicit reset
doExplicitReset(msg)
elseif msg:match("^"..INSTANCE_SAVED.."$") then -- just got saved
SI:ScheduleTimer("HistoryUpdate", delaytime+1)
elseif (msg:match("^"..raiddiffmsg.."$") or msg:match("^"..dungdiffmsg.."$")) and
not SI:histZoneKey() then -- ignore difficulty messages when creating a party while inside an instance
SI:HistoryUpdate(true)
elseif msg:match(TRANSFER_ABORT_TOO_MANY_INSTANCES) then
SI:HistoryUpdate(false,true)
else
for _,m in pairs(resetfails) do
if msg:match("^"..m.."$") then
doExplicitReset(msg, true) -- send failure chat message
end
end
end
elseif evt == "INSTANCE_BOOT_START" then -- left group inside instance, resets on boot
SI:HistoryUpdate(true)
elseif evt == "INSTANCE_BOOT_STOP" and SI:InGroup() then -- invited back
SI.delayedReset = false
elseif evt == "GROUP_ROSTER_UPDATE" and
SI.histInGroup and not SI:InGroup() and -- ignore failed invites when solo
not SI:histZoneKey() then -- left group outside instance, resets now
SI:HistoryUpdate(true)
elseif evt == "PLAYER_ENTERING_WORLD" or evt == "ZONE_CHANGED_NEW_AREA" or evt == "RAID_INSTANCE_WELCOME" then
-- delay updates while settings stabilize
local waittime = delaytime + math.max(0,10 - GetFramerate())
SI.delayUpdate = time() + waittime
SI:ScheduleTimer("HistoryUpdate", waittime+1)
end
end
SI.histReapTime = 60*60 -- 1 hour
SI.histLimit = 10 -- instances per hour
function SI:histZoneKey()
local instname, insttype, diff, diffname, maxPlayers, playerDifficulty, isDynamicInstance = GetInstanceInfo()
if insttype == nil or insttype == "none" or insttype == "arena" or insttype == "pvp" then -- pvp doesnt count
return nil
end
if (IsInLFGDungeon() or IsInScenarioGroup()) and diff ~= 19 and diff ~= 17 then -- LFG instances don't count, but Holiday Events and LFR both count
return nil
end
if C_Garrison.IsOnGarrisonMap() then -- Garrisons don't count
return nil
end
-- check if we're locked (using FindInstance so we don't complain about unsaved unknown instances)
local truename = SI:FindInstance(instname, insttype == "raid")
local locked = false
local inst = truename and SI.db.Instances[truename]
inst = inst and inst[SI.thisToon]
for d=1,maxdiff do
if inst and inst[d] and inst[d].Locked then
locked = true
end
end
if diff == 1 and maxPlayers == 5 then -- never locked to 5-man regs
locked = false
end
local toonstr = SI.thisToon
if not db.Tooltip.ShowServer then
toonstr = strsplit(" - ", toonstr)
end
local desc = toonstr .. ": " .. instname
if diffname and #diffname > 0 then
desc = desc .. " - " .. diffname
end
local key = SI.thisToon..":"..instname..":"..insttype..":"..diff
if not locked then
key = key..":"..SI.db.histGeneration
end
return key, desc, locked
end
function SI:HistoryUpdate(forcereset, forcemesg)
SI.db.histGeneration = SI.db.histGeneration or 1
if forcereset and SI:histZoneKey() then -- delay reset until we zone out
SI:Debug("HistoryUpdate reset delayed")
SI.delayedReset = true
end
if (forcereset or SI.delayedReset) and not SI:histZoneKey() then
SI:Debug("HistoryUpdate generation advance")
SI.db.histGeneration = (SI.db.histGeneration + 1) % 100000
SI.delayedReset = false
end
local now = time()
if SI.delayUpdate and now < SI.delayUpdate then
SI:Debug("HistoryUpdate delayed")
return
end
local zoningin = false
local newzone, newdesc, locked = SI:histZoneKey()
-- touch zone we left
if SI.histLastZone then
local lz = SI.db.History[SI.histLastZone]
if lz then
lz.last = now
end
elseif newzone then
zoningin = true
end
SI.histLastZone = newzone
SI.histInGroup = SI:InGroup()
-- touch/create new zone
if newzone then
local nz = SI.db.History[newzone]
if not nz then
nz = { create = now, desc = newdesc }
SI.db.History[newzone] = nz
if locked then -- creating a locked instance, delete unlocked version
SI.db.History[newzone..":"..SI.db.histGeneration] = nil
end
end
nz.last = now
end
-- reap old zones
local livecnt = 0
local oldestkey, oldesttime
for zk, zi in pairs(SI.db.History) do
if now > zi.last + SI.histReapTime or
zi.last > (now + 3600) then -- temporary bug fix
SI:Debug("Reaping %s",zi.desc)
SI.db.History[zk] = nil
else
livecnt = livecnt + 1
if not oldesttime or zi.last < oldesttime then
oldestkey = zk
oldesttime = zi.last
end
end
end
local oldestrem = oldesttime and (oldesttime+SI.histReapTime-now)
local oldestremt = (oldestrem and SecondsToTime(oldestrem,false,false,1)) or "n/a"
local oldestremtm = (oldestrem and SecondsToTime(math.floor((oldestrem+59)/60)*60,false,false,1)) or "n/a"
if SI.db.dbg then
local msg = livecnt.." live instances, oldest ("..(oldestkey or "none")..") expires in "..oldestremt..". Current Zone="..(newzone or "nil")
if msg ~= SI.lasthistdbg then
SI.lasthistdbg = msg
SI:Debug(msg)
end
-- SI:Debug(SI.db.History)
end
-- display update
if forcemesg or (SI.db.Tooltip.LimitWarn and zoningin and livecnt >= SI.histLimit-1) then
SI:ChatMsg(L["Warning: You've entered about %i instances recently and are approaching the %i instance per hour limit for your account. More instances should be available in %s."],livecnt, SI.histLimit, oldestremt)
end
SI.histLiveCount = livecnt
SI.histOldest = oldestremt
if db.Tooltip.HistoryText and livecnt > 0 then
SI.dataobject.text = "("..livecnt.."/"..(oldestremt or "?")..")"
SI.histTextthrottle = math.min(oldestrem+1, SI.histTextthrottle or 15)
SI.resetDetect:SetScript("OnUpdate", SI.histTextUpdate)
else
SI.dataobject.text = "SI"
SI.resetDetect:SetScript("OnUpdate", nil)
end
end
function SI.histTextUpdate(self, elap)
SI.histTextthrottle = SI.histTextthrottle - elap
if SI.histTextthrottle > 0 then return end
SI.histTextthrottle = 15
SI:HistoryUpdate()
end
local function localarr(name) -- save on memory churn by reusing arrays in updates
name = "localarr#"..name
SI[name] = SI[name] or {}
return wipe(SI[name])
end
function SI:memcheck(context)
UpdateAddOnMemoryUsage()
local newval = GetAddOnMemoryUsage("SavedInstances")
SI.memusage = SI.memusage or 0
if newval ~= SI.memusage then
SI:Debug("%.3f KB in %s",(newval - SI.memusage),context)
SI.memusage = newval
end
end
-- Lightweight refresh of just quest flag information
-- all may be nil if not instantiataed
function SI:QuestRefresh(recoverdaily, nextreset, weeklyreset)
local tiq = SI.db.Toons[SI.thisToon]
tiq = tiq and tiq.Quests
if not tiq then return end
nextreset = nextreset or SI:GetNextDailyResetTime()
weeklyreset = weeklyreset or SI:GetNextWeeklyResetTime()
if not nextreset or not weeklyreset then return end
for _, qinfo in pairs(SI:specialQuests()) do
local qid = qinfo.quest
if C_QuestLog.IsQuestFlaggedCompleted(qid) then
local q = tiq[qid] or {}
tiq[qid] = q
q.Title = qinfo.name
q.Zone = qinfo.zone
if qinfo.daily then
q.Expires = nextreset
q.isDaily = true
else
q.Expires = weeklyreset
q.isDaily = nil
end
end
end
local now = time()
db.QuestDB.Weekly.expires = weeklyreset
db.QuestDB.AccountWeekly.expires = weeklyreset
db.QuestDB.Darkmoon.expires = SI:GetNextDarkmoonResetTime()
for scope, list in pairs(db.QuestDB) do
local questlist = tiq
if scope:find("Account") then
questlist = db.Quests
end
if recoverdaily or (scope ~= "Daily") then
for qid, mapid in pairs(list) do
if tonumber(qid) and C_QuestLog.IsQuestFlaggedCompleted(qid) and not questlist[qid] and -- recovering a lost quest
(list.expires == nil or list.expires > now) then -- don't repop darkmoon quests from last faire
local title, link = SI:QuestInfo(qid)
if title then
local found
for _,info in pairs(questlist) do
if title == info.Title then -- avoid faction duplicates, since both flags are set
found = true
break
end
end
if not found then
SI:Debug("Recovering lost quest: "..title.." ("..scope..")")
questlist[qid] = { ["Title"] = title, ["Link"] = link,
["isDaily"] = (scope:find("Daily") and true) or nil,
["Expires"] = list.expires,
["Zone"] = C_Map.GetMapInfo(mapid) }
end
end
end
end
end
end
SI:QuestCount(SI.thisToon)
end
function SI:Refresh(recoverdaily)
-- update entire database from the current character's perspective
SI:UpdateInstanceData()
if not SI.instancesUpdated then
SI.RefreshPending = true
return
end -- wait for UpdateInstanceData to succeed
local nextreset = SI:GetNextDailyResetTime()
if not nextreset or ((nextreset - time()) > (24*3600 - 5*60)) then -- allow 5 minutes for quest DB to update after daily rollover
SI:Debug("Skipping SI:Refresh() near daily reset")
SI:UpdateToonData()
return
end
local temp = localarr("RefreshTemp")
for name, instance in pairs(SI.db.Instances) do -- clear current toons lockouts before refresh
local id = instance.LFDID
if instance[SI.thisToon]
-- disabled for ticket 178/195:
--and not (id and SI.LFRInstances[id] and select(2,GetLFGDungeonNumEncounters(id)) == 0) -- ticket 103
then
temp[name] = instance[SI.thisToon] -- use a temp to reduce memory churn
for diff,info in pairs(temp[name]) do
wipe(info)
end
instance[SI.thisToon] = nil
end
end
local numsaved = GetNumSavedInstances()
if numsaved > 0 then
for i = 1, numsaved do
local name, id, expires, diff, locked, extended, mostsig, raid, players, diffname = GetSavedInstanceInfo(i)
local truename, instance = SI:LookupInstance(nil, name, raid)
if expires and expires > 0 then
expires = expires + time()
else
expires = 0
end
instance.Raid = instance.Raid or raid
instance[SI.thisToon] = instance[SI.thisToon] or temp[truename] or { }
local info = instance[SI.thisToon][diff] or {}
wipe(info)
info.ID = id
info.Expires = expires
info.Link = GetSavedInstanceChatLink(i)
info.Locked = locked
info.Extended = extended
instance[SI.thisToon][diff] = info
end
end
local weeklyreset = SI:GetNextWeeklyResetTime()
for id,_ in pairs(SI.LFRInstances) do
local numEncounters, numCompleted = GetLFGDungeonNumEncounters(id)
if ( numCompleted and numCompleted > 0 and weeklyreset ) then
local truename, instance = SI:LookupInstance(id, nil, true)
instance[SI.thisToon] = instance[SI.thisToon] or temp[truename] or { }
local info = instance[SI.thisToon][2] or {}
instance[SI.thisToon][2] = info
if not (info.Expires and info.Expires < (time() + 300)) then -- ticket 109: don't refresh expiration close to reset
wipe(info)
info.Expires = weeklyreset
end
info.ID = -1*numEncounters
for i=1, numEncounters do
local bossName, texture, isKilled = GetLFGDungeonEncounterInfo(id, i)
info[i] = isKilled
end
end
end
local wbsave = localarr("wbsave")
if GetNumSavedWorldBosses and GetSavedWorldBossInfo then -- 5.4
for i=1,GetNumSavedWorldBosses() do
local name, id, reset = GetSavedWorldBossInfo(i)
wbsave[name] = true
end
end
for _,einfo in pairs(SI.WorldBosses) do
if weeklyreset and (
(einfo.quest and C_QuestLog.IsQuestFlaggedCompleted(einfo.quest)) or
wbsave[einfo.savename or einfo.name]
) then
local truename = einfo.name
local instance = SI.db.Instances[truename]
instance[SI.thisToon] = instance[SI.thisToon] or temp[truename] or { }
local info = instance[SI.thisToon][2] or {}
wipe(info)
instance[SI.thisToon][2] = info
info.Expires = weeklyreset
info.ID = -1
info[1] = true
end
end
SI:QuestRefresh(recoverdaily, nextreset, weeklyreset)
Warfront:UpdateQuest()
local icnt, dcnt = 0,0
for name, _ in pairs(temp) do
if SI.db.Instances[name][SI.thisToon] then
for diff,info in pairs(SI.db.Instances[name][SI.thisToon]) do
if not info.ID then
SI.db.Instances[name][SI.thisToon][diff] = nil
dcnt = dcnt + 1
end
end
else
icnt = icnt + 1
end
end
-- SI:Debug("Refresh temp reaped "..icnt.." instances and "..dcnt.." diffs")
wipe(temp)
SI:UpdateToonData()
end
local function UpdateTooltip(self, elapsed)
if not self.anchorframe then
self:SetScript('OnUpdate', nil)
return
end
self.elapsed = (self.elapsed or 10) + elapsed
if self.elapsed < 0.5 then return end
self.elapsed = 0
SI:ShowTooltip(self.anchorframe)
end
-- sorted traversal function for character table
local cpairs
do
local cnext_list = {}
local cnext_pos
local cnext_ekey
local function cnext(t,i)
local e = cnext_list[cnext_pos]
if not e then
return nil
else
cnext_pos = cnext_pos + 1
local n = e[cnext_ekey]
return n, t[n]
end
end
local function cpairs_sort(a,b)
-- generic multi-key sort
for k,av in ipairs(a) do
local bv = b[k]
if av ~= bv then
return av < bv
end
end
return false -- required for sort stability when a==a
end
cpairs = function(t, usecache)
local settings = db.Tooltip
local realmgroup_key
local realmgroup_min
if not usecache then
local thisrealm = GetRealmName()
if settings.ConnectedRealms ~= "ignore" then
local group = SI:getRealmGroup(thisrealm)
thisrealm = group or thisrealm
end
wipe(cnext_list)
cnext_pos = 1
for n,_ in pairs(t) do
local t = SI.db.Toons[n]
local tn, tr = n:match('^(.*) [-] (.*)$')
if t and
(t.Show ~= "never" or (n == SI.thisToon and settings.SelfAlways)) and
(not settings.ServerOnly
or thisrealm == tr
or thisrealm == SI:getRealmGroup(tr))
then
local e = {}
cnext_ekey = 1
if settings.SelfFirst then
if n == SI.thisToon then
e[cnext_ekey] = 1
else
e[cnext_ekey] = 2
end
cnext_ekey = cnext_ekey + 1
end
if settings.ServerSort then
if settings.ConnectedRealms == "ignore" then
e[cnext_ekey] = tr
cnext_ekey = cnext_ekey + 1
else
local rgroup = SI:getRealmGroup(tr)
if rgroup then -- connected realm
realmgroup_min = realmgroup_min or {}
if not realmgroup_min[rgroup] or tr < realmgroup_min[rgroup] then
realmgroup_min[rgroup] = tr -- lowest active realm in group
end
else
rgroup = tr
end
realmgroup_key = cnext_ekey
e[cnext_ekey] = rgroup
cnext_ekey = cnext_ekey + 1
if settings.ConnectedRealms == "group" then
e[cnext_ekey] = tr
cnext_ekey = cnext_ekey + 1
end
end
end
e[cnext_ekey] = t.Order
cnext_ekey = cnext_ekey + 1
e[cnext_ekey] = n
cnext_list[cnext_pos] = e
cnext_pos = cnext_pos + 1
end
end
if realmgroup_key then -- second pass, convert group id to min name
for _,e in ipairs(cnext_list) do
local id = e[realmgroup_key]
if type(id) == "number" then
e[realmgroup_key] = realmgroup_min[id]
end
end
end
table.sort(cnext_list, cpairs_sort)
-- SI:Debug(cnext_list)
end
cnext_pos = 1
return cnext, t, nil
end
end
SI.cpairs = cpairs
-----------------------------------------------------------------------------------------------
-- tooltip event handlers
local function OpenWeeklyRewards()
if _G.WeeklyRewardsFrame and _G.WeeklyRewardsFrame:IsVisible() then return end
if not IsAddOnLoaded('Blizzard_WeeklyRewards') then
LoadAddOn('Blizzard_WeeklyRewards')
end
_G.WeeklyRewardsFrame:Show()
end
local function OpenLFD(self, instanceid, button)
if LFDParentFrame and LFDParentFrame:IsVisible() and LFDQueueFrame.type ~= instanceid then
-- changing entries
else
ToggleLFDParentFrame()
end
if LFDParentFrame and LFDParentFrame:IsVisible() and LFDQueueFrame_SetType then
LFDQueueFrame_SetType(instanceid)
end
end
local function OpenLFR(self, instanceid, button)
if RaidFinderFrame and RaidFinderFrame:IsVisible() and RaidFinderQueueFrame.raid ~= instanceid then
-- changing entries
else
PVEFrame_ToggleFrame("GroupFinderFrame", RaidFinderFrame)
end
if RaidFinderFrame and RaidFinderFrame:IsVisible() and RaidFinderQueueFrame_SetRaid then
RaidFinderQueueFrame_SetRaid(instanceid)
end
end
local function ReportKeys(self, index, button)
MythicPlus:Keys(index)
end
local function OpenCurrency(self, _, button)
ToggleCharacter("TokenFrame")
end
local function ChatLink(self, link, button)
if not link then return end
if ChatEdit_GetActiveWindow() then
ChatEdit_InsertLink(link)
else
ChatFrame_OpenChat(link, DEFAULT_CHAT_FRAME)
end
end
local CloseTooltips = Tooltip.CloseIndicatorTip
local function DoNothing() end
-----------------------------------------------------------------------------------------------
local function ShowAll()
return (IsAltKeyDown() and true) or false
end
local columnCache = { [true] = {}, [false] = {} }
local function addColumns(columns, toon, tooltip)
for c = 1, maxcol do
columns[toon..c] = columns[toon..c] or tooltip:AddColumn("CENTER")
end
columnCache[ShowAll()][toon] = true
end
SI.scaleCache = {}
function SI:ShowTooltip(anchorframe)
local showall = ShowAll()
if Tooltip:IsTooltipShown() and
SI.showall == showall and
SI.scale == (SI.scaleCache[showall] or SI.db.Tooltip.Scale)
then
return -- skip update
end
local starttime = debugprofilestop()
SI.showall = showall
local showexpired = showall or SI.db.Tooltip.ShowExpired
local tooltip = Tooltip:AcquireTooltip("SavedInstancesTooltip", 1, "LEFT")
tooltip:SetCellMarginH(0)
tooltip.anchorframe = anchorframe
tooltip:SetScript("OnUpdate", UpdateTooltip)
tooltip:Clear()
SI.scale = SI.scaleCache[showall] or SI.db.Tooltip.Scale
tooltip:SetScale(SI.scale)
SI:HistoryUpdate()
local headText
if SI.histLiveCount and SI.histLiveCount > 0 then
headText = string.format("%s%s (%d/%s)%s",GOLDFONT,"SavedInstances",SI.histLiveCount,(SI.histOldest or "?"),FONTEND)
else
headText = string.format("%s%s%s",GOLDFONT,"SavedInstances",FONTEND)
end
local headLine = tooltip:AddHeader(headText)
tooltip:SetCellScript(headLine, 1, "OnEnter", hoverTooltip.ShowAccountSummary )
tooltip:SetCellScript(headLine, 1, "OnLeave", CloseTooltips)
tooltip:SetCellScript(headLine, 1, "OnMouseDown", OpenWeeklyRewards)
SI:UpdateToonData()
local columns = localarr("columns")
for toon,_ in cpairs(columnCache[showall]) do
addColumns(columns, toon, tooltip)
columnCache[showall][toon] = false
end
-- allocating columns for characters
for toon, t in cpairs(SI.db.Toons) do
if SI.db.Toons[toon].Show == "always" or
(toon == SI.thisToon and SI.db.Tooltip.SelfAlways) then
addColumns(columns, toon, tooltip)
end
end
-- determining how many instances will be displayed per category
local categoryshown = localarr("categoryshown") -- remember if each category will be shown
local instancesaved = localarr("instancesaved") -- remember if each instance has been saved or not (boolean)
local wbcons = SI.db.Tooltip.CombineWorldBosses
local worldbosses = wbcons and localarr("worldbosses")
local wbalways = false
local lfrcons = SI.db.Tooltip.CombineLFR
local lfrbox = lfrcons and localarr("lfrbox")
local lfrmap = lfrcons and localarr("lfrmap")
for _, category in ipairs(SI:OrderedCategories()) do
for _, instance in ipairs(SI:OrderedInstances(category)) do
local inst = SI.db.Instances[instance]
if inst.Show == "always" then
categoryshown[category] = true
end
if inst.Show ~= "never" then
if wbcons and inst.WorldBoss and inst.Expansion <= GetExpansionLevel() then
if SI.db.Tooltip.ReverseInstances then
table.insert(worldbosses, instance)
else
table.insert(worldbosses, 1, instance)
end
wbalways = wbalways or (inst.Show == "always")
end
local lfrinfo = lfrcons and inst.LFDID and SI.LFRInstances[inst.LFDID]
local lfrboxid
if lfrinfo then
lfrboxid = lfrinfo.parent
lfrmap[inst.LFDID] = instance
if inst.Show == "always" then
lfrbox[lfrboxid] = true
end
end
for toon, t in cpairs(SI.db.Toons, true) do
for diff = 1, maxdiff do
if inst[toon] and inst[toon][diff] then
if (inst[toon][diff].Expires > 0) then
if lfrinfo then
lfrbox[lfrboxid] = true
instancesaved[lfrboxid] = true
elseif wbcons and inst.WorldBoss then
instancesaved[L["World Bosses"]] = true
else
instancesaved[instance] = true
end
categoryshown[category] = true
elseif showall then
categoryshown[category] = true
end
end
end
end
end
end
end
local categories = 0
-- determining how many categories have instances that will be shown
if SI.db.Tooltip.ShowCategories then
for category, _ in pairs(categoryshown) do
categories = categories + 1
end
end
-- allocating tooltip space for instances, categories, and space between categories
local categoryrow = localarr("categoryrow") -- remember where each category heading goes
local instancerow = localarr("instancerow") -- remember where each instance goes
local blankrow = localarr("blankrow") -- track blank lines
local firstcategory = true -- use this to skip spacing before the first category
local function addsep()
if firstcategory then
firstcategory = false
else
local line = tooltip:AddSeparator(6,0,0,0,0)
blankrow[line] = true
end
end
for _, category in ipairs(SI:OrderedCategories()) do
if categoryshown[category] then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
if (categories > 1 or SI.db.Tooltip.ShowSoloCategory) and categoryshown[category] then
local line = tooltip:AddLine()
categoryrow[category] = line
blankrow[line] = true
end
for _, instance in ipairs(SI:OrderedInstances(category)) do
local inst = SI.db.Instances[instance]
if not (wbcons and inst.WorldBoss) and
not (lfrcons and SI.LFRInstances[inst.LFDID]) then
if inst.Show == "always" then
instancerow[instance] = instancerow[instance] or tooltip:AddLine()
end
if inst.Show ~= "never" then
for toon, t in cpairs(SI.db.Toons, true) do
for diff = 1, maxdiff do
if inst[toon] and inst[toon][diff] and (inst[toon][diff].Expires > 0 or showexpired) then
instancerow[instance] = instancerow[instance] or tooltip:AddLine()
addColumns(columns, toon, tooltip)
end
end
end
end
end
if lfrcons and inst.LFDID then
-- check if this parent instance has corresponding lfrboxes, and create them
if lfrbox[inst.LFDID] then
lfrbox[L["LFR"]..": "..instance] = tooltip:AddLine()
end
lfrbox[inst.LFDID] = nil
end
end
end
end
-- now printing instance data
for instance, row in pairs(instancerow) do
local inst = SI.db.Instances[instance]
tooltip:SetCell(row, 1, (instancesaved[instance] and GOLDFONT or GRAYFONT) .. instance .. FONTEND)
if SI.LFRInstances[inst.LFDID] then
tooltip:SetLineScript(row, "OnMouseDown", OpenLFR, inst.LFDID)
end
for toon, t in cpairs(SI.db.Toons, true) do
if inst[toon] then
local showcol = localarr("showcol")
local showcnt = 0
for diff = 1, maxdiff do
if inst[toon][diff] and (inst[toon][diff].Expires > 0 or showexpired) then
showcnt = showcnt + 1
showcol[diff] = true
end
end
local base = 1
local span = maxcol
if showcnt > 1 then
span = 1
end
if showcnt > maxcol then
SI:BugReport("Column overflow! showcnt="..showcnt)
end
for diff = 1, maxdiff do
if showcol[diff] then
local col = columns[toon..base]
tooltip:SetCell(row, col,
DifficultyString(instance, diff, toon, inst[toon][diff].Expires == 0), span)
tooltip:SetCellScript(row, col, "OnEnter", hoverTooltip.ShowIndicatorTooltip, {instance, toon, diff})
tooltip:SetCellScript(row, col, "OnLeave", CloseTooltips)
if SI.LFRInstances[inst.LFDID] then
tooltip:SetCellScript(row, col, "OnMouseDown", OpenLFR, inst.LFDID)
else
local link = inst[toon][diff].Link
if link then
tooltip:SetCellScript(row, col, "OnMouseDown", ChatLink, link)
end
end
base = base + 1
elseif columns[toon..diff] and showcnt > 1 then
tooltip:SetCell(row, columns[toon..diff], "")
end
end
end
end
end
-- combined LFRs
if lfrcons then
for boxname, line in pairs(lfrbox) do
if type(boxname) == "number" then
SI:BugReport("Unrecognized LFR instance parent id= "..boxname)
lfrbox[boxname] = nil
end
end
for boxname, line in pairs(lfrbox) do
local boxtype, pinstance = boxname:match("^([^:]+): (.+)$")
local pinst = SI.db.Instances[pinstance]
local boxid = pinst.LFDID
local firstid
local total = 0
local flag = false -- flag for LFRs that are different between two factions
local tbl, other = {}, {}
for lfdid, lfrinfo in pairs(SI.LFRInstances) do
if lfrinfo.parent == pinst.LFDID and lfrmap[lfdid] then
if (not lfrinfo.faction) or (lfrinfo.faction == UnitFactionGroup("player")) then
firstid = math.min(lfdid, firstid or lfdid)
end
if lfrinfo.faction and lfrinfo.faction == "Horde" then
flag = true
other[lfrinfo.base] = lfrmap[lfdid]
else
-- count total bosses for only one faction
total = total + lfrinfo.total
tbl[lfrinfo.base] = lfrmap[lfdid]
end
end
end
tooltip:SetCell(line, 1, (instancesaved[boxid] and GOLDFONT or GRAYFONT) .. boxname .. FONTEND)
tooltip:SetLineScript(line, "OnMouseDown", OpenLFR, firstid)
for toon, t in cpairs(SI.db.Toons, true) do
local saved = 0
local diff = 2
local curr = (flag and t.Faction == "Horde") and other or tbl
for key, instance in pairs(curr) do
saved = saved + SI:instanceBosses(instance, toon, diff)
end
if saved > 0 then
addColumns(columns, toon, tooltip)
local col = columns[toon..1]
tooltip:SetCell(line, col, DifficultyString(pinstance, diff, toon, false, saved, total),4)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowLFRTooltip, {boxname, toon, curr})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
end
-- combined world bosses
if wbcons and next(worldbosses) and (wbalways or instancesaved[L["World Bosses"]]) then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
local line = tooltip:AddLine((instancesaved[L["World Bosses"]] and YELLOWFONT or GRAYFONT) .. L["World Bosses"] .. FONTEND)
for toon, t in cpairs(SI.db.Toons, true) do
local saved = 0
local diff = 2
for _, instance in ipairs(worldbosses) do
local inst = SI.db.Instances[instance]
if inst[toon] and inst[toon][diff] and inst[toon][diff].Expires > 0 then
saved = saved + 1
end
end
if saved > 0 then
addColumns(columns, toon, tooltip)
local col = columns[toon..1]
tooltip:SetCell(line, col, DifficultyString(worldbosses[1], diff, toon, false, saved, #worldbosses),4)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowWorldBossTooltip, {worldbosses, toon, saved})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
local holidayinst = localarr("holidayinst")
local firstlfd = true
for instance, info in pairs(SI.db.Instances) do
if showall or
(info.Holiday and SI.db.Tooltip.ShowHoliday) or
(info.Random and SI.db.Tooltip.ShowRandom) then
for toon, t in cpairs(SI.db.Toons, true) do
local d = info[toon] and info[toon][1]
if d then
addColumns(columns, toon, tooltip)
local row = holidayinst[instance]
if not row then
if SI.db.Tooltip.CategorySpaces and firstlfd then
addsep()
firstlfd = false
end
row = tooltip:AddLine(YELLOWFONT .. abbreviate(instance) .. FONTEND)
holidayinst[instance] = row
end
local tstr = SecondsToTime(d.Expires - time(), false, false, 1)
tooltip:SetCell(row, columns[toon..1], ClassColorise(t.Class,tstr), "CENTER",maxcol)
tooltip:SetLineScript(row, "OnMouseDown", OpenLFD, info.LFDID)
end
end
end
end
-- random dungeon
if SI.db.Tooltip.TrackLFG or showall then
local cd1,cd2 = false,false
for toon, t in cpairs(SI.db.Toons, true) do
cd2 = cd2 or t.LFG2
cd1 = cd1 or (t.LFG1 and (not t.LFG2 or showall))
if t.LFG1 or t.LFG2 then
addColumns(columns, toon, tooltip)
end
end
local randomLine
if cd1 or cd2 then
if SI.db.Tooltip.CategorySpaces and firstlfd then
addsep()
firstlfd = false
end
local cooldown = ITEM_COOLDOWN_TOTAL:gsub("%%s",""):gsub("%p","")
cd1 = cd1 and tooltip:AddLine(YELLOWFONT .. LFG_TYPE_RANDOM_DUNGEON..cooldown .. FONTEND)
cd2 = cd2 and tooltip:AddLine(YELLOWFONT .. GetSpellInfo(71041) .. FONTEND)
end
for toon, t in cpairs(SI.db.Toons, true) do
local d1 = (t.LFG1 and t.LFG1 - time()) or -1
local d2 = (t.LFG2 and t.LFG2 - time()) or -1
if d1 > 0 and (d2 < 0 or showall) then
local col = columns[toon..1]
local tstr = SecondsToTime(d1, false, false, 1)
tooltip:SetCell(cd1, col, ClassColorise(t.Class,tstr), "CENTER",maxcol)
tooltip:SetCellScript(cd1, col, "OnEnter", hoverTooltip.ShowSpellIDTooltip, {toon,-1,tstr})
tooltip:SetCellScript(cd1, col, "OnLeave", CloseTooltips)
end
if d2 > 0 then
local col = columns[toon..1]
local tstr = SecondsToTime(d2, false, false, 1)
tooltip:SetCell(cd2, col, ClassColorise(t.Class,tstr), "CENTER",maxcol)
tooltip:SetCellScript(cd2, col, "OnEnter", hoverTooltip.ShowSpellIDTooltip, {toon,71041,tstr})
tooltip:SetCellScript(cd2, col, "OnLeave", CloseTooltips)
end
end
end
if SI.db.Tooltip.TrackDeserter or showall then
local show = false
for toon, t in cpairs(SI.db.Toons, true) do
if t.pvpdesert then
show = true
addColumns(columns, toon, tooltip)
end
end
if show then
if SI.db.Tooltip.CategorySpaces and firstlfd then
addsep()
firstlfd = false
end
show = tooltip:AddLine(YELLOWFONT .. DESERTER .. FONTEND)
end
for toon, t in cpairs(SI.db.Toons, true) do
if t.pvpdesert and time() < t.pvpdesert then
local col = columns[toon..1]
local tstr = SecondsToTime(t.pvpdesert - time(), false, false, 1)
tooltip:SetCell(show, col, ClassColorise(t.Class,tstr), "CENTER",maxcol)
tooltip:SetCellScript(show, col, "OnEnter", hoverTooltip.ShowSpellIDTooltip, {toon,26013,tstr})
tooltip:SetCellScript(show, col, "OnLeave", CloseTooltips)
end
end
end
do
local showd, showw
for toon, t in cpairs(SI.db.Toons, true) do
local dc, wc = SI:QuestCount(toon)
if dc > 0 and (SI.db.Tooltip.TrackDailyQuests or showall) then
showd = true
addColumns(columns, toon, tooltip)
end
if wc > 0 and (SI.db.Tooltip.TrackWeeklyQuests or showall) then
showw = true
addColumns(columns, toon, tooltip)
end
end
local adc, awc = SI:QuestCount(nil)
if adc > 0 and (SI.db.Tooltip.TrackDailyQuests or showall) then showd = true end
if awc > 0 and (SI.db.Tooltip.TrackWeeklyQuests or showall) then showw = true end
if SI.db.Tooltip.CategorySpaces and (showd or showw) then
addsep()
end
if showd then
showd = tooltip:AddLine(YELLOWFONT .. L["Daily Quests"] .. (adc > 0 and " ("..adc..")" or "") .. FONTEND)
if adc > 0 then
tooltip:SetCellScript(showd, 1, "OnEnter", hoverTooltip.ShowQuestTooltip, {nil,adc,true})
tooltip:SetCellScript(showd, 1, "OnLeave", CloseTooltips)
end
end
if showw then
showw = tooltip:AddLine(YELLOWFONT .. L["Weekly Quests"] .. (awc > 0 and " ("..awc..")" or "") .. FONTEND)
if awc > 0 then
tooltip:SetCellScript(showw, 1, "OnEnter", hoverTooltip.ShowQuestTooltip, {nil,awc,false})
tooltip:SetCellScript(showw, 1, "OnLeave", CloseTooltips)
end
end
for toon, t in cpairs(SI.db.Toons, true) do
local dc, wc = SI:QuestCount(toon)
local col = columns[toon..1]
if showd and col and dc > 0 then
tooltip:SetCell(showd, col, ClassColorise(t.Class,dc), "CENTER",maxcol)
tooltip:SetCellScript(showd, col, "OnEnter", hoverTooltip.ShowQuestTooltip, {toon,dc,true})
tooltip:SetCellScript(showd, col, "OnLeave", CloseTooltips)
end
if showw and col and wc > 0 then
tooltip:SetCell(showw, col, ClassColorise(t.Class,wc), "CENTER",maxcol)
tooltip:SetCellScript(showw, col, "OnEnter", hoverTooltip.ShowQuestTooltip, {toon,wc,false})
tooltip:SetCellScript(showw, col, "OnLeave", CloseTooltips)
end
end
end
Progress:ShowTooltip(tooltip, columns, showall, function()
if SI.db.Tooltip.CategorySpaces then
addsep()
end
if SI.db.Tooltip.ShowCategories then
tooltip:AddLine(YELLOWFONT .. L["Quest progresses"] .. FONTEND)
end
end)
Warfront:ShowTooltip(tooltip, columns, showall, function()
if SI.db.Tooltip.CategorySpaces then
addsep()
end
if SI.db.Tooltip.ShowCategories then
tooltip:AddLine(YELLOWFONT .. L["Warfronts"] .. FONTEND)
end
end)
if SI.db.Tooltip.TrackSkills or showall then
local show = false
for toon, t in cpairs(SI.db.Toons, true) do
if t.Skills and next(t.Skills) then
show = true
addColumns(columns, toon, tooltip)
end
end
if show then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Trade Skill Cooldowns"] .. FONTEND)
end
for toon, t in cpairs(SI.db.Toons, true) do
local cnt = 0
if t.Skills then
for _ in pairs(t.Skills) do cnt = cnt + 1 end
end
if cnt > 0 then
local col = columns[toon..1]
tooltip:SetCell(show, col, ClassColorise(t.Class,cnt), "CENTER",maxcol)
tooltip:SetCellScript(show, col, "OnEnter", hoverTooltip.ShowSkillTooltip, {toon, cnt})
tooltip:SetCellScript(show, col, "OnLeave", CloseTooltips)
end
end
end
if SI.db.Tooltip.MythicKey or showall then
local show = false
for toon, t in cpairs(SI.db.Toons, true) do
if t.MythicKey then
if t.MythicKey.link then
show = true
addColumns(columns, toon, tooltip)
end
end
end
if show then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Mythic Keystone"] .. FONTEND)
tooltip:SetCellScript(show, 1, "OnEnter", hoverTooltip.ShowKeyReportTarget)
tooltip:SetCellScript(show, 1, "OnLeave", CloseTooltips)
tooltip:SetCellScript(show, 1, "OnMouseDown", ReportKeys, 'MythicKey')
end
for toon, t in cpairs(SI.db.Toons, true) do
if t.MythicKey and t.MythicKey.link then
local col = columns[toon..1]
local name
if SI.db.Tooltip.AbbreviateKeystone then
name = SI.KeystoneAbbrev[t.MythicKey.mapID] or t.MythicKey.name
else
name = t.MythicKey.name
end
tooltip:SetCell(show, col, "|c" .. t.MythicKey.color .. name .. " (" .. t.MythicKey.level .. ")" .. FONTEND, "CENTER", maxcol)
tooltip:SetCellScript(show, col, "OnMouseDown", ChatLink, t.MythicKey.link)
end
end
end
if SI.db.Tooltip.TimewornMythicKey or showall then
local show = false
for toon, t in cpairs(SI.db.Toons, true) do
if t.TimewornMythicKey and t.TimewornMythicKey.link then
show = true
addColumns(columns, toon, tooltip)
end
end
if show then
if SI.db.Tooltip.CategorySpaces and not (SI.db.Tooltip.MythicKey or showall) then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Timeworn Mythic Keystone"] .. FONTEND)
tooltip:SetCellScript(show, 1, "OnEnter", hoverTooltip.ShowKeyReportTarget)
tooltip:SetCellScript(show, 1, "OnLeave", CloseTooltips)
tooltip:SetCellScript(show, 1, "OnMouseDown", ReportKeys, 'TimewornMythicKey')
end
for toon, t in cpairs(SI.db.Toons, true) do
if t.TimewornMythicKey and t.TimewornMythicKey.link then
local col = columns[toon..1]
local name
if SI.db.Tooltip.AbbreviateKeystone then
name = SI.KeystoneAbbrev[t.TimewornMythicKey.mapID] or t.TimewornMythicKey.name
else
name = t.TimewornMythicKey.name
end
tooltip:SetCell(show, col, "|c" .. t.TimewornMythicKey.color .. name .. " (" .. t.TimewornMythicKey.level .. ")" .. FONTEND, "CENTER", maxcol)
tooltip:SetCellScript(show, col, "OnMouseDown", ChatLink, t.TimewornMythicKey.link)
end
end
end
if SI.db.Tooltip.MythicKeyBest or showall then
local show = false
for toon, t in cpairs(SI.db.Toons, true) do
if t.MythicKeyBest then
if t.MythicKeyBest.lastCompletedIndex or t.MythicKeyBest.rewardWaiting then
show = true
addColumns(columns, toon, tooltip)
end
end
end
if show then
if SI.db.Tooltip.CategorySpaces and not (SI.db.Tooltip.MythicKey or SI.db.Tooltip.TimewornMythicKey or showall) then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Mythic Key Best"] .. FONTEND)
end
for toon, t in cpairs(SI.db.Toons, true) do
if t.MythicKeyBest then
local keydesc = ""
if t.MythicKeyBest.lastCompletedIndex then
for index = 1, t.MythicKeyBest.lastCompletedIndex do
if t.MythicKeyBest[index] then
keydesc = keydesc .. (index > 1 and " / " or "") .. t.MythicKeyBest[index]
end
end
end
if t.MythicKeyBest.rewardWaiting then
if keydesc == "" then
keydesc = SI.questTurnin
else
keydesc = keydesc .. "(" .. SI.questTurnin .. ")"
end
end
if keydesc ~= "" then
local col = columns[toon..1]
tooltip:SetCell(show, col, keydesc, "CENTER", maxcol)
tooltip:SetCellScript(show, col, "OnEnter", hoverTooltip.ShowMythicPlusTooltip, {toon, keydesc})
tooltip:SetCellScript(show, col, "OnLeave", CloseTooltips)
end
end
end
end
local firstEmissary = true
for expansionLevel, _ in pairs(SI.Emissaries) do
if SI.db.Tooltip["Emissary" .. expansionLevel] or showall then
local day, tbl, show
for toon, t in cpairs(SI.db.Toons, true) do
if t.Emissary and t.Emissary[expansionLevel] and t.Emissary[expansionLevel].unlocked then
for day, tbl in pairs(t.Emissary[expansionLevel].days) do
if showall or SI.db.Tooltip.EmissaryShowCompleted == true or tbl.isComplete == false then
if not show then show = {} end
if not show[day] then show[day] = {} end
if not show[day][1] then
show[day][1] = t.Faction
elseif show[day][1] ~= t.Faction then
show[day][2] = t.Faction
end
end
end
end
end
if show then
if firstEmissary == true then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
if SI.db.Tooltip.ShowCategories then
tooltip:AddLine(YELLOWFONT .. L["Emissary Quests"] .. FONTEND)
end
firstEmissary = false
end
if SI.db.Tooltip.CombineEmissary then
local line = tooltip:AddLine(GOLDFONT .. _G["EXPANSION_NAME" .. expansionLevel] .. FONTEND)
tooltip:SetCellScript(line, 1, "OnEnter", hoverTooltip.ShowEmissarySummary, {expansionLevel, {1, 2, 3}})
tooltip:SetCellScript(line, 1, "OnLeave", CloseTooltips)
for toon, t in cpairs(SI.db.Toons, true) do
if t.Emissary and t.Emissary[expansionLevel] and t.Emissary[expansionLevel].unlocked then
for day = 1, 3 do
tbl = t.Emissary[expansionLevel].days[day]
if tbl then
local col = columns[toon .. day]
local text = ""
if tbl.isComplete == true then
text = SI.questCheckMark
elseif tbl.isFinish == true then
text = SI.questTurnin
else
text = tbl.questDone
if (
SI.db.Emissary.Expansion[expansionLevel][day] and
SI.db.Emissary.Expansion[expansionLevel][day].questNeed
) then
text = text .. "/" .. SI.db.Emissary.Expansion[expansionLevel][day].questNeed
end
end
if col then
-- check if current toon is showing
-- don't add columns
tooltip:SetCell(line, col, text, "CENTER", 1)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowEmissaryTooltip, {expansionLevel, day, toon})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
end
end
else
for day = 1, 3 do
if show[day] and show[day][1] then
local name = ""
if not SI.db.Emissary.Expansion[expansionLevel][day] then
name = L["Emissary Missing"]
else
local length, tbl = 0, SI.db.Emissary.Expansion[expansionLevel][day].questID
if SI.db.Emissary.Cache[tbl[show[day][1]]] then
name = SI.db.Emissary.Cache[tbl[show[day][1]]]
length = length + 1
end
if (length == 0 or SI.db.Tooltip.EmissaryFullName) and show[day][2] then
if tbl[show[day][1]] ~= tbl[show[day][2]] and SI.db.Emissary.Cache[tbl[show[day][2]]] then
if length > 0 then
name = name .. " / "
end
name = name .. SI.db.Emissary.Cache[tbl[show[day][2]]]
length = length + 1
end
end
if length == 0 then
name = L["Emissary Missing"]
end
end
local line = tooltip:AddLine(GOLDFONT .. name .. " (+" .. (day - 1) .. " " .. L["Day"] .. ")" .. FONTEND)
tooltip:SetCellScript(line, 1, "OnEnter", hoverTooltip.ShowEmissarySummary, {expansionLevel, {day}})
tooltip:SetCellScript(line, 1, "OnLeave", CloseTooltips)
for toon, t in cpairs(SI.db.Toons, true) do
if t.Emissary and t.Emissary[expansionLevel] and t.Emissary[expansionLevel].unlocked then
tbl = t.Emissary[expansionLevel].days[day]
if tbl then
local col = columns[toon .. 1]
local text = ""
if tbl.isComplete == true then
text = SI.questCheckMark
elseif tbl.isFinish == true then
text = SI.questTurnin
else
text = tbl.questDone
if (
SI.db.Emissary.Expansion[expansionLevel][day] and
SI.db.Emissary.Expansion[expansionLevel][day].questNeed
) then
text = text .. "/" .. SI.db.Emissary.Expansion[expansionLevel][day].questNeed
end
end
if col then
-- check if current toon is showing
-- don't add columns
tooltip:SetCell(line, col, text, "CENTER", maxcol)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowEmissaryTooltip, {expansionLevel, day, toon})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
end
end
end
end
end
end
end
if SI.db.Tooltip.Calling or showall then
local show
for day = 1, 3 do
for toon, t in cpairs(SI.db.Toons, true) do
if t.Calling and t.Calling.unlocked then
if showall or SI.db.Tooltip.CallingShowCompleted or (t.Calling[day] and not t.Calling[day].isCompleted) then
if not show then show = {} end
show[day] = true
break
end
end
end
end
if show then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
if SI.db.Tooltip.CombineCalling then
local line = tooltip:AddLine(GOLDFONT .. CALLINGS_QUESTS .. FONTEND)
for toon, t in cpairs(SI.db.Toons, true) do
if t.Calling and t.Calling.unlocked then
for day = 1, 3 do
local col = columns[toon .. day]
local text = ""
if t.Calling[day].isCompleted then
text = SI.questCheckMark
elseif not t.Calling[day].isOnQuest then
text = SI.questNormal
elseif t.Calling[day].isFinished then
text = SI.questTurnin
else
if t.Calling[day].objectiveType == 'progressbar' then
text = floor(t.Calling[day].questDone / t.Calling[day].questNeed * 100) .. "%"
else
text = t.Calling[day].questDone .. '/' .. t.Calling[day].questNeed
end
end
if col then
-- check if current toon is showing
-- don't add columns
tooltip:SetCell(line, col, text, "CENTER", 1)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowCallingTooltip, {day, toon})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
end
else
if SI.db.Tooltip.ShowCategories then
tooltip:AddLine(YELLOWFONT .. CALLINGS_QUESTS .. FONTEND)
end
for day = 1, 3 do
if show[day] then
local name = L["Calling Missing"]
-- try current toon first
local t = SI.db.Toons[SI.thisToon]
if t and t.Calling and t.Calling[day] and t.Calling[day].title then
name = t.Calling[day].title
else
for _, t in pairs(SI.db.Toons) do
if t.Calling and t.Calling[day] and t.Calling[day].title then
name = t.Calling[day].title
break
end
end
end
local line = tooltip:AddLine(GOLDFONT .. name .. " (+" .. (day - 1) .. " " .. L["Day"] .. ")" .. FONTEND)
for toon, t in cpairs(SI.db.Toons, true) do
if t.Calling and t.Calling.unlocked then
local col = columns[toon .. 1]
local text = ""
if t.Calling[day].isCompleted then
text = SI.questCheckMark
elseif not t.Calling[day].isOnQuest then
text = SI.questNormal
elseif t.Calling[day].isFinished then
text = SI.questTurnin
else
if t.Calling[day].objectiveType == 'progressbar' then
text = floor(t.Calling[day].questDone / t.Calling[day].questNeed * 100) .. "%"
else
text = t.Calling[day].questDone .. '/' .. t.Calling[day].questNeed
end
end
if col then
-- check if current toon is showing
-- don't add columns
tooltip:SetCell(line, col, text, "CENTER", maxcol)
tooltip:SetCellScript(line, col, "OnEnter", hoverTooltip.ShowCallingTooltip, {day, toon})
tooltip:SetCellScript(line, col, "OnLeave", CloseTooltips)
end
end
end
end
end
end
end
end
if SI.db.Tooltip.TrackParagon or showall then
local show
for toon, t in cpairs(SI.db.Toons, true) do
if t.Paragon and #t.Paragon > 0 then
show = true
addColumns(columns, toon, tooltip)
end
end
if show then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Paragon Chests"] .. FONTEND)
for toon, t in cpairs(SI.db.Toons, true) do
if t.Paragon and #t.Paragon > 0 then
local col = columns[toon..1]
tooltip:SetCell(show, col, #t.Paragon, "CENTER", maxcol)
tooltip:SetCellScript(show, col, "OnEnter", hoverTooltip.ShowParagonTooltip, toon)
tooltip:SetCellScript(show, col, "OnLeave", CloseTooltips)
end
end
end
end
if SI.db.Tooltip.TrackBonus or showall then
local show
local toonbonus = localarr("toonbonus")
for toon, t in cpairs(SI.db.Toons, true) do
local count = SI:BonusRollCount(toon)
if count then
toonbonus[toon] = count
show = true
end
end
if show then
if SI.db.Tooltip.CategorySpaces then
addsep()
end
show = tooltip:AddLine(YELLOWFONT .. L["Roll Bonus"] .. FONTEND)
end
for toon, t in cpairs(SI.db.Toons, true) do
if toonbonus[toon] then
local col = columns[toon..1]
local str = toonbonus[toon]
if str > 0 then str = "+"..str end
if col then
-- check if current toon is showing
-- don't add columns
tooltip:SetCell(show, col, ClassColorise(t.Class,str), "CENTER",maxcol)
tooltip:SetCellScript(show, col, "OnEnter", hoverTooltip.ShowBonusTooltip, toon)
tooltip:SetCellScript(show, col, "OnLeave", CloseTooltips)
end
end
end
end
local firstcurrency = true
local ckeys = currency
if SI.db.Tooltip.CurrencySortName then
ckeys = SI.currencySorted
end
for _, idx in ipairs(ckeys) do
if SI.db.Tooltip["Currency" .. idx] or showall then
local show
for toon, t in cpairs(SI.db.Toons, true) do
-- ci.name, ci.amount, ci.earnedThisWeek, ci.weeklyMax, ci.totalMax, ci.relatedItemCount
local ci = t.currency and t.currency[idx]
if ci then
local gotThisWeek = ((ci.earnedThisWeek or 0) > 0 and (ci.weeklyMax or 0) > 0)
local gotSome = ((ci.relatedItemCount or 0) > 0) or ((ci.amount or 0) > 0)
if gotThisWeek or (gotSome and showall) then
addColumns(columns, toon, tooltip)
end
if not show and (gotThisWeek or gotSome) and columns[toon .. 1] then
local data = C_CurrencyInfo.GetCurrencyInfo(idx)
local name = Currency.OverrideName[idx] or data.name
local tex = Currency.OverrideTexture[idx] or data.iconFileID
show = format(" \124T%s:0\124t%s", tex, name)
end
end
end
local currLine
if show then
if SI.db.Tooltip.CategorySpaces and firstcurrency then
addsep()
firstcurrency = false
end
currLine = tooltip:AddLine(YELLOWFONT .. show .. FONTEND)
tooltip:SetLineScript(currLine, "OnMouseDown", OpenCurrency)
tooltip:SetCellScript(currLine, 1, "OnEnter", hoverTooltip.ShowCurrencySummary, idx)
tooltip:SetCellScript(currLine, 1, "OnLeave", CloseTooltips)
tooltip:SetCellScript(currLine, 1, "OnMouseDown", OpenCurrency)
for toon, t in cpairs(SI.db.Toons, true) do
local ci = t.currency and t.currency[idx]
local col = columns[toon..1]
if ci and col then
local earned, weeklymax, totalmax = "","",""
if SI.db.Tooltip.CurrencyMax then
if (ci.weeklyMax or 0) > 0 then
weeklymax = "/"..SI:formatNumber(ci.weeklyMax)
end
if (ci.totalMax or 0) > 0 then
totalmax = "/"..SI:formatNumber(ci.totalMax)
end
end
if SI.db.Tooltip.CurrencyEarned or showall then
earned = CurrencyColor(ci.amount,ci.totalMax)..totalmax
end
local str
if (ci.amount or 0) > 0 or (ci.earnedThisWeek or 0) > 0 or (ci.totalEarned or 0) > 0 then
if (ci.weeklyMax or 0) > 0 then
str = earned.." ("..CurrencyColor(ci.earnedThisWeek,ci.weeklyMax)..weeklymax..")"
elseif (ci.amount or 0) > 0 or (ci.totalEarned or 0) > 0 then
str = CurrencyColor(ci.amount,ci.totalMax)..totalmax
end
if SI.specialCurrency[idx] and SI.specialCurrency[idx].relatedItem then
if SI.specialCurrency[idx].relatedItem.holdingMax then
local holdingMax = SI.specialCurrency[idx].relatedItem.holdingMax
if SI.db.Tooltip.CurrencyMax then
str = str .. " (" .. CurrencyColor(ci.relatedItemCount or 0, holdingMax) .. "/" .. holdingMax .. ")"
else
str = str .. " (" .. CurrencyColor(ci.relatedItemCount or 0, holdingMax) .. ")"
end
else
str = str .. " (" .. (ci.relatedItemCount or 0) .. ")"
end
end
end
if str then
if not SI.db.Tooltip.CurrencyValueColor then
str = ClassColorise(t.Class,str)
end
tooltip:SetCell(currLine, col, str, "CENTER",maxcol)
tooltip:SetCellScript(currLine, col, "OnEnter", hoverTooltip.ShowCurrencyTooltip, {toon, idx, ci})
tooltip:SetCellScript(currLine, col, "OnLeave", CloseTooltips)
tooltip:SetCellScript(currLine, col, "OnMouseDown", OpenCurrency)
end
end
end
end
end
end
-- toon names
for toondiff, col in pairs(columns) do
local toon = strsub(toondiff, 1, #toondiff-1)
local diff = strsub(toondiff, #toondiff, #toondiff)
if diff == "1" then
local toonname, toonserver = toon:match('^(.*) [-] (.*)$')
local toonstr = toonname
if db.Tooltip.ShowServer then
toonstr = toonstr .. "\n" .. toonserver
end
tooltip:SetCell(headLine, col, ClassColorise(SI.db.Toons[toon].Class, toonstr),
tooltip:GetHeaderFont(), "CENTER", maxcol)
tooltip:SetCellScript(headLine, col, "OnEnter", hoverTooltip.ShowToonTooltip, toon)
tooltip:SetCellScript(headLine, col, "OnLeave", CloseTooltips)
end
end
-- we now know enough to put in the category names where necessary
if SI.db.Tooltip.ShowCategories then
for category, row in pairs(categoryrow) do
if (categories > 1 or SI.db.Tooltip.ShowSoloCategory) and categoryshown[category] then
tooltip:SetCell(row, 1, YELLOWFONT .. SI.Categories[category] .. FONTEND, "LEFT", tooltip:GetColumnCount())
end
end
end
local hi = true
for i=2,tooltip:GetLineCount() do -- row highlighting
tooltip:SetLineScript(i, "OnEnter", DoNothing)
tooltip:SetLineScript(i, "OnLeave", DoNothing)
if hi and not blankrow[i] then
tooltip:SetLineColor(i, 1,1,1, db.Tooltip.RowHighlight)
hi = false
else
tooltip:SetLineColor(i, 0,0,0, 0)
hi = true
end
end
-- finishing up, with hints
if TableLen(instancerow) == 0 then
local noneLine = tooltip:AddLine()
tooltip:SetCell(noneLine, 1, GRAYFONT .. NO_RAID_INSTANCES_SAVED .. FONTEND, "LEFT", tooltip:GetColumnCount())
end
if SI.db.Tooltip.ShowHints then
tooltip:AddSeparator(8,0,0,0,0)
local hintLine, hintCol
if not Tooltip:IsDetached() then
hintLine, hintCol = tooltip:AddLine()
tooltip:SetCell(hintLine, hintCol, L["|cffffff00Left-click|r to detach tooltip"], "LEFT", tooltip:GetColumnCount())
hintLine, hintCol = tooltip:AddLine()
tooltip:SetCell(hintLine, hintCol, L["|cffffff00Middle-click|r to show Blizzard's Raid Information"], "LEFT", tooltip:GetColumnCount())
hintLine, hintCol = tooltip:AddLine()
tooltip:SetCell(hintLine, hintCol, L["|cffffff00Right-click|r to configure SavedInstances"], "LEFT", tooltip:GetColumnCount())
end
hintLine, hintCol = tooltip:AddLine()
tooltip:SetCell(hintLine, hintCol, L["Hover mouse on indicator for details"], "LEFT", tooltip:GetColumnCount())
if not showall then
hintLine, hintCol = tooltip:AddLine()
tooltip:SetCell(hintLine, hintCol, L["Hold Alt to show all data"], "LEFT", math.max(1,tooltip:GetColumnCount()-maxcol))
if tooltip:GetColumnCount() < maxcol+1 then
tooltip:AddLine("SavedInstances".." version "..SI.version)
else
tooltip:SetCell(hintLine, tooltip:GetColumnCount()-maxcol+1, SI.version, "RIGHT", maxcol)
end
end
end
-- cache check
local fail = false
local maxidx = 0
for toon,val in cpairs(columnCache[showall]) do
if not val then -- remove stale column
columnCache[showall][toon] = nil
fail = true
else
local thisidx = columns[toon..1]
if thisidx < maxidx then -- sort failure caused by new middle-insertion
fail = true
end
maxidx = thisidx
end
end
if fail then -- retry with corrected cache
SI:Debug("Tooltip cache miss")
SI.scaleCache[showall] = nil
--SI:ShowTooltip(anchorframe)
-- reschedule continuation to reduce time-slice exceeded errors in combat
SI:ScheduleTimer("ShowTooltip", 0, anchorframe)
else -- render it
SI:SkinFrame(tooltip,"SavedInstancesTooltip")
if Tooltip:IsDetached() then
local detachFrame = Tooltip:GetDetachedFrame()
tooltip:Show()
QTip.layoutCleaner:CleanupLayouts()
tooltip:ClearAllPoints()
tooltip:SetPoint("BOTTOMLEFT", detachFrame)
tooltip:SetFrameLevel(detachFrame:GetFrameLevel() + 1)
else
tooltip:SmartAnchorTo(anchorframe)
tooltip:SetAutoHideDelay(0.1, anchorframe)
tooltip:Show()
end
if db.Tooltip.FitToScreen then
-- scale check
QTip.layoutCleaner:CleanupLayouts()
local scale = tooltip:GetScale()
local w,h = tooltip:GetSize()
local sw,sh = UIParent:GetSize()
w = w*scale
h = h*scale
if w > sw or h > sh then
scale = scale / math.max(w/sw, h/sh)
scale = scale*0.95 -- 5% slop to speed convergeance
SI:Debug("Downscaling to %.4f",scale)
tooltip:SetScale(scale)
tooltip:Hide()
SI.scaleCache[showall] = scale
SI:ScheduleTimer("ShowTooltip", 0, anchorframe) -- re-render fonts
end
end
end
starttime = debugprofilestop()-starttime
SI:Debug("ShowTooltip(): completed in %.3fms", starttime)
end