local Details = _G.Details
local addonName , Details222 = ...
local detailsFramework = DetailsFramework
local _
local GetTime = GetTime
local GetInstanceInfo = GetInstanceInfo
local time = time
local C_ChallengeMode = C_ChallengeMode
local InCombatLockdown = InCombatLockdown
local Loc = _G.LibStub ( " AceLocale-3.0 " ) : GetLocale ( " Details " )
--[[
all mythic segments have :
. is_mythic_dungeon_segment = true
. is_mythic_dungeon_run_id = run id from details.profile . mythic_dungeon_id
--]]
local DetailsMythicPlusFrame = _G [ " DetailsMythicPlusFrame " ]
--empty
function Details222 . MythicPlus . OnMythicDungeonFinished ( encounterID , encounterName ) end
function Details222 . MythicPlus . OnBossDefeated ( encounterID , encounterName )
local currentCombat = Details : GetCurrentCombat ( )
--add the mythic dungeon info to the combat
currentCombat.is_mythic_dungeon = {
StartedAt = Details.MythicPlus . StartedAt , --the start of the run
EndedAt = time ( ) , --when the boss got killed
SegmentID = Details.MythicPlus . SegmentID , --segment number within the dungeon
EncounterID = encounterID ,
EncounterName = encounterName or Loc [ " STRING_UNKNOW " ] ,
RunID = Details.mythic_dungeon_id ,
ZoneName = Details.MythicPlus . DungeonName ,
MapID = Details.MythicPlus . DungeonID ,
OverallSegment = false ,
Level = Details.MythicPlus . Level ,
EJID = Details.MythicPlus . ejID ,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSS ,
SegmentName = ( encounterName or Loc [ " STRING_UNKNOW " ] ) .. " ( " .. string.lower ( _G [ " BOSS " ] ) .. " ) "
}
local mythicLevel = C_ChallengeMode.GetActiveKeystoneInfo ( )
local mPlusTable = currentCombat.is_mythic_dungeon
--logs
Details222.MythicPlus . LogStep ( " BossDefeated | key level: | " .. mythicLevel .. " | " .. ( mPlusTable.EncounterName or " " ) .. " | " .. ( mPlusTable.ZoneName or " " ) )
--check if need to merge the trash for this boss
if ( Details.mythic_plus . merge_boss_trash and not Details.MythicPlus . IsRestoredState ) then
--store on an table all segments which should be merged
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled or { }
--table with all past semgnets
local segmentsTable = Details : GetCombatSegments ( )
--iterate among segments
for i = 1 , 25 do --from the newer combat to the oldest
---@type combat
local pastCombat = segmentsTable [ i ]
--does the combat exists
if ( pastCombat and not pastCombat._trashoverallalreadyadded ) then
--is the combat a mythic segment from this run?
local bIsMythicSegment , SegmentID = pastCombat : IsMythicDungeon ( )
if ( bIsMythicSegment and SegmentID == Details.mythic_dungeon_id ) then
local combatType = pastCombat : GetCombatType ( )
if ( combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_TRASH or combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE ) then
table.insert ( segmentsToMerge , pastCombat )
end
end
end
end
--add encounter information
segmentsToMerge.EncounterID = encounterID
segmentsToMerge.EncounterName = encounterName
segmentsToMerge.PreviousBossKilledAt = Details.MythicPlus . PreviousBossKilledAt
--reduce this boss encounter time from the trash lenght time, since the boss doesn't count towards the time spent cleaning trash
segmentsToMerge.LastBossKilledAt = time ( ) - currentCombat : GetCombatTime ( )
DetailsMythicPlusFrame.TrashMergeScheduled = segmentsToMerge
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " BossDefeated() > not in combat, merging trash now " )
end
--merge the trash clean up
DetailsMythicPlusFrame.MergeTrashCleanup ( )
end
end
--after each boss fight, if enalbed on settings, create an extra segment with all trash segments from the boss just killed
--this function does not have agency over what segments to merge, it just receives a list of segments to merge
function DetailsMythicPlusFrame . MergeTrashCleanup ( )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeTrashCleanup() > running " , DetailsMythicPlusFrame.TrashMergeScheduled and # DetailsMythicPlusFrame.TrashMergeScheduled )
end
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled
--table exists and there's at least one segment
if ( segmentsToMerge and segmentsToMerge [ 1 ] ) then
Details222.MythicPlus . LogStep ( " MergeTrashCleanup started. " )
--the first segment is the segment where all other trash segments will be added
local masterSegment = segmentsToMerge [ 1 ]
--get the current combat just created and the table with all past segments
local newCombat = masterSegment
local totalTime = newCombat : GetCombatTime ( )
local startDate , endDate = " " , " "
local lastSegment
--add segments
for i = 2 , # segmentsToMerge do --segment #1 is the host
local pastCombat = segmentsToMerge [ i ]
newCombat = newCombat + pastCombat
totalTime = totalTime + pastCombat : GetCombatTime ( )
newCombat : CopyDeathsFrom ( pastCombat , true )
--tag this combat as already added to a boss trash overall
pastCombat._trashoverallalreadyadded = true
if ( endDate == " " ) then
local _ , whenEnded = pastCombat : GetDate ( )
endDate = whenEnded
end
lastSegment = pastCombat
end
--get the date where the first segment started
if ( lastSegment ) then
startDate = lastSegment : GetDate ( )
end
local zoneName , instanceType , difficultyID , difficultyName , maxPlayers , dynamicDifficulty , isDynamic , instanceMapID , instanceGroupSize = GetInstanceInfo ( )
--tag the segment as mythic overall segment
newCombat.is_mythic_dungeon = {
StartedAt = segmentsToMerge.PreviousBossKilledAt , --start of the mythic run or when the previous boss got killed
EndedAt = segmentsToMerge.LastBossKilledAt , --the time() when encounter_end got triggered
SegmentID = " trashoverall " ,
RunID = Details.mythic_dungeon_id ,
TrashOverallSegment = true ,
ZoneName = Details.MythicPlus . DungeonName ,
MapID = instanceMapID ,
Level = Details.MythicPlus . Level ,
EJID = Details.MythicPlus . ejID ,
EncounterID = segmentsToMerge.EncounterID ,
EncounterName = segmentsToMerge.EncounterName or Loc [ " STRING_UNKNOW " ] ,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSTRASH ,
SegmentName = ( segmentsToMerge.EncounterName or Loc [ " STRING_UNKNOW " ] ) .. " ( " .. string.lower ( Loc [ " STRING_SEGMENTS_LIST_TRASH " ] ) .. " ) " ,
}
newCombat.is_challenge = true
newCombat.is_mythic_dungeon_segment = true
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
--set the segment time / using a sum of combat times, this combat time is reliable
newCombat : SetStartTime ( GetTime ( ) - totalTime )
newCombat : SetEndTime ( GetTime ( ) )
--set the segment date
newCombat : SetDate ( startDate , endDate )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeTrashCleanup() > finished merging trash segments. " , Details.tabela_vigente , Details.tabela_vigente . is_boss )
end
--delete all segments that were merged
local segmentsTable = Details : GetCombatSegments ( )
for segmentId = # segmentsTable , 1 , - 1 do
local segment = segmentsTable [ segmentId ]
if ( segment and segment._trashoverallalreadyadded ) then
table.remove ( segmentsTable , segmentId )
end
end
for i = # segmentsToMerge , 1 , - 1 do
table.remove ( segmentsToMerge , i )
end
--call the segment removed event to notify third party addons
Details : SendEvent ( " DETAILS_DATA_SEGMENTREMOVED " )
--update all windows
Details : InstanceCallDetailsFunc ( Details.FadeHandler . Fader , " IN " , nil , " barras " )
Details : InstanceCallDetailsFunc ( Details.UpdateCombatObjectInUse )
Details : InstanceCallDetailsFunc ( Details.AtualizaSoloMode_AfertReset )
Details : InstanceCallDetailsFunc ( Details.ResetaGump )
Details : RefreshMainWindow ( - 1 , true )
else
Details222.MythicPlus . LogStep ( " MergeTrashCleanup | no segments to merge. " )
end
end
function DetailsMythicPlusFrame . MergeSegmentsOnEnd ( ) --~merge
--at the end of a mythic run, if enable on settings, merge all the segments from the mythic run into only one
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeSegmentsOnEnd() > starting to merge mythic segments. " , " InCombatLockdown(): " , InCombatLockdown ( ) )
end
Details222.MythicPlus . LogStep ( " MergeSegmentsOnEnd started | creating the overall segment at the end of the run. " )
--create a new combat to be the overall for the mythic run
Details222.StartCombat ( )
--get the current combat just created and the table with all past segments
local newCombat = Details : GetCurrentCombat ( )
local segmentsTable = Details : GetCombatSegments ( )
newCombat.is_challenge = true
newCombat.is_mythic_dungeon_segment = true
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
local timeInCombat = 0
local startDate , endDate = " " , " "
local lastSegment
local totalSegments = 0
--copy deaths occured on all segments to the new segment, also sum the activity combat time
if ( Details.mythic_plus . reverse_death_log ) then
for i = 1 , 40 do --copy the deaths from the first segment to the last one
local thisCombat = segmentsTable [ i ]
if ( thisCombat and thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id ) then
newCombat : CopyDeathsFrom ( thisCombat , true )
timeInCombat = timeInCombat + thisCombat : GetCombatTime ( )
end
end
else
for i = 40 , 1 , - 1 do --copy the deaths from the last segment to the new segment
local thisCombat = segmentsTable [ i ]
if ( thisCombat ) then
if ( thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id ) then
newCombat : CopyDeathsFrom ( thisCombat , true )
timeInCombat = timeInCombat + thisCombat : GetCombatTime ( )
end
end
end
end
local zoneName , instanceType , difficultyID , difficultyName , maxPlayers , dynamicDifficulty , isDynamic , instanceMapID , instanceGroupSize = GetInstanceInfo ( )
--tag the segment as mythic overall segment
---@type mythicdungeoninfo
newCombat.is_mythic_dungeon = {
StartedAt = Details.MythicPlus . StartedAt , --the start of the run
EndedAt = Details.MythicPlus . EndedAt , --the end of the run
WorldStateTimerStart = Details222.MythicPlus . WorldStateTimerStartAt ,
WorldStateTimerEnd = Details222.MythicPlus . WorldStateTimerEndAt ,
RunTime = Details222.MythicPlus . time ,
TimeInCombat = timeInCombat ,
SegmentID = " overall " , --segment number within the dungeon
RunID = Details.mythic_dungeon_id ,
OverallSegment = true ,
ZoneName = Details.MythicPlus . DungeonName ,
EJID = Details.MythicPlus . ejID ,
MapID = Details222.MythicPlus . MapID ,
Level = Details222.MythicPlus . Level ,
OnTime = Details222.MythicPlus . OnTime ,
KeystoneUpgradeLevels = Details222.MythicPlus . KeystoneUpgradeLevels ,
PracticeRun = Details222.MythicPlus . PracticeRun ,
OldDungeonScore = Details222.MythicPlus . OldDungeonScore ,
NewDungeonScore = Details222.MythicPlus . NewDungeonScore ,
IsAffixRecord = Details222.MythicPlus . IsAffixRecord ,
IsMapRecord = Details222.MythicPlus . IsMapRecord ,
PrimaryAffix = Details222.MythicPlus . PrimaryAffix ,
IsEligibleForScore = Details222.MythicPlus . IsEligibleForScore ,
UpgradeMembers = Details222.MythicPlus . UpgradeMembers ,
TimeLimit = Details222.MythicPlus . TimeLimit ,
DungeonName = Details222.MythicPlus . DungeonName ,
DungeonID = Details222.MythicPlus . DungeonID ,
DungeonTexture = Details222.MythicPlus . Texture ,
DungeonBackgroundTexture = Details222.MythicPlus . BackgroundTexture ,
SegmentType = DETAILS_SEGMENTTYPE_MYTHICDUNGEON_OVERALL ,
SegmentName = Details.MythicPlus . DungeonName .. " + " .. Details222.MythicPlus . Level ,
}
--add all boss segments from this run to this new segment
for i = 1 , 40 do --from the newer combat to the oldest
local thisCombat = segmentsTable [ i ]
if ( thisCombat and thisCombat.is_mythic_dungeon_run_id == Details.mythic_dungeon_id ) then
local canAddThisSegment = true
if ( Details.mythic_plus . make_overall_boss_only ) then
if ( not thisCombat.is_boss ) then
--canAddThisSegment = false --disabled
end
end
if ( canAddThisSegment ) then
newCombat = newCombat + thisCombat
totalSegments = totalSegments + 1
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeSegmentsOnEnd() > adding time: " , thisCombat : GetCombatTime ( ) , thisCombat.is_boss and thisCombat.is_boss . name )
end
if ( endDate == " " ) then
local _ , whenEnded = thisCombat : GetDate ( )
endDate = whenEnded
end
lastSegment = thisCombat
end
end
end
--get the date where the first segment started
if ( lastSegment ) then
startDate = lastSegment : GetDate ( )
end
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeSegmentsOnEnd() > totalTime: " , timeInCombat , " startDate: " , startDate )
end
newCombat.total_segments_added = totalSegments
newCombat.is_mythic_dungeon_run_id = Details.mythic_dungeon_id
--check if both values are valid, this can get invalid if the player leaves the dungeon before the timer ends or the game crashes
if ( type ( Details222.MythicPlus . time ) == " number " ) then
newCombat.run_time = Details222.MythicPlus . time
Details222.MythicPlus . LogStep ( " GetCompletionInfo() Found, Time: " .. Details222.MythicPlus . time )
elseif ( newCombat.is_mythic_dungeon . WorldStateTimerEnd and newCombat.is_mythic_dungeon . WorldStateTimerStart ) then
local runTime = newCombat.is_mythic_dungeon . WorldStateTimerEnd - newCombat.is_mythic_dungeon . WorldStateTimerStart
newCombat.run_time = Details222.MythicPlus . time
Details222.MythicPlus . LogStep ( " World State Timers is Available, Run Time: " .. runTime .. " | start: " .. newCombat.is_mythic_dungeon . WorldStateTimerStart .. " | end: " .. newCombat.is_mythic_dungeon . WorldStateTimerEnd )
else
newCombat.run_time = timeInCombat
Details222.MythicPlus . LogStep ( " GetCompletionInfo() and World State Timers not Found, Activity Time: " .. timeInCombat )
end
newCombat : SetStartTime ( GetTime ( ) - timeInCombat )
newCombat : SetEndTime ( GetTime ( ) )
Details222.MythicPlus . LogStep ( " Activity Time: " .. timeInCombat )
--set the segment time and date
newCombat : SetDate ( startDate , endDate )
--immediatly finishes the segment just started
Details : SairDoCombate ( )
newCombat.is_mythic_dungeon_segment = true
--update all windows
Details : InstanceCallDetailsFunc ( Details.FadeHandler . Fader , " IN " , nil , " barras " )
Details : InstanceCallDetailsFunc ( Details.UpdateCombatObjectInUse )
Details : InstanceCallDetailsFunc ( Details.AtualizaSoloMode_AfertReset )
Details : InstanceCallDetailsFunc ( Details.ResetaGump )
Details : RefreshMainWindow ( - 1 , true )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeSegmentsOnEnd() > finished merging segments. " )
print ( " Details! " , " MergeSegmentsOnEnd() > all done, check in the segments list if everything is correct, if something is weird: '/details feedback' thanks in advance! " )
end
local lower_instance = Details : GetLowerInstanceNumber ( )
if ( lower_instance ) then
local instance = Details : GetInstance ( lower_instance )
if ( instance ) then
local func = { function ( ) end }
instance : InstanceAlert ( " Showing Mythic+ Run Segment " , { [[Interface\AddOns\Details\images\icons]] , 16 , 16 , false , 434 / 512 , 466 / 512 , 243 / 512 , 273 / 512 } , 6 , func , true )
end
end
local bHasObject = false
Details : SendEvent ( " COMBAT_MYTHICPLUS_OVERALL_READY " , bHasObject , newCombat )
end
--this function merges trash segments after all bosses of the mythic dungeon are defeated
--happens when the group finishes all bosses but don't complete the trash requirement
function DetailsMythicPlusFrame . MergeRemainingTrashAfterAllBossesDone ( )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeRemainingTrashAfterAllBossesDone() > running, #segments: " , # DetailsMythicPlusFrame.TrashMergeScheduled2 , " trash overall table: " , DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat )
end
Details222.MythicPlus . LogStep ( " running MergeRemainingTrashAfterAllBossesDone. " )
local segmentsToMerge = DetailsMythicPlusFrame.TrashMergeScheduled2
local latestBossTrashMergedCombat = DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat
--needs to merge, add the total combat time, set the date end to the date of the first segment
local totalTime = 0
local startDate , endDate = " " , " "
--add segments
for i , pastCombat in ipairs ( segmentsToMerge ) do
latestBossTrashMergedCombat = latestBossTrashMergedCombat + pastCombat
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeRemainingTrashAfterAllBossesDone() > segment added " )
end
totalTime = totalTime + pastCombat : GetCombatTime ( )
--tag this combat as already added to a boss trash overall
pastCombat._trashoverallalreadyadded = true
if ( endDate == " " ) then --get the end date of the first index only
local _ , whenEnded = pastCombat : GetDate ( )
endDate = whenEnded
end
end
--set the segment time / using a sum of combat times, this combat time is reliable
local startTime = latestBossTrashMergedCombat : GetStartTime ( )
latestBossTrashMergedCombat : SetStartTime ( startTime - totalTime )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeRemainingTrashAfterAllBossesDone() > total combat time: " , totalTime )
end
--set the segment date
startDate = latestBossTrashMergedCombat : GetDate ( )
latestBossTrashMergedCombat : SetDate ( startDate , endDate )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeRemainingTrashAfterAllBossesDone() > new end date: " , endDate )
end
local mythicDungeonInfo = latestBossTrashMergedCombat : GetMythicDungeonInfo ( )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeRemainingTrashAfterAllBossesDone() > elapsed time before: " , mythicDungeonInfo.EndedAt - mythicDungeonInfo.StartedAt )
end
mythicDungeonInfo.StartedAt = mythicDungeonInfo.StartedAt - ( Details.MythicPlus . EndedAt - Details.MythicPlus . PreviousBossKilledAt )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MergeRemainingTrashAfterAllBossesDone() > elapsed time after: " , mythicDungeonInfo.EndedAt - mythicDungeonInfo.StartedAt )
end
--remove trash segments from the segment history after the merge
local removedCurrentSegment = false
local segmentsTable = Details : GetCombatSegments ( )
for _ , pastCombat in ipairs ( segmentsToMerge ) do
for i = # segmentsTable , 1 , - 1 do
local segment = segmentsTable [ i ]
if ( segment == pastCombat ) then
--remove the segment
if ( Details : GetCurrentCombat ( ) == segment ) then
removedCurrentSegment = true
end
table.remove ( segmentsTable , i )
break
end
end
end
for i = # segmentsToMerge , 1 , - 1 do
table.remove ( segmentsToMerge , i )
end
if ( removedCurrentSegment ) then
--find another current segment
segmentsTable = Details : GetCombatSegments ( )
Details : SetCurrentCombat ( segmentsTable [ 1 ] )
if ( not Details : GetCurrentCombat ( ) ) then
--assuming there's no segment from the dungeon run
Details222.StartCombat ( )
Details : EndCombat ( )
end
--update all windows
Details : InstanceCallDetailsFunc ( Details.FadeHandler . Fader , " IN " , nil , " barras " )
Details : InstanceCallDetailsFunc ( Details.UpdateCombatObjectInUse )
Details : InstanceCallDetailsFunc ( Details.AtualizaSoloMode_AfertReset )
Details : InstanceCallDetailsFunc ( Details.ResetaGump )
Details : RefreshMainWindow ( - 1 , true )
end
Details222.MythicPlus . LogStep ( " delete_trash_after_merge | concluded " )
Details : SendEvent ( " DETAILS_DATA_SEGMENTREMOVED " )
DetailsMythicPlusFrame.TrashMergeScheduled2 = nil
DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat = nil
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MergeRemainingTrashAfterAllBossesDone() > done merging " )
end
end
--does not do the merge, just scheduling, the merger is on the function above
function Details222 . MythicPlus . MergeTrashAfterLastBoss ( )
local segmentsToMerge = { }
--table with all past segments
local segmentsTable = Details : GetCombatSegments ( )
for i = 1 , # segmentsTable do
local pastCombat = segmentsTable [ i ]
--does the combat exists
if ( pastCombat and not pastCombat._trashoverallalreadyadded and pastCombat : GetCombatTime ( ) > 5 ) then
--is the last boss?
if ( pastCombat.is_boss ) then
break
end
--is the combat a mythic segment from this run?
local bIsMythicSegment , SegmentID = pastCombat : IsMythicDungeon ( )
if ( bIsMythicSegment and SegmentID == Details.mythic_dungeon_id ) then
local combatType = pastCombat : GetCombatType ( )
if ( combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_TRASH or combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSWIPE ) then
table.insert ( segmentsToMerge , pastCombat )
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " MythicDungeonFinished() > found after last boss combat " )
end
end
end
end
end
if ( # segmentsToMerge > 0 ) then
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > found " , # segmentsToMerge , " segments after the last boss " )
end
--find the latest trash overall
segmentsTable = Details : GetCombatSegments ( )
local latestTrashOverall
for i = 1 , # segmentsTable do
local pastCombat = segmentsTable [ i ]
if ( pastCombat and pastCombat.is_mythic_dungeon ) then
local combatType = pastCombat : GetCombatType ( )
if ( combatType == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_BOSSTRASH ) then
latestTrashOverall = pastCombat
break
end
end
end
if ( latestTrashOverall ) then
--stores the segment table and the trash overall segment to use on the merge
DetailsMythicPlusFrame.TrashMergeScheduled2 = segmentsToMerge
DetailsMythicPlusFrame.TrashMergeScheduled2_OverallCombat = latestTrashOverall
if ( DetailsMythicPlusFrame.DevelopmentDebug ) then
print ( " Details! " , " MythicDungeonFinished() > not in combat, merging last pack of trash now " )
end
DetailsMythicPlusFrame.MergeRemainingTrashAfterAllBossesDone ( )
end
end
end