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.
3702 lines
117 KiB
3702 lines
117 KiB
|
|
local Details = _G.Details
|
|
local addonName, Details222 = ...
|
|
local Loc = LibStub("AceLocale-3.0"):GetLocale( "Details" )
|
|
---@framework
|
|
local detailsFramework = DetailsFramework
|
|
local _
|
|
|
|
local UnitGUID = UnitGUID
|
|
local UnitGroupRolesAssigned = DetailsFramework.UnitGroupRolesAssigned
|
|
local GetNumGroupMembers = GetNumGroupMembers
|
|
local GetSpellInfo = Details222.GetSpellInfo
|
|
local select = select
|
|
local floor = floor
|
|
|
|
local CONST_INSPECT_ACHIEVEMENT_DISTANCE = 1 --Compare Achievements, 28 yards
|
|
local CONST_SPELLBOOK_GENERAL_TABID = 1
|
|
local CONST_SPELLBOOK_CLASSSPELLS_TABID = 2
|
|
|
|
local GetItemInfo = C_Item and C_Item.GetItemInfo or GetItemInfo
|
|
|
|
local storageDebug = false --remember to turn this to false!
|
|
|
|
function Details:UpdateGears()
|
|
Details:UpdateParser()
|
|
Details:UpdateControl()
|
|
Details:UpdateCombat()
|
|
end
|
|
|
|
---@alias raid_difficulty_eng_name_lowercase "normal" | "heroic" | "mythic" | "raidfinder"
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
--chat hooks
|
|
|
|
Details.chat_embed = Details:CreateEventListener()
|
|
Details.chat_embed.startup = true
|
|
|
|
Details.chat_embed.hook_settabname = function(frame, name, doNotSave)
|
|
if (not doNotSave) then
|
|
if (Details.chat_tab_embed.enabled and Details.chat_tab_embed.tab_name ~= "") then
|
|
if (Details.chat_tab_embed_onframe == frame) then
|
|
Details.chat_tab_embed.tab_name = name
|
|
Details:DelayOptionsRefresh(Details:GetInstance(1))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Details.chat_embed.hook_closetab = function(frame, fallback)
|
|
if (Details.chat_tab_embed.enabled and Details.chat_tab_embed.tab_name ~= "") then
|
|
if (Details.chat_tab_embed_onframe == frame) then
|
|
Details.chat_tab_embed.enabled = false
|
|
Details.chat_tab_embed.tab_name = ""
|
|
Details.chat_tab_embed_onframe = nil
|
|
Details:DelayOptionsRefresh(Details:GetInstance(1))
|
|
Details.chat_embed:ReleaseEmbed()
|
|
end
|
|
end
|
|
end
|
|
|
|
hooksecurefunc("FCF_SetWindowName", Details.chat_embed.hook_settabname)
|
|
hooksecurefunc("FCF_Close", Details.chat_embed.hook_closetab)
|
|
|
|
function Details.chat_embed:SetTabSettings(tab_name, bNewStateEnabled, is_single)
|
|
local current_enabled_state = Details.chat_tab_embed.enabled
|
|
local current_name = Details.chat_tab_embed.tab_name
|
|
local current_is_single = Details.chat_tab_embed.single_window
|
|
|
|
tab_name = tab_name or Details.chat_tab_embed.tab_name
|
|
if (bNewStateEnabled == nil) then
|
|
bNewStateEnabled = Details.chat_tab_embed.enabled
|
|
end
|
|
if (is_single == nil) then
|
|
is_single = Details.chat_tab_embed.single_window
|
|
end
|
|
|
|
Details.chat_tab_embed.tab_name = tab_name or ""
|
|
Details.chat_tab_embed.enabled = bNewStateEnabled
|
|
Details.chat_tab_embed.single_window = is_single
|
|
|
|
if (current_name ~= tab_name) then
|
|
--rename the tab on chat frame
|
|
local ChatFrame = Details.chat_embed:GetTab(current_name)
|
|
if (ChatFrame) then
|
|
FCF_SetWindowName(ChatFrame, tab_name, false)
|
|
end
|
|
end
|
|
|
|
if (bNewStateEnabled) then
|
|
--was disabled, so we need to save the current window positions.
|
|
if (not current_enabled_state) then
|
|
local window1 = Details:GetInstance(1)
|
|
if (window1) then
|
|
window1:SaveMainWindowPosition()
|
|
if (window1.libwindow) then
|
|
local pos = window1:CreatePositionTable()
|
|
Details.chat_tab_embed.w1_pos = pos
|
|
end
|
|
end
|
|
|
|
local window2 = Details:GetInstance(2)
|
|
if (window2) then
|
|
window2:SaveMainWindowPosition()
|
|
if (window2.libwindow) then
|
|
local pos = window2:CreatePositionTable()
|
|
Details.chat_tab_embed.w2_pos = pos
|
|
end
|
|
end
|
|
|
|
elseif (not is_single and current_is_single) then
|
|
local window2 = Details:GetInstance(2)
|
|
if (window2) then
|
|
window2:SaveMainWindowPosition()
|
|
if (window2.libwindow) then
|
|
local pos = window2:CreatePositionTable()
|
|
Details.chat_tab_embed.w2_pos = pos
|
|
end
|
|
end
|
|
end
|
|
|
|
--need to make the embed
|
|
Details.chat_embed:DoEmbed()
|
|
else
|
|
--need to release the frame
|
|
if (current_enabled_state) then
|
|
Details.chat_embed:ReleaseEmbed()
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details.chat_embed:CheckChatEmbed(bIsStartup)
|
|
if (Details.chat_tab_embed.enabled) then
|
|
Details.chat_embed:DoEmbed(bIsStartup)
|
|
end
|
|
end
|
|
|
|
--debug
|
|
-- /run _detalhes.chat_embed:SetTabSettings("Dano", true, false)
|
|
-- /run _detalhes.chat_embed:SetTabSettings(nil, false, false)
|
|
-- /dump _detalhes.chat_tab_embed.tab_name
|
|
|
|
function Details.chat_embed:DelayedChatEmbed()
|
|
Details.chat_embed.startup = nil
|
|
Details.chat_embed:DoEmbed()
|
|
end
|
|
|
|
function Details.chat_embed:DoEmbed(bIsStartup)
|
|
if (Details.chat_embed.startup and not bIsStartup) then
|
|
if (Details.AddOnStartTime + 5 < GetTime()) then
|
|
Details.chat_embed.startup = nil
|
|
else
|
|
return
|
|
end
|
|
end
|
|
|
|
if (bIsStartup) then
|
|
return Details.chat_embed:ScheduleTimer("DelayedChatEmbed", 5)
|
|
end
|
|
|
|
local tabname = Details.chat_tab_embed.tab_name
|
|
|
|
if (Details.chat_tab_embed.enabled and tabname ~= "") then
|
|
local chatFrame, chatFrameTab, chatFrameBackground = Details.chat_embed:GetTab(tabname)
|
|
|
|
if (not chatFrame) then
|
|
FCF_OpenNewWindow(tabname)
|
|
chatFrame, chatFrameTab, chatFrameBackground = Details.chat_embed:GetTab(tabname)
|
|
end
|
|
|
|
if (chatFrame) then
|
|
for index, t in pairs(chatFrame.messageTypeList) do
|
|
ChatFrame_RemoveMessageGroup(chatFrame, t)
|
|
chatFrame.messageTypeList [index] = nil
|
|
end
|
|
|
|
Details.chat_tab_embed_onframe = chatFrame
|
|
|
|
if (Details.chat_tab_embed.single_window) then
|
|
--only one window
|
|
local window1 = Details:GetInstance(1)
|
|
|
|
window1:UngroupInstance()
|
|
window1.baseframe:ClearAllPoints()
|
|
|
|
window1.baseframe:SetParent(chatFrame)
|
|
|
|
window1.rowframe:SetParent(window1.baseframe)
|
|
window1.rowframe:ClearAllPoints()
|
|
window1.rowframe:SetAllPoints()
|
|
|
|
window1.windowSwitchButton:SetParent(window1.baseframe)
|
|
window1.windowSwitchButton:ClearAllPoints()
|
|
window1.windowSwitchButton:SetAllPoints()
|
|
|
|
local topOffset = window1.toolbar_side == 1 and -20 or 0
|
|
local bottomOffset =(window1.show_statusbar and 14 or 0) + (window1.toolbar_side == 2 and 20 or 0)
|
|
|
|
window1.baseframe:SetPoint("topleft", chatFrameBackground, "topleft", 0, topOffset + Details.chat_tab_embed.y_offset)
|
|
window1.baseframe:SetPoint("bottomright", chatFrameBackground, "bottomright", Details.chat_tab_embed.x_offset, bottomOffset)
|
|
|
|
window1:LockInstance(true)
|
|
window1:SaveMainWindowPosition()
|
|
|
|
local window2 = Details:GetInstance(2)
|
|
if (window2 and window2.baseframe) then
|
|
if (window2.baseframe:GetParent() == chatFrame) then
|
|
--need to detach
|
|
Details.chat_embed:ReleaseEmbed(true)
|
|
end
|
|
end
|
|
else
|
|
--window #1 and #2
|
|
local window1 = Details:GetInstance(1)
|
|
local window2 = Details:GetInstance(2)
|
|
if (not window2) then
|
|
window2 = Details:CriarInstancia()
|
|
end
|
|
|
|
window1:UngroupInstance()
|
|
window2:UngroupInstance()
|
|
window1.baseframe:ClearAllPoints()
|
|
window2.baseframe:ClearAllPoints()
|
|
|
|
window1.baseframe:SetParent(chatFrame)
|
|
window2.baseframe:SetParent(chatFrame)
|
|
window1.rowframe:SetParent(window1.baseframe)
|
|
window2.rowframe:SetParent(window2.baseframe)
|
|
|
|
window1.windowSwitchButton:SetParent(window1.baseframe)
|
|
window1.windowSwitchButton:ClearAllPoints()
|
|
window1.windowSwitchButton:SetAllPoints()
|
|
window2.windowSwitchButton:SetParent(window2.baseframe)
|
|
window2.windowSwitchButton:ClearAllPoints()
|
|
window2.windowSwitchButton:SetAllPoints()
|
|
|
|
window1:LockInstance(true)
|
|
window2:LockInstance(true)
|
|
|
|
local statusbar_enabled1 = window1.show_statusbar
|
|
local statusbar_enabled2 = window2.show_statusbar
|
|
|
|
Details:Destroy(window1.snap)
|
|
Details:Destroy(window2.snap)
|
|
window1.snap[3] = 2; window2.snap[1] = 1;
|
|
window1.horizontalSnap = true; window2.horizontalSnap = true
|
|
|
|
local topOffset = window1.toolbar_side == 1 and -20 or 0
|
|
local bottomOffset = (window1.show_statusbar and 14 or 0) + (window1.toolbar_side == 2 and 20 or 0)
|
|
|
|
local width = chatFrameBackground:GetWidth() / 2
|
|
local height = chatFrameBackground:GetHeight() - bottomOffset + topOffset
|
|
|
|
window1.baseframe:SetSize(width +(Details.chat_tab_embed.x_offset/2), height + Details.chat_tab_embed.y_offset)
|
|
window2.baseframe:SetSize(width +(Details.chat_tab_embed.x_offset/2), height + Details.chat_tab_embed.y_offset)
|
|
|
|
window1.baseframe:SetPoint("topleft", chatFrameBackground, "topleft", 0, topOffset + Details.chat_tab_embed.y_offset)
|
|
window2.baseframe:SetPoint("topright", chatFrameBackground, "topright", Details.chat_tab_embed.x_offset, topOffset + Details.chat_tab_embed.y_offset)
|
|
|
|
window1:SaveMainWindowPosition()
|
|
window2:SaveMainWindowPosition()
|
|
|
|
--/dump ChatFrame3Background:GetSize()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details.chat_embed:ReleaseEmbed(bSecondWindow)
|
|
--release
|
|
local window1 = Details:GetInstance(1)
|
|
local window2 = Details:GetInstance(2)
|
|
|
|
if (bSecondWindow) then
|
|
window2:UngroupInstance()
|
|
window2.baseframe:ClearAllPoints()
|
|
window2.baseframe:SetParent(UIParent)
|
|
window2.rowframe:SetParent(UIParent)
|
|
window2.rowframe:ClearAllPoints()
|
|
window2.windowSwitchButton:SetParent(UIParent)
|
|
window2.baseframe:SetPoint("center", UIParent, "center", 200, 0)
|
|
window2.rowframe:SetPoint("center", UIParent, "center", 200, 0)
|
|
window2:LockInstance(false)
|
|
window2:SaveMainWindowPosition()
|
|
|
|
local previous_pos = Details.chat_tab_embed.w2_pos
|
|
if (previous_pos) then
|
|
window2:RestorePositionFromPositionTable(previous_pos)
|
|
end
|
|
return
|
|
end
|
|
window1:UngroupInstance();
|
|
window1.baseframe:ClearAllPoints()
|
|
window1.baseframe:SetParent(UIParent)
|
|
window1.rowframe:SetParent(UIParent)
|
|
window1.windowSwitchButton:SetParent(UIParent)
|
|
window1.baseframe:SetPoint("center", UIParent, "center")
|
|
window1.rowframe:SetPoint("center", UIParent, "center")
|
|
window1:LockInstance(false)
|
|
window1:SaveMainWindowPosition()
|
|
|
|
local previous_pos = Details.chat_tab_embed.w1_pos
|
|
if (previous_pos) then
|
|
window1:RestorePositionFromPositionTable(previous_pos)
|
|
end
|
|
|
|
if (not Details.chat_tab_embed.single_window and window2) then
|
|
window2:UngroupInstance()
|
|
window2.baseframe:ClearAllPoints()
|
|
window2.baseframe:SetParent(UIParent)
|
|
window2.rowframe:SetParent(UIParent)
|
|
window2.windowSwitchButton:SetParent(UIParent);
|
|
window2.baseframe:SetPoint("center", UIParent, "center", 200, 0)
|
|
window2.rowframe:SetPoint("center", UIParent, "center", 200, 0)
|
|
window2:LockInstance(false)
|
|
window2:SaveMainWindowPosition()
|
|
|
|
local previousPos = Details.chat_tab_embed.w2_pos
|
|
if (previousPos) then
|
|
window2:RestorePositionFromPositionTable(previousPos)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details.chat_embed:GetTab(tabname)
|
|
tabname = tabname or Details.chat_tab_embed.tab_name
|
|
for i = 1, 20 do
|
|
local tabtext = _G ["ChatFrame" .. i .. "Tab"]
|
|
if (tabtext) then
|
|
if (tabtext:GetText() == tabname) then
|
|
return _G ["ChatFrame" .. i], _G ["ChatFrame" .. i .. "Tab"], _G ["ChatFrame" .. i .. "Background"], i
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--[[
|
|
--create a tab on chat
|
|
--FCF_OpenNewWindow(name)
|
|
--rename it? perhaps need to hook
|
|
--FCF_SetWindowName(chatFrame, name, true) --FCF_SetWindowName(3, "DDD", true)
|
|
--/run local chatFrame = _G["ChatFrame3"]; FCF_SetWindowName(chatFrame, "DDD", true)
|
|
|
|
--FCF_SetWindowName(frame, name, doNotSave)
|
|
--API SetChatWindowName(frame:GetID(), name); -- set when doNotSave is false
|
|
|
|
-- need to store the chat frame reference
|
|
-- hook set window name and check if the rename was on our window
|
|
|
|
--FCF_Close
|
|
-- ^ when the window is closed
|
|
--]]
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
function Details:SetDeathLogLimit(limitAmount)
|
|
if (limitAmount and type(limitAmount) == "number" and limitAmount >= 8) then
|
|
Details.deadlog_events = limitAmount
|
|
|
|
local combatObject = Details:GetCurrentCombat()
|
|
|
|
for playerName, eventTable in pairs(combatObject.player_last_events) do
|
|
if (limitAmount > #eventTable) then
|
|
for i = #eventTable + 1, limitAmount do
|
|
eventTable [i] = {}
|
|
end
|
|
else
|
|
eventTable.n = 1
|
|
for _, t in ipairs(eventTable) do
|
|
Details:Destroy(t)
|
|
end
|
|
end
|
|
end
|
|
|
|
Details:UpdateParserGears()
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------------------------------------------
|
|
|
|
function Details:TrackSpecsNow(bTrackEverything)
|
|
local specSpellList = Details.SpecSpellList
|
|
---@type combat
|
|
local currentCombat = Details:GetCurrentCombat()
|
|
|
|
if (not bTrackEverything) then
|
|
local damageContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_DAMAGE) --DETAILS_ATTRIBUTE_DAMAGE is the integer 1, container 1 store DAMAGER data
|
|
for _, actor in damageContainer:ListActors() do
|
|
---@cast actor actor
|
|
if (actor:IsPlayer()) then
|
|
for spellId, spellTable in pairs(actor:GetSpellList()) do
|
|
if (specSpellList[spellTable.id]) then
|
|
actor:SetSpecId(specSpellList[spellTable.id])
|
|
Details.cached_specs[actor.serial] = actor.spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local healContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_HEAL) --DETAILS_ATTRIBUTE_HEAL is the integer 2, container 2 store heal data
|
|
for _, actor in healContainer:ListActors() do
|
|
---@cast actor actor
|
|
if (actor:IsPlayer()) then
|
|
for spellId, spellTable in pairs(actor:GetSpellList()) do
|
|
if (specSpellList[spellTable.id]) then
|
|
actor:SetSpecId(specSpellList[spellTable.id])
|
|
Details.cached_specs[actor.serial] = actor.spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
else
|
|
---@type combat[]
|
|
local combatList = {}
|
|
---@type combat[]
|
|
local segmentsTable = Details:GetCombatSegments()
|
|
---@type combat
|
|
local combatOverall = Details:GetOverallCombat()
|
|
|
|
for _, combat in ipairs(segmentsTable) do
|
|
tinsert(combatList, combat)
|
|
end
|
|
|
|
tinsert(combatList, currentCombat)
|
|
tinsert(combatList, combatOverall)
|
|
|
|
for _, combatObject in ipairs(combatList) do
|
|
local damageContainer = combatObject:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
|
|
for _, actor in damageContainer:ListActors() do
|
|
---@cast actor actor
|
|
if (actor:IsPlayer()) then
|
|
for spellId, spellTable in pairs(actor:GetSpellList()) do
|
|
if (specSpellList[spellTable.id]) then
|
|
actor:SetSpecId(specSpellList[spellTable.id])
|
|
Details.cached_specs[actor.serial] = actor.spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local healContainer = combatObject:GetContainer(DETAILS_ATTRIBUTE_HEAL)
|
|
for _, actor in healContainer:ListActors() do
|
|
---@cast actor actor
|
|
if (actor:IsPlayer()) then
|
|
for spellId, spellTable in pairs(actor:GetSpellList()) do
|
|
if (specSpellList[spellTable.id]) then
|
|
actor:SetSpecId(specSpellList[spellTable.id])
|
|
Details.cached_specs[actor.serial] = actor.spec
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details:ResetSpecCache(forced)
|
|
local bIsInInstance = IsInInstance()
|
|
|
|
if (forced or (not bIsInInstance and not Details.in_group)) then
|
|
Details:Destroy(Details.cached_specs)
|
|
|
|
if (Details.track_specs) then
|
|
local playerSpec = DetailsFramework.GetSpecialization()
|
|
if (type(playerSpec) == "number") then
|
|
local specId = DetailsFramework.GetSpecializationInfo(playerSpec)
|
|
if (type(specId) == "number") then
|
|
local playerGuid = UnitGUID(Details.playername)
|
|
if (playerGuid) then
|
|
Details.cached_specs[playerGuid] = specId
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
elseif (Details.in_group and not bIsInInstance) then
|
|
Details:Destroy(Details.cached_specs)
|
|
|
|
if (Details.track_specs) then
|
|
if (IsInRaid()) then
|
|
---@type combat
|
|
local currentCombat = Details:GetCurrentCombat()
|
|
local damageContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
|
|
local healContainer = currentCombat:GetContainer(DETAILS_ATTRIBUTE_HEAL)
|
|
local unitIdRaidCache = Details222.UnitIdCache.Raid
|
|
|
|
for i = 1, GetNumGroupMembers() do
|
|
local unitName = Details:GetFullName(unitIdRaidCache[i])
|
|
local actorObject = damageContainer:GetActor(unitName)
|
|
if (actorObject and actorObject.spec) then
|
|
Details.cached_specs[actorObject.serial] = actorObject.spec
|
|
else
|
|
actorObject = healContainer:GetActor(unitName)
|
|
if (actorObject and actorObject.spec) then
|
|
Details.cached_specs[actorObject.serial] = actorObject.spec
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
local specialserials = {
|
|
["3209-082F39F5"] = true, --quick
|
|
}
|
|
|
|
function Details:RefreshUpdater(intervalAmount)
|
|
local updateInterval = intervalAmount or Details.update_speed
|
|
|
|
if (Details.streamer_config.faster_updates) then
|
|
--force 60 updates per second
|
|
updateInterval = 0.016
|
|
end
|
|
|
|
if (Details.atualizador) then
|
|
--_detalhes:CancelTimer(_detalhes.atualizador)
|
|
Details.Schedules.Cancel(Details.atualizador)
|
|
end
|
|
|
|
local specialSerial = UnitGUID("player") and UnitGUID("player"):gsub("Player%-", "")
|
|
if (specialserials[specialSerial]) then return end
|
|
|
|
--_detalhes.atualizador = _detalhes:ScheduleRepeatingTimer("RefreshMainWindow", updateInterval, -1)
|
|
--_detalhes.atualizador = Details.Schedules.NewTicker(updateInterval, Details.RefreshMainWindow, Details, -1)
|
|
Details.atualizador = C_Timer.NewTicker(updateInterval, Details.RefreshAllMainWindowsTemp)
|
|
end
|
|
|
|
---set the amount of time between each update of all windows
|
|
---@param newInterval number?
|
|
---@param bNoSave boolean?
|
|
function Details:SetWindowUpdateSpeed(newInterval, bNoSave)
|
|
if (not newInterval) then
|
|
newInterval = Details.update_speed
|
|
end
|
|
|
|
if (type(newInterval) ~= "number") then
|
|
newInterval = Details.update_speed or 0.3
|
|
end
|
|
|
|
if (not bNoSave) then
|
|
Details.update_speed = newInterval
|
|
end
|
|
|
|
Details:RefreshUpdater(newInterval)
|
|
end
|
|
|
|
function Details:SetUseAnimations(bEnableAnimations, bNoSave)
|
|
if (bEnableAnimations == nil) then
|
|
bEnableAnimations = Details.use_row_animations
|
|
end
|
|
|
|
if (not bNoSave) then
|
|
Details.use_row_animations = bEnableAnimations
|
|
end
|
|
|
|
Details.is_using_row_animations = bEnableAnimations
|
|
end
|
|
|
|
function Details:HavePerformanceProfileEnabled()
|
|
return Details.performance_profile_enabled
|
|
end
|
|
|
|
Details.PerformanceIcons = {
|
|
["RaidFinder"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, 1, 1, 1}},
|
|
["Raid15"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .8, 0, 1}},
|
|
["Raid30"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .8, 0, 1}},
|
|
["Mythic"] = {icon = [[Interface\PvPRankBadges\PvPRank15]], color = {1, .4, 0, 1}},
|
|
["Battleground15"] = {icon = [[Interface\PvPRankBadges\PvPRank07]], color = {1, 1, 1, 1}},
|
|
["Battleground40"] = {icon = [[Interface\PvPRankBadges\PvPRank07]], color = {1, 1, 1, 1}},
|
|
["Arena"] = {icon = [[Interface\PvPRankBadges\PvPRank12]], color = {1, 1, 1, 1}},
|
|
["Dungeon"] = {icon = [[Interface\PvPRankBadges\PvPRank01]], color = {1, 1, 1, 1}},
|
|
}
|
|
|
|
function Details:CheckForPerformanceProfile()
|
|
local performanceType = Details:GetPerformanceRaidType()
|
|
local profile = Details.performance_profiles[performanceType]
|
|
|
|
if (profile and profile.enabled) then
|
|
Details:SetWindowUpdateSpeed(profile.update_speed, true)
|
|
Details:SetUseAnimations(profile.use_row_animations, true)
|
|
Details:CaptureSet(profile.damage, "damage")
|
|
Details:CaptureSet(profile.heal, "heal")
|
|
Details:CaptureSet(profile.energy, "energy")
|
|
Details:CaptureSet(profile.miscdata, "miscdata")
|
|
Details:CaptureSet(profile.aura, "aura")
|
|
|
|
if (not Details.performance_profile_lastenabled or Details.performance_profile_lastenabled ~= performanceType) then
|
|
Details:InstanceAlert(Loc ["STRING_OPTIONS_PERFORMANCE_PROFILE_LOAD"] .. performanceType, {Details.PerformanceIcons [performanceType].icon, 14, 14, false, 0, 1, 0, 1, unpack(Details.PerformanceIcons [performanceType].color)} , 5, {Details.empty_function})
|
|
end
|
|
|
|
Details.performance_profile_enabled = performanceType
|
|
Details.performance_profile_lastenabled = performanceType
|
|
else
|
|
Details:SetWindowUpdateSpeed(Details.update_speed)
|
|
Details:SetUseAnimations(Details.use_row_animations)
|
|
Details:CaptureSet(Details.capture_real ["damage"], "damage")
|
|
Details:CaptureSet(Details.capture_real ["heal"], "heal")
|
|
Details:CaptureSet(Details.capture_real ["energy"], "energy")
|
|
Details:CaptureSet(Details.capture_real ["miscdata"], "miscdata")
|
|
Details:CaptureSet(Details.capture_real ["aura"], "aura")
|
|
Details.performance_profile_enabled = nil
|
|
end
|
|
|
|
end
|
|
|
|
function Details:GetPerformanceRaidType()
|
|
local name, instanceType, difficulty, difficultyName, maxPlayers = GetInstanceInfo()
|
|
|
|
if (instanceType == "none") then
|
|
return nil
|
|
end
|
|
|
|
if (instanceType == "pvp") then
|
|
if (maxPlayers == 40) then
|
|
return "Battleground40"
|
|
elseif (maxPlayers == 15) then
|
|
return "Battleground15"
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
if (instanceType == "arena") then
|
|
return "Arena"
|
|
end
|
|
|
|
if (instanceType == "raid") then
|
|
--mythic
|
|
if (difficulty == 15) then
|
|
return "Mythic"
|
|
end
|
|
|
|
--raid finder
|
|
if (difficulty == 7) then
|
|
return "RaidFinder"
|
|
end
|
|
|
|
--flex
|
|
if (difficulty == 14) then
|
|
if (GetNumGroupMembers() > 15) then
|
|
return "Raid30"
|
|
else
|
|
return "Raid15"
|
|
end
|
|
end
|
|
|
|
--normal heroic
|
|
if (maxPlayers == 10) then
|
|
return "Raid15"
|
|
elseif (maxPlayers == 25) then
|
|
return "Raid30"
|
|
end
|
|
end
|
|
|
|
if (instanceType == "party") then
|
|
return "Dungeon"
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--background tasks
|
|
|
|
local backgroundTasks = {}
|
|
local taskTimers = {
|
|
["LOW"] = 30,
|
|
["MEDIUM"] = 18,
|
|
["HIGH"] = 10,
|
|
}
|
|
|
|
function Details:RegisterBackgroundTask(name, func, priority, ...)
|
|
if true then return end
|
|
assert(type(self) == "table", "RegisterBackgroundTask 'self' must be a table.")
|
|
assert(type(name) == "string", "RegisterBackgroundTask param #1 must be a string.")
|
|
if (type(func) == "string") then
|
|
assert(type(self[func]) == "function", "RegisterBackgroundTask param #2 function not found on main object.")
|
|
else
|
|
assert(type(func) == "function", "RegisterBackgroundTask param #2 expect a function or function name.")
|
|
end
|
|
|
|
priority = priority or "LOW"
|
|
priority = string.upper(priority)
|
|
|
|
if (not taskTimers[priority]) then
|
|
priority = "LOW"
|
|
end
|
|
|
|
if (backgroundTasks[name]) then
|
|
backgroundTasks[name].func = func
|
|
backgroundTasks[name].priority = priority
|
|
backgroundTasks[name].args = {...}
|
|
backgroundTasks[name].args_amt = select("#", ...)
|
|
backgroundTasks[name].object = self
|
|
return
|
|
else
|
|
backgroundTasks[name] = {func = func, lastexec = time(), priority = priority, nextexec = time() + taskTimers [priority] * 60, args = {...}, args_amt = select("#", ...), object = self}
|
|
end
|
|
end
|
|
|
|
function Details:UnregisterBackgroundTask(name)
|
|
backgroundTasks[name] = nil
|
|
end
|
|
|
|
function Details:DoBackgroundTasks()
|
|
if (Details:GetZoneType() ~= "none" or Details:InGroup()) then
|
|
return
|
|
end
|
|
|
|
local t = time()
|
|
|
|
for taskName, taskTable in pairs(backgroundTasks) do
|
|
if (t > taskTable.nextexec) then
|
|
if (type(taskTable.func) == "string") then
|
|
taskTable.object[taskTable.func](taskTable.object, unpack(taskTable.args, 1, taskTable.args_amt))
|
|
else
|
|
taskTable.func(unpack(taskTable.args, 1, taskTable.args_amt))
|
|
end
|
|
|
|
taskTable.nextexec = math.random(30, 120) + t + (taskTimers[taskTable.priority] * 60)
|
|
end
|
|
end
|
|
end
|
|
|
|
Details.background_tasks_loop = Details:ScheduleRepeatingTimer("DoBackgroundTasks", 120)
|
|
|
|
------
|
|
local hasGroupMemberInCombat = function()
|
|
--iterate over party or raid members and check if any one of them are in combat, if any are return true
|
|
if (IsInRaid()) then
|
|
local amountOfPartyMembers = GetNumGroupMembers()
|
|
for i, unitId in ipairs(Details222.UnitIdCache.Raid) do
|
|
if (i <= amountOfPartyMembers) then
|
|
if (UnitAffectingCombat(unitId)) then
|
|
return true
|
|
end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
else
|
|
local amountOfPartyMembers = GetNumGroupMembers() + 1
|
|
for i, unitId in ipairs(Details222.UnitIdCache.Party) do
|
|
if (i <= amountOfPartyMembers) then
|
|
if (UnitAffectingCombat(unitId)) then
|
|
return true
|
|
end
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
local checkForGroupCombat_Ticker = function()
|
|
local instanceName, isntanceType = GetInstanceInfo()
|
|
if (isntanceType ~= "none") then
|
|
if (Details222.parser_frame:GetScript("OnEvent") ~= Details222.Parser.OnParserEvent) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
end
|
|
Details222.Parser.EventFrame.ticker:Cancel()
|
|
Details222.Parser.EventFrame.ticker = nil
|
|
return
|
|
end
|
|
|
|
if (hasGroupMemberInCombat()) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
else
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
|
|
Details222.Parser.EventFrame.ticker:Cancel()
|
|
Details222.Parser.EventFrame.ticker = nil
|
|
end
|
|
end
|
|
|
|
--~parser
|
|
local bConsiderGroupMembers = true
|
|
Details222.Parser.Handler = {}
|
|
Details222.Parser.EventFrame = CreateFrame("frame")
|
|
Details222.Parser.EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
Details222.Parser.EventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
|
Details222.Parser.EventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
|
|
Details222.Parser.EventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
|
|
Details222.Parser.EventFrame:SetScript("OnEvent", function(self, event, ...)
|
|
local instanceName, isntanceType = GetInstanceInfo()
|
|
|
|
if (isntanceType == "pvp" or isntanceType == "arena") then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventPVP)
|
|
return
|
|
end
|
|
|
|
if (isntanceType ~= "none") then
|
|
if (Details222.parser_frame:GetScript("OnEvent") ~= Details222.Parser.OnParserEvent) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
end
|
|
return
|
|
end
|
|
|
|
if (event == "PLAYER_ENTERING_WORLD" or event == "ZONE_CHANGED_NEW_AREA") then
|
|
if (bConsiderGroupMembers) then
|
|
--check if any group member is in combat
|
|
if (hasGroupMemberInCombat()) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
--initiate a ticker to check if a unit in the group is still in combat
|
|
if (not Details222.Parser.EventFrame.ticker) then
|
|
Details222.Parser.EventFrame.ticker = C_Timer.NewTicker(1, checkForGroupCombat_Ticker)
|
|
end
|
|
else
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
|
|
end
|
|
else
|
|
--player is alone
|
|
if (InCombatLockdown()) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
else
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
|
|
end
|
|
end
|
|
|
|
elseif (event == "PLAYER_REGEN_DISABLED") then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
|
|
elseif (event == "PLAYER_REGEN_ENABLED") then
|
|
if (bConsiderGroupMembers) then
|
|
--check if any group member is in combat
|
|
if (hasGroupMemberInCombat()) then
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEvent)
|
|
--initiate a ticker to check if a unit in the group is still in combat
|
|
if (not Details222.Parser.EventFrame.ticker) then
|
|
Details222.Parser.EventFrame.ticker = C_Timer.NewTicker(1, checkForGroupCombat_Ticker)
|
|
end
|
|
else
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
|
|
end
|
|
else
|
|
Details222.parser_frame:SetScript("OnEvent", Details222.Parser.OnParserEventOutOfCombat)
|
|
end
|
|
end
|
|
end)
|
|
|
|
--create a details listener which registers the combat enter event, create the function to receive the registerted event ad call the Details222.Parser.EventFrame:OnEvent with player_regen_disable
|
|
local detailsEnterInCombatListener = Details:CreateEventListener()
|
|
detailsEnterInCombatListener:RegisterEvent("COMBAT_PLAYER_ENTER") --COMBAT_PLAYER_ENTER from events.lua, this event is triggered when Details! enter in combat
|
|
function detailsEnterInCombatListener:OnEvent()
|
|
if (Details222.Parser.GetState() == "STATE_RESTRICTED") then
|
|
Details222.Parser.EventFrame:GetScript("OnEvent")(Details222.Parser.EventFrame, "PLAYER_REGEN_DISABLED")
|
|
end
|
|
end
|
|
|
|
function Details222.Parser.GetState()
|
|
local parserEngine = Details222.parser_frame:GetScript("OnEvent")
|
|
if (parserEngine == Details222.Parser.OnParserEvent) then
|
|
return "STATE_REGULAR"
|
|
elseif (parserEngine == Details222.Parser.OnParserEventPVP) then
|
|
return "STATE_PVP"
|
|
elseif (parserEngine == Details222.Parser.OnParserEventOutOfCombat) then
|
|
return "STATE_RESTRICTED"
|
|
end
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--storage stuff ~storage
|
|
|
|
---@class details_storage_unitresult : table
|
|
---@field total number
|
|
---@field itemLevel number
|
|
---@field classId number
|
|
|
|
---@class details_encounterkillinfo : table
|
|
---@field guild guildname
|
|
---@field time unixtime
|
|
---@field date date
|
|
---@field elapsed number
|
|
---@field HEALER table<actorname, details_storage_unitresult>
|
|
---@field servertime unixtime
|
|
---@field DAMAGER table<actorname, details_storage_unitresult>
|
|
|
|
---@class details_bosskillinfo : table
|
|
---@field kills number
|
|
---@field wipes number
|
|
---@field time_fasterkill number
|
|
---@field time_fasterkill_when unixtime
|
|
---@field time_incombat number
|
|
---@field dps_best number
|
|
---@field dps_best_when unixtime
|
|
---@field dps_best_raid number
|
|
---@field dps_best_raid_when unixtime
|
|
|
|
---@class details_storage : table
|
|
---@field VERSION number the database version
|
|
---@field normal table<encounterid, details_encounterkillinfo[]>
|
|
---@field heroic table<encounterid, details_encounterkillinfo[]>
|
|
---@field mythic table<encounterid, details_encounterkillinfo[]>
|
|
---@field mythic_plus table
|
|
---@field saved_encounters table
|
|
---@field totalkills table<string, table<encounterid, details_bosskillinfo>>
|
|
|
|
---@class details_storage_feature : table
|
|
---@field diffNames string[] {"normal", "heroic", "mythic", "raidfinder"}
|
|
---@field OpenRaidStorage fun():details_storage
|
|
---@field HaveDataForEncounter fun(difficulty:string, encounterId:number, guildName:string|boolean):boolean
|
|
---@field GetBestFromGuild fun(difficulty:string, encounterId:number, role:role, dps:boolean, guildName:string):actorname, details_storage_unitresult, details_encounterkillinfo
|
|
---@field GetUnitGuildRank fun(difficulty:string, encounterId:number, role:role, guildName:guildname, unitName:actorname):number?, details_storage_unitresult?, details_encounterkillinfo?
|
|
---@field GetBestFromPlayer fun(difficulty:string, encounterId:number, role:role, dps:boolean, playerName:actorname):details_storage_unitresult, details_encounterkillinfo
|
|
---@field DBGuildSync fun()
|
|
|
|
local CONST_ADDONNAME_DATASTORAGE = "Details_DataStorage"
|
|
|
|
local diffNumberToName = Details222.storage.DiffIdToName
|
|
|
|
local createStorageTables = function()
|
|
local storageDatabase = DetailsDataStorage
|
|
|
|
if (not storageDatabase and Details.CreateStorageDB) then
|
|
storageDatabase = Details:CreateStorageDB()
|
|
if (not storageDatabase) then
|
|
return
|
|
end
|
|
|
|
elseif (not storageDatabase) then
|
|
return
|
|
end
|
|
|
|
return storageDatabase
|
|
end
|
|
|
|
---@return details_storage?
|
|
function Details222.storage.OpenRaidStorage()
|
|
--check if the storage is already loaded
|
|
if (not C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
local loaded, reason = C_AddOns.LoadAddOn(CONST_ADDONNAME_DATASTORAGE)
|
|
if (not loaded) then
|
|
return
|
|
end
|
|
end
|
|
|
|
--get the storage table
|
|
local savedData = DetailsDataStorage
|
|
|
|
if (not savedData and Details.CreateStorageDB) then
|
|
savedData = Details:CreateStorageDB()
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
elseif (not savedData) then
|
|
return
|
|
end
|
|
|
|
return savedData
|
|
end
|
|
|
|
---check if there is data for a specific encounter and difficulty, if a guildName is passed, check if there is data for the guild
|
|
---@param difficulty string
|
|
---@param encounterId number
|
|
---@param guildName string|boolean
|
|
---@return boolean bHasData
|
|
function Details222.storage.HaveDataForEncounter(difficulty, encounterId, guildName)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
if (not savedData) then
|
|
return false
|
|
end
|
|
|
|
difficulty = diffNumberToName[difficulty] or difficulty
|
|
|
|
if (guildName and type(guildName) == "boolean") then
|
|
guildName = GetGuildInfo("player")
|
|
end
|
|
|
|
---@type table<encounterid, details_encounterkillinfo[]>
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
local allEncountersStored = encountersTable[encounterId]
|
|
if (allEncountersStored) then
|
|
--didn't requested a guild name, so just return 'we have data for this encounter'
|
|
if (not guildName) then
|
|
return true
|
|
end
|
|
|
|
--data for a specific guild is requested, check if there is data for the guild
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
if (encounterKillInfo.guild == guildName) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
---find the best unit from a specific role from a specific guild in a specific encounter and difficulty
|
|
---check all encounters saved for the guild and difficulty and return the unit with the best performance
|
|
---@param difficulty string
|
|
---@param encounterId number
|
|
---@param role role
|
|
---@param dps boolean?
|
|
---@param guildName string
|
|
---@return boolean|string playerName
|
|
---@return boolean|details_storage_unitresult storageUnitResult
|
|
---@return boolean|details_encounterkillinfo encounterKillInfo
|
|
function Details222.storage.GetBestFromGuild(difficulty, encounterId, role, dps, guildName)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return false, false, false
|
|
end
|
|
|
|
if (not guildName) then
|
|
guildName = GetGuildInfo("player")
|
|
end
|
|
|
|
if (not guildName) then
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) GetBestFromGuild() guild name invalid.")
|
|
end
|
|
return false, false, false
|
|
end
|
|
|
|
local best = 0
|
|
local bestDps = 0
|
|
local bestEncounterKillInfo
|
|
local bestUnitName
|
|
local bestStorageResultTable
|
|
|
|
if (not role) then
|
|
role = "DAMAGER"
|
|
end
|
|
|
|
---@type table<encounterid, details_encounterkillinfo[]>
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
local allEncountersStored = encountersTable[encounterId]
|
|
if (allEncountersStored) then
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
if (encounterKillInfo.guild == guildName) then
|
|
---@type table<actorname, details_storage_unitresult>
|
|
local unitListFromRole = encounterKillInfo[role]
|
|
if (unitListFromRole) then
|
|
for unitName, storageUnitResult in pairs(unitListFromRole) do
|
|
if (dps) then
|
|
if (storageUnitResult.total / encounterKillInfo.elapsed > bestDps) then
|
|
bestDps = storageUnitResult.total / encounterKillInfo.elapsed
|
|
bestUnitName = unitName
|
|
bestEncounterKillInfo = encounterKillInfo
|
|
bestStorageResultTable = storageUnitResult
|
|
end
|
|
else
|
|
if (storageUnitResult.total > best) then
|
|
best = storageUnitResult.total
|
|
bestUnitName = unitName
|
|
bestEncounterKillInfo = encounterKillInfo
|
|
bestStorageResultTable = storageUnitResult
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return bestUnitName, bestStorageResultTable, bestEncounterKillInfo
|
|
end
|
|
|
|
---find and return the rank position of a unit among all other players guild
|
|
---the rank is based on the biggest total amount of damage or healing (role) done in a specific encounter and difficulty
|
|
---@param difficulty string
|
|
---@param encounterId number
|
|
---@param role role
|
|
---@param unitName actorname
|
|
---@param dps boolean?
|
|
---@param guildName guildname
|
|
---@return number positionIndex?
|
|
---@return details_storage_unitresult storageUnitResult?
|
|
---@return details_encounterkillinfo encounterKillInfo?
|
|
function Details222.storage.GetUnitGuildRank(difficulty, encounterId, role, unitName, dps, guildName)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
if (not guildName) then
|
|
guildName = GetGuildInfo("player")
|
|
end
|
|
|
|
if (not guildName) then
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) GetBestFromGuild() guild name invalid.")
|
|
end
|
|
return
|
|
end
|
|
|
|
if (not role) then
|
|
role = "DAMAGER"
|
|
end
|
|
|
|
---@class details_storage_unitscore : table
|
|
---@field total number
|
|
---@field persecond number
|
|
---@field storageUnitResult details_storage_unitresult?
|
|
---@field encounterKillInfo details_encounterkillinfo?
|
|
---@field unitName actorname?
|
|
|
|
---@type table<actorname, details_storage_unitscore>
|
|
local unitScores = {}
|
|
|
|
---@type table<encounterid, details_encounterkillinfo[]>
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
local allEncountersStored = encountersTable[encounterId]
|
|
if (allEncountersStored) then
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
if (encounterKillInfo.guild == guildName) then
|
|
local roleTable = encounterKillInfo[role]
|
|
for thisUnitName, storageUnitResult in pairs(roleTable) do
|
|
---@cast storageUnitResult details_storage_unitresult
|
|
if (not unitScores[thisUnitName]) then
|
|
unitScores[thisUnitName] = {
|
|
total = 0,
|
|
persecond = 0,
|
|
unitName = thisUnitName,
|
|
}
|
|
end
|
|
|
|
--in this part the code is searching what is the performance of each unit in
|
|
--all encounters saved for the guild in the specific difficulty and role
|
|
|
|
local total = storageUnitResult.total
|
|
local persecond = total / encounterKillInfo.elapsed
|
|
|
|
if (dps) then
|
|
if (persecond > unitScores[thisUnitName].persecond) then
|
|
unitScores[thisUnitName].total = total
|
|
unitScores[thisUnitName].persecond = total / encounterKillInfo.elapsed
|
|
unitScores[thisUnitName].storageUnitResult = storageUnitResult
|
|
unitScores[thisUnitName].encounterKillInfo = encounterKillInfo
|
|
end
|
|
else
|
|
if (total > unitScores[thisUnitName].total) then
|
|
unitScores[thisUnitName].total = total
|
|
unitScores[thisUnitName].persecond = total / encounterKillInfo.elapsed
|
|
unitScores[thisUnitName].storageUnitResult = storageUnitResult
|
|
unitScores[thisUnitName].encounterKillInfo = encounterKillInfo
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--if the unit requested in the function parameter is not in the unitScores table, return
|
|
if (not unitScores[unitName]) then
|
|
return
|
|
end
|
|
|
|
local sortedResults = {}
|
|
for playerName, playerTable in pairs(unitScores) do
|
|
playerTable[1] = playerTable.total
|
|
playerTable[2] = playerTable.persecond
|
|
tinsert(sortedResults, playerTable)
|
|
end
|
|
|
|
table.sort(sortedResults, dps and Details.Sort2 or Details.Sort1)
|
|
|
|
for positionIndex = 1, #sortedResults do
|
|
if (sortedResults[positionIndex].unitName == unitName) then
|
|
local result = {positionIndex, sortedResults[positionIndex].storageUnitResult, sortedResults[positionIndex].encounterKillInfo}
|
|
Details:Destroy(unitScores)
|
|
Details:Destroy(sortedResults)
|
|
return unpack(result)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
---find and return the best result from a specific unit in a specific encounter and difficulty
|
|
---@param difficulty string
|
|
---@param encounterId number
|
|
---@param role role
|
|
---@param unitName actorname
|
|
---@param dps boolean?
|
|
---@return details_storage_unitresult storageUnitResult?
|
|
---@return details_encounterkillinfo encounterKillInfo?
|
|
function Details222.storage.GetBestFromPlayer(difficulty, encounterId, role, unitName, dps)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
---@type details_storage_unitresult
|
|
local bestStorageUnitResult
|
|
---@type details_encounterkillinfo
|
|
local bestEncounterKillInfo
|
|
local topPerSecond
|
|
|
|
if (not role) then
|
|
role = "DAMAGER"
|
|
end
|
|
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
local allEncountersStored = encountersTable[encounterId]
|
|
if (allEncountersStored) then
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
local storageUnitResult = encounterKillInfo[role] and encounterKillInfo[role] [unitName]
|
|
if (storageUnitResult) then
|
|
if (bestStorageUnitResult) then
|
|
if (dps) then
|
|
if (storageUnitResult.total/encounterKillInfo.elapsed > topPerSecond) then
|
|
bestEncounterKillInfo = encounterKillInfo
|
|
bestStorageUnitResult = storageUnitResult
|
|
topPerSecond = storageUnitResult.total/encounterKillInfo.elapsed
|
|
end
|
|
else
|
|
if (storageUnitResult.total > bestStorageUnitResult.total) then
|
|
bestEncounterKillInfo = encounterKillInfo
|
|
bestStorageUnitResult = storageUnitResult
|
|
end
|
|
end
|
|
else
|
|
bestEncounterKillInfo = encounterKillInfo
|
|
bestStorageUnitResult = storageUnitResult
|
|
topPerSecond = storageUnitResult.total/encounterKillInfo.elapsed
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return bestStorageUnitResult, bestEncounterKillInfo
|
|
end
|
|
|
|
--network
|
|
function Details222.storage.DBGuildSync()
|
|
Details:SendGuildData("GS", "R")
|
|
end
|
|
|
|
local hasEncounterByEncounterSyncId = function(savedData, encounterSyncId)
|
|
local minTime = encounterSyncId - 120
|
|
local maxTime = encounterSyncId + 120
|
|
|
|
for difficultyId, encounterIdTable in pairs(savedData or {}) do
|
|
if (type(encounterIdTable) == "table") then
|
|
for dungeonEncounterID, encounterTable in pairs(encounterIdTable) do
|
|
for index, encounter in ipairs(encounterTable) do
|
|
--check if the encounter fits in the timespam window
|
|
if (encounter.time >= minTime and encounter.time <= maxTime) then
|
|
return true
|
|
end
|
|
if (encounter.servertime) then
|
|
if (encounter.servertime >= minTime and encounter.servertime <= maxTime) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local recentRequestedIDs = {}
|
|
local hasRecentRequestedEncounterSyncId = function(encounterSyncId)
|
|
local minTime = encounterSyncId - 120
|
|
local maxTime = encounterSyncId + 120
|
|
|
|
for requestedID in pairs(recentRequestedIDs) do
|
|
if (requestedID >= minTime and requestedID <= maxTime) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
|
|
local allowedBossesCached = nil
|
|
local getBossIdsForCurrentExpansion = function() --need to check this!
|
|
if (allowedBossesCached) then
|
|
return allowedBossesCached
|
|
end
|
|
|
|
--make a list of raids and bosses that belong to the current expansion
|
|
local _, bossInfoTable = Details:GetExpansionBossList()
|
|
local allowedBosses = {}
|
|
|
|
for bossId, bossTable in pairs(bossInfoTable) do
|
|
---@cast bossTable details_bossinfo
|
|
allowedBosses[bossTable.dungeonEncounterID] = true
|
|
allowedBosses[bossTable.journalEncounterID] = true
|
|
allowedBosses[bossId] = true
|
|
end
|
|
|
|
allowedBossesCached = allowedBosses
|
|
return allowedBosses
|
|
end
|
|
|
|
function Details:IsBossIdFromCurrentExpansion(bossId)
|
|
local allowedBosses = getBossIdsForCurrentExpansion()
|
|
return allowedBosses[bossId]
|
|
end
|
|
|
|
local currentExpZoneIds = nil
|
|
function Details:IsZoneIdFromCurrentExpansion(zoneId)
|
|
if (currentExpZoneIds) then
|
|
return currentExpZoneIds[zoneId]
|
|
end
|
|
|
|
currentExpZoneIds = {}
|
|
|
|
local _, bossInfoTable, raidInfoTable = Details:GetExpansionBossList()
|
|
for bossId, bossTable in pairs(bossInfoTable) do
|
|
---@cast bossTable details_bossinfo
|
|
currentExpZoneIds[bossTable.uiMapId] = true
|
|
currentExpZoneIds[bossTable.instanceId] = true
|
|
currentExpZoneIds[bossTable.journalInstanceId] = true
|
|
end
|
|
|
|
for raidInstanceID, raidTable in pairs(raidInfoTable) do
|
|
currentExpZoneIds[raidInstanceID] = true
|
|
currentExpZoneIds[raidTable.raidMapID] = true
|
|
end
|
|
|
|
return currentExpZoneIds[zoneId]
|
|
end
|
|
|
|
---remote call RoS
|
|
---get the server time of each encounter defeated by the guild
|
|
---@return servertime[]
|
|
function Details222.storage.GetIDsToGuildSync()
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return {}
|
|
end
|
|
|
|
local myGuildName = GetGuildInfo("player")
|
|
if (not myGuildName) then
|
|
return {}
|
|
end
|
|
--myGuildName = "Patifaria"
|
|
|
|
---@type servertime[]
|
|
local encounterSyncIds = {}
|
|
local allowedBosses = getBossIdsForCurrentExpansion()
|
|
|
|
--build the encounter synchronized ID list
|
|
for i, diffName in ipairs(Details222.storage.DiffNames) do
|
|
---@type table<encounterid, details_encounterkillinfo>
|
|
local encountersTable = savedData[diffName]
|
|
|
|
for dungeonEncounterID, allEncountersStored in pairs(encountersTable) do
|
|
if (allowedBosses[dungeonEncounterID]) then
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
if (encounterKillInfo.servertime) then
|
|
if (myGuildName == encounterKillInfo.guild) then
|
|
tinsert(encounterSyncIds, encounterKillInfo.servertime)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoS-EncounterSync] sending " .. #encounterSyncIds .. " IDs.")
|
|
end
|
|
|
|
return encounterSyncIds
|
|
end
|
|
|
|
--local call RoC - received the encounterSyncIds - need to know which fights is missing
|
|
---@param encounterSyncIds servertime[]
|
|
function Details222.storage.CheckMissingIDsToGuildSync(encounterSyncIds)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
if (type(encounterSyncIds) ~= "table") then
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoS-EncounterSync] RoC encounterSyncIds isn't a table.")
|
|
end
|
|
return
|
|
end
|
|
|
|
--store the IDs which need to be sync
|
|
local requestEncounterSyncIds = {}
|
|
|
|
--check missing IDs
|
|
for index, encounterSyncId in ipairs(encounterSyncIds) do
|
|
if (not hasEncounterByEncounterSyncId(savedData, encounterSyncId)) then
|
|
if (not hasRecentRequestedEncounterSyncId(encounterSyncId)) then
|
|
tinsert(requestEncounterSyncIds, encounterSyncId)
|
|
recentRequestedIDs[encounterSyncId] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoC-EncounterSync] RoS found " .. #requestEncounterSyncIds .. " encounters out dated.")
|
|
end
|
|
|
|
return requestEncounterSyncIds
|
|
end
|
|
|
|
--remote call RoS - build the encounter list from the encounterSyncIds
|
|
---@param encounterSyncIds servertime[]
|
|
function Details222.storage.BuildEncounterDataToGuildSync(encounterSyncIds)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
if (type(encounterSyncIds) ~= "table") then
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoS-EncounterSync] IDsList isn't a table.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local amtToSend = 0
|
|
local maxAmount = 0
|
|
|
|
---@type table<string, table<number, details_encounterkillinfo[]>>[]
|
|
local encounterList = {}
|
|
|
|
---@type table<raid_difficulty_eng_name_lowercase, table<encounterid, details_encounterkillinfo[]>>
|
|
local currentTable = {}
|
|
|
|
tinsert(encounterList, currentTable)
|
|
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoS-EncounterSync] the client requested " .. #encounterSyncIds .. " encounters.")
|
|
end
|
|
|
|
for index, encounterSyncId in ipairs(encounterSyncIds) do
|
|
for difficulty, encountersTable in pairs(savedData) do
|
|
---@cast encountersTable details_encounterkillinfo[]
|
|
if (Details222.storage.DiffNamesHash[difficulty]) then --this ensures that the difficulty is valid
|
|
for dungeonEncounterID, allEncountersStored in pairs(encountersTable) do
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
---@cast encounterKillInfo details_encounterkillinfo
|
|
if (encounterSyncId == encounterKillInfo.time or encounterSyncId == encounterKillInfo.servertime) then --the time here is always exactly
|
|
--send this encounter
|
|
currentTable[difficulty] = currentTable[difficulty] or {}
|
|
currentTable[difficulty][dungeonEncounterID] = currentTable[difficulty][dungeonEncounterID] or {}
|
|
|
|
tinsert(currentTable[difficulty][dungeonEncounterID], encounterKillInfo)
|
|
|
|
amtToSend = amtToSend + 1
|
|
maxAmount = maxAmount + 1
|
|
|
|
if (maxAmount == 3) then
|
|
currentTable = {}
|
|
tinsert(encounterList, currentTable)
|
|
maxAmount = 0
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoS-EncounterSync] sending " .. amtToSend .. " encounters.")
|
|
end
|
|
|
|
--the resulting table is a table with subtables, each subtable has a maximum of 3 encounters on indexes 1, 2 and 3
|
|
--resulting in
|
|
--{
|
|
-- {[raid_difficulty_eng_name_lowercase][encounterid] = {details_encounterkillinfo, details_encounterkillinfo, details_encounterkillinfo}},
|
|
-- {[raid_difficulty_eng_name_lowercase][encounterid] = {details_encounterkillinfo, details_encounterkillinfo, details_encounterkillinfo}}
|
|
--}
|
|
return encounterList
|
|
end
|
|
|
|
|
|
--local call RoC - add the fights to the client db
|
|
function Details222.storage.AddGuildSyncData(data, source)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
if (not data or type(data) ~= "table") then
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoC-AddGuildSyncData] data isn't a table.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local addedAmount = 0
|
|
Details.LastGuildSyncReceived = GetTime()
|
|
local allowedBosses = getBossIdsForCurrentExpansion()
|
|
|
|
---@cast data raid_difficulty_eng_name_lowercase, table<encounterid, details_encounterkillinfo[]>
|
|
|
|
for difficulty, encounterIdTable in pairs(data) do
|
|
---@cast encounterIdTable table<encounterid, details_encounterkillinfo[]>
|
|
|
|
if (Details222.storage.DiffNamesHash[difficulty] and type(encounterIdTable) == "table") then
|
|
for dungeonEncounterID, allEncountersStored in pairs(encounterIdTable) do
|
|
if (type(dungeonEncounterID) == "number" and type(allEncountersStored) == "table" and allowedBosses[dungeonEncounterID]) then
|
|
for index, encounterKillInfo in ipairs(allEncountersStored) do
|
|
--validate the encounter
|
|
if (type(encounterKillInfo.servertime) == "number" and type(encounterKillInfo.time) == "number" and type(encounterKillInfo.guild) == "string" and type(encounterKillInfo.date) == "string" and type(encounterKillInfo.HEALER) == "table" and type(encounterKillInfo.elapsed) == "number" and type(encounterKillInfo.DAMAGER) == "table") then
|
|
--check if this encounter already has been added from another sync
|
|
if (not hasEncounterByEncounterSyncId(savedData, encounterKillInfo.servertime)) then
|
|
savedData[difficulty] = savedData[difficulty] or {}
|
|
savedData[difficulty][dungeonEncounterID] = savedData[difficulty][dungeonEncounterID] or {}
|
|
tinsert(savedData[difficulty][dungeonEncounterID], encounterKillInfo)
|
|
|
|
if (_G.DetailsRaidHistoryWindow and _G.DetailsRaidHistoryWindow:IsShown()) then
|
|
_G.DetailsRaidHistoryWindow:Refresh()
|
|
end
|
|
|
|
addedAmount = addedAmount + 1
|
|
else
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoC-AddGuildSyncData] received a duplicated encounter table.")
|
|
end
|
|
end
|
|
else
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoC-AddGuildSyncData] received an invalid encounter table.")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) [RoC-AddGuildSyncData] added " .. addedAmount .. " to database.")
|
|
end
|
|
|
|
if (_G.DetailsRaidHistoryWindow and _G.DetailsRaidHistoryWindow:IsShown()) then
|
|
_G.DetailsRaidHistoryWindow:UpdateDropdowns()
|
|
_G.DetailsRaidHistoryWindow:Refresh()
|
|
end
|
|
end
|
|
|
|
---@param difficulty string
|
|
---@return encounterid[]
|
|
function Details222.storage.ListEncounters(difficulty)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return {}
|
|
end
|
|
|
|
if (not difficulty) then
|
|
return {}
|
|
end
|
|
|
|
---@type encounterid[]
|
|
local resultTable = {}
|
|
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
for dungeonEncounterID in pairs(encountersTable) do
|
|
tinsert(resultTable, dungeonEncounterID)
|
|
end
|
|
end
|
|
|
|
return resultTable
|
|
end
|
|
|
|
---@param difficulty string
|
|
---@param dungeonEncounterID encounterid
|
|
---@param role role
|
|
---@param unitName actorname
|
|
---@return details_storage_unitresult[]
|
|
function Details222.storage.GetUnitData(difficulty, dungeonEncounterID, role, unitName)
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return {}
|
|
end
|
|
|
|
assert(type(unitName) == "string", "unitName must be a string.")
|
|
assert(type(dungeonEncounterID) == "number", "dungeonEncounterID must be a string.")
|
|
|
|
---@type details_storage_unitresult[]
|
|
local resultTable = {}
|
|
|
|
---@type details_encounterkillinfo[]
|
|
local encountersTable = savedData[difficulty]
|
|
if (encountersTable) then
|
|
local allEncountersStored = encountersTable[dungeonEncounterID]
|
|
if (allEncountersStored) then
|
|
for i = 1, #allEncountersStored do
|
|
---@type details_encounterkillinfo
|
|
local encounterKillInfo = allEncountersStored[i]
|
|
local playerData = encounterKillInfo[role][unitName]
|
|
if (playerData) then
|
|
tinsert(resultTable, playerData)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return resultTable
|
|
end
|
|
|
|
---return a table with all encounters saved for a specific guild in a specific difficulty for a specific encounter
|
|
---@param difficulty string
|
|
---@param dungeonEncounterID encounterid
|
|
---@param guildName guildname
|
|
---@return details_encounterkillinfo[]
|
|
function Details222.storage.GetEncounterData(difficulty, dungeonEncounterID, guildName)
|
|
---@type details_storage?
|
|
local savedData = Details222.storage.OpenRaidStorage()
|
|
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
local encountersTable = savedData[difficulty]
|
|
|
|
assert(encountersTable, "Difficulty not found. Use: normal, heroic or mythic.")
|
|
assert(type(dungeonEncounterID) == "number", "dungeonEncounterID must be a number.")
|
|
|
|
---@type details_encounterkillinfo[]
|
|
local allEncountersStored = encountersTable[dungeonEncounterID]
|
|
|
|
local resultTable = {}
|
|
|
|
if (not allEncountersStored) then
|
|
return resultTable
|
|
end
|
|
|
|
for i = 1, #allEncountersStored do
|
|
local encounterKillInfo = allEncountersStored[i]
|
|
if (encounterKillInfo.guild == guildName) then
|
|
tinsert(resultTable, encounterKillInfo)
|
|
end
|
|
end
|
|
|
|
return resultTable
|
|
end
|
|
|
|
---load the storage addon when the player leave combat, this function is also called from the parser when the player has its regen enabled
|
|
function Details.ScheduleLoadStorage()
|
|
--check first if the storage is already loaded
|
|
if (C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
Details.schedule_storage_load = nil
|
|
Details222.storageLoaded = true
|
|
return
|
|
end
|
|
|
|
if (InCombatLockdown() or UnitAffectingCombat("player")) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! storage scheduled to load (player in combat).")
|
|
end
|
|
--load when the player leave combat
|
|
Details.schedule_storage_load = true
|
|
return
|
|
else
|
|
if (not C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
local bSuccessLoaded, reason = C_AddOns.LoadAddOn(CONST_ADDONNAME_DATASTORAGE)
|
|
if (not bSuccessLoaded) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't load storage, may be the addon is disabled.")
|
|
end
|
|
return
|
|
end
|
|
createStorageTables()
|
|
end
|
|
end
|
|
|
|
if (C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
Details.schedule_storage_load = nil
|
|
Details222.storageLoaded = true
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! storage loaded.")
|
|
end
|
|
else
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! fail to load storage, scheduled once again.")
|
|
end
|
|
Details.schedule_storage_load = true
|
|
end
|
|
end
|
|
|
|
function Details.GetStorage()
|
|
return DetailsDataStorage
|
|
end
|
|
|
|
--this function is used on the breakdown window to show ranking and on the main window when hovering over the spec icon
|
|
--if the storage is not loaded, it will try to load it even if the player is in combat
|
|
function Details.OpenStorage()
|
|
--if the player is in combat, this function return false, if failed to load by other reason it returns nil
|
|
--check if the storage is already loaded
|
|
if (not C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
--can't open it during combat
|
|
if (InCombatLockdown() or UnitAffectingCombat("player")) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't load storage due to combat.")
|
|
end
|
|
return false
|
|
end
|
|
|
|
local loaded, reason = C_AddOns.LoadAddOn(CONST_ADDONNAME_DATASTORAGE)
|
|
if (not loaded) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't load storage, may be the addon is disabled.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local savedData = createStorageTables()
|
|
|
|
if (savedData and C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
Details222.storageLoaded = true
|
|
end
|
|
|
|
return DetailsDataStorage
|
|
else
|
|
return DetailsDataStorage
|
|
end
|
|
end
|
|
|
|
Details.Database = {}
|
|
|
|
--this function is called on storewipe and storeencounter
|
|
---@return details_storage?
|
|
function Details.Database.LoadDB()
|
|
--check if the storage is not loaded yet and try to load it
|
|
if (not C_AddOns.IsAddOnLoaded(CONST_ADDONNAME_DATASTORAGE)) then
|
|
local loaded, reason = C_AddOns.LoadAddOn(CONST_ADDONNAME_DATASTORAGE)
|
|
if (not loaded) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't save the encounter, couldn't load DataStorage, may be the addon is disabled.")
|
|
end
|
|
return
|
|
end
|
|
end
|
|
|
|
--get the storage table
|
|
local savedData = _G.DetailsDataStorage
|
|
|
|
if (not savedData and Details.CreateStorageDB) then
|
|
savedData = Details:CreateStorageDB()
|
|
if (not savedData) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't save the encounter, couldn't load DataStorage, may be the addon is disabled.")
|
|
end
|
|
return
|
|
end
|
|
|
|
elseif (not savedData) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't save the encounter, couldn't load DataStorage, may be the addon is disabled.")
|
|
end
|
|
return
|
|
end
|
|
|
|
return savedData
|
|
end
|
|
|
|
---@param savedData details_storage
|
|
function Details.Database.GetBossKillsDB(savedData)
|
|
return savedData.totalkills
|
|
end
|
|
|
|
---@param combat combat?
|
|
function Details.Database.StoreWipe(combat)
|
|
if (not combat) then
|
|
combat = Details:GetCurrentCombat()
|
|
end
|
|
|
|
if (not combat) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: combat not found.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local name, type, zoneDifficulty, difficultyName, maxPlayers, playerDifficulty, isDynamicInstance, mapID, instanceGroupSize = GetInstanceInfo()
|
|
|
|
if (not Details:IsZoneIdFromCurrentExpansion(mapID) and not Details222.storage.IsDebug) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: instance not allowed.") --again
|
|
end
|
|
return
|
|
end
|
|
|
|
local bossInfo = combat:GetBossInfo()
|
|
local dungeonEncounterID = bossInfo and bossInfo.id
|
|
|
|
if (not dungeonEncounterID) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: encounter ID not found.")
|
|
end
|
|
return
|
|
end
|
|
|
|
--get the difficulty
|
|
local _, difficulty = combat:GetDifficulty()
|
|
|
|
--load database
|
|
---@type details_storage?
|
|
local savedData = Details.Database.LoadDB()
|
|
if (not savedData) then
|
|
return
|
|
end
|
|
|
|
if (IsInRaid()) then
|
|
--total kills in a boss on raid or dungeon
|
|
local totalKillsDataBase = Details.Database.GetBossKillsDB(savedData)
|
|
|
|
totalKillsDataBase[difficulty] = totalKillsDataBase[difficulty] or {}
|
|
totalKillsDataBase[difficulty][dungeonEncounterID] = totalKillsDataBase[difficulty][dungeonEncounterID] or {
|
|
kills = 0,
|
|
wipes = 0,
|
|
time_fasterkill = 0,
|
|
time_fasterkill_when = 0,
|
|
time_incombat = 0,
|
|
dps_best = 0,
|
|
dps_best_when = 0,
|
|
dps_best_raid = 0,
|
|
dps_best_raid_when = 0
|
|
}
|
|
|
|
local bossData = totalKillsDataBase[difficulty][dungeonEncounterID]
|
|
bossData.wipes = bossData.wipes + 1
|
|
end
|
|
end
|
|
|
|
---@param combat combat
|
|
function Details.Database.StoreEncounter(combat)
|
|
--stop execution if the expansion isn't retail
|
|
if (not detailsFramework:IsDragonflightAndBeyond()) then
|
|
return
|
|
end
|
|
|
|
combat = combat or Details:GetCurrentCombat()
|
|
|
|
if (not combat) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: combat not found.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local name, type, difficulty, difficultyName, maxPlayers, playerDifficulty, isDynamicInstance, mapID, instanceGroupSize = GetInstanceInfo()
|
|
|
|
--Details:IsZoneIdFromCurrentExpansion(select(8, GetInstanceInfo()))
|
|
|
|
if (not Details:IsZoneIdFromCurrentExpansion(mapID) and not Details222.storage.IsDebug) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: instance not allowed.")
|
|
end
|
|
return
|
|
end
|
|
|
|
local encounterInfo = combat:GetBossInfo()
|
|
local encounterId = encounterInfo and encounterInfo.id
|
|
|
|
if (not encounterId) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: encounter ID not found.")
|
|
end
|
|
return
|
|
end
|
|
|
|
--get the difficulty
|
|
local diffId, diffName = combat:GetDifficulty()
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: difficulty identified:", diffId, diffName)
|
|
end
|
|
|
|
--database
|
|
---@type details_storage?
|
|
local savedData = Details.Database.LoadDB()
|
|
if (not savedData) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: Details.Database.LoadDB() FAILED!")
|
|
end
|
|
return
|
|
end
|
|
|
|
--[=[
|
|
savedData[mythic] = {
|
|
[encounterId] = { --indexed table
|
|
[1] = {
|
|
DAMAGER = {
|
|
[actorname] = details_storage_unitresult
|
|
},
|
|
HEALER = {
|
|
[actorname] = details_storage_unitresult
|
|
},
|
|
date = date("%H:%M %d/%m/%y"),
|
|
time = time(),
|
|
servertime = GetServerTime(),
|
|
elapsed = combat:GetCombatTime(),
|
|
guild = guildName,
|
|
}
|
|
}
|
|
}
|
|
--]=]
|
|
|
|
---@type combattime
|
|
local elapsedCombatTime = combat:GetCombatTime()
|
|
|
|
---@type table<encounterid, details_encounterkillinfo[]>
|
|
local encountersTable = savedData[diffName]
|
|
if (not encountersTable) then
|
|
Details:Msg("encountersTable not found, diffName:", diffName)
|
|
savedData[diffName] = {}
|
|
encountersTable = savedData[diffName]
|
|
end
|
|
|
|
---@type details_encounterkillinfo[]
|
|
local allEncountersStored = encountersTable[encounterId]
|
|
if (not allEncountersStored) then
|
|
encountersTable[encounterId] = {}
|
|
allEncountersStored = encountersTable[encounterId]
|
|
end
|
|
|
|
--total kills in a boss on raid or dungeon
|
|
local totalkillsTable = Details.Database.GetBossKillsDB(savedData)
|
|
|
|
--store total kills on this boss
|
|
--if the player is facing a raid boss
|
|
if (IsInRaid()) then
|
|
totalkillsTable[encounterId] = totalkillsTable[encounterId] or {}
|
|
totalkillsTable[encounterId][diffName] = totalkillsTable[encounterId][diffName] or {
|
|
kills = 0,
|
|
wipes = 0,
|
|
time_fasterkill = 1000000,
|
|
time_fasterkill_when = 0,
|
|
time_incombat = 0,
|
|
dps_best = 0, --player best dps
|
|
dps_best_when = 0, --when the player did the best dps
|
|
dps_best_raid = 0,
|
|
dps_best_raid_when = 0
|
|
}
|
|
|
|
---@type details_bosskillinfo
|
|
local bossData = totalkillsTable[encounterId][diffName]
|
|
---@type combattime
|
|
local encounterElapsedTime = elapsedCombatTime
|
|
|
|
--kills amount
|
|
bossData.kills = bossData.kills + 1
|
|
|
|
--best time
|
|
if (encounterElapsedTime < bossData.time_fasterkill) then
|
|
bossData.time_fasterkill = encounterElapsedTime
|
|
bossData.time_fasterkill_when = time()
|
|
end
|
|
|
|
--total time in combat
|
|
bossData.time_incombat = bossData.time_incombat + encounterElapsedTime
|
|
|
|
--player best dps
|
|
---@actor
|
|
local playerActorObject = combat(DETAILS_ATTRIBUTE_DAMAGE, Details.playername)
|
|
if (playerActorObject) then
|
|
local playerDps = playerActorObject.total / encounterElapsedTime
|
|
if (playerDps > bossData.dps_best) then
|
|
bossData.dps_best = playerDps
|
|
bossData.dps_best_when = time()
|
|
end
|
|
end
|
|
|
|
--raid best dps
|
|
local raidTotalDamage = combat:GetTotal(DETAILS_ATTRIBUTE_DAMAGE, nil, true)
|
|
local raidDps = raidTotalDamage / encounterElapsedTime
|
|
if (raidDps > bossData.dps_best_raid) then
|
|
bossData.dps_best_raid = raidDps
|
|
bossData.dps_best_raid_when = time()
|
|
end
|
|
end
|
|
|
|
--check for heroic and mythic
|
|
if (Details222.storage.IsDebug or Details222.storage.DiffNamesHash[diffName]) then
|
|
--check the guild name
|
|
local match = 0
|
|
local guildName = GetGuildInfo("player")
|
|
local raidSize = GetNumGroupMembers() or 0
|
|
|
|
local cachedRaidUnitIds = Details222.UnitIdCache.Raid
|
|
|
|
if (not Details222.storage.IsDebug) then
|
|
if (guildName) then
|
|
for i = 1, raidSize do
|
|
local gName = GetGuildInfo(cachedRaidUnitIds[i]) or ""
|
|
if (gName == guildName) then
|
|
match = match + 1
|
|
end
|
|
end
|
|
|
|
if (match < raidSize * 0.75) then
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: can't save the encounter, need at least 75% of players be from your guild.")
|
|
end
|
|
return
|
|
end
|
|
else
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: player isn't in a guild.")
|
|
end
|
|
return
|
|
end
|
|
else
|
|
guildName = "Test Guild"
|
|
end
|
|
|
|
---@type details_encounterkillinfo
|
|
local combatResultData = {
|
|
DAMAGER = {},
|
|
HEALER = {},
|
|
date = date("%H:%M %d/%m/%y"),
|
|
time = time(),
|
|
servertime = GetServerTime(),
|
|
elapsed = elapsedCombatTime,
|
|
guild = guildName,
|
|
}
|
|
|
|
local damageContainer = combat:GetContainer(DETAILS_ATTRIBUTE_DAMAGE)
|
|
local healingContainer = combat:GetContainer(DETAILS_ATTRIBUTE_HEAL)
|
|
|
|
for i = 1, GetNumGroupMembers() do
|
|
local role = UnitGroupRolesAssigned(cachedRaidUnitIds[i])
|
|
|
|
if (UnitIsInMyGuild(cachedRaidUnitIds[i])) then
|
|
if (role == "DAMAGER" or role == "TANK") then
|
|
local playerName = Details:GetFullName(cachedRaidUnitIds[i])
|
|
local _, _, class = Details:GetUnitClassFull(playerName)
|
|
|
|
local damagerActor = damageContainer:GetActor(playerName)
|
|
if (damagerActor) then
|
|
local guid = UnitGUID(cachedRaidUnitIds[i])
|
|
|
|
---@type details_storage_unitresult
|
|
local unitResultInfo = {
|
|
total = floor(damagerActor.total),
|
|
itemLevel = Details:GetItemLevelFromGuid(guid),
|
|
classId = class or 0
|
|
}
|
|
combatResultData.DAMAGER[playerName] = unitResultInfo
|
|
end
|
|
|
|
elseif (role == "HEALER") then
|
|
local playerName = Details:GetFullName(cachedRaidUnitIds[i])
|
|
local _, _, class = Details:GetUnitClassFull(playerName)
|
|
|
|
local healingActor = healingContainer:GetActor(playerName)
|
|
if (healingActor) then
|
|
local guid = UnitGUID(cachedRaidUnitIds[i])
|
|
|
|
---@type details_storage_unitresult
|
|
local unitResultInfo = {
|
|
total = floor(healingActor.total),
|
|
itemLevel = Details:GetItemLevelFromGuid(guid),
|
|
classId = class or 0
|
|
}
|
|
combatResultData.HEALER[playerName] = unitResultInfo
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--add the encounter data
|
|
tinsert(allEncountersStored, combatResultData)
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: combat data added to encounter database.")
|
|
end
|
|
|
|
local playerRole = UnitGroupRolesAssigned("player")
|
|
---@type details_storage_unitresult, details_encounterkillinfo
|
|
local bestRank, encounterKillInfo = Details222.storage.GetBestFromPlayer(diffName, encounterId, playerRole, Details.playername, true) --get dps or hps
|
|
|
|
if (bestRank and encounterKillInfo) then
|
|
local registeredBestTotal = bestRank and bestRank.total or 0
|
|
local registeredBestPerSecond = registeredBestTotal / encounterKillInfo.elapsed
|
|
|
|
local currentPerSecond = 0
|
|
if (playerRole == "DAMAGER" or playerRole == "TANK") then
|
|
---@actor
|
|
local playerActorObject = damageContainer:GetActor(Details.playername)
|
|
if (playerActorObject) then
|
|
currentPerSecond = playerActorObject.total / elapsedCombatTime
|
|
end
|
|
elseif (playerRole == "HEALER") then
|
|
---@actor
|
|
local playerActorObject = healingContainer:GetActor(Details.playername)
|
|
if (playerActorObject) then
|
|
currentPerSecond = playerActorObject.total / elapsedCombatTime
|
|
end
|
|
end
|
|
|
|
if (registeredBestPerSecond > currentPerSecond) then
|
|
if (not Details.deny_score_messages) then
|
|
print(Loc ["STRING_DETAILS1"] .. format(Loc ["STRING_SCORE_NOTBEST"], Details:ToK2(currentPerSecond), Details:ToK2(registeredBestPerSecond), encounterKillInfo.date, bestRank[2]))
|
|
end
|
|
else
|
|
if (not Details.deny_score_messages) then
|
|
print(Loc ["STRING_DETAILS1"] .. format(Loc ["STRING_SCORE_BEST"], Details:ToK2(currentPerSecond)))
|
|
end
|
|
end
|
|
end
|
|
|
|
local lowerInstanceId = Details:GetLowerInstanceNumber()
|
|
if (lowerInstanceId) then
|
|
local instanceObject = Details:GetInstance(lowerInstanceId)
|
|
if (instanceObject) then
|
|
if (playerRole == "TANK") then
|
|
playerRole = "DAMAGER"
|
|
end
|
|
|
|
local raidName = GetInstanceInfo()
|
|
local func = {Details.OpenRaidHistoryWindow, Details, raidName, encounterId, diffName, playerRole, guildName}
|
|
local icon = {[[Interface\PvPRankBadges\PvPRank08]], 16, 16, false, 0, 1, 0, 1}
|
|
if (not Details.deny_score_messages) then
|
|
instanceObject:InstanceAlert(Loc ["STRING_GUILDDAMAGERANK_WINDOWALERT"], icon, Details.update_warning_timeout, func, true)
|
|
end
|
|
end
|
|
end
|
|
else
|
|
if (Details.debug) then
|
|
print("|cFFFFFF00Details! Storage|r: raid difficulty must be heroic or mythic.")
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--inspect stuff
|
|
|
|
Details.ilevel = {}
|
|
local ilvl_core = Details:CreateEventListener()
|
|
ilvl_core.amt_inspecting = 0
|
|
Details.ilevel.core = ilvl_core
|
|
|
|
ilvl_core:RegisterEvent("GROUP_ONENTER", "OnEnter")
|
|
ilvl_core:RegisterEvent("GROUP_ONLEAVE", "OnLeave")
|
|
ilvl_core:RegisterEvent("COMBAT_PLAYER_ENTER", "EnterCombat")
|
|
ilvl_core:RegisterEvent("COMBAT_PLAYER_LEAVE", "LeaveCombat")
|
|
ilvl_core:RegisterEvent("ZONE_TYPE_CHANGED", "ZoneChanged")
|
|
|
|
local inspecting = {}
|
|
ilvl_core.forced_inspects = {}
|
|
|
|
function ilvl_core:HasQueuedInspec(unitName)
|
|
local guid = UnitGUID(unitName)
|
|
if (guid) then
|
|
return ilvl_core.forced_inspects [guid]
|
|
end
|
|
end
|
|
|
|
local inspect_frame = CreateFrame("frame")
|
|
inspect_frame:RegisterEvent("INSPECT_READY")
|
|
|
|
local two_hand = {
|
|
["INVTYPE_2HWEAPON"] = true,
|
|
["INVTYPE_RANGED"] = true,
|
|
["INVTYPE_RANGEDRIGHT"] = true,
|
|
}
|
|
|
|
local MAX_INSPECT_AMOUNT = 1
|
|
local MIN_ILEVEL_TO_STORE = 50
|
|
local LOOP_TIME = 7
|
|
|
|
function Details:IlvlFromNetwork(unitName, realmName, coreVersion, unitGUID, itemLevel, talentsSelected, currentSpec)
|
|
if (Details.debug and false) then
|
|
local talents = "Invalid Talents"
|
|
if (type(talentsSelected) == "table") then
|
|
talents = ""
|
|
for i = 1, #talentsSelected do
|
|
talents = talents .. talentsSelected [i] .. ","
|
|
end
|
|
end
|
|
Details222.DebugMsg("Received PlayerInfo Data: " ..(unitName or "Invalid Player Name") .. " | " ..(itemLevel or "Invalid Item Level") .. " | " ..(currentSpec or "Invalid Spec") .. " | " .. talents .. " | " ..(unitGUID or "Invalid Serial"))
|
|
end
|
|
|
|
if (not unitName) then
|
|
return
|
|
end
|
|
|
|
--older versions of details wont send serial nor talents nor spec
|
|
if (not unitGUID or not itemLevel or not talentsSelected or not currentSpec) then
|
|
--if any data is invalid, abort
|
|
return
|
|
end
|
|
|
|
--won't inspect this actor
|
|
Details.trusted_characters[unitGUID] = true
|
|
|
|
if (type(unitGUID) ~= "string") then
|
|
return
|
|
end
|
|
|
|
--store the item level
|
|
if (type(itemLevel) == "number") then
|
|
Details.item_level_pool[unitGUID] = {name = unitName, ilvl = itemLevel, time = time()}
|
|
end
|
|
|
|
--store talents
|
|
if (type(talentsSelected) == "table") then
|
|
if (talentsSelected[1]) then
|
|
Details.cached_talents[unitGUID] = talentsSelected
|
|
end
|
|
|
|
elseif (type(talentsSelected) == "string" and talentsSelected ~= "") then
|
|
Details.cached_talents[unitGUID] = talentsSelected
|
|
end
|
|
|
|
--store the spec the player is playing
|
|
if (type(currentSpec) == "number") then
|
|
Details.cached_specs[unitGUID] = currentSpec
|
|
end
|
|
end
|
|
|
|
--test
|
|
--/run _detalhes.ilevel:CalcItemLevel("player", UnitGUID("player"), true)
|
|
--/run wipe(_detalhes.item_level_pool)
|
|
|
|
function ilvl_core:CalcItemLevel(unitid, guid, shout)
|
|
|
|
if (type(unitid) == "table") then
|
|
shout = unitid [3]
|
|
guid = unitid [2]
|
|
unitid = unitid [1]
|
|
end
|
|
|
|
--disable due to changes to CheckInteractDistance()
|
|
if (not InCombatLockdown() and unitid and UnitPlayerControlled(unitid) and CheckInteractDistance(unitid, CONST_INSPECT_ACHIEVEMENT_DISTANCE) and CanInspect(unitid)) then
|
|
|
|
--16 = all itens including main and off hand
|
|
local item_amount = 16
|
|
local item_level = 0
|
|
local failed = 0
|
|
|
|
for equip_id = 1, 17 do
|
|
if (equip_id ~= 4) then --shirt slot
|
|
local item = GetInventoryItemLink(unitid, equip_id)
|
|
if (item) then
|
|
local _, _, itemRarity, iLevel, _, _, _, _, equipSlot = GetItemInfo(item)
|
|
if (iLevel) then
|
|
item_level = item_level + iLevel
|
|
|
|
--16 = main hand 17 = off hand
|
|
-- if using a two-hand, ignore the off hand slot
|
|
if (equip_id == 16 and two_hand [equipSlot]) then
|
|
item_amount = 15
|
|
break
|
|
end
|
|
end
|
|
else
|
|
failed = failed + 1
|
|
if (failed > 2) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local average = item_level / item_amount
|
|
|
|
--register
|
|
if (average > 0) then
|
|
if (shout) then
|
|
Details:Msg(UnitName(unitid) .. " item level: " .. average)
|
|
end
|
|
|
|
if (average > MIN_ILEVEL_TO_STORE) then
|
|
local unitName = Details:GetFullName(unitid)
|
|
Details.item_level_pool [guid] = {name = unitName, ilvl = average, time = time()}
|
|
end
|
|
end
|
|
|
|
local spec
|
|
local talents = {}
|
|
|
|
if (not DetailsFramework.IsTimewalkWoW()) then
|
|
spec = GetInspectSpecialization(unitid)
|
|
if (spec and spec ~= 0) then
|
|
Details.cached_specs [guid] = spec
|
|
Details:SendEvent("UNIT_SPEC", nil, unitid, spec, guid)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------------------
|
|
|
|
for i = 1, 7 do
|
|
for o = 1, 3 do
|
|
--need to review this in classic
|
|
local talentID, name, texture, selected, available = GetTalentInfo(i, o, 1, true, unitid)
|
|
if (selected) then
|
|
tinsert(talents, talentID)
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
if (talents [1]) then
|
|
Details.cached_talents [guid] = talents
|
|
Details:SendEvent("UNIT_TALENTS", nil, unitid, talents, guid)
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------------------
|
|
|
|
if (ilvl_core.forced_inspects [guid]) then
|
|
if (type(ilvl_core.forced_inspects [guid].callback) == "function") then
|
|
local okey, errortext = pcall(ilvl_core.forced_inspects[guid].callback, guid, unitid, ilvl_core.forced_inspects[guid].param1, ilvl_core.forced_inspects[guid].param2)
|
|
if (not okey) then
|
|
Details:Msg("Error on QueryInspect callback: " .. errortext)
|
|
end
|
|
end
|
|
ilvl_core.forced_inspects [guid] = nil
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------------------
|
|
|
|
end
|
|
end
|
|
|
|
Details.ilevel.CalcItemLevel = ilvl_core.CalcItemLevel
|
|
|
|
inspect_frame:SetScript("OnEvent", function(self, event, ...)
|
|
local guid = select(1, ...)
|
|
|
|
if (inspecting [guid]) then
|
|
local unitid, cancel_tread = inspecting [guid] [1], inspecting [guid] [2]
|
|
inspecting [guid] = nil
|
|
ilvl_core.amt_inspecting = ilvl_core.amt_inspecting - 1
|
|
|
|
ilvl_core:CancelTimer(cancel_tread)
|
|
|
|
--do inspect stuff
|
|
if (unitid) then
|
|
local t = {unitid, guid}
|
|
--ilvl_core:ScheduleTimer("CalcItemLevel", 0.5, t)
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 0.5, t)
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 2, t)
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 4, t)
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 8, t)
|
|
end
|
|
else
|
|
if (IsInRaid()) then
|
|
--get the unitID
|
|
local serial = ...
|
|
if (serial and type(serial) == "string") then
|
|
for i = 1, GetNumGroupMembers() do
|
|
if (UnitGUID("raid" .. i) == serial) then
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 2, {"raid" .. i, serial})
|
|
ilvl_core:ScheduleTimer("CalcItemLevel", 4, {"raid" .. i, serial})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end)
|
|
|
|
function ilvl_core:InspectTimeOut(guid)
|
|
inspecting [guid] = nil
|
|
ilvl_core.amt_inspecting = ilvl_core.amt_inspecting - 1
|
|
end
|
|
|
|
function ilvl_core:ReGetItemLevel(t)
|
|
local unitid, guid, is_forced, try_number = unpack(t)
|
|
return ilvl_core:GetItemLevel(unitid, guid, is_forced, try_number)
|
|
end
|
|
|
|
function ilvl_core:GetItemLevel(unitid, guid, is_forced, try_number)
|
|
|
|
--disable for timewalk wow ~timewalk
|
|
if (DetailsFramework.IsTimewalkWoW()) then
|
|
return
|
|
end
|
|
|
|
--ddouble check
|
|
if (not is_forced and(UnitAffectingCombat("player") or InCombatLockdown())) then
|
|
return
|
|
end
|
|
|
|
if (InCombatLockdown() or not unitid or not CanInspect(unitid) or not UnitPlayerControlled(unitid) or not CheckInteractDistance(unitid, CONST_INSPECT_ACHIEVEMENT_DISTANCE)) then
|
|
if (is_forced) then
|
|
try_number = try_number or 0
|
|
if (try_number > 18) then
|
|
return
|
|
else
|
|
try_number = try_number + 1
|
|
end
|
|
ilvl_core:ScheduleTimer("ReGetItemLevel", 3, {unitid, guid, is_forced, try_number})
|
|
end
|
|
return
|
|
end
|
|
|
|
inspecting [guid] = {unitid, ilvl_core:ScheduleTimer("InspectTimeOut", 12, guid)}
|
|
ilvl_core.amt_inspecting = ilvl_core.amt_inspecting + 1
|
|
|
|
--NotifyInspect(unitid)
|
|
end
|
|
|
|
local NotifyInspectHook = function(unitid) --not in use
|
|
local unit = unitid:gsub("%d+", "")
|
|
|
|
if ((IsInRaid() or IsInGroup()) and(Details:GetZoneType() == "raid" or Details:GetZoneType() == "party")) then
|
|
local guid = UnitGUID(unitid)
|
|
local name = Details:GetFullName(unitid)
|
|
if (guid and name and not inspecting [guid]) then
|
|
for i = 1, GetNumGroupMembers() do
|
|
if (name == Details:GetFullName(unit .. i)) then
|
|
unitid = unit .. i
|
|
break
|
|
end
|
|
end
|
|
|
|
inspecting [guid] = {unitid, ilvl_core:ScheduleTimer("InspectTimeOut", 12, guid)}
|
|
ilvl_core.amt_inspecting = ilvl_core.amt_inspecting + 1
|
|
end
|
|
end
|
|
end
|
|
--hooksecurefunc("NotifyInspect", NotifyInspectHook)
|
|
|
|
function ilvl_core:Reset()
|
|
ilvl_core.raid_id = 1
|
|
ilvl_core.amt_inspecting = 0
|
|
|
|
for guid, t in pairs(inspecting) do
|
|
ilvl_core:CancelTimer(t[2])
|
|
inspecting [guid] = nil
|
|
end
|
|
end
|
|
|
|
function ilvl_core:QueryInspect(unitName, callback, param1)
|
|
--disable for timewalk wow ~timewalk
|
|
if (DetailsFramework.IsTimewalkWoW()) then
|
|
return
|
|
end
|
|
|
|
local unitid
|
|
|
|
if (IsInRaid()) then
|
|
for i = 1, GetNumGroupMembers() do
|
|
if (Details:GetFullName("raid" .. i, "none") == unitName) then
|
|
unitid = "raid" .. i
|
|
break
|
|
end
|
|
end
|
|
elseif (IsInGroup()) then
|
|
for i = 1, GetNumGroupMembers()-1 do
|
|
if (Details:GetFullName("party" .. i, "none") == unitName) then
|
|
unitid = "party" .. i
|
|
break
|
|
end
|
|
end
|
|
else
|
|
unitid = unitName
|
|
end
|
|
|
|
if (not unitid) then
|
|
return false
|
|
end
|
|
|
|
local guid = UnitGUID(unitid)
|
|
if (not guid) then
|
|
return false
|
|
elseif (ilvl_core.forced_inspects [guid]) then
|
|
return true
|
|
end
|
|
|
|
if (inspecting [guid]) then
|
|
return true
|
|
end
|
|
|
|
ilvl_core.forced_inspects [guid] = {callback = callback, param1 = param1}
|
|
ilvl_core:GetItemLevel(unitid, guid, true)
|
|
|
|
if (ilvl_core.clear_queued_list) then
|
|
ilvl_core:CancelTimer(ilvl_core.clear_queued_list)
|
|
end
|
|
ilvl_core.clear_queued_list = ilvl_core:ScheduleTimer("ClearQueryInspectQueue", 60)
|
|
|
|
return true
|
|
end
|
|
|
|
function ilvl_core:ClearQueryInspectQueue()
|
|
Details:Destroy(ilvl_core.forced_inspects)
|
|
ilvl_core.clear_queued_list = nil
|
|
end
|
|
|
|
function ilvl_core:Loop()
|
|
--disable for timewalk wow ~timewalk
|
|
if (DetailsFramework.IsTimewalkWoW()) then
|
|
return
|
|
end
|
|
|
|
if (ilvl_core.amt_inspecting >= MAX_INSPECT_AMOUNT) then
|
|
return
|
|
end
|
|
|
|
local members_amt = GetNumGroupMembers()
|
|
if (ilvl_core.raid_id > members_amt) then
|
|
ilvl_core.raid_id = 1
|
|
end
|
|
|
|
local unitid
|
|
if (IsInRaid()) then
|
|
unitid = "raid" .. ilvl_core.raid_id
|
|
elseif (IsInGroup()) then
|
|
unitid = "party" .. ilvl_core.raid_id
|
|
else
|
|
return
|
|
end
|
|
|
|
local guid = UnitGUID(unitid)
|
|
if (not guid) then
|
|
ilvl_core.raid_id = ilvl_core.raid_id + 1
|
|
return
|
|
end
|
|
|
|
--if already inspecting or the actor is in the list of trusted actors
|
|
if (inspecting [guid] or Details.trusted_characters [guid]) then
|
|
return
|
|
end
|
|
|
|
local ilvl_table = Details.ilevel:GetIlvl(guid)
|
|
if (ilvl_table and ilvl_table.time + 3600 > time()) then
|
|
ilvl_core.raid_id = ilvl_core.raid_id + 1
|
|
return
|
|
end
|
|
|
|
ilvl_core:GetItemLevel(unitid, guid)
|
|
ilvl_core.raid_id = ilvl_core.raid_id + 1
|
|
end
|
|
|
|
function ilvl_core:EnterCombat()
|
|
if (ilvl_core.loop_process) then
|
|
ilvl_core:CancelTimer(ilvl_core.loop_process)
|
|
ilvl_core.loop_process = nil
|
|
end
|
|
end
|
|
|
|
local can_start_loop = function()
|
|
--disable for timewalk wow ~timewalk
|
|
if (DetailsFramework.IsTimewalkWoW()) then
|
|
return false
|
|
end
|
|
|
|
if ((Details:GetZoneType() ~= "raid" and Details:GetZoneType() ~= "party") or ilvl_core.loop_process or Details.in_combat or not Details.track_item_level) then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
function ilvl_core:LeaveCombat()
|
|
if (can_start_loop()) then
|
|
ilvl_core:Reset()
|
|
ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
|
|
end
|
|
end
|
|
|
|
function ilvl_core:ZoneChanged(zone_type)
|
|
if (can_start_loop()) then
|
|
ilvl_core:Reset()
|
|
ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
|
|
end
|
|
end
|
|
|
|
function ilvl_core:OnEnter()
|
|
Details:SendCharacterData()
|
|
|
|
if (can_start_loop()) then
|
|
ilvl_core:Reset()
|
|
ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
|
|
end
|
|
end
|
|
|
|
function ilvl_core:OnLeave()
|
|
if (ilvl_core.loop_process) then
|
|
ilvl_core:CancelTimer(ilvl_core.loop_process)
|
|
ilvl_core.loop_process = nil
|
|
end
|
|
end
|
|
|
|
--ilvl API
|
|
function Details.ilevel:IsTrackerEnabled()
|
|
return Details.track_item_level
|
|
end
|
|
function Details.ilevel:TrackItemLevel(bool)
|
|
if (type(bool) == "boolean") then
|
|
if (bool) then
|
|
Details.track_item_level = true
|
|
if (can_start_loop()) then
|
|
ilvl_core:Reset()
|
|
ilvl_core.loop_process = ilvl_core:ScheduleRepeatingTimer("Loop", LOOP_TIME)
|
|
end
|
|
else
|
|
Details.track_item_level = false
|
|
if (ilvl_core.loop_process) then
|
|
ilvl_core:CancelTimer(ilvl_core.loop_process)
|
|
ilvl_core.loop_process = nil
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details.ilevel:GetPool()
|
|
return Details.item_level_pool
|
|
end
|
|
|
|
function Details:GetItemLevelFromGuid(guid)
|
|
return Details.item_level_pool[guid] and Details.item_level_pool[guid].ilvl or 0
|
|
end
|
|
|
|
function Details.ilevel:GetIlvl(guid)
|
|
return Details.item_level_pool[guid]
|
|
end
|
|
|
|
function Details.ilevel:GetInOrder()
|
|
local order = {}
|
|
|
|
for guid, t in pairs(Details.item_level_pool) do
|
|
order[#order+1] = {t.name, t.ilvl or 0, t.time}
|
|
end
|
|
|
|
table.sort(order, Details.Sort2)
|
|
|
|
return order
|
|
end
|
|
|
|
function Details.ilevel:ClearIlvl(guid)
|
|
Details.item_level_pool[guid] = nil
|
|
end
|
|
|
|
function Details:GetTalents(guid)
|
|
return Details.cached_talents [guid]
|
|
end
|
|
|
|
function Details:GetSpecFromSerial(guid)
|
|
return Details.cached_specs [guid]
|
|
end
|
|
|
|
--------------------------------------------------------------------------------------------------------------------------------------------
|
|
--compress data
|
|
|
|
-- ~compress ~zip ~export ~import ~deflate ~serialize
|
|
function Details:CompressData(data, dataType)
|
|
local LibDeflate = LibStub:GetLibrary("LibDeflate")
|
|
local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0")
|
|
|
|
--check if there isn't funtions in the data to export
|
|
local dataCopied = DetailsFramework.table.copytocompress({}, data)
|
|
|
|
if (LibDeflate and LibAceSerializer) then
|
|
local dataSerialized = LibAceSerializer:Serialize(dataCopied)
|
|
if (dataSerialized) then
|
|
local dataCompressed = LibDeflate:CompressDeflate(dataSerialized, {level = 9})
|
|
if (dataCompressed) then
|
|
if (dataType == "print") then
|
|
local dataEncoded = LibDeflate:EncodeForPrint(dataCompressed)
|
|
return dataEncoded
|
|
|
|
elseif (dataType == "comm") then
|
|
local dataEncoded = LibDeflate:EncodeForWoWAddonChannel(dataCompressed)
|
|
return dataEncoded
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details:DecompressData(data, dataType)
|
|
local LibDeflate = LibStub:GetLibrary("LibDeflate")
|
|
local LibAceSerializer = LibStub:GetLibrary("AceSerializer-3.0")
|
|
|
|
if (LibDeflate and LibAceSerializer) then
|
|
|
|
local dataCompressed
|
|
|
|
if (dataType == "print") then
|
|
|
|
data = DetailsFramework:Trim(data)
|
|
|
|
dataCompressed = LibDeflate:DecodeForPrint(data)
|
|
if (not dataCompressed) then
|
|
Details:Msg("couldn't decode the data.")
|
|
return false
|
|
end
|
|
|
|
elseif (dataType == "comm") then
|
|
dataCompressed = LibDeflate:DecodeForWoWAddonChannel(data)
|
|
if (not dataCompressed) then
|
|
Details:Msg("couldn't decode the data.")
|
|
return false
|
|
end
|
|
end
|
|
local dataSerialized = LibDeflate:DecompressDeflate(dataCompressed)
|
|
|
|
if (not dataSerialized) then
|
|
Details:Msg("couldn't uncompress the data.")
|
|
return false
|
|
end
|
|
|
|
local okay, data = LibAceSerializer:Deserialize(dataSerialized)
|
|
if (not okay) then
|
|
Details:Msg("couldn't unserialize the data.")
|
|
return false
|
|
end
|
|
|
|
return data
|
|
end
|
|
end
|
|
|
|
Details.specToRole = {
|
|
--DRUID
|
|
[102] = "DAMAGER", --BALANCE
|
|
[103] = "DAMAGER", --FERAL DRUID
|
|
[105] = "HEALER", --RESTORATION
|
|
|
|
--HUNTER
|
|
[253] = "DAMAGER", --BM
|
|
[254] = "DAMAGER", --MM
|
|
[255] = "DAMAGER", --SURVIVOR
|
|
|
|
--MAGE
|
|
[62] = "DAMAGER", --ARCANE
|
|
[64] = "DAMAGER", --FROST
|
|
[63] = "DAMAGER", ---FIRE
|
|
|
|
--PALADIN
|
|
[70] = "DAMAGER", --RET
|
|
[65] = "HEALER", --HOLY
|
|
[66] = "TANK", --PROT
|
|
|
|
--PRIEST
|
|
[257] = "HEALER", --HOLY
|
|
[256] = "HEALER", --DISC
|
|
[258] = "DAMAGER", --SHADOW
|
|
|
|
--ROGUE
|
|
[259] = "DAMAGER", --ASSASSINATION
|
|
[260] = "DAMAGER", --COMBAT
|
|
[261] = "DAMAGER", --SUB
|
|
|
|
--SHAMAN
|
|
[262] = "DAMAGER", --ELEMENTAL
|
|
[263] = "DAMAGER", --ENHAN
|
|
[264] = "HEALER", --RESTO
|
|
|
|
--WARLOCK
|
|
[265] = "DAMAGER", --AFF
|
|
[266] = "DAMAGER", --DESTRO
|
|
[267] = "DAMAGER", --DEMO
|
|
|
|
--WARRIOR
|
|
[71] = "DAMAGER", --ARMS
|
|
[72] = "DAMAGER", --FURY
|
|
[73] = "TANK", --PROT
|
|
|
|
--DK
|
|
[250] = "TANK", --Blood
|
|
[251] = "DAMAGER", --Frost
|
|
[252] = "DAMAGER", --Unholy
|
|
|
|
--MONK
|
|
[268] = "TANK", -- Brewmaster Monk
|
|
[269] = "DAMAGER", -- Windwalker Monk
|
|
[270] = "HEALER", -- Mistweaver Monk
|
|
|
|
--DH
|
|
[577] = "DAMAGER", -- Havoc Demon Hunter
|
|
[581] = "TANK", -- Vengeance Demon Hunter
|
|
|
|
--EVOKER
|
|
[1467] = "DAMAGER", --Devastation Evoker
|
|
[1468] = "HEALER", --Preservation Evoker
|
|
[1473] = "DAMAGER", --Augmentation Evoker
|
|
}
|
|
|
|
--oldschool talent tree
|
|
if (DetailsFramework.IsWotLKWow() or DetailsFramework.IsCataWow()) then
|
|
local talentWatchClassic = CreateFrame("frame")
|
|
talentWatchClassic:RegisterEvent("CHARACTER_POINTS_CHANGED")
|
|
talentWatchClassic:RegisterEvent("SPELLS_CHANGED")
|
|
talentWatchClassic:RegisterEvent("PLAYER_ENTERING_WORLD")
|
|
talentWatchClassic:RegisterEvent("GROUP_ROSTER_UPDATE")
|
|
|
|
talentWatchClassic.cooldown = 0
|
|
|
|
C_Timer.NewTicker(600, function()
|
|
Details:GetOldSchoolTalentInformation()
|
|
end)
|
|
|
|
talentWatchClassic:SetScript("OnEvent", function(self, event, ...)
|
|
if (talentWatchClassic.delayedUpdate and not talentWatchClassic.delayedUpdate:IsCancelled()) then
|
|
return
|
|
else
|
|
talentWatchClassic.delayedUpdate = C_Timer.NewTimer(5, Details.GetOldSchoolTalentInformation)
|
|
end
|
|
end)
|
|
|
|
function Details.GetOldSchoolTalentInformation()
|
|
--cancel any schedule
|
|
if (talentWatchClassic.delayedUpdate and not talentWatchClassic.delayedUpdate:IsCancelled()) then
|
|
talentWatchClassic.delayedUpdate:Cancel()
|
|
end
|
|
talentWatchClassic.delayedUpdate = nil
|
|
|
|
--amount of tabs existing
|
|
local numTabs = GetNumTalentTabs() or 3
|
|
|
|
--store the background textures for each tab
|
|
local pointsPerSpec = {}
|
|
local talentsSelected = {}
|
|
|
|
for i = 1,(MAX_TALENT_TABS or 3) do
|
|
if (i <= numTabs) then
|
|
--tab information
|
|
local id, name, description, iconTexture, pointsSpent, fileName = GetTalentTabInfo(i)
|
|
|
|
if (name) then
|
|
tinsert(pointsPerSpec, {name, pointsSpent, fileName})
|
|
end
|
|
|
|
--talents information
|
|
local numTalents = GetNumTalents(i) or 20
|
|
local MAX_NUM_TALENTS = MAX_NUM_TALENTS or 20
|
|
|
|
for talentIndex = 1, MAX_NUM_TALENTS do
|
|
if (talentIndex <= numTalents) then
|
|
local name, iconTexture, tier, column, rank, maxRank, isExceptional, available = GetTalentInfo(i, talentIndex)
|
|
if (name and rank and type(rank) == "number") then
|
|
--send the specID instead of the specName
|
|
local specID = Details.textureToSpec [fileName]
|
|
tinsert(talentsSelected, {iconTexture, rank, tier, column, i, specID, maxRank})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local MIN_SPECS = 4
|
|
|
|
--put the spec with more talent point to the top
|
|
table.sort(pointsPerSpec, function(t1, t2) return t1[2] > t2[2] end)
|
|
|
|
--get the spec with more points spent
|
|
local spec = pointsPerSpec[1]
|
|
if (spec and spec[2] >= MIN_SPECS) then
|
|
local specTexture = spec[3]
|
|
|
|
--add the spec into the spec cache
|
|
Details.playerClassicSpec = {}
|
|
Details.playerClassicSpec.specs = Details.GetClassicSpecByTalentTexture(specTexture)
|
|
Details.playerClassicSpec.talents = talentsSelected
|
|
|
|
--cache the player specId
|
|
Details.cached_specs [UnitGUID("player")] = Details.playerClassicSpec.specs
|
|
--cache the player talents
|
|
Details.cached_talents [UnitGUID("player")] = talentsSelected
|
|
|
|
local role = Details:GetRoleFromSpec(Details.playerClassicSpec.specs, UnitGUID("player"))
|
|
|
|
if (Details.playerClassicSpec.specs == 103) then
|
|
if (role == "TANK") then
|
|
Details.playerClassicSpec.specs = 104
|
|
Details.cached_specs [UnitGUID("player")] = Details.playerClassicSpec.specs
|
|
end
|
|
end
|
|
|
|
Details.cached_roles[UnitGUID("player")] = role
|
|
|
|
--gear status
|
|
local item_amount = 16
|
|
local item_level = 0
|
|
local failed = 0
|
|
|
|
local two_hand = {
|
|
["INVTYPE_2HWEAPON"] = true,
|
|
["INVTYPE_RANGED"] = true,
|
|
["INVTYPE_RANGEDRIGHT"] = true,
|
|
}
|
|
|
|
for equip_id = 1, 17 do
|
|
if (equip_id ~= 4) then --shirt slot, trinkets
|
|
local item = GetInventoryItemLink("player", equip_id)
|
|
if (item) then
|
|
local _, _, itemRarity, iLevel, _, _, _, _, equipSlot = GetItemInfo(item)
|
|
if (iLevel) then
|
|
item_level = item_level + iLevel
|
|
|
|
--16 = main hand 17 = off hand
|
|
-- if using a two-hand, ignore the off hand slot
|
|
if (equip_id == 16 and two_hand [equipSlot]) then
|
|
item_amount = 15
|
|
break
|
|
end
|
|
end
|
|
else
|
|
failed = failed + 1
|
|
if (failed > 2) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local itemLevel = floor(item_level / item_amount)
|
|
local dataToShare = {role or "NONE", Details.playerClassicSpec.specs or 0, itemLevel or 0, talentsSelected, UnitGUID("player")}
|
|
--local serialized = _detalhes:Serialize(dataToShare)
|
|
local compressedData = Details:CompressData(dataToShare, "comm")
|
|
|
|
if (IsInRaid()) then
|
|
Details:SendRaidData(DETAILS_PREFIX_TBC_DATA, compressedData)
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) sent talents data to Raid")
|
|
end
|
|
|
|
elseif (IsInGroup()) then
|
|
Details:SendPartyData(DETAILS_PREFIX_TBC_DATA, compressedData)
|
|
if (Details.debug) then
|
|
Details:Msg("(debug) sent talents data to Party")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Details:GetRoleFromSpec(specId, unitGUID)
|
|
if (specId == 103) then --feral druid
|
|
local talents = Details.cached_talents [unitGUID]
|
|
if (talents) then
|
|
local tankTalents = 0
|
|
for i = 1, #talents do
|
|
local iconTexture, rank, tier, column = unpack(talents [i])
|
|
if (tier == 2) then
|
|
if (column == 1 and rank == 5) then
|
|
tankTalents = tankTalents + 5
|
|
end
|
|
if (column == 3 and rank == 5) then
|
|
tankTalents = tankTalents + 5
|
|
end
|
|
|
|
if (tankTalents >= 10) then
|
|
return "TANK"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return Details.specToRole [specId] or "NONE"
|
|
end
|
|
|
|
Details.validSpecIds = {
|
|
[250] = true,
|
|
[252] = true,
|
|
[251] = true,
|
|
[102] = true,
|
|
[103] = true,
|
|
[104] = true,
|
|
[105] = true,
|
|
[253] = true,
|
|
[254] = true,
|
|
[255] = true,
|
|
[62] = true,
|
|
[63] = true,
|
|
[64] = true,
|
|
[70] = true,
|
|
[65] = true,
|
|
[66] = true,
|
|
[257] = true,
|
|
[256] = true,
|
|
[258] = true,
|
|
[259] = true,
|
|
[260] = true,
|
|
[261] = true,
|
|
[262] = true,
|
|
[263] = true,
|
|
[264] = true,
|
|
[265] = true,
|
|
[266] = true,
|
|
[267] = true,
|
|
[71] = true,
|
|
[72] = true,
|
|
[73] = true,
|
|
}
|
|
|
|
Details.textureToSpec = {
|
|
|
|
DruidBalance = 102,
|
|
DruidFeralCombat = 103,
|
|
DruidRestoration = 105,
|
|
|
|
HunterBeastMastery = 253,
|
|
HunterMarksmanship = 254,
|
|
HunterSurvival = 255,
|
|
|
|
MageArcane = 62,
|
|
MageFrost = 64,
|
|
MageFire = 63,
|
|
|
|
PaladinCombat = 70,
|
|
PaladinHoly = 65,
|
|
PaladinProtection = 66,
|
|
|
|
PriestHoly = 257,
|
|
PriestDiscipline = 256,
|
|
PriestShadow = 258,
|
|
|
|
RogueAssassination = 259,
|
|
RogueCombat = 260,
|
|
RogueSubtlety = 261,
|
|
|
|
ShamanElementalCombat = 262,
|
|
ShamanEnhancement = 263,
|
|
ShamanRestoration = 264,
|
|
|
|
WarlockCurses = 265, --affliction
|
|
WarlockSummoning = 266, --demo
|
|
WarlockDestruction = 267, --destruction
|
|
|
|
--WarriorArm = 71,
|
|
WarriorArms = 71,
|
|
WarriorFury = 72,
|
|
WarriorProtection = 73,
|
|
|
|
DeathKnightBlood = 250,
|
|
DeathKnightFrost = 251,
|
|
DeathKnightUnholy = 252,
|
|
}
|
|
|
|
|
|
Details.specToTexture = {
|
|
[102] = "DruidBalance",
|
|
[103] = "DruidFeralCombat",
|
|
[105] = "DruidRestoration",
|
|
|
|
[253] = "HunterBeastMastery",
|
|
[254] = "HunterMarksmanship",
|
|
[255] = "HunterSurvival",
|
|
|
|
[62] = "MageArcane",
|
|
[64] = "MageFrost",
|
|
[63] = "MageFire",
|
|
|
|
[70] = "PaladinCombat",
|
|
[65] = "PaladinHoly",
|
|
[66] = "PaladinProtection",
|
|
|
|
[257] = "PriestHoly",
|
|
[256] = "PriestDiscipline",
|
|
[258] = "PriestShadow",
|
|
|
|
[259] = "RogueAssassination",
|
|
[260] = "RogueCombat",
|
|
[261] = "RogueSubtlety",
|
|
|
|
[262] = "ShamanElementalCombat",
|
|
[263] = "ShamanEnhancement",
|
|
[264] = "ShamanRestoration",
|
|
|
|
[265] = "WarlockCurses",
|
|
[266] = "WarlockDestruction",
|
|
[267] = "WarlockSummoning",
|
|
|
|
--[71] = "WarriorArm",
|
|
[71] = "WarriorArms",
|
|
[72] = "WarriorFury",
|
|
[73] = "WarriorProtection",
|
|
|
|
[250] = "DeathKnightBlood",
|
|
[251] = "DeathKnightFrost",
|
|
[252] = "DeathKnightUnholy",
|
|
}
|
|
|
|
function Details.IsValidSpecId(specId)
|
|
return Details.validSpecIds [specId]
|
|
end
|
|
|
|
function Details.GetClassicSpecByTalentTexture(talentTexture)
|
|
return Details.textureToSpec [talentTexture] or nil
|
|
end
|
|
end
|
|
|
|
|
|
--dragonflight talents, return {[spellId] = true}
|
|
function Details.GetDragonflightTalentsAsHashTable()
|
|
local allTalents = {}
|
|
local configId = C_ClassTalents.GetActiveConfigID()
|
|
if (not configId) then
|
|
return allTalents
|
|
end
|
|
|
|
local configInfo = C_Traits.GetConfigInfo(configId)
|
|
|
|
for treeIndex, treeId in ipairs(configInfo.treeIDs) do
|
|
local treeNodes = C_Traits.GetTreeNodes(treeId)
|
|
|
|
for nodeIdIndex, treeNodeID in ipairs(treeNodes) do
|
|
local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID)
|
|
|
|
if (traitNodeInfo) then
|
|
local activeEntry = traitNodeInfo.activeEntry
|
|
if (activeEntry) then
|
|
local entryId = activeEntry.entryID
|
|
local rank = activeEntry.rank
|
|
if (rank > 0) then
|
|
--get the entry info
|
|
local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId)
|
|
local definitionId = traitEntryInfo.definitionID
|
|
|
|
--definition info
|
|
local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId)
|
|
local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID
|
|
local spellName, _, spellTexture = GetSpellInfo(spellId)
|
|
if (spellName) then
|
|
allTalents[spellId] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return allTalents
|
|
end
|
|
|
|
|
|
--called from inside the function Details.GenerateSpecSpellList()
|
|
local getSpellList = function(specIndex, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
|
|
|
|
local specId, specName, _, specIconTexture = GetSpecializationInfo(specIndex)
|
|
completeListOfSpells[specId] = {}
|
|
specNames[specId] = specName
|
|
|
|
--get spells from talents
|
|
local configId = C_ClassTalents.GetActiveConfigID()
|
|
if (not configId) then
|
|
return completeListOfSpells
|
|
end
|
|
|
|
local configInfo = C_Traits.GetConfigInfo(configId)
|
|
--get the spells from the SPEC from talents
|
|
for treeIndex, treeId in ipairs(configInfo.treeIDs) do
|
|
local treeNodes = C_Traits.GetTreeNodes(treeId)
|
|
for nodeIdIndex, treeNodeID in ipairs(treeNodes) do
|
|
local traitNodeInfo = C_Traits.GetNodeInfo(configId, treeNodeID)
|
|
if (traitNodeInfo and traitNodeInfo.posX > 9000) then
|
|
local entryIds = traitNodeInfo.entryIDs
|
|
for i = 1, #entryIds do
|
|
local entryId = entryIds[i] --number
|
|
local traitEntryInfo = C_Traits.GetEntryInfo(configId, entryId)
|
|
local borderTypes = Enum.TraitNodeEntryType
|
|
if (traitEntryInfo.type == borderTypes.SpendSquare) then
|
|
local definitionId = traitEntryInfo.definitionID
|
|
local traitDefinitionInfo = C_Traits.GetDefinitionInfo(definitionId)
|
|
local spellId = traitDefinitionInfo.overriddenSpellID or traitDefinitionInfo.spellID
|
|
local spellName, _, spellTexture = GetSpellInfo(spellId)
|
|
if (spellName) then
|
|
completeListOfSpells[specId][spellId] = specId
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--get spells of the SPEC from the spell book
|
|
for i = 1, GetNumSpellTabs() do
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i)
|
|
if (tabTexture == specIconTexture) then
|
|
offset = offset + 1
|
|
local tabEnd = offset + numSpells
|
|
for entryOffset = offset, tabEnd - 1 do
|
|
local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
|
|
if (spellId) then
|
|
if (spellType == "SPELL") then
|
|
spellId = C_SpellBook.GetOverrideSpell(spellId)
|
|
local spellName = GetSpellInfo(spellId)
|
|
local isPassive = IsPassiveSpell(entryOffset, "player")
|
|
if (spellName and not isPassive) then
|
|
completeListOfSpells[specId][spellId] = specId
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--get shared spells from the spell book
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID)
|
|
offset = offset + 1
|
|
local tabEnd = offset + numSpells
|
|
for entryOffset = offset, tabEnd - 1 do
|
|
local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
|
|
if (spellId) then
|
|
if (spellType == "SPELL") then
|
|
spellId = C_SpellBook.GetOverrideSpell(spellId)
|
|
local spellName = GetSpellInfo(spellId)
|
|
local isPassive = IsPassiveSpell(entryOffset, "player")
|
|
if (spellName and not isPassive) then
|
|
sharedSpellsBetweenSpecs[spellId] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local classNameLoc = UnitClass("player")
|
|
print(specName .. " " .. classNameLoc .. " spells recorded.")
|
|
return completeListOfSpells, sharedSpellsBetweenSpecs, specNames
|
|
end
|
|
|
|
function Details.GenerateSpecSpellList()
|
|
local dumpSpellTable = 1
|
|
|
|
local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization())
|
|
local classNameLoc, className, classId = UnitClass("player")
|
|
|
|
local completeListOfSpells = {}
|
|
local sharedSpellsBetweenSpecs = {}
|
|
local specNames = {}
|
|
|
|
local amountSpecs = GetNumSpecializationsForClassID(classId)
|
|
|
|
local totalTimeToWait = 0
|
|
DetailsFramework.Schedules.NewTimer(0, function() SetSpecialization(1) end)
|
|
DetailsFramework.Schedules.NewTimer(6, getSpellList, 1, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
|
|
totalTimeToWait = 7
|
|
DetailsFramework.Schedules.NewTimer(7, function() SetSpecialization(2) end)
|
|
DetailsFramework.Schedules.NewTimer(13, getSpellList, 2, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
|
|
totalTimeToWait = 14
|
|
|
|
if (amountSpecs >= 3) then
|
|
DetailsFramework.Schedules.NewTimer(14, function() SetSpecialization(3) end)
|
|
DetailsFramework.Schedules.NewTimer(20, getSpellList, 3, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
|
|
totalTimeToWait = 21
|
|
end
|
|
|
|
if (amountSpecs >= 4) then
|
|
DetailsFramework.Schedules.NewTimer(21, function() SetSpecialization(4) end)
|
|
DetailsFramework.Schedules.NewTimer(28, getSpellList, 4, completeListOfSpells, sharedSpellsBetweenSpecs, specNames)
|
|
totalTimeToWait = 29
|
|
end
|
|
|
|
print("Total Time to Wait:", totalTimeToWait)
|
|
DetailsFramework.Schedules.NewTimer(totalTimeToWait, function()
|
|
if (dumpSpellTable) then
|
|
local parsedSpells = {}
|
|
local sharedSpells = sharedSpellsBetweenSpecs
|
|
|
|
for specId, spellTable in pairs(completeListOfSpells) do
|
|
parsedSpells[specId] = {}
|
|
|
|
--create a list of spells which is in use in the other spec talent tree
|
|
local spellsInUse = {}
|
|
for specId2, spellTable2 in pairs(completeListOfSpells) do
|
|
if (specId2 ~= specId) then
|
|
for spellId in pairs(spellTable2) do
|
|
spellsInUse[spellId] = true
|
|
end
|
|
end
|
|
end
|
|
for spellId in pairs(sharedSpells) do
|
|
spellsInUse[spellId] = true
|
|
end
|
|
|
|
--build the list of spells for this spec
|
|
for spellId in pairs(spellTable) do
|
|
if (not spellsInUse[spellId]) then
|
|
parsedSpells[specId][spellId] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local result = ""
|
|
for specId, spellsTable in pairs(parsedSpells) do
|
|
local specName = specNames[specId]
|
|
result = result .. "\n--" .. specName .. " " .. classNameLoc .. ":\n"
|
|
for spellId in pairs(spellsTable) do
|
|
local spellName = GetSpellInfo(spellId)
|
|
result = result .. "[" .. spellId .. "] = " .. specId .. ", --" .. spellName .. "\n"
|
|
end
|
|
end
|
|
|
|
Details:Dump({result})
|
|
end
|
|
end)
|
|
end
|
|
|
|
function Details.GenerateRacialSpellList()
|
|
local racialsSpells = "|n"
|
|
local locClassName, unitClass = UnitClass("player")
|
|
local locPlayerRace, playerRace, playerRaceId = UnitRace("player")
|
|
--get general spells from the spell book
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_GENERAL_TABID)
|
|
offset = offset + 1
|
|
local tabEnd = offset + numSpells
|
|
for entryOffset = offset, tabEnd - 1 do
|
|
local spellType, spellId = GetSpellBookItemInfo(entryOffset, "player")
|
|
if (spellId) then
|
|
local spell = Spell:CreateFromSpellID(spellId)
|
|
local subSpellName = spell:GetSpellSubtext()
|
|
if (subSpellName == "Racial") then
|
|
spellId = C_SpellBook.GetOverrideSpell(spellId)
|
|
local spellName = GetSpellInfo(spellId)
|
|
local isPassive = IsPassiveSpell(entryOffset, "player")
|
|
if (spellName and not isPassive) then
|
|
local cooldownTime = floor(GetSpellBaseCooldown(spellId) / 1000)
|
|
racialsSpells = racialsSpells .. "[" .. spellId .. "] = {cooldown = " .. cooldownTime .. ", duration = 0, specs = {}, talent = false, charges = 1, raceid = " .. playerRaceId .. ", race = \"".. playerRace .."\", class = \"\", type = 9}, --" .. spellName .. "(" .. playerRace .. ")|n"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
racialsSpells = racialsSpells .. "|n"
|
|
dumpt(racialsSpells)
|
|
end
|
|
|
|
--fill the passed table with spells from talents and spellbook, affect only the active spec
|
|
function Details.FillTableWithPlayerSpells(completeListOfSpells)
|
|
local GetItemStats = C_Item.GetItemStats
|
|
local GetSpellInfo = GetSpellInfo or function(spellID)
|
|
if not spellID then return nil end
|
|
|
|
local spellInfo = C_Spell.GetSpellInfo(spellID)
|
|
if spellInfo then
|
|
return spellInfo.name, nil, spellInfo.iconID, spellInfo.castTime, spellInfo.minRange,
|
|
spellInfo.maxRange, spellInfo.spellID, spellInfo.originalIconID
|
|
end
|
|
end
|
|
local GetSpellTabInfo = GetSpellTabInfo or (function(tabLine)
|
|
if not tabLine then return nil end
|
|
local skillLine = C_SpellBook.GetSpellBookSkillLineInfo(tabLine)
|
|
if skillLine then
|
|
return skillLine.name, skillLine.iconID, skillLine.itemIndexOffset,
|
|
skillLine.numSpellBookItems, skillLine.isGuild, skillLine.specID
|
|
end
|
|
end)
|
|
|
|
local GetSpellBookItemInfo = C_SpellBook and C_SpellBook.GetSpellBookItemType or GetSpellBookItemInfo
|
|
local IsPassiveSpell = C_SpellBook and C_SpellBook.IsSpellBookItemPassive or IsPassiveSpell
|
|
local GetNumSpellTabs = C_SpellBook and C_SpellBook.GetNumSpellBookSkillLines or GetNumSpellTabs
|
|
local spellBookPlayerEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Player or "player"
|
|
local HasPetSpells = C_SpellBook and C_SpellBook.HasPetSpells or HasPetSpells
|
|
local GetOverrideSpell = C_Spell and C_Spell.GetOverrideSpell or C_SpellBook.GetOverrideSpell
|
|
local GetSpellBookItemName = C_SpellBook and C_SpellBook.GetSpellBookItemName or GetSpellBookItemName
|
|
local spellBookPetEnum = Enum.SpellBookSpellBank and Enum.SpellBookSpellBank.Pet or "pet"
|
|
|
|
local GetSpellCharges = GetSpellCharges or function(spellId)
|
|
local chargesInfo = C_Spell.GetSpellCharges(spellId)
|
|
if (chargesInfo) then
|
|
return chargesInfo.currentCharges, chargesInfo.maxCharges, chargesInfo.cooldownStartTime, chargesInfo.cooldownDuration, chargesInfo.chargeModRate
|
|
end
|
|
end
|
|
|
|
local specId, specName, _, specIconTexture = GetSpecializationInfo(GetSpecialization())
|
|
local locPlayerRace, playerRace, playerRaceId = UnitRace("player")
|
|
local generalIndex = Enum.SpellBookSkillLineIndex and Enum.SpellBookSkillLineIndex.General or CONST_SPELLBOOK_GENERAL_TABID
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(generalIndex) --CONST_SPELLBOOK_GENERAL_TABID
|
|
|
|
if (not offset) then
|
|
return completeListOfSpells
|
|
end
|
|
|
|
offset = offset + 1
|
|
|
|
--get spells from the Spec spellbook
|
|
for i = 1, GetNumSpellTabs() do --called "lines" in new v11 api
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(i)
|
|
--print(tabName)
|
|
--if (tabTexture == specIconTexture) then
|
|
offset = offset + 1
|
|
local tabEnd = offset + numSpells
|
|
for entryOffset = offset, tabEnd - 1 do
|
|
local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum)
|
|
if (spellId) then
|
|
--print(GetSpellInfo(spellId))
|
|
if (spellType == "SPELL" or spellType == 1) then
|
|
--print(tabName, tabTexture == specIconTexture, offset, tabEnd,spellType, spellId)
|
|
spellId = GetOverrideSpell(spellId)
|
|
local spellName = GetSpellInfo(spellId)
|
|
local bIsPassive = IsPassiveSpell(entryOffset, spellBookPlayerEnum)
|
|
if (spellName and not bIsPassive) then
|
|
completeListOfSpells[spellId] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--end
|
|
end
|
|
|
|
--get class shared spells from the spell book
|
|
local tabName, tabTexture, offset, numSpells, isGuild, offspecId = GetSpellTabInfo(CONST_SPELLBOOK_CLASSSPELLS_TABID)
|
|
offset = offset + 1
|
|
local tabEnd = offset + numSpells
|
|
for entryOffset = offset, tabEnd - 1 do
|
|
local spellType, spellId = GetSpellBookItemInfo(entryOffset, spellBookPlayerEnum)
|
|
if (spellId) then
|
|
if (spellType == "SPELL" or spellType == 1) then
|
|
spellId = GetOverrideSpell(spellId)
|
|
local spellName = GetSpellInfo(spellId)
|
|
local bIsPassive = IsPassiveSpell(entryOffset, spellBookPlayerEnum)
|
|
|
|
if (spellName and not bIsPassive) then
|
|
completeListOfSpells[spellId] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local getNumPetSpells = function()
|
|
--'HasPetSpells' contradicts the name and return the amount of pet spells available instead of a boolean
|
|
return HasPetSpells()
|
|
end
|
|
|
|
--get pet spells from the pet spellbook
|
|
local numPetSpells = getNumPetSpells()
|
|
if (numPetSpells) then
|
|
for i = 1, numPetSpells do
|
|
local spellName, _, unmaskedSpellId = GetSpellBookItemName(i, spellBookPetEnum)
|
|
if (unmaskedSpellId) then
|
|
unmaskedSpellId = GetOverrideSpell(unmaskedSpellId)
|
|
local bIsPassive = IsPassiveSpell(i, spellBookPetEnum)
|
|
if (spellName and not bIsPassive) then
|
|
completeListOfSpells[unmaskedSpellId] = true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--dumpt(completeListOfSpells)
|
|
return completeListOfSpells
|
|
end
|
|
|
|
function Details.SavePlayTimeOnClass()
|
|
local className = select(2, UnitClass("player"))
|
|
if (className) then
|
|
--played time by expansion
|
|
local expansionLevel = GetExpansionLevel()
|
|
|
|
local expansionTable = Details.class_time_played[expansionLevel]
|
|
if (not expansionTable) then
|
|
expansionTable = {}
|
|
Details.class_time_played[expansionLevel] = expansionTable
|
|
end
|
|
|
|
local playedTime = expansionTable[className] or 0
|
|
expansionTable[className] = playedTime + GetTime() - Details.GetStartupTime()
|
|
end
|
|
end
|
|
|
|
function Details.GetPlayTimeOnClass()
|
|
local className = select(2, UnitClass("player"))
|
|
if (className) then
|
|
--played time by expansion
|
|
local expansionLevel = GetExpansionLevel()
|
|
|
|
local expansionTable = Details.class_time_played[expansionLevel]
|
|
if (not expansionTable) then
|
|
expansionTable = {}
|
|
Details.class_time_played[expansionLevel] = expansionTable
|
|
end
|
|
|
|
local playedTime = expansionTable[className]
|
|
if (playedTime) then
|
|
playedTime = playedTime +(GetTime() - Details.GetStartupTime())
|
|
return playedTime
|
|
end
|
|
end
|
|
return 0
|
|
end
|
|
|
|
function Details.GetPlayTimeOnClassString()
|
|
local playedTime = Details.GetPlayTimeOnClass()
|
|
local days = floor(playedTime / 86400) .. " days"
|
|
playedTime = playedTime % 86400
|
|
local hours = floor(playedTime / 3600) .. " hours"
|
|
playedTime = playedTime % 3600
|
|
local minutes = floor(playedTime / 60) .. " minutes"
|
|
|
|
local expansionLevel = GetExpansionLevel()
|
|
local expansionName = _G["EXPANSION_NAME" .. GetExpansionLevel()]
|
|
|
|
return "|cffffff00Time played this class(" .. expansionName .. "): " .. days .. " " .. hours .. " " .. minutes
|
|
end
|
|
|
|
hooksecurefunc("ChatFrame_DisplayTimePlayed", function()
|
|
if (Details.played_class_time) then
|
|
C_Timer.After(0, function()
|
|
local expansionName = _G["EXPANSION_NAME" .. GetExpansionLevel()]
|
|
for fontString in ChatFrame1.fontStringPool:EnumerateActive() do
|
|
if (fontString:GetText() and fontString:GetText():find(expansionName)) then
|
|
return
|
|
end
|
|
end
|
|
|
|
local levelText = TIME_PLAYED_LEVEL and TIME_PLAYED_LEVEL:gsub("%%s", "") or ""
|
|
for fontString in ChatFrame1.fontStringPool:EnumerateActive() do
|
|
if (fontString:GetText() and fontString:GetText():find(levelText)) then
|
|
print(Details.GetPlayTimeOnClassString() .. " \ncommand: /details playedclass")
|
|
break
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
end)
|
|
|
|
--game freeze prevention, there are people calling UpdateAddOnMemoryUsage() making the game client on the end user to freeze, this is bad, really bad.
|
|
--Details! replace the function call with one that do the same thing, but warns the player if the function freezes the client too many times.
|
|
--this feature is disabled by default, to enable it, type /run Details.check_stuttering = true and reload the game
|
|
local stutterCounter = 0
|
|
local bigStutterCounter = 0
|
|
local UpdateAddOnMemoryUsage_Original = _G.UpdateAddOnMemoryUsage
|
|
Details.UpdateAddOnMemoryUsage_Original = _G.UpdateAddOnMemoryUsage
|
|
|
|
Details.UpdateAddOnMemoryUsage_Custom = function()
|
|
local currentTime = debugprofilestop()
|
|
UpdateAddOnMemoryUsage_Original()
|
|
local deltaTime = debugprofilestop() - currentTime
|
|
|
|
if (deltaTime > 16) then
|
|
local callStack = debugstack(2, 0, 4)
|
|
--ignore if is coming from the micro menu tooltip
|
|
if (callStack:find("MainMenuBarPerformanceBarFrame_OnEnter")) then
|
|
return
|
|
end
|
|
|
|
if (deltaTime >= 500) then
|
|
bigStutterCounter = bigStutterCounter + 1
|
|
if (bigStutterCounter >= 6) then
|
|
Details:Msg("an addon made your game freeze for more than a half second, use '/details perf' to know more.")
|
|
bigStutterCounter = -10000 --make this msg appear only once
|
|
end
|
|
end
|
|
|
|
stutterCounter = stutterCounter + 1
|
|
local stutterDegree = 0
|
|
if (stutterCounter > 60) then
|
|
if (deltaTime < 48) then
|
|
Details:Msg("some addon may be causing small framerate stuttering, use '/details perf' to know more.")
|
|
stutterDegree = 1
|
|
|
|
elseif (deltaTime <= 100) then
|
|
Details:Msg("some addon may be causing framerate drops, use '/details perf' to know more.")
|
|
stutterDegree = 2
|
|
|
|
else
|
|
Details:Msg("some addon might be causing performance issues, use '/details perf' to know more.")
|
|
stutterDegree = 3
|
|
end
|
|
|
|
stutterCounter = -10000 --make this msg appear only once
|
|
end
|
|
|
|
Details.performanceData = {
|
|
deltaTime = deltaTime,
|
|
callStack = callStack,
|
|
culpritFunc = "_G.UpdateAddOnMemoryUsage()",
|
|
culpritDesc = "Calculates memory usage of addons",
|
|
}
|
|
end
|
|
end
|
|
|
|
Details.performanceData = {
|
|
deltaTime = 0,
|
|
callStack = "",
|
|
culpritFunc = "",
|
|
culpritDesc = "",
|
|
}
|
|
|
|
function Details:HandleRogueCombatSpecIconByGameVersion()
|
|
local _, _, _, patchVersion = GetBuildInfo()
|
|
if (patchVersion >= 70000) then --Legion
|
|
--rogue combat is a rogue outlaw
|
|
local rogueCombatCoords = Details.class_specs_coords[260]
|
|
rogueCombatCoords[1] = 0
|
|
rogueCombatCoords[2] = 64 / 512
|
|
rogueCombatCoords[3] = 384 / 512
|
|
rogueCombatCoords[4] = 448 / 512
|
|
|
|
--new versions of the game has a different icon for assassination
|
|
local rogueAssassinationCoords = Details.class_specs_coords[259]
|
|
rogueAssassinationCoords[1] = 64 / 512
|
|
rogueAssassinationCoords[2] = 128 / 512
|
|
rogueAssassinationCoords[3] = 384 / 512
|
|
rogueAssassinationCoords[4] = 448 / 512
|
|
end
|
|
end
|
|
|
|
function CopyText(text) --[[GLOBAL]]
|
|
if (not Details.CopyTextField) then
|
|
Details.CopyTextField = CreateFrame("Frame", "DetailsCopyText", UIParent, "BackdropTemplate")
|
|
Details.CopyTextField:SetHeight(14)
|
|
Details.CopyTextField:SetWidth(120)
|
|
Details.CopyTextField:SetPoint("center", UIParent, "center")
|
|
Details.CopyTextField:SetBackdrop(backdrop)
|
|
|
|
DetailsFramework:ApplyStandardBackdrop(Details.CopyTextField)
|
|
|
|
tinsert(UISpecialFrames, "DetailsCopyText")
|
|
|
|
Details.CopyTextField.textField = CreateFrame("editbox", nil, Details.CopyTextField, "BackdropTemplate")
|
|
Details.CopyTextField.textField:SetPoint("topleft", Details.CopyTextField, "topleft")
|
|
Details.CopyTextField.textField:SetAutoFocus(false)
|
|
Details.CopyTextField.textField:SetFontObject("GameFontHighlightSmall")
|
|
Details.CopyTextField.textField:SetAllPoints()
|
|
Details.CopyTextField.textField:EnableMouse(true)
|
|
|
|
Details.CopyTextField.textField:SetScript("OnEnterPressed", function()
|
|
Details.CopyTextField.textField:ClearFocus()
|
|
Details.CopyTextField:Hide()
|
|
end)
|
|
|
|
Details.CopyTextField.textField:SetScript("OnEscapePressed", function()
|
|
Details.CopyTextField.textField:ClearFocus()
|
|
Details.CopyTextField:Hide()
|
|
end)
|
|
|
|
Details.CopyTextField.textField:SetScript("OnChar", function()
|
|
Details.CopyTextField.textField:ClearFocus()
|
|
Details.CopyTextField:Hide()
|
|
end)
|
|
end
|
|
|
|
C_Timer.After(0.1, function()
|
|
Details.CopyTextField:Show()
|
|
Details.CopyTextField.textField:SetFocus()
|
|
Details.CopyTextField.textField:SetText(text)
|
|
Details.CopyTextField.textField:HighlightText()
|
|
end)
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------
|
|
--> cache maintenance
|
|
|
|
function Details222.Cache.DoMaintenance()
|
|
local currentTime = time()
|
|
local delay = 1036800 --12 days
|
|
|
|
if (currentTime > Details.latest_spell_pool_access + delay) then
|
|
local spellIdPoolBackup = DetailsFramework.table.copy({}, Details.spell_pool)
|
|
|
|
Details:Destroy(Details.spell_pool)
|
|
|
|
--preserve ignored spells spellId
|
|
for spellId in pairs(Details.spellid_ignored) do
|
|
Details.spell_pool[spellId] = spellIdPoolBackup[spellId]
|
|
end
|
|
|
|
Details.latest_spell_pool_access = currentTime
|
|
Details:Destroy(spellIdPoolBackup)
|
|
end
|
|
|
|
if (currentTime > Details.latest_npcid_pool_access + delay) then
|
|
local npcIdPoolBackup = DetailsFramework.table.copy({}, Details.npcid_pool)
|
|
|
|
Details:Destroy(Details.npcid_pool)
|
|
|
|
--preserve ignored npcs npcId
|
|
for npcId in pairs(Details.npcid_ignored) do
|
|
Details.npcid_pool[npcId] = npcIdPoolBackup[npcId]
|
|
end
|
|
Details.latest_npcid_pool_access = currentTime
|
|
Details:Destroy(npcIdPoolBackup)
|
|
end
|
|
|
|
if (currentTime > Details.latest_encounter_spell_pool_access + delay) then
|
|
Details:Destroy(Details.encounter_spell_pool)
|
|
Details.latest_encounter_spell_pool_access = currentTime
|
|
end
|
|
|
|
if (Details.boss_mods_timers and Details.boss_mods_timers.latest_boss_mods_access) then
|
|
if (currentTime > Details.boss_mods_timers.latest_boss_mods_access + delay) then
|
|
Details:Destroy(Details.boss_mods_timers.encounter_timers_bw)
|
|
Details:Destroy(Details.boss_mods_timers.encounter_timers_dbm)
|
|
Details.boss_mods_timers.latest_boss_mods_access = currentTime
|
|
end
|
|
end
|
|
|
|
--latest_shield_spellid_cache_access
|
|
--shield_spellid_cache
|
|
end
|