local Details = _G.Details
local DF = _G.DetailsFramework
local addonName , Details222 = ...
local _
local time = time
local C_Timer = _G.C_Timer
local unpack = _G.unpack
local GetTime = _G.GetTime
local GetInstanceInfo = _G.GetInstanceInfo
local Loc = _G.LibStub ( " AceLocale-3.0 " ) : GetLocale ( " Details " )
--data for the current mythic + dungeon
Details.MythicPlus = {
RunID = 0 ,
}
local mythicDungeonFrames = Details222.MythicPlus . Frames
local mythicDungeonCharts = Details : CreateEventListener ( )
Details222.MythicPlus . Charts.Listener = mythicDungeonCharts
-- ~mythic ~dungeon
local DetailsMythicPlusFrame = _G.CreateFrame ( " frame " , " DetailsMythicPlusFrame " , UIParent )
DetailsMythicPlusFrame.DevelopmentDebug = false
--disabling the mythic+ feature if the user is playing in wow classic
if ( not DF.IsTimewalkWoW ( ) ) then
DetailsMythicPlusFrame : RegisterEvent ( " CHALLENGE_MODE_START " )
DetailsMythicPlusFrame : RegisterEvent ( " CHALLENGE_MODE_COMPLETED " )
DetailsMythicPlusFrame : RegisterEvent ( " ZONE_CHANGED_NEW_AREA " )
DetailsMythicPlusFrame : RegisterEvent ( " ENCOUNTER_END " )
DetailsMythicPlusFrame : RegisterEvent ( " START_TIMER " )
end
function Details222 . MythicPlus . LogStep ( log )
local today = date ( " %d/%m/%y %H:%M:%S " )
table.insert ( Details.mythic_plus_log , 1 , today .. " | " .. log )
tremove ( Details.mythic_plus_log , 50 )
end
function DetailsMythicPlusFrame . BossDefeated ( this_is_end_end , encounterID , encounterName , difficultyID , raidSize , endStatus ) --hold your breath and count to ten
--this function is called right after defeat a boss inside a mythic dungeon
--it comes from details! control leave combat
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " BossDefeated() > boss defeated | SegmentID: " , Details.MythicPlus . SegmentID , " | mapID: " , Details.MythicPlus . DungeonID )
end
--local zoneName, instanceType, difficultyID, difficultyName, maxPlayers, dynamicDifficulty, isDynamic, instanceMapID, instanceGroupSize = GetInstanceInfo()
Details222.MythicPlus . OnBossDefeated ( encounterID , encounterName ) --data capture
--increase the segment number for the mythic run
Details.MythicPlus . SegmentID = Details.MythicPlus . SegmentID + 1
--register the time when the last boss has been killed (started a clean up for the next trash)
Details.MythicPlus . PreviousBossKilledAt = time ( )
--update the saved table inside the profile
Details : UpdateState_CurrentMythicDungeonRun ( true , Details.MythicPlus . SegmentID , Details.MythicPlus . PreviousBossKilledAt )
end
--this function is called 2 seconds after the event COMBAT_MYTHICDUNGEON_END
function DetailsMythicPlusFrame . MythicDungeonFinished ( bFromZoneLeft )
if ( DetailsMythicPlusFrame.IsDoingMythicDungeon ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > the dungeon was a Mythic+ and just ended. " )
end
DetailsMythicPlusFrame.IsDoingMythicDungeon = false
Details.MythicPlus . Started = false
Details.MythicPlus . EndedAt = time ( ) - 1.9
Details : UpdateState_CurrentMythicDungeonRun ( )
--at this point, details! should not be in combat, but if something triggered a combat start, just close the combat right away
if ( Details.in_combat ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > was in combat, calling SairDoCombate(): " , InCombatLockdown ( ) )
end
Details : SairDoCombate ( )
Details222.MythicPlus . LogStep ( " MythicDungeonFinished() | Details was in combat. " )
end
--check if there is trash segments after the last boss. need to merge these segments with the trash segment of the last boss
local bCanMergeBossTrash = Details.mythic_plus . merge_boss_trash
Details222.MythicPlus . LogStep ( " MythicDungeonFinished() | merge_boss_trash = " .. ( bCanMergeBossTrash and " true " or " false " ) )
--check if there's trash after the last boss, if does, merge it with the trash of the last boss defeated
if ( bCanMergeBossTrash and not Details.MythicPlus . IsRestoredState ) then -- and not bFromZoneLeft
--is the current combat not a boss fight?
--this mean a combat was opened after the last boss of the dungeon was killed
if ( not Details.tabela_vigente . is_boss and Details.tabela_vigente : GetCombatTime ( ) > 5 ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > the last combat isn't a boss fight, might have trash after bosses done. " )
end
Details222.MythicPlus . MergeTrashAfterLastBoss ( )
end
end
--merge segments
if ( Details.mythic_plus . make_overall_when_done and not Details.MythicPlus . IsRestoredState ) then -- and not bFromZoneLeft
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > not in combat, creating overall segment now " )
end
DetailsMythicPlusFrame.MergeSegmentsOnEnd ( )
end
Details.MythicPlus . IsRestoredState = nil
--the run is valid, schedule to open the chart window
Details.mythic_plus . delay_to_show_graphic = 1
if ( not bFromZoneLeft ) then
C_Timer.After ( Details.mythic_plus . delay_to_show_graphic , mythicDungeonFrames.ShowEndOfMythicPlusPanel )
end
--shutdown parser for a few seconds to avoid opening new segments after the run ends
if ( not bFromZoneLeft ) then
Details : CaptureSet ( false , " damage " , false , 15 )
Details : CaptureSet ( false , " energy " , false , 15 )
Details : CaptureSet ( false , " aura " , false , 15 )
Details : CaptureSet ( false , " energy " , false , 15 )
Details : CaptureSet ( false , " spellcast " , false , 15 )
end
end
end
function DetailsMythicPlusFrame . MythicDungeonStarted ( )
--flag as a mythic dungeon
DetailsMythicPlusFrame.IsDoingMythicDungeon = true
--this counter is individual for each character
Details.mythic_dungeon_id = Details.mythic_dungeon_id + 1
local mythicLevel = C_ChallengeMode.GetActiveKeystoneInfo and C_ChallengeMode.GetActiveKeystoneInfo ( )
local zoneName , _ , _ , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
local mapID = C_Map.GetBestMapForUnit ( " player " )
if ( not mapID ) then
return
end
local ejID = Details : GetInstanceEJID ( mapID )
--setup the mythic run info
Details.MythicPlus . Started = true
Details.MythicPlus . DungeonName = zoneName
Details.MythicPlus . DungeonID = currentZoneID
--Details:Msg("(debug) mythic dungeon start time: ", time()+9.7, "time now:", time(), "diff:", time()+9.7-time())
Details.MythicPlus . StartedAt = time ( ) + 9.7 --there's the countdown timer of 10 seconds
Details.MythicPlus . EndedAt = nil --reset
Details.MythicPlus . SegmentID = 1
Details.MythicPlus . Level = mythicLevel
Details.MythicPlus . ejID = ejID
Details.MythicPlus . PreviousBossKilledAt = time ( )
Details : SaveState_CurrentMythicDungeonRun ( Details.mythic_dungeon_id , zoneName , currentZoneID , time ( ) + 9.7 , 1 , mythicLevel , ejID , time ( ) )
local name , groupType , difficultyID , difficult = GetInstanceInfo ( )
if ( groupType == " party " and Details.overall_clear_newchallenge ) then
Details.historico : ResetOverallData ( )
Details : Msg ( " the overall data has been reset. " ) --localize-me
if ( Details.debug ) then
Details : Msg ( " (debug) timer is for a mythic+ dungeon, overall has been reseted. " )
end
end
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonStarted() > State set to Mythic Dungeon, new combat starting in 10 seconds. " )
end
end
function DetailsMythicPlusFrame . OnChallengeModeStart ( )
--is this a mythic dungeon?
local _ , _ , difficultyID , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
if ( difficultyID == 8 ) then
--start the dungeon on Details!
DetailsMythicPlusFrame.MythicDungeonStarted ( )
Details222.MythicPlus . LogStep ( " OnChallengeModeStart() " )
else
--print("D! mythic dungeon was already started!")
--from zone changed
local mythicLevel = C_ChallengeMode.GetActiveKeystoneInfo and C_ChallengeMode.GetActiveKeystoneInfo ( )
local zoneName , _ , _ , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
if ( not Details.MythicPlus . Started and Details.MythicPlus . DungeonID == currentZoneID and Details.MythicPlus . Level == mythicLevel ) then
Details.MythicPlus . Started = true
Details.MythicPlus . EndedAt = nil
Details.mythic_dungeon_currentsaved . started = true
DetailsMythicPlusFrame.IsDoingMythicDungeon = true
--print("D! mythic dungeon was NOT already started! debug 2")
end
end
end
--make an event listener to sync combat data
DetailsMythicPlusFrame.EventListener = Details : CreateEventListener ( )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_ENCOUNTER_START " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_ENCOUNTER_END " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_PLAYER_ENTER " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_PLAYER_LEAVE " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_MYTHICDUNGEON_START " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_MYTHICDUNGEON_END " )
DetailsMythicPlusFrame.EventListener : RegisterEvent ( " COMBAT_MYTHICPLUS_OVERALL_READY " )
function DetailsMythicPlusFrame . EventListener . OnDetailsEvent ( contextObject , event , ... )
--these events triggers within Details control functions, they run exactly after details! creates or close a segment
if ( event == " COMBAT_PLAYER_ENTER " ) then
elseif ( event == " COMBAT_PLAYER_LEAVE " ) then
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
return
end
if ( DetailsMythicPlusFrame.IsDoingMythicDungeon ) then
local combatObject = ...
if ( combatObject.is_boss ) then
if ( not combatObject.is_boss . killed ) then
local encounterName = combatObject.is_boss . encounter
local zoneName = combatObject.is_boss . zone
local mythicLevel = C_ChallengeMode.GetActiveKeystoneInfo and C_ChallengeMode.GetActiveKeystoneInfo ( )
local currentCombat = Details : GetCurrentCombat ( )
--just in case the combat get tagged as boss fight
combatObject.is_boss = nil
--tag the combat as mythic dungeon trash
local zoneName , instanceType , difficultyID , difficultyName , maxPlayers , dynamicDifficulty , isDynamic , instanceMapID , instanceGroupSize = GetInstanceInfo ( )
---@type mythicdungeoninfo
local mythicPlusInfo = {
ZoneName = Details.MythicPlus . DungeonName or zoneName ,
MapID = Details.MythicPlus . DungeonID or instanceMapID ,
Level = Details.MythicPlus . Level ,
EJID = Details.MythicPlus . ejID ,
RunID = Details.mythic_dungeon_id ,
StartedAt = time ( ) - currentCombat : GetCombatTime ( ) ,
EndedAt = time ( ) ,
SegmentID = Details.MythicPlus . SegmentID , --segment number within the dungeon
OverallSegment = false ,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE ,
SegmentName = ( encounterName or Loc [ " STRING_UNKNOW " ] ) .. " ( " .. string.lower ( _G [ " BOSS " ] ) .. " ) "
}
combatObject.is_mythic_dungeon = mythicPlusInfo
Details222.MythicPlus . LogStep ( " COMBAT_PLAYER_LEAVE | wiped on boss | key level: | " .. mythicLevel .. " | " .. ( encounterName or " " ) .. " " .. zoneName )
else
DetailsMythicPlusFrame.BossDefeated ( false , combatObject.is_boss . id , combatObject.is_boss . name , combatObject.is_boss . diff , 5 , 1 )
end
end
end
elseif ( event == " COMBAT_ENCOUNTER_START " ) then
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
Details222.MythicPlus . LogStep ( " COMBAT_ENCOUNTER_START | streamer_config.disable_mythic_dungeon is true and the code cannot continue. " )
return
end
local encounterID , encounterName , difficultyID , raidSize , endStatus = ...
--nothing
elseif ( event == " COMBAT_ENCOUNTER_END " ) then
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
Details222.MythicPlus . LogStep ( " COMBAT_ENCOUNTER_END | streamer_config.disable_mythic_dungeon is true and the code cannot continue. " )
return
end
local encounterID , encounterName , difficultyID , raidSize , endStatus = ...
--nothing
elseif ( event == " COMBAT_MYTHICDUNGEON_START " ) then
local lowerInstance = Details : GetLowerInstanceNumber ( )
if ( lowerInstance ) then
lowerInstance = Details : GetInstance ( lowerInstance )
if ( lowerInstance ) then
C_Timer.After ( 3 , function ( )
--if (lowerInstance:IsEnabled()) then
--todo, need localization
--lowerInstance:InstanceAlert("Details!" .. " " .. "Damage" .. " " .. "Meter", {[[Interface\AddOns\Details\images\minimap]], 16, 16, false}, 3, {function() end}, false, true)
--end
end )
end
end
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
return
end
--reset spec cache if broadcaster requested
if ( Details.streamer_config . reset_spec_cache ) then
Details : Destroy ( Details.cached_specs )
end
C_Timer.After ( 0.25 , DetailsMythicPlusFrame.OnChallengeModeStart )
--debugging
local mPlusSettings = Details.mythic_plus
local result = " "
for key , value in pairs ( Details.mythic_plus ) do
if ( type ( value ) ~= " table " ) then
result = result .. key .. " = " .. tostring ( value ) .. " | "
end
end
local mythicLevel = C_ChallengeMode.GetActiveKeystoneInfo and C_ChallengeMode.GetActiveKeystoneInfo ( )
local zoneName , _ , _ , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
Details222.MythicPlus . LogStep ( " COMBAT_MYTHICDUNGEON_START | settings: " .. result .. " | level: " .. mythicLevel .. " | zone: " .. zoneName .. " | zoneId: " .. currentZoneID )
elseif ( event == " COMBAT_MYTHICDUNGEON_END " ) then
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
Details222.MythicPlus . LogStep ( " COMBAT_MYTHICDUNGEON_END | streamer_config.disable_mythic_dungeon is true and the code cannot continue. " )
return
end
--delay to wait the encounter_end trigger first
--assuming here the party cleaned the mobs kill objective before going to kill the last boss
C_Timer.After ( 2 , DetailsMythicPlusFrame.MythicDungeonFinished )
elseif ( event == " COMBAT_MYTHICPLUS_OVERALL_READY " ) then
DetailsMythicPlusFrame.SaveMythicPlusStats ( ... )
end
end
local playerLeftDungeonZoneTimer_Callback = function ( )
if ( DetailsMythicPlusFrame.IsDoingMythicDungeon ) then
local _ , _ , difficulty , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
if ( currentZoneID ~= Details.MythicPlus . DungeonID ) then
Details222.MythicPlus . LogStep ( " ZONE_CHANGED_NEW_AREA | player has left the dungeon and Details! finished the dungeon because of that. " )
--send mythic dungeon end event
Details : SendEvent ( " COMBAT_MYTHICDUNGEON_END " ) --on leave dungeon
--finish the segment
DetailsMythicPlusFrame.BossDefeated ( true )
--finish the mythic run
DetailsMythicPlusFrame.MythicDungeonFinished ( true )
DetailsMythicPlusFrame.ZoneLeftTimer = nil
end
end
end
DetailsMythicPlusFrame : SetScript ( " OnEvent " , function ( _ , event , ... )
if ( event == " START_TIMER " ) then
--DetailsMythicPlusFrame.LastTimer = GetTime()
elseif ( event == " ZONE_CHANGED_NEW_AREA " ) then
if ( DetailsMythicPlusFrame.IsDoingMythicDungeon ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , event , ... )
print ( " Zone changed and is Doing Mythic Dungeon " )
end
--ignore the event if ignoring mythic dungeon special treatment
if ( Details.streamer_config . disable_mythic_dungeon ) then
Details222.MythicPlus . LogStep ( " ZONE_CHANGED_NEW_AREA | streamer_config.disable_mythic_dungeon is true and the code cannot continue. " )
return
end
local _ , _ , difficulty , _ , _ , _ , _ , currentZoneID = GetInstanceInfo ( )
if ( currentZoneID ~= Details.MythicPlus . DungeonID ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Zone changed and the zone is different than the dungeon " )
end
--player left the dungeon zone, start a timer to check if the player will return to the dungeon
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " ZONE_CHANGED_NEW_AREA | player left the dungeon zone, return to dungeon timer started. " )
end
--check if the timer already exists, if does, ignore this event
if ( DetailsMythicPlusFrame.ZoneLeftTimer and not DetailsMythicPlusFrame.ZoneLeftTimer : IsCancelled ( ) ) then
return
end
DetailsMythicPlusFrame.ZoneLeftTimer = C_Timer.NewTimer ( 40 , playerLeftDungeonZoneTimer_Callback )
end
end
end
end )
---@param combatObject combat
function DetailsMythicPlusFrame . SaveMythicPlusStats ( combatObject )
local completionInfo = C_ChallengeMode.GetChallengeCompletionInfo ( )
local mapChallengeModeID = C_ChallengeMode.GetActiveChallengeMapID ( )
local PrimaryAffix = 0
local upgradeMembers = completionInfo.members
local mythicLevel = completionInfo.level
local time = completionInfo.time
local onTime = completionInfo.onTime
local keystoneUpgradeLevels = completionInfo.keystoneUpgradeLevels
local practiceRun = completionInfo.practiceRun
local isAffixRecord = completionInfo.isAffixRecord
local isMapRecord = completionInfo.isMapRecord
local isEligibleForScore = completionInfo.isEligibleForScore
local oldDungeonScore = completionInfo.oldOverallDungeonScore
local newDungeonScore = completionInfo.newOverallDungeonScore
if ( mapChallengeModeID ) then
local statName = " mythicdungeoncompletedDF2 "
---@type table<challengemapid, table>
local mythicDungeonRuns = Details222.PlayerStats : GetStat ( statName )
if ( not mythicDungeonRuns ) then
mythicDungeonRuns = mythicDungeonRuns or { }
end
--mythicDungeonRuns [mapChallengeModeID] [mythicLevel]
---@class mythicplusrunstats
---@field onTime boolean
---@field deaths number
---@field date number
---@field affix number
---@field runTime milliseconds
---@field combatTime number
---@class mythicplusstats
---@field completed number
---@field totalTime number
---@field minTime number
---@field history mythicplusrunstats[]
---@type table<keylevel, mythicplusstats>
local statsForDungeon = mythicDungeonRuns [ mapChallengeModeID ]
if ( not statsForDungeon ) then
statsForDungeon = { }
mythicDungeonRuns [ mapChallengeModeID ] = statsForDungeon
end
---@type mythicplusstats
local statsForLevel = statsForDungeon [ mythicLevel ]
if ( not statsForLevel ) then
---@type mythicplusstats
statsForLevel = {
completed = 0 ,
totalTime = 0 ,
minTime = 0 ,
history = { } ,
}
statsForDungeon [ mythicLevel ] = statsForLevel
end
statsForLevel.completed = ( statsForLevel.completed or 0 ) + 1
statsForLevel.totalTime = ( statsForLevel.totalTime or 0 ) + time
if ( not statsForLevel.minTime or time < statsForLevel.minTime ) then
statsForLevel.minTime = time
end
statsForLevel.history = statsForLevel.history or { }
local amountDeaths = C_ChallengeMode.GetDeathCount and C_ChallengeMode.GetDeathCount ( ) or 0
---@type mythicplusrunstats
local runStats = {
date = _G.time ( ) ,
runTime = math.floor ( time / 1000 ) ,
onTime = onTime ,
deaths = amountDeaths ,
affix = PrimaryAffix ,
combatTime = combatObject : GetCombatTime ( ) ,
}
table.insert ( statsForLevel.history , runStats )
Details222.PlayerStats : SetStat ( " mythicdungeoncompletedDF2 " , mythicDungeonRuns )
end
end