----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --global name declaration --use lua-language-server annotations to help the linter: --https://github.com/LuaLS/lua-language-server/wiki/Annotations#documenting-types --follow definitions declared in the file definitions.lua --follow game api definitions in the file LibLuaServer.lua _ = nil _G.Details = LibStub("AceAddon-3.0"):NewAddon("_detalhes", "AceTimer-3.0", "AceComm-3.0", "AceSerializer-3.0", "NickTag-1.0") --add the original name to the global namespace _detalhes = _G.Details --[[GLOBAL]] __details_debug = __details_debug or {} if (__details_debug.prescience_timeline) then wipe(__details_debug.prescience_timeline) end local addonName, Details222 = ... local version, build, date, tvs = GetBuildInfo() Details.build_counter = 13642 Details.alpha_build_counter = 13642 --if this is higher than the regular counter, use it instead Details.dont_open_news = true Details.game_version = version Details.userversion = version .. " " .. Details.build_counter Details.realversion = 165 --core version, this is used to check API version for scripts and plugins (see alias below) Details.gametoc = tvs Details.APIVersion = Details.realversion --core version Details.version = Details.userversion .. " (core " .. Details.realversion .. ")" --simple stirng to show to players Details.acounter = 1 --in case of a second release with the same .build_counter Details.curseforgeVersion = C_AddOns and C_AddOns.GetAddOnMetadata and C_AddOns.GetAddOnMetadata("Details", "Version") if (not Details.curseforgeVersion and GetAddOnMetadata) then Details.curseforgeVersion = GetAddOnMetadata("Details", "Version") end function Details:GetCoreVersion() return Details.realversion end Details.BFACORE = 131 --core version on BFA launch Details.SHADOWLANDSCORE = 143 --core version on Shadowlands launch Details.DRAGONFLIGHT = 147 --core version on Dragonflight launch Details.V11CORE = 160 --core version on V11 launch Details = Details local gameVersionPrefix = "VCT" --v1, v4, v11 Details.gameVersionPrefix = gameVersionPrefix pcall(function() Details.version_alpha_id = tonumber(Details.curseforgeVersion:match("%-(%d+)%-")) end) --WD 10288 RELEASE 10.0.2 --WD 10288 ALPHA 21 10.0.2 function Details.GetVersionString() local curseforgeVersion = Details.curseforgeVersion or "" local alphaId = curseforgeVersion:match("%-(%d+)%-") if (not alphaId) then --this is a release version alphaId = "RELEASE" else alphaId = "ALPHA " .. alphaId end return Details.gameVersionPrefix .. " " .. Details.build_counter .. " " .. alphaId .. " " .. Details.game_version .. "" end Details.DefaultTooltipIconSize = 20 --namespace for the player breakdown window Details.PlayerBreakdown = {} Details222.PlayerBreakdown = { DamageSpellsCache = {} } Details222.StartUp = {} Details222.Unknown = _G["UNKNOWN"] --namespace color Details222.ColorScheme = { ["gradient-background"] = {0.1215, 0.1176, 0.1294, 0.8}, } function Details222.ColorScheme.GetColorFor(colorScheme) return Details222.ColorScheme[colorScheme] end function Details222.DebugMsg(...) if (Details.debug) then print("|cFFCCAAAADetails! Debug:|r", ...) end end --cache of frames to call :SetColor() when the color scheme changes Details222.RegisteredFramesToColor = {} Details222.TrainingDummiesNpcId = { [194649] = true, --valdraken [189617] = true, --valdraken [194644] = true, --valdraken [198594] = true, --valdraken [194643] = true, --valdraken [189632] = true, --valdraken [194648] = true, --valdraken [194646] = true, --valdraken [197834] = true, --valdraken [31146] = true, --orgrimmar [153285] = true, --orgrimmar [114840] = true, --orgrimmar [114832] = true, --stormwind [153292] = true, --stormwind } ---@type details_storage_feature ---@diagnostic disable-next-line: missing-fields local storage = { DiffNames = {"normal", "heroic", "mythic", "raidfinder", "10player", "25player", "10playerheroic", "25playerheroic", "raidfinderclassic", "raidfindertimewalking", "timewalking"}, DiffNamesHash = {normal = 14, heroic = 15, mythic = 16, raidfinder = 17, ["10player"] = 3, ["25player"] = 4, ["10playerheroic"] = 5, ["25playerheroic"] = 6, raidfinderclassic = 7, raidfindertimewalking = 151, timewalking = 33}, DiffIdToName = { [14] = "normal", [15] = "heroic", [16] = "mythic", [17] = "raidfinder", [3] = "10player", [4] = "25player", [5] = "10playerheroic", [6] = "25playerheroic", [7] = "raidfinderclassic", [8] = "mythicdungeon", [151] = "raidfindertimewalking", [33] = "timewalking" }, IsDebug = false } Details222.storage = storage --namespace for damage spells (spellTable) Details222.DamageSpells = {} --namespace for texture Details222.Textures = {} Details222.Debug = { DebugPets = false, DebugPlayerPets = false, DebugBuff = false, } Details222.Tvs = tvs --namespace for pet Details222.Pets = {} Details222.PetContainer = { ---@type table Pets = {}, ---@type table IgnoredActors = {}, ---table that stores the player guid as keys and their petguid as values ---this is useful to know which pets are the legit class pet from the UNIT_PET event ---@type table UnitPetCache = {}, } --auto run code Details222.AutoRunCode = {} --options panel Details222.OptionsPanel = {} --store bar icons (left side of the damage bar) Details222.BarIconSetList = {} Details222.Instances = {} Details222.Combat = {} Details222.MythicPlus = { Charts = {}, Frames = {}, } Details222.Notes = {} Details222.MythicPlusBreakdown = {} Details222.EJCache = {} Details222.Segments = {} Details222.Tables = {} Details222.Mixins = {} Details222.Cache = {} Details222.Perf = {} Details222.Cooldowns = {} Details222.GarbageCollector = {} Details222.BreakdownWindow = {} Details222.PlayerStats = {} Details222.LoadSavedVariables = {} Details222.SaveVariables = {} Details222.GuessSpecSchedules = { Schedules = {}, } Details222.Profiling = {} Details222.ProfilingCache = {} Details222.TimeMachine = {} Details222.OnUseItem = {Trinkets = {}} Details222.Date = { GetDateForLogs = function() return _G.date("%Y-%m-%d %H:%M:%S") end, } Details222.ClassCache = {} Details222.ClassCache.ByName = {} Details222.ClassCache.ByGUID = {} Details222.UnitIdCache = {} Details222.Roskash = {} Details222.SpecHelpers = { [1473] = {}, } Details222.IgnoredWorldAuras = {} Details222.OneHourAuras = {} Details222.Parser = {} Details222.Actors = {} Details222.CurrentDPS = { Cache = {} } --store all data from the encounter journal Details222.EncounterJournalDump = {} --aura scanner Details222.AuraScan = {} ---@type instancedifficulty Details222.InstanceDifficulty = { ["DungeonNormal"] = 1, ["DungeonHeroic"] = 2, ["DungeonMythic"] = 23, ["DungeonMythicPlus"] = 8, ["RaidLFR"] = 17, ["RaidNormal"] = 14, ["RaidHeroic"] = 15, ["RaidMythic"] = 16, } Details222.DHook = hooksecurefunc local emptyFunction = function()end local emptyTable = {} ---context manager is a system that evaluates where the player is and create a set of extra rules that fit the content the player is doing ---@class contextmanager : table ---@field instanceType string ---@field instanceName string ---@field instanceId number ---@field instanceDifficulty number ---@field lastInstanceType string ---@field lastInstanceName string ---@field lastInstanceDifficulty number ---@field contextId string ---@field bContextStarted boolean ---@field bContextFinished boolean ---@field bHasContext boolean ---@field fHasLostInterest function ---@field fOnContextFinished function ---@field fOnCombatFinished function ---@field eventFrame frame ---@field DetailsEventListener table ---@field contextEventTable table ---@field StartContext function ---@field CheckContextInterest function ---@field FinishContext function ---@field GetContext function --tells what is the activity the player is doing Details222.ContextManager = { instanceType = "INIT", instanceName = "INIT", instanceDifficulty = 0, lastInstanceType = "INIT", lastInstanceName = "INIT", lastInstanceDifficulty = 0, contextId = "INIT", bContextStarted = false, bContextFinished = false, bHasContext = false, fOnContextFinished = emptyFunction, fHasLostInterest = emptyFunction, fOnCombatFinished = emptyFunction, contextEventTable = emptyTable, eventFrame = CreateFrame("frame"), ---start a new context, this is called from the CheckContextInterest() function ---@param self contextmanager ---@param instanceId number ---@param instanceName string ---@param instanceType string ---@param difficultyId number ---@param contextEventTable table ---@param fOnCombatFinished function run when details! finishes a combat ---@param fOnContextFinished function run when the context is finished ---@param fHasLostInterest function run when CheckContextInterest() fails to find a context StartContext = function(self, instanceId, instanceName, instanceType, difficultyId, contextEventTable, fOnCombatFinished, fOnContextFinished, fHasLostInterest) self.instanceType = instanceType self.instanceName = instanceName self.instanceId = instanceId self.instanceDifficulty = difficultyId self.bContextStarted = true self.bContextFinished = false self.bHasContext = true self.fOnContextFinished = fOnContextFinished self.fHasLostInterest = fHasLostInterest self.fOnCombatFinished = fOnCombatFinished self.contextEventTable = contextEventTable --create an event listener to grab the event when Details! finishes a combat if (not self.DetailsEventListener) then self.DetailsEventListener = Details:CreateEventListener() end self.DetailsEventListener:UnregisterEvent("COMBAT_PLAYER_LEAVE") --register the onFinishCombat for the context self.DetailsEventListener:RegisterEvent("COMBAT_PLAYER_LEAVE", fOnCombatFinished) --unregister all events self.eventFrame:UnregisterAllEvents() --register the events that the context require for i = 1, #contextEventTable.events do self.eventFrame:RegisterEvent(contextEventTable.events[i]) end --if the callback function returns true, the context is finished self.eventFrame:SetScript("OnEvent", function(eventFrame, event, ...) if (contextEventTable.callback(event, ...)) then Details222.DebugMsg("context manager event", event) --context completed Details222.DebugMsg("Context Completed!") C_Timer.After(1, fOnContextFinished) C_Timer.After(1.1, function() self:FinishContext() end) end end) Details222.DebugMsg("a new context has been set.") end, ---check if the player is in a context of interest ---@param self contextmanager ---@param instanceId number ---@param instanceName string ---@param instanceType string ---@param difficultyId number CheckContextInterest = function(self, instanceId, instanceName, instanceType, difficultyId) Details222.DebugMsg("Checking for new context:", instanceId, instanceName, instanceType, difficultyId) --normal, heroic and mythic0 dungeons on Retail local diffTable = Details222.InstanceDifficulty if (difficultyId == diffTable.DungeonNormal or difficultyId == diffTable.DungeonHeroic or difficultyId == diffTable.DungeonMythic) then if (DetailsFramework.IsDragonflightAndBeyond()) then --check if the player is in the same context if (self.bHasContext and self.instanceId == instanceId and self.instanceType == instanceType and self.instanceName == instanceName and self.instanceDifficulty == difficultyId) then return end do return end --if a context is found, finishes it before a new one is created if (self.bHasContext) then --discard the context Details222.DebugMsg("had an active context, finishing it.") self:FinishContext() end --set a new context where at the end of the dungeon it creates an overall segment for the run --function to verify if context is finished, in this case if all objectives of the dungeon has been completed by listening to the SCENARIO_COMPLETED event local contextEventTable = { events = {"SCENARIO_COMPLETED"}, callback = function(...) --when a context return true, the context is finished and will trigger a call on the fOnContextFinished function return true end } --create a contextId to tag combats that are part of the same context self.contextId = instanceName .. tostring(time()) --called when a combat finishes and this context is still active local fOnCombatFinished = function() local currentCombat = Details:GetCurrentCombat() currentCombat.context = self.contextId end ---this function evaluates if this context has lost its interest and should be discarded, return true if the context is no longer valid local fHasLostInterest = function(instanceId, instanceName, instanceType, difficultyId) --check if the player is still in the same context if (self.instanceId ~= instanceId or self.instanceType ~= instanceType or self.instanceName ~= instanceName or self.instanceDifficulty ~= difficultyId) then return true end end --will ba called when the context finishes, in this case when the SCENARIO_COMPLETED event is triggered local fOnContextFinished = function() --check if this is not a mythic+ run if (C_ChallengeMode) then if (C_ChallengeMode.GetActiveChallengeMapID() or C_ChallengeMode.GetActiveKeystoneInfo() or C_ChallengeMode.IsChallengeModeActive()) then print("did not start as this is a m+ run") return else print("this is not a m+ run") end end ---@type combat[] local interestCombats = {} --get all segments local segments = Details:GetCombatSegments() for i = 1, #segments do local segment = segments[i] if (segment.context == self.contextId) then interestCombats[#interestCombats+1] = segment end end if (#interestCombats > 0) then --start a new combat Details222.StartCombat() Details222.DebugMsg("merging", #interestCombats, "combats into a single combat.") ---@type combat local currentCombat = Details:GetCurrentCombat() --iterate over all interest combats for i = 1, #interestCombats do local interestCombat = interestCombats[i] --add the combat to the new combat currentCombat:AddCombat(interestCombat, i == 1, i == #interestCombats) end Details222.DebugMsg("combat time:", currentCombat:GetCombatTime()) --finish the new combat Details:EndCombat() currentCombat.is_trash = false currentCombat.combat_type = DETAILS_SEGMENTTYPE_DUNGEON_OVERALL currentCombat.is_dungeon_overall = true end Details222.DebugMsg("overall segment has been created.") end self:StartContext(instanceId, instanceName, instanceType, difficultyId, contextEventTable, fOnCombatFinished, fOnContextFinished, fHasLostInterest) return end else --if no context is found, check if there is a current context and check if it lost its interest if (self.bHasContext) then if (self.fHasLostInterest(self, instanceId, instanceName, instanceType, difficultyId)) then Details222.DebugMsg("no context found, but context is active, finishing the current context.") --discard the context self:FinishContext() end end end end, ---finish the current context ---@param self contextmanager FinishContext = function(self) if (not self.bHasContext or not self.bContextStarted or self.bContextFinished) then return end --mark this context as finished self.bContextFinished = true --reset context self.instanceType = "INIT" self.instanceName = "INIT" self.contextId = "INIT" self.instanceId = -1 self.instanceDifficulty = 0 self.bContextStarted = false self.bHasContext = false self.fOnContextFinished = emptyFunction self.fHasLostInterest = emptyFunction self.fOnCombatFinished = emptyFunction self.contextEventTable = emptyTable end, ---return the current contextIndex ---@param self contextmanager ---@return number|boolean, string?, string?, number? GetContext = function(self) if (self.bHasContext) then return self.instanceId, self.instanceName, self.instanceType, self.instanceDifficulty end return false end, } local GetSpellInfo = C_Spell and C_Spell.GetSpellInfo or GetSpellInfo Details222.GetSpellInfo = GetSpellInfo local UnitBuff = C_UnitAuras and C_UnitAuras.GetBuffDataByIndex or UnitBuff Details222.UnitBuff = UnitBuff local UnitDebuff = C_UnitAuras and C_UnitAuras.GetDebuffDataByIndex or UnitDebuff Details222.UnitDebuff = UnitDebuff if (C_Spell and C_Spell.GetSpellInfo) then Details222.GetSpellInfo = function(...) local result = GetSpellInfo(...) if result then return result.name, 1, result.iconID end end end if (C_UnitAuras and C_UnitAuras.GetAuraDataByIndex) then Details222.UnitBuff = function(unitToken, index, filter) local auraData = UnitBuff(unitToken, index, filter) if (not auraData) then return nil end return AuraUtil.UnpackAuraData(auraData) end Details222.UnitDebuff = function(unitToken, index, filter) local auraData = UnitDebuff(unitToken, index, filter) if (not auraData) then return nil end return AuraUtil.UnpackAuraData(auraData) end end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --initialization stuff local _ do local _detalhes = _G.Details _detalhes.resize_debug = {} local Loc = _G.LibStub("AceLocale-3.0"):GetLocale("Details") local news = { {"v11.0.7.13388.161", "January 19th, 2025"}, "Check Details! Damage Meter github page for update notes.", } local newsString = "|cFFF1F1F1" for i = 1, #news do local line = news[i] if (type(line) == "table") then local version = line[1] local date = line[2] newsString = newsString .. "|cFFFFFF00" .. version .. " (|cFFFF8800" .. date .. "|r):|r\n\n" else if (line ~= "") then newsString = newsString .. "|cFFFFFF00-|r " .. line .. "\n\n" else newsString = newsString .. " \n" end end end Loc["STRING_VERSION_LOG"] = newsString Loc ["STRING_DETAILS1"] = "|cffffaeaeDetails!:|r " --startup _detalhes.max_windowline_columns = 11 _detalhes.initializing = true _detalhes.enabled = true _detalhes.__index = _detalhes _detalhes._tempo = time() _detalhes.debug = false _detalhes.debug_chr = false _detalhes.opened_windows = 0 _detalhes.last_combat_time = 0 _detalhes.last_zone_type = "INIT" _detalhes.last_zone_id = -1 --store functions to create options frame Details.optionsSection = {} --containers --armazenas as fun��es do parser - All parse functions _detalhes.parser = {} _detalhes.parser_functions = {} Details222.parser_frame = CreateFrame("Frame") Details222.parser_frame:Hide() _detalhes.pvp_parser_frame = CreateFrame("Frame") _detalhes.MacroList = { {Name = "Click on Your Own Bar", Desc = "To open the player details window on your character, like if you click on your bar in the damage window. The number '1' is the window number where it'll click.", MacroText = "/script Details:OpenPlayerDetails(1)"}, {Name = "Open Encounter Breakdown", Desc = "Open the encounter breakdown plugin. Details! Encounter Breakdown (plugin) must be enabled.", MacroText = "/script Details:OpenPlugin ('Encounter Breakdown')"}, {Name = "Open Damage per Phase", Desc = "Open the encounter breakdown plugin in the phase tab. Details! Encounter Breakdown (plugin) must be enabled.", MacroText = "/script Details:OpenPlugin ('Encounter Breakdown'); local a=Details_EncounterDetails and Details_EncounterDetails.buttonSwitchPhases:Click()"}, {Name = "Reset Data", Desc = "Reset the overall and regular segments data. Use 'ResetSegmentOverallData' to reset only the overall.", MacroText = "/script Details:ResetSegmentData()"}, {Name = "Change What the Window Shows", Desc = "Make a window show different data. SetDisplay uses (segment, displayGroup, displayID), the menu from the sword icon is in order (damage = group 1, overheal is: displayGroup 2 displayID 3.", MacroText = "/script Details:GetWindow(1):SetDisplay( DETAILS_SEGMENTID_CURRENT, 4, 5 )"}, {Name = "Toggle Window Height to Max Size", Desc = "Make a window be 450 pixel height, pressing the macro again toggle back to the original size. The number '1' if the window number. Hold a click in any window to show their number.", MacroText = "/script Details:GetWindow(1):ToggleMaxSize()"}, -- /script Details:OpenPlugin ('Advanced Death Logs'); local a = Details_DeathGraphsModeEnduranceButton and Details_DeathGraphsModeEnduranceButton.MyObject:Click() {Name = "Report What is Shown In the Window", Desc = "Report the current data shown in the window, the number 1 is the window number, replace it to report another window.", MacroText = "/script Details:FastReportWindow(1)"}, } --current instances of the exp (need to maintain) - deprecated july 2024 - should do this automatically Details.InstancesToStoreData = { --mapId [2769] = true, --Liberation of Undermine v11.1 T2 [2657] = true, --Nerub-ar Palace v11 T1 [2294] = true, --Nerub-ar Palace v11 T1 } --store shield information for absorbs _detalhes.ShieldCache = {} --armazena as fun��es dos frames - Frames functions _detalhes.gump = _G ["DetailsFramework"] function _detalhes:GetFramework() return self.gump end GameCooltip = GameCooltip2 --anima��es dos icones _detalhes.icon_animations = { load = { in_use = {}, available = {}, }, } --make a color namespace Details.Colors = {} function Details.Colors.GetMenuTextColor() return "orange" end function Details:GetTextureAtlasTable() return Details.TextureAtlas end --armazena as fun��es para inicializa��o dos dados - Metatable functions _detalhes.refresh = {} --armazena as fun��es para limpar e guardas os dados - Metatable functions _detalhes.clear = {} --armazena a config do painel de fast switch _detalhes.switch = {} --armazena os estilos salvos _detalhes.savedStyles = {} --armazena quais atributos possue janela de atributos - contain attributes and sub attributos wich have a detailed window (left click on a row) _detalhes.row_singleclick_overwrite = {} --report _detalhes.ReportOptions = {} --armazena os buffs registrados - store buffs ids and functions _detalhes.Buffs = {} --initialize buff table -- cache de grupo _detalhes.cache_damage_group = {} _detalhes.cache_healing_group = {} _detalhes.cache_npc_ids = {} --cache de specs _detalhes.cached_specs = {} _detalhes.cached_talents = {} --ignored pets _detalhes.pets_ignored = {} _detalhes.pets_no_owner = {} --dual candidates _detalhes.duel_candidates = {} --armazena as skins dispon�veis para as janelas _detalhes.skins = {} --armazena os hooks das fun��es do parser ---@type table _detalhes.hooks = {} --informa��es sobre a luta do boss atual _detalhes.encounter_end_table = {} _detalhes.encounter_table = {} _detalhes.encounter_counter = {} _detalhes.encounter_dungeons = {} --unitId dos inimigos dentro de uma arena _detalhes.arena_enemies = {} --reliable char data sources --actors that are using details! and sent character data, we don't need query inspect on these actors _detalhes.trusted_characters = {} --informa��es sobre a arena atual _detalhes.arena_table = {} _detalhes.arena_info = { --need to get the new mapID for 8.0.1 [562] = {file = "LoadScreenBladesEdgeArena", coords = {0, 1, 0.29296875, 0.9375}}, -- Circle of Blood Arena [617] = {file = "LoadScreenDalaranSewersArena", coords = {0, 1, 0.29296875, 0.857421875}}, --Dalaran Arena [559] = {file = "LoadScreenNagrandArenaBattlegrounds", coords = {0, 1, 0.341796875, 1}}, --Ring of Trials [980] = {file = "LoadScreenTolvirArena", coords = {0, 1, 0.29296875, 0.857421875}}, --Tol'Viron Arena [572] = {file = "LoadScreenRuinsofLordaeronBattlegrounds", coords = {0, 1, 0.341796875, 1}}, --Ruins of Lordaeron [1134] = {file = "LoadingScreen_Shadowpan_bg", coords = {0, 1, 0.29296875, 0.857421875}}, -- Tiger's Peak --legion, thanks @pas06 on curse forge for the mapIds [1552] = {file = "LoadingScreen_ArenaValSharah_wide", coords = {0, 1, 0.29296875, 0.857421875}}, -- Ashmane's Fall [1504] = {file = "LoadingScreen_BlackrookHoldArena_wide", coords = {0, 1, 0.29296875, 0.857421875}}, --Black Rook Hold --"LoadScreenOrgrimmarArena", --Ring of Valor } Details.IgnoredEnemyNpcsTable = { [31216] = true, --mirror image [53006] = true, --spirit link totem [63508] = true, --xuen [73967] = true, --xuen } function _detalhes:GetArenaInfo (mapid) local t = _detalhes.arena_info [mapid] if (t) then return t.file, t.coords end end _detalhes.battleground_info = { --need to get the nwee mapID for 8.0.1 [489] = {file = "LoadScreenWarsongGulch", coords = {0, 1, 121/512, 484/512}}, --warsong gulch [727] = {file = "LoadScreenSilvershardMines", coords = {0, 1, 251/1024, 840/1024}}, --silvershard mines [529] = {file = "LoadscreenArathiBasin", coords = {0, 1, 126/512, 430/512}}, --arathi basin [566] = {file = "LoadScreenNetherBattlegrounds", coords = {0, 1, 142/512, 466/512}}, --eye of the storm [30] = {file = "LoadScreenPvpBattleground", coords = {0, 1, 127/512, 500/512}}, --alterac valley [761] = {file = "LoadScreenGilneasBG2", coords = {0, 1, 281/1024, 878/1024}}, --the battle for gilneas [726] = {file = "LoadScreenTwinPeaksBG", coords = {0, 1, 294/1024, 876/1024}}, --twin peaks [998] = {file = "LoadScreenValleyofPower", coords = {0, 1, 257/1024, 839/1024}}, --temple of kotmogu [1105] = {file = "LoadScreen_GoldRush", coords = {0, 1, 264/1024, 840/1024}}, --deepwind gorge [607] = {file = "LoadScreenNorthrendBG", coords = {0, 1, 302/1024, 879/1024}}, --strand of the ancients [628] = {file = "LOADSCREENISLEOFCONQUEST", coords = {0, 1, 297/1024, 878/1024}}, --isle of conquest --[] = {file = "", coords = {0, 1, 0, 0}}, -- } function _detalhes:GetBattlegroundInfo(mapid) local battlegroundInfo = _detalhes.battleground_info[mapid] if (battlegroundInfo) then return battlegroundInfo.file, battlegroundInfo.coords end end --tokenid _detalhes.TokenID = { ["SPELL_PERIODIC_DAMAGE"] = 1, ["SPELL_EXTRA_ATTACKS"] = 2, ["SPELL_DAMAGE"] = 3, ["SPELL_BUILDING_DAMAGE"] = 4, ["SWING_DAMAGE"] = 5, ["RANGE_DAMAGE"] = 6, ["DAMAGE_SHIELD"] = 7, ["DAMAGE_SPLIT"] = 8, ["RANGE_MISSED"] = 9, ["SWING_MISSED"] = 10, ["SPELL_MISSED"] = 11, ["SPELL_PERIODIC_MISSED"] = 12, ["SPELL_BUILDING_MISSED"] = 13, ["DAMAGE_SHIELD_MISSED"] = 14, ["ENVIRONMENTAL_DAMAGE"] = 15, ["SPELL_HEAL"] = 16, ["SPELL_PERIODIC_HEAL"] = 17, ["SPELL_HEAL_ABSORBED"] = 18, ["SPELL_ABSORBED"] = 19, ["SPELL_AURA_APPLIED"] = 20, ["SPELL_AURA_REMOVED"] = 21, ["SPELL_AURA_REFRESH"] = 22, ["SPELL_AURA_APPLIED_DOSE"] = 23, ["SPELL_ENERGIZE"] = 24, ["SPELL_PERIODIC_ENERGIZE"] = 25, ["SPELL_CAST_SUCCESS"] = 26, ["SPELL_DISPEL"] = 27, ["SPELL_STOLEN"] = 28, ["SPELL_AURA_BROKEN"] = 29, ["SPELL_AURA_BROKEN_SPELL"] = 30, ["SPELL_RESURRECT"] = 31, ["SPELL_INTERRUPT"] = 32, ["UNIT_DIED"] = 33, ["UNIT_DESTROYED"] = 34, } ---@type table local npcIdToIcon = { [98035] = 1378282, --dreadstalker [17252] = 136216, --felguard [136404] = 132182, --bilescourge [136398] = 626007, --illidari satyr [136403] = 1100177, --void terror [136402] = 1581747, --ur'zyk [136399] = 1709931, --visious hellhound [136406] = 615148, --shivarra [136407] = 615025, --wrathguard [136408] = 1709932, --darkhound } _detalhes.NpcIdToIcon = npcIdToIcon --armazena instancias inativas _detalhes.unused_instances = {} _detalhes.default_skin_to_use = "Minimalistic" _detalhes.instance_title_text_timer = {} --player detail skin _detalhes.playerdetailwindow_skins = {} _detalhes.BitfieldSwapDebuffsIDs = {265646, 272407, 269691, 273401, 269131, 260900, 260926, 284995, 292826, 311367, 310567, 308996, 307832, 327414, 337253, 36797, 37122, 362397} _detalhes.BitfieldSwapDebuffsSpellIDs = { [360418] = true } --auto run code _detalhes.RunCodeTypes = { {Name = "On Initialization", Desc = "Run code when Details! initialize or when a profile is changed.", Value = 1, ProfileKey = "on_init"}, {Name = "On Zone Changed", Desc = "Run code when the zone where the player is in has changed (e.g. entered in a raid).", Value = 2, ProfileKey = "on_zonechanged"}, {Name = "On Enter Combat", Desc = "Run code when the player enters in combat.", Value = 3, ProfileKey = "on_entercombat"}, {Name = "On Leave Combat", Desc = "Run code when the player left combat.", Value = 4, ProfileKey = "on_leavecombat"}, {Name = "On Spec Change", Desc = "Run code when the player has changed its specialization.", Value = 5, ProfileKey = "on_specchanged"}, {Name = "On Enter/Leave Group", Desc = "Run code when the player has entered or left a party or raid group.", Value = 6, ProfileKey = "on_groupchange"}, } --run a function without stopping the execution in case of an error function Details.SafeRun(func, executionName, ...) local runToCompletion, errorText = pcall(func, ...) if (not runToCompletion) then if (Details.debug) then Details:Msg("Safe run failed:", executionName, errorText) end return false end return true end --tooltip _detalhes.tooltip_backdrop = { bgFile = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]], edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], tile = true, edgeSize = 16, tileSize = 16, insets = {left = 3, right = 3, top = 4, bottom = 4} } _detalhes.tooltip_border_color = {1, 1, 1, 1} _detalhes.tooltip_spell_icon = {file = [[Interface\CHARACTERFRAME\UI-StateIcon]], coords = {36/64, 58/64, 7/64, 26/64}} _detalhes.tooltip_target_icon = {file = [[Interface\Addons\Details\images\icons]], coords = {0, 0.03125, 0.126953125, 0.15625}} --icons _detalhes.attribute_icons = [[Interface\AddOns\Details\images\atributos_icones]] function _detalhes:GetAttributeIcon (attribute) return _detalhes.attribute_icons, 0.125 * (attribute - 1), 0.125 * attribute, 0, 1 end --colors _detalhes.default_backdropcolor = {.094117, .094117, .094117, .8} _detalhes.default_backdropbordercolor = {0, 0, 0, 1} --Plugins --plugin templates DetailsFramework:NewColor("DETAILS_PLUGIN_BUTTONTEXT_COLOR", 0.9999, 0.8196, 0, 1) DetailsFramework:NewColor("DETAILS_HEADER_YELLOW", 227/255, 186/255, 4/255) DetailsFramework:InstallTemplate("button", "DETAILS_PLUGINPANEL_BUTTON_TEMPLATE", { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {0, 0, 0, .5}, backdropbordercolor = {0, 0, 0, .5}, onentercolor = {0.3, 0.3, 0.3, .5}, } ) DetailsFramework:InstallTemplate("button", "DETAILS_PLUGINPANEL_BUTTONSELECTED_TEMPLATE", { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {0, 0, 0, .5}, backdropbordercolor = {1, 1, 0, 1}, onentercolor = {0.3, 0.3, 0.3, .5}, } ) DetailsFramework:InstallTemplate("button", "DETAILS_PLUGIN_BUTTON_TEMPLATE", { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {0, 0, 0, 1}, onentercolor = {1, 1, 1, .9}, textcolor = "DETAILS_PLUGIN_BUTTONTEXT_COLOR", textsize = 10, width = 120, height = 20, } ) DetailsFramework:InstallTemplate("button", "DETAILS_PLUGIN_BUTTONSELECTED_TEMPLATE", { backdrop = {edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true}, backdropcolor = {1, 1, 1, .5}, backdropbordercolor = {1, .7, 0, 1}, onentercolor = {1, 1, 1, .9}, textcolor = "DETAILS_PLUGIN_BUTTONTEXT_COLOR", textsize = 10, width = 120, height = 20, } ) DetailsFramework:InstallTemplate("button", "DETAILS_TAB_BUTTON_TEMPLATE", { width = 100, height = 20, }, "DETAILS_PLUGIN_BUTTON_TEMPLATE" ) DetailsFramework:InstallTemplate("button","DETAILS_TAB_BUTTONSELECTED_TEMPLATE", { width = 100, height = 20, }, "DETAILS_PLUGIN_BUTTONSELECTED_TEMPLATE" ) Details.PluginsGlobalNames = {} Details.PluginsLocalizedNames = {} --raid ------------------------------------------------------------------- --general function for raid mode plugins Details.RaidTables = {} --menu for raid modes Details.RaidTables.Menu = {} --plugin objects for raid mode Details.RaidTables.Plugins = {} --name to plugin object Details.RaidTables.NameTable = {} --using by Details.RaidTables.InstancesInUse = {} Details.RaidTables.PluginsInUse = {} --solo ------------------------------------------------------------------- --general functions for solo mode plugins Details.SoloTables = {} --maintain plugin menu Details.SoloTables.Menu = {} --plugins objects for solo mode Details.SoloTables.Plugins = {} --name to plugin object Details.SoloTables.NameTable = {} --toolbar ------------------------------------------------------------------- --plugins container Details.ToolBar = {} --current showing icons Details.ToolBar.Shown = {} Details.ToolBar.AllButtons = {} --plugin objects Details.ToolBar.Plugins = {} --name to plugin object Details.ToolBar.NameTable = {} Details.ToolBar.Menu = {} --statusbar ------------------------------------------------------------------- --plugins container Details.StatusBar = {} --maintain plugin menu Details.StatusBar.Menu = {} --plugins object Details.StatusBar.Plugins = {} --name to plugin object Details.StatusBar.NameTable = {} --constants if (DetailsFramework.IsWotLKWow()) then --[[global]] DETAILS_HEALTH_POTION_ID = 33447 -- Runic Healing Potion --[[global]] DETAILS_HEALTH_POTION2_ID = 41166 -- Runic Healing Injector --[[global]] DETAILS_REJU_POTION_ID = 40087 -- Powerful Rejuvenation Potion --[[global]] DETAILS_REJU_POTION2_ID = 40077 -- Crazy Alchemist's Potion --[[global]] DETAILS_MANA_POTION_ID = 33448 -- Runic Mana Potion --[[global]] DETAILS_MANA_POTION2_ID = 42545 -- Runic Mana Injector --[[global]] DETAILS_FOCUS_POTION_ID = 307161 --[[global]] DETAILS_HEALTHSTONE_ID = 47875 --Warlock's Healthstone --[[global]] DETAILS_HEALTHSTONE2_ID = 47876 --Warlock's Healthstone (1/2 Talent) --[[global]] DETAILS_HEALTHSTONE3_ID = 47877 --Warlock's Healthstone (2/2 Talent) --[[global]] DETAILS_INT_POTION_ID = 40212 --Potion of Wild Magic --[[global]] DETAILS_AGI_POTION_ID = 40211 --Potion of Speed --[[global]] DETAILS_STR_POTION_ID = 307164 --[[global]] DETAILS_STAMINA_POTION_ID = 40093 --Indestructible Potion --[[global]] DETAILS_HEALTH_POTION_LIST = { [DETAILS_HEALTH_POTION_ID] = true, -- Runic Healing Potion [DETAILS_HEALTH_POTION2_ID] = true, -- Runic Healing Injector [DETAILS_HEALTHSTONE_ID] = true, --Warlock's Healthstone [DETAILS_HEALTHSTONE2_ID] = true, --Warlock's Healthstone (1/2 Talent) [DETAILS_HEALTHSTONE3_ID] = true, --Warlock's Healthstone (2/2 Talent) [DETAILS_REJU_POTION_ID] = true, -- Powerful Rejuvenation Potion [DETAILS_REJU_POTION2_ID] = true, -- Crazy Alchemist's Potion [DETAILS_MANA_POTION_ID] = true, -- Runic Mana Potion [DETAILS_MANA_POTION2_ID] = true, -- Runic Mana Injector } else --[[global]] DETAILS_HEALTH_POTION_ID = 307192 -- spiritual healing potion --[[global]] DETAILS_HEALTH_POTION2_ID = 359867 --cosmic healing potion --[[global]] DETAILS_REJU_POTION_ID = 307194 --[[global]] DETAILS_MANA_POTION_ID = 307193 --[[global]] DETAILS_FOCUS_POTION_ID = 307161 --[[global]] DETAILS_HEALTHSTONE_ID = 6262 --[[global]] DETAILS_INT_POTION_ID = 307162 --[[global]] DETAILS_AGI_POTION_ID = 307159 --[[global]] DETAILS_STR_POTION_ID = 307164 --[[global]] DETAILS_STAMINA_POTION_ID = 307163 --[[global]] DETAILS_HEALTH_POTION_LIST = { [DETAILS_HEALTH_POTION_ID] = true, --Healing Potion [DETAILS_HEALTHSTONE_ID] = true, --Warlock's Healthstone [DETAILS_REJU_POTION_ID] = true, --Rejuvenation Potion [DETAILS_MANA_POTION_ID] = true, --Mana Potion [323436] = true, --Phial of Serenity (from Kyrians) [DETAILS_HEALTH_POTION2_ID] = true, } end --[[global]] DETAILS_MODE_GROUP = 2 --[[global]] DETAILS_MODE_ALL = 3 Details._detalhes_props = { DATA_TYPE_START = 1, --Something on start DATA_TYPE_END = 2, --Something on end MODO_ALONE = 1, --Solo MODO_GROUP = 2, --Group MODO_ALL = 3, --Everything MODO_RAID = 4, --Raid } Details.modos = { alone = 1, --Solo group = 2, --Group all = 3, --Everything raid = 4 --Raid } Details.divisores = { abre = "(", --open fecha = ")", --close colocacao = ". " --dot } Details.role_texcoord = { DAMAGER = "72:130:69:127", HEALER = "72:130:2:60", TANK = "5:63:69:127", NONE = "139:196:69:127", } Details.role_texcoord_normalized = { DAMAGER = {72/256, 130/256, 69/256, 127/256}, HEALER = {72/256, 130/256, 2/256, 60/256}, TANK = {5/256, 63/256, 69/256, 127/256}, NONE = {139/256, 196/256, 69/256, 127/256}, } Details.player_class = { ["HUNTER"] = true, ["WARRIOR"] = true, ["PALADIN"] = true, ["SHAMAN"] = true, ["MAGE"] = true, ["ROGUE"] = true, ["PRIEST"] = true, ["WARLOCK"] = true, ["DRUID"] = true, ["MONK"] = true, ["DEATHKNIGHT"] = true, ["DEMONHUNTER"] = true, } Details.classstring_to_classid = { ["WARRIOR"] = 1, ["PALADIN"] = 2, ["HUNTER"] = 3, ["ROGUE"] = 4, ["PRIEST"] = 5, ["DEATHKNIGHT"] = 6, ["SHAMAN"] = 7, ["MAGE"] = 8, ["WARLOCK"] = 9, ["MONK"] = 10, ["DRUID"] = 11, ["DEMONHUNTER"] = 12, } Details.classid_to_classstring = { [1] = "WARRIOR", [2] = "PALADIN", [3] = "HUNTER", [4] = "ROGUE", [5] = "PRIEST", [6] = "DEATHKNIGHT", [7] = "SHAMAN", [8] = "MAGE", [9] = "WARLOCK", [10] = "MONK", [11] = "DRUID", [12] = "DEMONHUNTER", } local Loc = LibStub("AceLocale-3.0"):GetLocale ("Details") Details.segmentos = { label = Loc ["STRING_SEGMENT"]..": ", overall = Loc ["STRING_TOTAL"], overall_standard = Loc ["STRING_OVERALL"], current = Loc ["STRING_CURRENT"], current_standard = Loc ["STRING_CURRENTFIGHT"], past = Loc ["STRING_FIGHTNUMBER"] } Details._detalhes_props["modo_nome"] = { [_detalhes._detalhes_props["MODO_ALONE"]] = Loc ["STRING_MODE_SELF"], [_detalhes._detalhes_props["MODO_GROUP"]] = Loc ["STRING_MODE_GROUP"], [_detalhes._detalhes_props["MODO_ALL"]] = Loc ["STRING_MODE_ALL"], [_detalhes._detalhes_props["MODO_RAID"]] = Loc ["STRING_MODE_RAID"] } --[[global]] DETAILS_MODE_SOLO = 1 --[[global]] DETAILS_MODE_RAID = 4 --[[global]] DETAILS_MODE_GROUP = 2 --[[global]] DETAILS_MODE_ALL = 3 Details.icones = { --report window report = { up = "Interface\\FriendsFrame\\UI-Toast-FriendOnlineIcon", down = "Interface\\ItemAnimations\\MINIMAP\\TRACKING\\Profession", disabled = "Interface\\ItemAnimations\\MINIMAP\\TRACKING\\Profession", highlight = nil } } Details.missTypes = {"ABSORB", "BLOCK", "DEFLECT", "DODGE", "EVADE", "IMMUNE", "MISS", "PARRY", "REFLECT", "RESIST"} --do not localize-me function Details.SendHighFive() Details.users = {{UnitName("player"), GetRealmName(), (Details.userversion or "") .. " (" .. Details.APIVersion .. ")"}} Details.sent_highfive = GetTime() if (IsInRaid()) then Details:SendRaidData(Details.network.ids.HIGHFIVE_REQUEST) else Details:SendPartyData(Details.network.ids.HIGHFIVE_REQUEST) end end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --frames local CreateFrame = CreateFrame --api locals local UIParent = UIParent --api locals --create the breakdown window frame ---@type breakdownwindow Details.BreakdownWindowFrame = CreateFrame("Frame", "DetailsBreakdownWindow", UIParent, "BackdropTemplate") Details.PlayerDetailsWindow = Details.BreakdownWindowFrame Details.BreakdownWindow = Details.BreakdownWindowFrame --Event Frame Details.listener = CreateFrame("Frame", nil, UIParent) Details.listener:RegisterEvent("ADDON_LOADED") Details.listener:SetFrameStrata("LOW") Details.listener:SetFrameLevel(9) Details.listener.FrameTime = 0 Details.overlay_frame = CreateFrame("Frame", nil, UIParent) Details.overlay_frame:SetFrameStrata("TOOLTIP") --Pet Owner Finder CreateFrame("GameTooltip", "DetailsPetOwnerFinder", nil, "GameTooltipTemplate") ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --plugin defaults --backdrop Details.PluginDefaults = {} Details.PluginDefaults.Backdrop = {bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, insets = {left = 1, right = 1, top = 1, bottom = 1}} Details.PluginDefaults.BackdropColor = {0, 0, 0, .6} Details.PluginDefaults.BackdropBorderColor = {0, 0, 0, 1} function Details.GetPluginDefaultBackdrop() return Details.PluginDefaults.Backdrop, Details.PluginDefaults.BackdropColor, Details.PluginDefaults.BackdropBorderColor end ------------------------------------------------------------------------------------------ -- welcome panel function _detalhes:CreateWelcomePanel(name, parent, width, height, makeMovable) local newWelcomePanel = CreateFrame("frame", name, parent or UIParent, "BackdropTemplate") DetailsFramework:ApplyStandardBackdrop(newWelcomePanel) newWelcomePanel:SetSize(width or 1, height or 1) if (makeMovable) then newWelcomePanel:SetScript("OnMouseDown", function(self, button) if (self.isMoving) then return end if (button == "RightButton") then self:Hide() else self:StartMoving() self.isMoving = true end end) newWelcomePanel:SetScript("OnMouseUp", function(self, button) if (self.isMoving and button == "LeftButton") then self:StopMovingOrSizing() self.isMoving = nil end end) newWelcomePanel:SetToplevel(true) newWelcomePanel:SetMovable(true) end return newWelcomePanel end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --functions _detalhes.empty_function = function() end _detalhes.empty_table = {} --register textures and fonts for shared media ---@type table local SharedMedia = LibStub:GetLibrary ("LibSharedMedia-3.0") --default bars SharedMedia:Register("statusbar", "Details Hyanda Reverse", [[Interface\AddOns\Details\images\bar_textures\bar_hyanda_reverse.png]]) SharedMedia:Register("statusbar", "You Are the Best!", [[Interface\AddOns\Details\images\bar_textures\bar_best.png]]) SharedMedia:Register("statusbar", "Details Hyanda", [[Interface\AddOns\Details\images\bar_hyanda]]) SharedMedia:Register("statusbar", "Details D'ictum", [[Interface\AddOns\Details\images\bar4]]) SharedMedia:Register("statusbar", "Details Vidro", [[Interface\AddOns\Details\images\bar4_vidro]]) SharedMedia:Register("statusbar", "Details D'ictum (reverse)", [[Interface\AddOns\Details\images\bar4_reverse]]) --flat bars SharedMedia:Register("statusbar", "Skyline", [[Interface\AddOns\Details\images\bar_skyline]]) SharedMedia:Register("statusbar", "Details Serenity", [[Interface\AddOns\Details\images\bar_serenity]]) SharedMedia:Register("statusbar", "BantoBar", [[Interface\AddOns\Details\images\BantoBar]]) SharedMedia:Register("statusbar", "Skyline Compact", [[Interface\AddOns\Details\images\bar_textures\bar_skyline_compact.png]]) SharedMedia:Register("statusbar", "WorldState Score", [[Interface\WorldStateFrame\WORLDSTATEFINALSCORE-HIGHLIGHT]]) SharedMedia:Register("statusbar", "DGround", [[Interface\AddOns\Details\images\bar_background]]) SharedMedia:Register("statusbar", "Details Flat", [[Interface\AddOns\Details\images\bar_background]]) SharedMedia:Register("statusbar", "Splitbar", [[Interface\AddOns\Details\images\bar_textures\split_bar]]) SharedMedia:Register("statusbar", "Details2020", [[Interface\AddOns\Details\images\bar_textures\texture2020]]) SharedMedia:Register("statusbar", "Left White Gradient", [[Interface\AddOns\Details\images\bar_textures\gradient_white_10percent_left]]) SharedMedia:Register("statusbar", "Details! Slash", [[Interface\AddOns\Details\images\bar_textures\bar_of_bars.png]]) --window bg and bar order SharedMedia:Register("background", "Details Ground", [[Interface\AddOns\Details\images\background]]) SharedMedia:Register("border", "Details BarBorder 1", [[Interface\AddOns\Details\images\border_1]]) SharedMedia:Register("border", "Details BarBorder 2", [[Interface\AddOns\Details\images\border_2]]) SharedMedia:Register("border", "Details BarBorder 3", [[Interface\AddOns\Details\images\border_3]]) SharedMedia:Register("border", "1 Pixel", [[Interface\Buttons\WHITE8X8]]) --misc fonts SharedMedia:Register("font", "Oswald", [[Interface\Addons\Details\fonts\Oswald-Regular.ttf]]) SharedMedia:Register("font", "Nueva Std Cond", [[Interface\Addons\Details\fonts\Nueva Std Cond.ttf]]) SharedMedia:Register("font", "Accidental Presidency", [[Interface\Addons\Details\fonts\Accidental Presidency.ttf]]) SharedMedia:Register("font", "TrashHand", [[Interface\Addons\Details\fonts\TrashHand.TTF]]) SharedMedia:Register("font", "Harry P", [[Interface\Addons\Details\fonts\HARRYP__.TTF]]) SharedMedia:Register("font", "FORCED SQUARE", [[Interface\Addons\Details\fonts\FORCED SQUARE.ttf]]) SharedMedia:Register("sound", "Details Gun1", [[Interface\Addons\Details\sounds\sound_gun2.ogg]]) SharedMedia:Register("sound", "Details Gun2", [[Interface\Addons\Details\sounds\sound_gun3.ogg]]) SharedMedia:Register("sound", "Details Jedi1", [[Interface\Addons\Details\sounds\sound_jedi1.ogg]]) SharedMedia:Register("sound", "Details Whip1", [[Interface\Addons\Details\sounds\sound_whip1.ogg]]) SharedMedia:Register("sound", "Details Horn", [[Interface\Addons\Details\sounds\Details Horn.ogg]]) SharedMedia:Register("sound", "Details Warning", [[Interface\Addons\Details\sounds\Details Warning 100.ogg]]) SharedMedia:Register("sound", "Details Truck", [[Interface\Addons\Details\sounds\Details Truck.ogg]]) SharedMedia:Register("sound", "Details Bass Drop", [[Interface\Addons\Details\sounds\bassdrop2.mp3]]) --dump table contents over chat panel function Details.VarDump(t) if (type(t) ~= "table") then return end for a,b in pairs(t) do print(a,b) end end local bIsDump = false local waitForSpellLoad = CreateFrame("frame") if (C_EventUtils.IsEventValid("SPELL_TEXT_UPDATE")) then waitForSpellLoad:RegisterEvent("SPELL_TEXT_UPDATE") waitForSpellLoad:SetScript("OnEvent", function(self, event, spellId) if (bIsDump) then dumpt(spellId) end end) end function dumpt(value) --[[GLOBAL]] --check if this is a spellId local spellId = tonumber(value) if (spellId) then local spellInfo = {Details222.GetSpellInfo(spellId)} if (type(spellInfo[1]) == "string") then local desc = C_Spell.GetSpellDescription and C_Spell.GetSpellDescription(spellId) or GetSpellDescription(spellId) if (not desc or desc == "") then bIsDump = true return end if (C_Spell.GetSpellInfo) then Details:Dump({desc, C_Spell.GetSpellInfo(spellId)}) return else return Details:Dump({desc, spellInfo}) end bIsDump = false end end --check if is an atlas texture local atlas if (type(value) == "string") then atlas = C_Texture.GetAtlasInfo(value) if (atlas) then return Details:Dump(atlas) end end if (value == nil) then local allTooltips = {"GameTooltip", "GameTooltipTooltip", "EventTraceTooltip", "FrameStackTooltip", "GarrisonMissionMechanicTooltip", "GarrisonMissionMechanicFollowerCounterTooltip", "ItemSocketingDescription", "NamePlateTooltip", "PrivateAurasTooltip", "RuneforgeFrameResultTooltip", "ItemRefTooltip", "QuickKeybindTooltip", "SettingsTooltip"} for i = 1, #allTooltips do local tooltipName = allTooltips[i] local tooltip = _G[tooltipName] if (tooltip and tooltip:IsVisible()) then if (tooltip.GetTooltipData) then local tooltipData = tooltip:GetTooltipData() if (tooltipData) then if (tooltip.ItemTooltip and tooltip.ItemTooltip:IsVisible()) then local icon = tooltip.ItemTooltip.Icon if (icon) then local texture = icon:GetTexture() local atlas = icon:GetAtlas() if (texture or atlas) then tooltipData.IconTexture = texture tooltipData.IconAtlas = atlas end end end if (tooltipData.hyperlink) then local itemName, itemLink, itemQuality, itemLevel, itemMinLevel, itemType, itemSubType, itemStackCount, itemEquipLoc, itemTexture, sellPrice, classID, subclassID, bindType, expacID, setID, isCraftingReagent = GetItemInfo(tooltipData.hyperlink) local itemInfo = { itemName = itemName, itemLink = itemLink, itemQuality = itemQuality, itemLevel = itemLevel, itemMinLevel = itemMinLevel, itemType = itemType, itemSubType = itemSubType, itemStackCount = itemStackCount, itemEquipLoc = itemEquipLoc, itemTexture = itemTexture, sellPrice = sellPrice, classID = classID, subclassID = subclassID, bindType = bindType, expacID = expacID, setID = setID, isCraftingReagent = isCraftingReagent } DetailsFramework.table.deploy(tooltipData, itemInfo) end return Details:Dump(tooltipData) end else local outputTable = {} for lineNumber = 1, 10 do local leftText = _G[tooltipName..'TextLeft'..lineNumber] local rightText = _G[tooltipName..'TextRight'..lineNumber] if not (leftText and rightText) then break end outputTable[#outputTable+1] = {left = leftText:GetText(), right = rightText:GetText()} end return Details:Dump(outputTable) end end end end return Details:Dump(value) end function FindSpellByName(spellName) --[[GLOBAL]] if (spellName and type(spellName) == "string") then local GSI = GetSpellInfo local foundSpells = {} spellName = spellName:lower() for i = 1, 450000 do local thisSpellName = GSI(i) if (thisSpellName) then thisSpellName = thisSpellName:lower() if (spellName == thisSpellName) then foundSpells[#foundSpells+1] = {GSI(i)} end end end if (#foundSpells > 0) then dumpt(foundSpells) else Details:Msg("spell", spellName, "not found.") end end end --copies a full table function Details.CopyTable(orig) local orig_type = type(orig) local copy if orig_type == 'table' then copy = {} for orig_key, orig_value in next, orig, nil do --print(orig_key, orig_value) copy[Details.CopyTable(orig_key)] = Details.CopyTable(orig_value) end else copy = orig end return copy end --delay messages function _detalhes:DelayMsg(msg) _detalhes.delaymsgs = _detalhes.delaymsgs or {} _detalhes.delaymsgs[#_detalhes.delaymsgs+1] = msg end function _detalhes:ShowDelayMsg() if (_detalhes.delaymsgs and #_detalhes.delaymsgs > 0) then for _, msg in ipairs(_detalhes.delaymsgs) do print(msg) end end _detalhes.delaymsgs = {} end --print messages function _detalhes:Msg(str, arg1, arg2, arg3, arg4) if (self.__name) then print("|cffffaeae" .. self.__name .. "|r |cffcc7c7c(plugin)|r: " .. (str or ""), arg1 or "", arg2 or "", arg3 or "", arg4 or "") else print(Loc ["STRING_DETAILS1"] .. (str or ""), arg1 or "", arg2 or "", arg3 or "", arg4 or "") end end --welcome function _detalhes:WelcomeMsgLogon() _detalhes:Msg("you can always reset the addon running the command |cFFFFFF00'/details reinstall'|r if it does fail to load after being updated.") function _detalhes:wipe_combat_after_failed_load() _detalhes.tabela_historico = _detalhes.historico:CreateNewSegmentDatabase() _detalhes.tabela_overall = _detalhes.combate:NovaTabela() _detalhes.tabela_vigente = _detalhes.combate:NovaTabela (_, _detalhes.tabela_overall) Details222.PetContainer.Reset() _detalhes_database.tabela_overall = nil _detalhes_database.tabela_historico = nil _detalhes:Msg("seems failed to load, please type /reload to try again.") end Details.Schedules.After(5, _detalhes.wipe_combat_after_failed_load) end Details.failed_to_load = C_Timer.NewTimer(1, function() Details.Schedules.NewTimer(20, _detalhes.WelcomeMsgLogon) end) --key binds --[= --header _G ["BINDING_HEADER_Details"] = "Details!" _G ["BINDING_HEADER_DETAILS_KEYBIND_SEGMENTCONTROL"] = Loc ["STRING_KEYBIND_SEGMENTCONTROL"] _G ["BINDING_HEADER_DETAILS_KEYBIND_SCROLLING"] = Loc ["STRING_KEYBIND_SCROLLING"] _G ["BINDING_HEADER_DETAILS_KEYBIND_WINDOW_CONTROL"] = Loc ["STRING_KEYBIND_WINDOW_CONTROL"] _G ["BINDING_HEADER_DETAILS_KEYBIND_BOOKMARK"] = Loc ["STRING_KEYBIND_BOOKMARK"] _G ["BINDING_HEADER_DETAILS_KEYBIND_REPORT"] = Loc ["STRING_KEYBIND_WINDOW_REPORT_HEADER"] --keys _G ["BINDING_NAME_DETAILS_TOGGLE_ALL"] = Loc ["STRING_KEYBIND_TOGGLE_WINDOWS"] _G ["BINDING_NAME_DETAILS_RESET_SEGMENTS"] = Loc ["STRING_KEYBIND_RESET_SEGMENTS"] _G ["BINDING_NAME_DETAILS_SCROLL_UP"] = Loc ["STRING_KEYBIND_SCROLL_UP"] _G ["BINDING_NAME_DETAILS_SCROLL_DOWN"] = Loc ["STRING_KEYBIND_SCROLL_DOWN"] _G ["BINDING_NAME_DETAILS_REPORT_WINDOW1"] = string.format(Loc ["STRING_KEYBIND_WINDOW_REPORT"], 1) _G ["BINDING_NAME_DETAILS_REPORT_WINDOW2"] = string.format(Loc ["STRING_KEYBIND_WINDOW_REPORT"], 2) _G ["BINDING_NAME_DETAILS_TOOGGLE_WINDOW1"] = string.format(Loc ["STRING_KEYBIND_TOGGLE_WINDOW"], 1) _G ["BINDING_NAME_DETAILS_TOOGGLE_WINDOW2"] = string.format(Loc ["STRING_KEYBIND_TOGGLE_WINDOW"], 2) _G ["BINDING_NAME_DETAILS_TOOGGLE_WINDOW3"] = string.format(Loc ["STRING_KEYBIND_TOGGLE_WINDOW"], 3) _G ["BINDING_NAME_DETAILS_TOOGGLE_WINDOW4"] = string.format(Loc ["STRING_KEYBIND_TOGGLE_WINDOW"], 4) _G ["BINDING_NAME_DETAILS_TOOGGLE_WINDOW5"] = string.format(Loc ["STRING_KEYBIND_TOGGLE_WINDOW"], 5) _G ["BINDING_NAME_DETAILS_BOOKMARK1"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 1) _G ["BINDING_NAME_DETAILS_BOOKMARK2"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 2) _G ["BINDING_NAME_DETAILS_BOOKMARK3"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 3) _G ["BINDING_NAME_DETAILS_BOOKMARK4"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 4) _G ["BINDING_NAME_DETAILS_BOOKMARK5"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 5) _G ["BINDING_NAME_DETAILS_BOOKMARK6"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 6) _G ["BINDING_NAME_DETAILS_BOOKMARK7"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 7) _G ["BINDING_NAME_DETAILS_BOOKMARK8"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 8) _G ["BINDING_NAME_DETAILS_BOOKMARK9"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 9) _G ["BINDING_NAME_DETAILS_BOOKMARK10"] = string.format(Loc ["STRING_KEYBIND_BOOKMARK_NUMBER"], 10) --]=] end if (select(4, GetBuildInfo()) >= 100000) then local f = CreateFrame("frame") f:RegisterEvent("ADDON_ACTION_FORBIDDEN") f:SetScript("OnEvent", function() local text = StaticPopup1 and StaticPopup1.text and StaticPopup1.text:GetText() if (text and text:find("Details")) then --fix false-positive taints that are being attributed to random addons StaticPopup1.button2:Click() end end) end local classCacheName = Details222.ClassCache.ByName local classCacheGUID = Details222.ClassCache.ByGUID function Details222.ClassCache.GetClassFromCache(value) return classCacheName[value] or classCacheGUID[value] end function Details222.ClassCache.AddClassToCache(value, whichCache) if (whichCache == "name") then classCacheName[value] = true elseif (whichCache == "guid") then classCacheGUID[value] = true end end function Details222.ClassCache.GetClass(value) local className = Details222.ClassCache.ByName[value] or Details222.ClassCache.ByGUID[value] if (className) then return className end local _, unitClass = UnitClass(value) return unitClass end function Details222.ClassCache.MakeCache() --iterage among all segments in the container history, get the damage container and get the actor list, check if the actor is a player and if it is, get the class and store it in the cache local segmentsTable = Details:GetCombatSegments() for _, combatObject in ipairs(segmentsTable) do for _, actorObject in combatObject:GetContainer(DETAILS_ATTRIBUTE_DAMAGE):ListActors() do if (actorObject:IsPlayer()) then local actorName = actorObject.nome local actorClass = actorObject.classe local actorGUID = actorObject.serial Details222.ClassCache.ByName[actorName] = actorClass Details222.ClassCache.ByGUID[actorGUID] = actorClass end end end end Details222.UnitIdCache.Party = {"player"} Details222.UnitIdCache.PartyPet = {"playerpet"} for i = 1, 4 do table.insert(Details222.UnitIdCache.Party, "party" .. i) table.insert(Details222.UnitIdCache.PartyPet, "partypet" .. i) end Details.PartyUnits = Details222.UnitIdCache.Party Details.PartyPetUnits = Details222.UnitIdCache.PartyPet Details222.UnitIdCache.Raid = {} Details222.UnitIdCache.RaidPet = {} Details222.UnitIdCache.RaidTargets = {} for i = 1, 40 do Details222.UnitIdCache.Raid[i] = "raid" .. i Details222.UnitIdCache.RaidPet[i] = "raidpet" .. i Details222.UnitIdCache.RaidTargets[i] = "raidtarget" .. i end Details.RaidUnits = Details222.UnitIdCache.Raid Details.RaidPetUnits = Details222.UnitIdCache.RaidPet Details.RaidTargetUnits = Details222.UnitIdCache.RaidTargets Details222.UnitIdCache.Boss = {} for i = 1, 9 do Details222.UnitIdCache.Boss[i] = "boss" .. i end Details.BossUnits = Details222.UnitIdCache.Boss Details222.UnitIdCache.Nameplate = {} for i = 1, 40 do Details222.UnitIdCache.Nameplate[i] = "nameplate" .. i end Details.NameplateUnits = Details222.UnitIdCache.Nameplate Details222.UnitIdCache.Arena = {} for i = 1, 5 do Details222.UnitIdCache.Arena[i] = "arena" .. i end Details.ArenaUnits = Details222.UnitIdCache.Arena function Details222.Tables.MakeWeakTable(mode) local newTable = {} setmetatable(newTable, {__mode = mode or "v"}) return newTable end --STRING_CUSTOM_POT_DEFAULT ---add a statistic, log, or any other data to the player stat table ---@param statName string ---@param value number function Details222.PlayerStats:AddStat(statName, value) Details.player_stats[statName] = (Details.player_stats[statName] or 0) + value end ---get the value of a saved stat ---@param statName string ---@return any function Details222.PlayerStats:GetStat(statName) return Details.player_stats[statName] end ---same thing as above but set the value instead of adding ---@param statName string ---@param value number function Details222.PlayerStats:SetStat(statName, value) Details.player_stats[statName] = value end local profileStartFunc = function(functionName) local profile = Details222.ProfilingCache[functionName] if (not profile) then Details222.ProfilingCache[functionName] = {elapsed = 0, startTime = 0, runs = 0} profile = Details222.ProfilingCache[functionName] end profile.startTime = debugprofilestop() profile.runs = profile.runs + 1 end local profileStopFunc = function(functionName) local profile = Details222.ProfilingCache[functionName] if (profile) then profile.elapsed = profile.elapsed + debugprofilestop() - profile.startTime end end function Details222.Profiling.ProfileStart()end function Details222.Profiling.ProfileStop()end function Details222.Profiling.EnableProfiler() Details222.Profiling.ProfileStart = profileStartFunc Details222.Profiling.ProfileStop = profileStopFunc end function Details222.Profiling.DisableProfiler() Details222.Profiling.ProfileStart = function()end Details222.Profiling.ProfileStop = function()end end function Details222.Profiling.ResetProfiler() table.wipe(Details222.ProfilingCache) end if (select(4, GetBuildInfo()) >= 100000) then Details222.Profiling.EnableProfiler() end function Details:ProfilerResult() local resultTable = {} local total = 0 for functionName, profile in pairs(Details222.ProfilingCache) do local runTime = string.format("%.3f", profile.elapsed / 1000) resultTable[functionName] = runTime .. " ms | runs: " .. profile.runs total = total + profile.elapsed end resultTable["Total"] = string.format("%.3f", total / 1000) .. " ms" dumpt(resultTable) end function Details:ResetProfilerResult() end ---destroy a table and remove it from the object, if the key isn't passed, the object itself is destroyed ---@param object any ---@param key string|nil function Details:Destroy(object, key) if (key) then if (getmetatable(object[key])) then setmetatable(object[key], nil) end object[key].__index = nil table.wipe(object[key]) object[key] = nil else if (getmetatable(object)) then setmetatable(object, nil) end object.__index = nil table.wipe(object) end end function Details:DestroyCombat(combatObject) --destroy each individual actor, hence more cleanups are done for i = 1, DETAILS_COMBAT_AMOUNT_CONTAINERS do local actorContainer = combatObject:GetContainer(i) for index, actorObject in actorContainer:ListActors() do Details:DestroyActor(actorObject, actorContainer, combatObject, 3) end end setmetatable(combatObject, nil) combatObject.__index = nil combatObject.__newindex = nil combatObject.__call = nil Details:Destroy(combatObject) --leave a trace that the actor has been deleted combatObject.__destroyed = true combatObject.__destroyedBy = debugstack(2, 1, 0) end ---destroy the actor, also calls container:RemoveActor(actor) ---@param self details ---@param actorObject actor ---@param actorContainer actorcontainer ---@param combatObject combat function Details:DestroyActor(actorObject, actorContainer, combatObject, callStackDepth) local containerType = actorContainer:GetType() local combatTotalsTable = combatObject.totals[containerType] --without group local combatTotalsTableInGroup = combatObject.totals_grupo[containerType] --with group --remove the actor from the parser cache local c1, c2, c3, c4 = Details222.Cache.GetParserCacheTables() c1[actorObject.serial] = nil c2[actorObject.serial] = nil c3[actorObject.serial] = nil c4[actorObject.serial] = nil if (not actorObject.ownerName) then --not a pet if (containerType == 1 or containerType == 2) then --damage|healing done combatTotalsTable = combatTotalsTable - actorObject.total if (actorObject.grupo) then combatTotalsTableInGroup = combatTotalsTableInGroup - actorObject.total end elseif (containerType == 3) then ---@cast actorObject actorresource if (actorObject.total and actorObject.total > 0) then if (actorObject.powertype) then combatTotalsTable[actorObject.powertype] = combatTotalsTable[actorObject.powertype] - actorObject.total combatTotalsTableInGroup[actorObject.powertype] = combatTotalsTableInGroup[actorObject.powertype] - actorObject.total end end if (actorObject.alternatepower and actorObject.alternatepower > 0) then combatTotalsTable.alternatepower = combatTotalsTable.alternatepower - actorObject.alternatepower combatTotalsTableInGroup.alternatepower = combatTotalsTableInGroup.alternatepower - actorObject.alternatepower end elseif (containerType == 4) then ---@cast actorObject actorutility --decrease the amount of CC break from the combat totals if (actorObject.cc_break and actorObject.cc_break > 0) then if (combatTotalsTable.cc_break) then combatTotalsTable.cc_break = combatTotalsTable.cc_break - actorObject.cc_break end if (combatTotalsTableInGroup.cc_break) then combatTotalsTableInGroup.cc_break = combatTotalsTableInGroup.cc_break - actorObject.cc_break end end --decrease the amount of dispell from the combat totals if (actorObject.dispell and actorObject.dispell > 0) then if (combatTotalsTable.dispell) then combatTotalsTable.dispell = combatTotalsTable.dispell - actorObject.dispell end if (combatTotalsTableInGroup.dispell) then combatTotalsTableInGroup.dispell = combatTotalsTableInGroup.dispell - actorObject.dispell end end --decrease the amount of interrupt from the combat totals if (actorObject.interrupt and actorObject.interrupt > 0) then if (combatTotalsTable.interrupt) then combatTotalsTable.interrupt = combatTotalsTable.interrupt - actorObject.interrupt end if (combatTotalsTableInGroup.interrupt) then combatTotalsTableInGroup.interrupt = combatTotalsTableInGroup.interrupt - actorObject.interrupt end end --decrease the amount of ress from the combat totals if (actorObject.ress and actorObject.ress > 0) then if (combatTotalsTable.ress) then combatTotalsTable.ress = combatTotalsTable.ress - actorObject.ress end if (combatTotalsTableInGroup.ress) then combatTotalsTableInGroup.ress = combatTotalsTableInGroup.ress - actorObject.ress end end --decrease the amount of dead from the combat totals if (actorObject.dead and actorObject.dead > 0) then if (combatTotalsTable.dead) then combatTotalsTable.dead = combatTotalsTable.dead - actorObject.dead end if (combatTotalsTableInGroup.dead) then combatTotalsTableInGroup.dead = combatTotalsTableInGroup.dead - actorObject.dead end end --decreate the amount of cooldowns used from the combat totals if (actorObject.cooldowns_defensive and actorObject.cooldowns_defensive > 0) then if (combatTotalsTable.cooldowns_defensive) then combatTotalsTable.cooldowns_defensive = combatTotalsTable.cooldowns_defensive - actorObject.cooldowns_defensive end if (combatTotalsTableInGroup.cooldowns_defensive) then combatTotalsTableInGroup.cooldowns_defensive = combatTotalsTableInGroup.cooldowns_defensive - actorObject.cooldowns_defensive end end --decrease the amount of buff uptime from the combat totals if (actorObject.buff_uptime and actorObject.buff_uptime > 0) then if (combatTotalsTable.buff_uptime) then combatTotalsTable.buff_uptime = combatTotalsTable.buff_uptime - actorObject.buff_uptime end if (combatTotalsTableInGroup.buff_uptime) then combatTotalsTableInGroup.buff_uptime = combatTotalsTableInGroup.buff_uptime - actorObject.buff_uptime end end --decrease the amount of debuff uptime from the combat totals if (actorObject.debuff_uptime and actorObject.debuff_uptime > 0) then if (combatTotalsTable.debuff_uptime) then combatTotalsTable.debuff_uptime = combatTotalsTable.debuff_uptime - actorObject.debuff_uptime end if (combatTotalsTableInGroup.debuff_uptime) then combatTotalsTableInGroup.debuff_uptime = combatTotalsTableInGroup.debuff_uptime - actorObject.debuff_uptime end end end end Details222.TimeMachine.RemoveActor(actorObject) if (not actorObject.Name) then print("error: actorObject.Name is nil", actorObject.tipo, actorObject.serial, actorObject.nome) end local actorName = actorObject:Name() combatObject:RemoveActorFromSpellCastTable(actorName) setmetatable(actorObject, nil) actorObject.__index = nil actorObject.__newindex = nil Details:Destroy(actorObject) --leave a trace that the actor has been deleted actorObject.__destroyed = true actorObject.__destroyedBy = debugstack(callStackDepth or 2, 1, 0) end C_Timer.After(5, function() --TutorialPointerFrame_1:HookScript("OnShow", function(self) self:Hide() end) --remove on v11 launch end)