From 065c61c8ffeea45698fc26e6842465158ba3b230 Mon Sep 17 00:00:00 2001 From: Tercio Jose Date: Fri, 9 Aug 2024 20:55:16 -0300 Subject: [PATCH] Major rework on the M+ end of run panel --- Libs/DF/button.lua | 2 +- Libs/DF/definitions.lua | 3 +- Libs/DF/fw.lua | 8 +- Libs/DF/panel.lua | 48 +- Libs/DF/timebar.lua | 144 ++- Libs/LibOpenRaid/LibOpenRaid.lua | 2 +- boot.lua | 5 +- core/parser.lua | 18 +- .../window_mythicplus/window_end_of_run.lua | 869 +++++++++++++----- functions/profiles.lua | 6 +- images/end_of_mplus.png | Bin 0 -> 58760 bytes images/end_of_mplus_banner_mask.png | Bin 0 -> 3249 bytes 12 files changed, 816 insertions(+), 289 deletions(-) create mode 100644 images/end_of_mplus.png create mode 100644 images/end_of_mplus_banner_mask.png diff --git a/Libs/DF/button.lua b/Libs/DF/button.lua index c112bbb2..04a170b5 100644 --- a/Libs/DF/button.lua +++ b/Libs/DF/button.lua @@ -892,7 +892,7 @@ end ---@field IsEnabled fun(self: df_button) : boolean returns true if the button is enabled ---@field SetIcon fun(self: df_button,texture: string|number, width: number|nil, height: number|nil, layout: string|nil, texcoord: table|nil, overlay: table|nil, textDistance: number|nil, leftPadding: number|nil, textHeight: number|nil, shortMethod: any|nil) ---@field GetIconTexture fun(self: df_button) : string returns the texture path of the button icon - ---@field SetTexture fun(self: df_button, normalTexture: string, highlightTexture: string, pressedTexture: string, disabledTexture: string) set the regular button textures + ---@field SetTexture fun(self: df_button, normalTexture: any, highlightTexture: any, pressedTexture: any, disabledTexture: any) set the regular button textures ---@field SetFontFace fun(self: df_button, font: string) set the button font ---@field SetFontSize fun(self: df_button, size: number) set the button font size ---@field SetTextColor fun(self: df_button, color: any) set the button text color diff --git a/Libs/DF/definitions.lua b/Libs/DF/definitions.lua index 33b35426..29974f3e 100644 --- a/Libs/DF/definitions.lua +++ b/Libs/DF/definitions.lua @@ -314,7 +314,8 @@ ---@field CreateHeader fun(self:table, parent:frame, headerTable:df_headercolumndata[], options:table?, frameName:string?) : df_headerframe ---@field CreateGraphicMultiLineFrame fun(self:table, parent:frame, name:string) : df_chartmulti ---@field CreateGraphicLineFrame fun(self:table, parent:frame, name:string) : df_chart ----@field +---@field CreateFlashAnimation fun(self:table, frame:uiobject, onFinishFunc:function?, onLoopFunc:function?) : animationgroup +---@field CreateTimeBar fun(self:table, parent:frame, texture:texturepath|textureid, width:number?, height:number?, value:number?, member:string?, name:string?) : df_timebar --[=[ Wrapped objects: when using the following functions, the object will be wrapped in a table, e.g. detailsFramework:CreateButton() will return a table with the button, the button will be accessible through the "button" key. diff --git a/Libs/DF/fw.lua b/Libs/DF/fw.lua index 04668637..6eb75abe 100644 --- a/Libs/DF/fw.lua +++ b/Libs/DF/fw.lua @@ -1,6 +1,6 @@ -local dversion = 557 +local dversion = 559 local major, minor = "DetailsFramework-1.0", dversion local DF, oldminor = LibStub:NewLibrary(major, minor) @@ -1723,6 +1723,10 @@ local startFlash_Method = function(self, fadeInTime, fadeOutTime, flashDuration, flashAnimation:Play() end +---create a flash animation for a frame +---@param frame table +---@param onFinishFunc function? +---@param onLoopFunc function? function DF:CreateFlashAnimation(frame, onFinishFunc, onLoopFunc) local flashAnimation = frame:CreateAnimationGroup() @@ -2619,7 +2623,7 @@ end --DF.font_templates ["ORANGE_FONT_TEMPLATE"] = {color = "orange", size = 11, font = "Accidental Presidency"} --DF.font_templates ["OPTIONS_FONT_TEMPLATE"] = {color = "yellow", size = 12, font = "Accidental Presidency"} --DF.font_templates["ORANGE_FONT_TEMPLATE"] = {color = "orange", size = 10, font = DF:GetBestFontForLanguage()} -DF.font_templates["ORANGE_FONT_TEMPLATE"] = {color = {1, 0.8235, 0, 1}, size = 12, font = DF:GetBestFontForLanguage()} +DF.font_templates["ORANGE_FONT_TEMPLATE"] = {color = {1, 0.8235, 0, 1}, size = 11, font = DF:GetBestFontForLanguage()} --DF.font_templates["OPTIONS_FONT_TEMPLATE"] = {color = "yellow", size = 9.6, font = DF:GetBestFontForLanguage()} DF.font_templates["OPTIONS_FONT_TEMPLATE"] = {color = {1, 1, 1, 0.9}, size = 9.6, font = DF:GetBestFontForLanguage()} DF.font_templates["SMALL_SILVER"] = {color = "silver", size = 9, font = DF:GetBestFontForLanguage()} diff --git a/Libs/DF/panel.lua b/Libs/DF/panel.lua index ce73b36f..df229ea9 100644 --- a/Libs/DF/panel.lua +++ b/Libs/DF/panel.lua @@ -1646,9 +1646,12 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) --fill with icons local MACRO_ICON_FILENAMES = {} local SPELLNAMES_CACHE = {} + local SPELLIDS_CACHE = {} detailsFramework.IconPickFrame:SetScript("OnShow", function() MACRO_ICON_FILENAMES[1] = "INV_MISC_QUESTIONMARK" + SPELLNAMES_CACHE[1] = "INV_MISC_QUESTIONMARK" + SPELLIDS_CACHE[1] = IS_WOW_PROJECT_MAINLINE and 74008 or 25675 local index = 2 for i = 1, GetNumSpellTabs() do @@ -1658,10 +1661,11 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) for j = offset, tabEnd - 1 do --to get spell info by slot, you have to pass in a pet argument - local spellType, ID = GetSpellBookItemInfo(j, "player") + local spellType, ID, si = GetSpellBookItemInfo(j, SPELLBOOK_BANK_PLAYER) if (spellType ~= "FLYOUT") then - MACRO_ICON_FILENAMES [index] = GetSpellBookItemTexture(j, "player") or 0 - SPELLNAMES_CACHE [index] = GetSpellInfo(ID) + MACRO_ICON_FILENAMES [index] = si and si.iconID or GetSpellBookItemTexture(j, SPELLBOOK_BANK_PLAYER) or 0 + SPELLNAMES_CACHE [index] = si and si.name or GetSpellInfo(ID) + SPELLIDS_CACHE [index] = si and (si.spellID or si.actionID) or ID index = index + 1 elseif (spellType == "FLYOUT") then @@ -1672,6 +1676,7 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) if (isKnown) then MACRO_ICON_FILENAMES [index] = GetSpellTexture(spellID) or 0 SPELLNAMES_CACHE [index] = GetSpellInfo(spellID) + SPELLIDS_CACHE [index] = spellID index = index + 1 end end @@ -1744,10 +1749,20 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) line.buttons[o].icon:SetTexture(texture) line.buttons[o].texture = texture else - line.buttons[o].icon:SetTexture(iconsInThisLine[o]) - line.buttons[o].texture = iconsInThisLine[o] + local lineIcon = iconsInThisLine[o] + if type(lineIcon) == "string" and not string.find(lineIcon, "^[Ii]nterface") then + lineIcon = "Interface/ICONS/" .. lineIcon + end + DevTool:AddData(lineIcon, "lineIcon") + line.buttons[o].icon:SetTexture(lineIcon) + line.buttons[o].texture = lineIcon end end + + for o = #iconsInThisLine+1, 10 do -- cleanup unused + line.buttons[o].icon:SetTexture(nil) + line.buttons[o].texture = nil + end end end end @@ -1780,7 +1795,26 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) currentTable = t end - currentTable[index] = SPELLNAMES_CACHE[i] + currentTable[index] = SPELLIDS_CACHE[i] --SPELLNAMES_CACHE[i] --spellName won't work in 11.0, use IDs instead. + + index = index + 1 + if (index == 11) then + index = nil + end + end + + end + + for i = 1, #MACRO_ICON_FILENAMES do + if (MACRO_ICON_FILENAMES[i] and type(MACRO_ICON_FILENAMES[i]) == "string" and MACRO_ICON_FILENAMES[i]:lower():find(filter)) then + if (not index) then + index = 1 + local t = {} + iconList[#iconList+1] = t + currentTable = t + end + + currentTable[index] = MACRO_ICON_FILENAMES[i] index = index + 1 if (index == 11) then @@ -1795,7 +1829,7 @@ function detailsFramework:IconPick (callback, close_when_select, param1, param2) iconList[#iconList+1] = t for o = i, i+9 do if (SPELLNAMES_CACHE[o]) then - t[#t+1] = SPELLNAMES_CACHE[o] + t[#t+1] = SPELLIDS_CACHE[o] --SPELLNAMES_CACHE[o] --spellName won't work in 11.0, use IDs instead. end end end diff --git a/Libs/DF/timebar.lua b/Libs/DF/timebar.lua index e9176a36..f7027a35 100644 --- a/Libs/DF/timebar.lua +++ b/Libs/DF/timebar.lua @@ -1,6 +1,7 @@ -local DF = _G["DetailsFramework"] -if (not DF or not DetailsFrameworkCanLoad) then +---@type detailsframework +local detailsFramework = _G["DetailsFramework"] +if (not detailsFramework or not DetailsFrameworkCanLoad) then return end @@ -9,20 +10,69 @@ local type = type local floor = math.floor local GetTime = GetTime +---@class df_timebar : statusbar, df_scripthookmixin, df_widgets +---@field type string +---@field dframework boolean +---@field statusBar df_timebar_statusbar +---@field widget statusbar +---@field direction string +---@field HookList table +---@field tooltip string +---@field locked boolean +---@field HasTimer fun(self:df_timebar):boolean return if the timer bar is active showing a timer +---@field SetTimer fun(self:df_timebar, currentTime:number, startTime:number|boolean|nil, endTime:number|nil) +---@field StartTimer fun(self:df_timebar, currentTime:number, startTime:number, endTime:number) +---@field StopTimer fun(self:df_timebar) +---@field ShowSpark fun(self:df_timebar, state:boolean, alpha:number|nil, color:string|nil) +---@field ShowTimer fun(self:df_timebar, bShowTimer:boolean) +---@field SetIcon fun(self:df_timebar, texture:string, L:number|nil, R:number|nil, T:number|nil, B:number|nil) +---@field SetIconSize fun(self:df_timebar, width:number, height:number) +---@field SetTexture fun(self:df_timebar, texture:texturepath|textureid) +---@field SetColor fun(self:df_timebar, color:any, green:number|nil, blue:number|nil, alpha:number|nil) +---@field SetLeftText fun(self:df_timebar, text:string) +---@field SetRightText fun(self:df_timebar, text:string) +---@field SetFont fun(self:df_timebar, font:string|nil, size:number|nil, color:any, shadow:boolean|nil) +---@field SetThrottle fun(self:df_timebar, seconds:number) +---@field SetDirection fun(self:df_timebar, direction:string) + +---@class df_timebar_statusbar : statusbar +---@field MyObject df_timebar +---@field hasTimer boolean +---@field startTime number +---@field endTime number +---@field timeLeft1 number +---@field timeLeft2 number +---@field throttle number +---@field isUsingThrottle boolean +---@field showTimer boolean +---@field amountThrottle number +---@field sparkAlpha number +---@field sparkColorR number +---@field sparkColorG number +---@field sparkColorB number +---@field dontShowSpark boolean +---@field direction string +---@field spark texture +---@field icon texture +---@field leftText fontstring +---@field rightText fontstring +---@field backgroundTexture texture +---@field barTexture texture + local APITimeBarFunctions do local metaPrototype = { WidgetType = "timebar", - dversion = DF.dversion, + dversion = detailsFramework.dversion, } --check if there's a metaPrototype already existing - if (_G[DF.GlobalWidgetControlNames["timebar"]]) then + if (_G[detailsFramework.GlobalWidgetControlNames["timebar"]]) then --get the already existing metaPrototype - local oldMetaPrototype = _G[DF.GlobalWidgetControlNames["timebar"]] + local oldMetaPrototype = _G[detailsFramework.GlobalWidgetControlNames["timebar"]] --check if is older - if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < DF.dversion) ) then + if ( (not oldMetaPrototype.dversion) or (oldMetaPrototype.dversion < detailsFramework.dversion) ) then --the version is older them the currently loading one --copy the new values into the old metatable for funcName, _ in pairs(metaPrototype) do @@ -31,12 +81,12 @@ do end else --first time loading the framework - _G[DF.GlobalWidgetControlNames["timebar"]] = metaPrototype + _G[detailsFramework.GlobalWidgetControlNames["timebar"]] = metaPrototype end end -local TimeBarMetaFunctions = _G[DF.GlobalWidgetControlNames["timebar"]] -DF:Mixin(TimeBarMetaFunctions, DF.ScriptHookMixin) +local TimeBarMetaFunctions = _G[detailsFramework.GlobalWidgetControlNames["timebar"]] +detailsFramework:Mixin(TimeBarMetaFunctions, detailsFramework.ScriptHookMixin) --methods TimeBarMetaFunctions.SetMembers = TimeBarMetaFunctions.SetMembers or {} @@ -159,7 +209,7 @@ function TimeBarMetaFunctions:SetTexture(texture) end function TimeBarMetaFunctions:SetColor(color, green, blue, alpha) - local r, g, b, a = DF:ParseColors(color, green, blue, alpha) + local r, g, b, a = detailsFramework:ParseColors(color, green, blue, alpha) self.statusBar.barTexture:SetVertexColor(r, g, b, a) end @@ -172,22 +222,25 @@ end function TimeBarMetaFunctions:SetFont(font, size, color, shadow) if (font) then - DF:SetFontFace(self.statusBar.leftText, font) + detailsFramework:SetFontFace(self.statusBar.leftText, font) end if (size) then - DF:SetFontSize(self.statusBar.leftText, size) + detailsFramework:SetFontSize(self.statusBar.leftText, size) end if (color) then - DF:SetFontColor(self.statusBar.leftText, color) + detailsFramework:SetFontColor(self.statusBar.leftText, color) end if (shadow) then - DF:SetFontOutline(self.statusBar.leftText, shadow) + detailsFramework:SetFontOutline(self.statusBar.leftText, shadow) end end +---set a throttle for the timer bar, the timer will only update every X seconds +---calling without parameters will disable the throttle +---@param seconds number|nil the amount of seconds to throttle the timer function TimeBarMetaFunctions:SetThrottle(seconds) if (seconds and seconds > 0) then self.statusBar.isUsingThrottle = true @@ -197,6 +250,8 @@ function TimeBarMetaFunctions:SetThrottle(seconds) end end +---accept 'left' 'right' or nil, if ommited will default to right +---@param direction "left"|"right"|nil the direction of the timer bar function TimeBarMetaFunctions:SetDirection(direction) direction = direction or "right" self.direction = direction @@ -225,6 +280,14 @@ function TimeBarMetaFunctions:StopTimer() statusBar.spark:Hide() end +function TimeBarMetaFunctions:ShowTimer(bShowTimer) + if (bShowTimer) then + self.statusBar.showTimer = true + else + self.statusBar.showTimer = nil + end +end + function TimeBarMetaFunctions:ShowSpark(state, alpha, color) if (type(state) == "boolean" and state == false) then self.statusBar.dontShowSpark = true @@ -239,7 +302,7 @@ function TimeBarMetaFunctions:ShowSpark(state, alpha, color) end if (color) then - local r, g, b = DF:ParseColors(color) + local r, g, b = detailsFramework:ParseColors(color) if (r and g and b) then self.statusBar.sparkColorR = r self.statusBar.sparkColorG = g @@ -252,6 +315,8 @@ function TimeBarMetaFunctions:ShowSpark(state, alpha, color) end end +---@param self df_timebar_statusbar +---@param deltaTime number local OnUpdateFunc = function(self, deltaTime) if (self.isUsingThrottle) then self.throttle = self.throttle + deltaTime @@ -283,9 +348,11 @@ local OnUpdateFunc = function(self, deltaTime) end end - local timeLeft = floor(endTime - timeNow) - local formatedTimeLeft = DF:IntegerToTimer(timeLeft) - self.rightText:SetText(formatedTimeLeft) + if (self.showTimer) then + local timeLeft = floor(endTime - timeNow) + local formatedTimeLeft = detailsFramework:IntegerToTimer(timeLeft) + self.rightText:SetText(formatedTimeLeft) + end --check if finished if (timeNow >= self.endTime) then @@ -293,6 +360,12 @@ local OnUpdateFunc = function(self, deltaTime) end end +---start a timer on the timebar +---calling without parameters will stop the timer +---@param self df_timebar +---@param currentTime number the time in seconds if startTime is a boolean true. GetTime() when start and end time are passed +---@param startTime number|boolean|nil GetTime() when the timer started. if passed true: startTime and endTime are GetTime() and GetTime() + currentTime, currenTime is the time in seconds +---@param endTime number|nil GetTime() when the timer will end. ignored if startTime is a boolean true function TimeBarMetaFunctions:SetTimer(currentTime, startTime, endTime) self.statusBar:Show() @@ -306,7 +379,7 @@ function TimeBarMetaFunctions:SetTimer(currentTime, startTime, endTime) --it is the same timer called again return end - self.statusBar.startTime = startTime + self.statusBar.startTime = tonumber(startTime) or 0 --fit the number type self.statusBar.endTime = endTime else local bForceNewTimer = type(startTime) == "boolean" and startTime @@ -352,18 +425,26 @@ function TimeBarMetaFunctions:SetTimer(currentTime, startTime, endTime) end end - -function DF:CreateTimeBar(parent, texture, width, height, value, member, name) +---create a time bar widget, a timebar is a statubar that can have a timer and a spark +---@param parent frame the parent frame +---@param texture texturepath|textureid the texture of the bar +---@param width number? the width of the bar, default is 150 +---@param height number? the height of the bar, default is 20 +---@param value number? the initial value of the bar, default is 0 +---@param member string? the name of the member in the parent frame +---@param name string? the name of the widget +---@return df_timebar +function detailsFramework:CreateTimeBar(parent, texture, width, height, value, member, name) if (not name) then - name = "DetailsFrameworkBarNumber" .. DF.BarNameCounter - DF.BarNameCounter = DF.BarNameCounter + 1 + name = "DetailsFrameworkBarNumber" .. detailsFramework.BarNameCounter + detailsFramework.BarNameCounter = detailsFramework.BarNameCounter + 1 elseif (not parent) then - return error("Details! FrameWork: parent not found.", 2) + error("Details! FrameWork: parent not found.", 2) end if (name:find("$parent")) then - local parentName = DF:GetParentName(parent) + local parentName = detailsFramework:GetParentName(parent) name = name:gsub("$parent", parentName) end @@ -375,18 +456,21 @@ function DF:CreateTimeBar(parent, texture, width, height, value, member, name) if (member) then parent[member] = timeBar end + + ---@diagnostic disable-next-line: undefined-field if (parent.dframework) then + ---@diagnostic disable-next-line: undefined-field parent = parent.widget end value = value or 0 width = width or 150 - height = height or 14 + height = height or 20 timeBar.locked = false timeBar.statusBar = CreateFrame("statusbar", name, parent, "BackdropTemplate") timeBar.widget = timeBar.statusBar - DF:Mixin(timeBar.statusBar, DF.WidgetFunctions) + detailsFramework:Mixin(timeBar.statusBar, detailsFramework.WidgetFunctions) timeBar.statusBar.MyObject = timeBar timeBar.direction = "right" @@ -397,7 +481,9 @@ function DF:CreateTimeBar(parent, texture, width, height, value, member, name) if (not TimeBarMetaFunctions[funcName]) then TimeBarMetaFunctions[funcName] = function(object, ...) local x = loadstring("return _G['"..object.statusBar:GetName().."']:"..funcName.."(...)") - return x(...) + if (x) then + return x(...) + end end end end @@ -433,7 +519,7 @@ function DF:CreateTimeBar(parent, texture, width, height, value, member, name) timeBar.statusBar.rightText = timeBar.statusBar:CreateFontString(nil, "overlay", "GameFontNormal", 4) timeBar.statusBar.rightText:SetPoint("right", timeBar.statusBar, "right", -2, 0) timeBar.statusBar.rightText:SetJustifyH("left") - + --hooks timeBar.HookList = { OnEnter = {}, diff --git a/Libs/LibOpenRaid/LibOpenRaid.lua b/Libs/LibOpenRaid/LibOpenRaid.lua index 696123d1..f1e462b4 100644 --- a/Libs/LibOpenRaid/LibOpenRaid.lua +++ b/Libs/LibOpenRaid/LibOpenRaid.lua @@ -31,7 +31,7 @@ LIB_OPEN_RAID_CAN_LOAD = false local major = "LibOpenRaid-1.0" -local CONST_LIB_VERSION = 134 +local CONST_LIB_VERSION = 136 if (LIB_OPEN_RAID_MAX_VERSION) then if (CONST_LIB_VERSION <= LIB_OPEN_RAID_MAX_VERSION) then diff --git a/boot.lua b/boot.lua index 7222b643..5d7a3dae 100644 --- a/boot.lua +++ b/boot.lua @@ -19,8 +19,8 @@ local addonName, Details222 = ... local version = GetBuildInfo() - Details.build_counter = 12829 - Details.alpha_build_counter = 12829 --if this is higher than the regular counter, use it instead + Details.build_counter = 12830 + Details.alpha_build_counter = 12830 --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 @@ -1237,6 +1237,7 @@ do _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", [[Interface\AddOns\Details\images\bar_hyanda]]) diff --git a/core/parser.lua b/core/parser.lua index 72bfb145..3c702453 100755 --- a/core/parser.lua +++ b/core/parser.lua @@ -5343,10 +5343,12 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 end local keystoneLevels = {} + local playerRatings = {} Details.KeystoneLevels = keystoneLevels - --save the keystone level for each of the 5 party members + Details.PlayerRatings = playerRatings + --save the keystone and rating level for each of the 5 party members - local saveGroupMembersKeystoneLevel = function() + local saveGroupMembersKeystoneAndRatingLevel = function() wipe(keystoneLevels) local libOpenRaid = LibStub("LibOpenRaid-1.0", true) @@ -5358,6 +5360,7 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 if (unitKeystoneInfo) then local unitName = Details:GetFullName(unitId) keystoneLevels[unitName] = unitKeystoneInfo.level + playerRatings[unitName] = unitKeystoneInfo.rating end end end @@ -5368,6 +5371,7 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 if (unitKeystoneInfo) then local unitName = Details:GetFullName(unitId) keystoneLevels[unitName] = unitKeystoneInfo.level + playerRatings[unitName] = unitKeystoneInfo.rating end end end @@ -5376,7 +5380,7 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 function Details222.CacheKeystoneForAllGroupMembers() local _, instanceType, difficultyID = GetInstanceInfo() if (instanceType == "party") then - saveGroupMembersKeystoneLevel() + saveGroupMembersKeystoneAndRatingLevel() end end @@ -5384,7 +5388,7 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 Details222.MythicPlus.WorldStateTimerEndAt = time() --wait until the keystone is updated and send it to the party - saveGroupMembersKeystoneLevel() + saveGroupMembersKeystoneAndRatingLevel() ---@type number mapID ---@type number level @@ -5982,8 +5986,14 @@ local SPELL_POWER_PAIN = SPELL_POWER_PAIN or (PowerEnum and PowerEnum.Pain) or 1 end end + --open world out of combat spell damage + local outofcombat_spell_damage = function(unused, token, time, whoGUID, whoName, whoFlags, targetGUID, targetName, targetFlags) + --identify if the attacker is a group member + end + local out_of_combat_interresting_events = { ["SPELL_SUMMON"] = parser.summon, + ["SPELL_DAMAGE"] = outofcombat_spell_damage, } function Details222.Parser.OnParserEventOutOfCombat(self, event, ...) diff --git a/frames/window_mythicplus/window_end_of_run.lua b/frames/window_mythicplus/window_end_of_run.lua index d853b91a..47a5d1d0 100644 --- a/frames/window_mythicplus/window_end_of_run.lua +++ b/frames/window_mythicplus/window_end_of_run.lua @@ -17,6 +17,8 @@ local PixelUtil = PixelUtil local C_Timer = C_Timer local GameTooltip = GameTooltip local SOUNDKIT = SOUNDKIT +local C_EventUtils = C_EventUtils +local C_AddOns = C_AddOns local GetItemInfo = GetItemInfo or C_Item.GetItemInfo local GetDetailedItemLevelInfo = GetDetailedItemLevelInfo or C_Item.GetDetailedItemLevelInfo --C_Item.GetDetailedItemLevelInfo does not return a table @@ -26,9 +28,26 @@ local Loc = _G.LibStub("AceLocale-3.0"):GetLocale("Details") local mythicDungeonCharts = Details222.MythicPlus.Charts.Listener local mythicDungeonFrames = Details222.MythicPlus.Frames -local CONST_DEBUG_MODE = false +local CONST_DEBUG_MODE = true local LOOT_DEBUG_MODE = false +--fallback if the class color isn't found +local defaultColor = {r = 0.9, g = 0.9, b = 0.9} + +local playerBannerSettings = { + background_width = 286, + background_height = 64, + playername_background_width = 68, + playername_background_height = 12, + playername_fontsize = 12, + playername_fontcolor = {1, 1, 1}, + dungeon_texture_width = 32, + dungeon_texture_height = 32, + loot_square_amount = 2, + trans_anim_duration = 0.5, --time that the translation animation takes to move the banner from right to left +} + + function Details222.Debug.SetMythicPlusDebugState(bState) if (bState == nil) then bState = not CONST_DEBUG_MODE @@ -59,16 +78,23 @@ _G.MythicDungeonFrames = mythicDungeonFrames ---@field BounceFrameShake df_frameshake ---@class playerbanner : frame +---@field index number +---@field BackgroundBannerMaskTexture texture +---@field BackgroundBannerGradient texture ---@field FadeInAnimation animationgroup ----@field BackgroundBannerTextureScaleAnimation animationgroup +---@field BackgroundShowAnim animationgroup +---@field DungeonBackdropShowAnim animationgroup +---@field BackgroundGradientAnim animationgroup ---@field BackgroundBannerFlashTextureColorAnimation animationgroup ---@field BounceFrameShake df_frameshake ---@field NextLootSquare number ---@field LootSquares details_lootsquare[] ---@field LevelUpFrame frame ---@field LevelUpTextFrame frame +---@field WaitingForLootLabel df_label +---@field RantingLabel df_label ---@field LevelFontString fontstring ----@field DungeonTexture texture +---@field KeyStoneDungeonTexture texture ---@field DungeonBorderTexture texture ---@field FlashTexture texture ---@field LootSquare frame @@ -78,6 +104,8 @@ _G.MythicDungeonFrames = mythicDungeonFrames ---@field unitId string ---@field unitName string ---@field PlayerNameFontString fontstring +---@field PlayerNameBackgroundTexture texture +---@field DungeonBackdropTexture texture ---@field BackgroundBannerTexture animatedtexture ---@field BackgroundBannerFlashTexture animatedtexture ---@field RoleIcon texture @@ -86,6 +114,8 @@ _G.MythicDungeonFrames = mythicDungeonFrames ---@field Name fontstring ---@field AnimIn animationgroup ---@field AnimOut animationgroup +---@field StartTextDotAnimation fun(self:playerbanner) +---@field StopTextDotAnimation fun(self:playerbanner) ---@field ClearLootSquares fun(self:playerbanner) ---@field GetLootSquare fun(self:playerbanner):details_lootsquare @@ -93,7 +123,9 @@ _G.MythicDungeonFrames = mythicDungeonFrames ---@field LootIcon texture ---@field LootIconBorder texture ---@field LootItemLevel fontstring +---@field LootItemLevelBackgroundTexture texture ---@field itemLink string +---@field ShadowTexture texture ---@class details_loot_cache : table ---@field playerName string @@ -106,6 +138,38 @@ _G.MythicDungeonFrames = mythicDungeonFrames ---@class lootframe : frame ---@field LootCache details_loot_cache[] +---@class details_mplus_endframe : frame +---@field unitCacheByName playerbanner[] +---@field entryAnimationDuration number +---@field AutoCloseTimeBar df_timebar +---@field OpeningAnimation animationgroup +---@field HeaderFadeInAnimation animationgroup +---@field HeaderTexture texture +---@field TopFrame frame +---@field ContentFrame frame +---@field ContentFrameFadeInAnimation animationgroup +---@field YellowSpikeCircle texture +---@field YellowFlash texture +---@field Level fontstring +---@field leftFiligree texture +---@field rightFiligree texture +---@field bottomFiligree texture +---@field CloseButton df_closebutton +---@field ConfigButton df_button +---@field ShowBreakdownButton df_button +---@field ShowChartButton df_button +---@field PlayerBanners playerbanner[] +---@field YouBeatTheTimerLabel fontstring +---@field RantingLabel df_label +---@field ElapsedTimeIcon texture +---@field ElapsedTimeText fontstring +---@field OutOfCombatIcon texture +---@field OutOfCombatText fontstring +---@field SandTimeIcon texture +---@field KeylevelText fontstring +---@field StrongArmIcon texture + + --frame to handle loot events local lootFrame = CreateFrame("frame", "DetailsEndOfMythicLootFrame", UIParent) lootFrame:RegisterEvent("BOSS_KILL") @@ -115,9 +179,10 @@ lootFrame:RegisterEvent("ENCOUNTER_LOOT_RECEIVED") lootFrame.LootCache = {} --currently being called after a updatPlayerBanner() -function lootFrame.UpdateUnitLoot(unitBanner) - local unitId = unitBanner.unitId - local unitName = unitBanner.unitName +function lootFrame.UpdateUnitLoot(playerBanner) + ---@cast playerBanner playerbanner + local unitId = playerBanner.unitId + local unitName = playerBanner.unitName local timeNow = GetTime() local lootCache = lootFrame.LootCache[unitName] @@ -131,6 +196,10 @@ function lootFrame.UpdateUnitLoot(unitBanner) end end + if (#lootCache > 0) then + playerBanner:StopTextDotAnimation() + end + if (lootCache) then local lootCacheSize = #lootCache if (lootCacheSize > 0) then @@ -154,16 +223,17 @@ function lootFrame.UpdateUnitLoot(unitBanner) local itemQuality = lootInfo.itemQuality local itemID = lootInfo.itemID - local lootSquare = unitBanner:GetLootSquare() + local lootSquare = playerBanner:GetLootSquare() --internally controls the loot square index lootSquare.itemLink = itemLink --will error if this the thrid lootSquare (creates only 2 per banner) - local rarityColor = --[[GLOBAL]]ITEM_QUALITY_COLORS[itemQuality] + local rarityColor = --[[GLOBAL]] ITEM_QUALITY_COLORS[itemQuality] + lootSquare.LootIcon:SetTexture(C_Item.GetItemIconByID(itemID)) lootSquare.LootIconBorder:SetVertexColor(rarityColor.r, rarityColor.g, rarityColor.b, 1) - - lootSquare.LootIcon:SetTexture(GetItemIcon(itemID)) lootSquare.LootItemLevel:SetText(effectiveILvl or "0") - mythicDungeonFrames.ReadyFrame.StopTextDotAnimation() + --update size + lootSquare.LootIcon:SetSize(playerBannerSettings.dungeon_texture_width, playerBannerSettings.dungeon_texture_height) + lootSquare.LootIconBorder:SetSize(playerBannerSettings.dungeon_texture_width, playerBannerSettings.dungeon_texture_height) lootSquare:Show() @@ -195,6 +265,13 @@ lootFrame:SetScript("OnEvent", function(self, event, ...) itemStackCount, itemEquipLoc, itemTexture, sellPrice, classID, subclassID, bindType, expacID, setID, isCraftingReagent = GetItemInfo(itemLink) + if (mythicDungeonFrames.ReadyFrame and mythicDungeonFrames.ReadyFrame:IsVisible()) then + local unitBanner = mythicDungeonFrames.ReadyFrame.unitCacheByName[unitName] + if (unitBanner) then + unitBanner:StopTextDotAnimation() + end + end + if (Details.debug) then Details222.DebugMsg("Loot Received:", unitName, itemLink, effectiveILvl, itemQuality, baseItemLevel, "itemType:", itemType, "itemSubType:", itemSubType, "itemEquipLoc:", itemEquipLoc) end @@ -234,11 +311,15 @@ lootFrame:SetScript("OnEvent", function(self, event, ...) end end) +---@param playerBanner playerbanner +---@param name string +---@param parent frame +---@param lootIndex number local createLootSquare = function(playerBanner, name, parent, lootIndex) ---@type details_lootsquare local lootSquare = CreateFrame("frame", playerBanner:GetName() .. "LootSquare" .. lootIndex, parent) lootSquare:SetSize(46, 46) - lootSquare:SetFrameLevel(parent:GetFrameLevel()+1) + lootSquare:SetFrameLevel(parent:GetFrameLevel()+10) lootSquare:Hide() lootSquare:SetScript("OnEnter", function(self) @@ -253,6 +334,14 @@ local createLootSquare = function(playerBanner, name, parent, lootIndex) GameTooltip:Hide() end) + local shadowTexture = playerBanner:CreateTexture("$parentShadowTexture", "artwork") + shadowTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus_banner_mask]]) + shadowTexture:SetTexCoord(441/512, 511/512, 81/512, 151/512) + shadowTexture:SetSize(32, 32) + shadowTexture:SetVertexColor(0.05, 0.05, 0.05, 0.6) + shadowTexture:SetPoint("center", lootSquare, "center", 0, 0) + lootSquare.ShadowTexture = shadowTexture + local lootIcon = lootSquare:CreateTexture("$parentLootIcon", "artwork") lootIcon:SetSize(46, 46) lootIcon:SetPoint("center", lootSquare, "center", 0, 0) @@ -267,62 +356,115 @@ local createLootSquare = function(playerBanner, name, parent, lootIndex) lootSquare.LootIconBorder = lootIconBorder local lootItemLevel = lootSquare:CreateFontString("$parentLootItemLevel", "overlay", "GameFontNormal") - lootItemLevel:SetPoint("top", lootSquare, "bottom", 0, -2) + lootItemLevel:SetPoint("bottom", lootSquare, "bottom", 0, -4) lootItemLevel:SetTextColor(1, 1, 1) - detailsFramework:SetFontSize(lootItemLevel, 12) + detailsFramework:SetFontSize(lootItemLevel, 11) lootSquare.LootItemLevel = lootItemLevel + local lootItemLevelBackgroundTexture = lootSquare:CreateTexture("$parentItemLevelBackgroundTexture", "artwork", nil, 6) + lootItemLevelBackgroundTexture:SetTexture([[Interface\Cooldown\LoC-ShadowBG]]) + lootItemLevelBackgroundTexture:SetPoint("bottomleft", lootSquare, "bottomleft", -7, -3) + lootItemLevelBackgroundTexture:SetPoint("bottomright", lootSquare, "bottomright", 7, -15) + lootItemLevelBackgroundTexture:SetHeight(10) + lootSquare.LootItemLevelBackgroundTexture = lootItemLevelBackgroundTexture + return lootSquare end -local createPlayerBanner = function(parent, name) +local createPlayerBanner = function(parent, name, index) local template = "MythicPlusBannerPartyMemberTemplate" ---@type playerbanner local playerBanner = CreateFrame("frame", name, parent, template) + playerBanner.index = index playerBanner:SetAlpha(1) playerBanner:EnableMouse(true) playerBanner:SetFrameLevel(parent:GetFrameLevel()+2) + --size is set on the template --make an fade in animation local fadeInAnimation = detailsFramework:CreateAnimationHub(playerBanner, function() playerBanner:Show() end, function() playerBanner:SetAlpha(1) end) - detailsFramework:CreateAnimation(fadeInAnimation, "Alpha", 1, 0.1, 0, 1) + detailsFramework:CreateAnimation(fadeInAnimation, "Alpha", 1, 0.2, 0, 1) playerBanner.FadeInAnimation = fadeInAnimation --there's already a role icon on .RoleIcon, created from the template local playerNameFontString = playerBanner:CreateFontString("$parentPlayerNameText", "overlay", "GameFontNormal") - playerNameFontString:SetTextColor(1, 1, 1) - playerNameFontString:SetPoint("top", playerBanner, "bottom", -1, -7) - detailsFramework:SetFontSize(playerNameFontString, 12) + playerNameFontString:SetTextColor(unpack(playerBannerSettings.playername_fontcolor)) + playerNameFontString:SetPoint("bottom", playerBanner, "bottom", 0, 0) + detailsFramework:SetFontSize(playerNameFontString, playerBannerSettings.playername_fontsize) playerBanner.PlayerNameFontString = playerNameFontString local playerNameBackgroundTexture = playerBanner:CreateTexture("$parentPlayerNameBackgroundTexture", "overlay", nil, 6) playerNameBackgroundTexture:SetTexture([[Interface\Cooldown\LoC-ShadowBG]]) - playerNameBackgroundTexture:SetSize(68, 12) + playerNameBackgroundTexture:SetSize(playerBannerSettings.playername_background_width, playerBannerSettings.playername_background_height) playerNameBackgroundTexture:SetPoint("center", playerNameFontString, "center", 0, 0) + playerBanner.PlayerNameBackgroundTexture = playerNameBackgroundTexture - local createPlayerBannerBackgroundTexture = function(playerBanner, color, drawLevel) - local backgroundBannerTexture = playerBanner:CreateTexture("$parentBannerTexture", "background", nil, 0) + local createPlayerBannerBackgroundTexture = function(playerBanner) + ---@cast playerBanner playerbanner + local backgroundBannerTexture = playerBanner:CreateTexture("$parentBannerTexture", "background", nil, -1) ---@cast backgroundBannerTexture animatedtexture - backgroundBannerTexture:SetTexture([[Interface\ACHIEVEMENTFRAME\GuildTabard]]) - backgroundBannerTexture:SetDrawLayer("background", drawLevel or 0) - backgroundBannerTexture:SetSize(63, 129) - backgroundBannerTexture:SetTexCoord(5/128, 68/128, 123/256, 252/256) - backgroundBannerTexture:SetPoint("topleft", playerBanner, "bottomleft", -5, playerBanner:GetHeight()/2) - backgroundBannerTexture:SetPoint("topright", playerBanner, "bottomright", 4, playerBanner:GetHeight()/2) - local r, g, b = detailsFramework:ParseColors(color or "dark1") + backgroundBannerTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus_banner_mask]]) + backgroundBannerTexture:SetSize(playerBannerSettings.background_width, playerBannerSettings.background_height) + backgroundBannerTexture:SetPoint("topright", playerBanner, "topleft", playerBanner:GetHeight()/2, 0) + backgroundBannerTexture:SetPoint("bottomright", playerBanner, "bottomleft", playerBanner:GetHeight()/2, 0) + local r, g, b = detailsFramework:ParseColors("dark1") backgroundBannerTexture:SetVertexColor(r, g, b) + backgroundBannerTexture:SetAlpha(0.95) + + --backdrop gradient from bottom to top + local maskTexture = playerBanner:CreateMaskTexture("$parentBackgroundBannerMaskTexture", "artwork") + maskTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus_banner_mask]]) + maskTexture:SetPoint("topright", backgroundBannerTexture, "topright", 0, 0) + maskTexture:SetSize(backgroundBannerTexture:GetSize()) + playerBanner.BackgroundBannerMaskTexture = maskTexture + + ---@type df_gradienttable + local gradientTable = {gradient = "vertical", fromColor = {0.01, 0.01, 0.01, 0.5}, toColor = "transparent"} + local gradientBelowTheLine = detailsFramework:CreateTexture(playerBanner, gradientTable, 1, 64, "background", {0, 1, 0, 1}, "BackgroundGradient", "$parentBackgroundGradient") + gradientBelowTheLine:SetDrawLayer("background", 1) + gradientBelowTheLine:SetPoint("bottomleft", backgroundBannerTexture, "bottomleft", 0, 0) + gradientBelowTheLine:SetPoint("bottomright", backgroundBannerTexture, "bottomright", 0, 0) + gradientBelowTheLine:AddMaskTexture(maskTexture) + playerBanner.BackgroundBannerGradient = gradientBelowTheLine + + local dungeonBackdropTexture = playerBanner:CreateTexture("$parentDungeonBackdropTexture", "background", nil, 0) + dungeonBackdropTexture:SetVertexColor(0.2, 0.2, 0.2, 0.8) + dungeonBackdropTexture:SetDesaturation(0.5) + dungeonBackdropTexture:SetAlpha(0.5) + dungeonBackdropTexture:SetHeight(61) + dungeonBackdropTexture:SetPoint("bottomleft", backgroundBannerTexture, "bottomleft", 0, 0) + dungeonBackdropTexture:SetPoint("bottomright", backgroundBannerTexture, "bottomright", 0, 0) + --image height = 244 = 48 pixels + local topStart = 49 --pixel start for the lorebg image + local pixelsPerImage = 48 + local topCoord = (topStart + ((playerBanner.index - 1) * pixelsPerImage)) / 512 + local bottomCoord = (topStart + (playerBanner.index * pixelsPerImage)) / 512 + dungeonBackdropTexture:SetTexCoord(35/512, 291/512, topCoord, bottomCoord) + dungeonBackdropTexture:AddMaskTexture(maskTexture) + playerBanner.DungeonBackdropTexture = dungeonBackdropTexture + return backgroundBannerTexture end do - playerBanner.BackgroundBannerFlashTexture = createPlayerBannerBackgroundTexture(playerBanner, "white", -1) + ---@type animatedtexture + local bannerFlash = playerBanner:CreateTexture("$parentBannerTexture", "background", nil, 0) + bannerFlash:SetAlpha(0) + bannerFlash:SetTexture([[Interface\AddOns\Details\images\end_of_mplus_banner_mask]]) + bannerFlash:SetSize(playerBannerSettings.background_width, playerBannerSettings.background_height) + bannerFlash:SetPoint("topright", playerBanner, "topleft", playerBanner:GetHeight()/2, 0) + bannerFlash:SetPoint("bottomright", playerBanner, "bottomleft", playerBanner:GetHeight()/2, 0) + playerBanner.BackgroundBannerFlashTexture = bannerFlash + --create a color animation for playerBanner.BackgroundBannerFlashTexture, the color start as white and goes to dark1 --the start delay for this animation is 0.2 - local backgroundBannerFlashTextureColorAnimation = detailsFramework:CreateAnimationHub(playerBanner.BackgroundBannerFlashTexture, function() end, function() playerBanner.BackgroundBannerFlashTexture:SetVertexColor(0.1, 0.1, 0.1) end) - local colorAnim = detailsFramework:CreateAnimation(backgroundBannerFlashTextureColorAnimation, "VertexColor", 1, 0.2, "white", "dark1") - colorAnim:SetStartDelay(0.175) + local backgroundBannerFlashTextureColorAnimation = detailsFramework:CreateAnimationHub(playerBanner.BackgroundBannerFlashTexture, function() end, function() playerBanner.BackgroundBannerFlashTexture:SetVertexColor(0.1, 0.1, 0.1, 0) end) + local alpha1 = detailsFramework:CreateAnimation(backgroundBannerFlashTextureColorAnimation, "Alpha", 1, 0.1, 0, 0.3) + local alpha2 = detailsFramework:CreateAnimation(backgroundBannerFlashTextureColorAnimation, "Alpha", 2, 0.1, 0.6, 0) + local scale1 = detailsFramework:CreateAnimation(backgroundBannerFlashTextureColorAnimation, "Scale", 1, 0.1, 1, 0, 1, 1, "TOP") + alpha2:SetStartDelay(0.075) playerBanner.BackgroundBannerFlashTextureColorAnimation = backgroundBannerFlashTextureColorAnimation end @@ -330,13 +472,13 @@ local createPlayerBanner = function(parent, name) playerBanner.BackgroundBannerTexture = createPlayerBannerBackgroundTexture(playerBanner) function playerBanner.BackgroundBannerTexture:CreateRandomBounceSettings() - local duration = RandomFloatInRange(0.78, 0.82) + local duration = RandomFloatInRange(0.78 + (playerBanner.index/10), 0.82 + (playerBanner.index/10)) local amplitude = RandomFloatInRange(4.50, 5.5) local frequency = RandomFloatInRange(19.8, 20.8) local absoluteSineX = false - local absoluteSineY = true - local scaleX = 0 - local scaleY = RandomFloatInRange(0.90, 1.1) + local absoluteSineY = false + local scaleX = RandomFloatInRange(0.90, 1.1) + local scaleY = 0 local fadeInTime = 0 local fadeOutTime = RandomFloatInRange(0.7, 0.8) @@ -351,49 +493,67 @@ local createPlayerBanner = function(parent, name) local absoluteSineY = true local scaleX = 0 local scaleY = 1 - local fadeInTime = 0 + local fadeInTime = 0.5 local fadeOutTime = lossOfMomentum local backgroundBannerTextureFS2 = detailsFramework:CreateFrameShake(playerBanner.BackgroundBannerTexture, duration, amplitude, frequency, absoluteSineX, absoluteSineY, scaleX, scaleY, fadeInTime, fadeOutTime) playerBanner.BackgroundBannerTexture.BounceFrameShake = backgroundBannerTextureFS2 - --scale animation for backgroundBannerTexture, which starts at 1 x 0 y and goes to 1 x 1 y, anchor top - local backgroundBannerTextureScaleAnimation = detailsFramework:CreateAnimationHub(playerBanner.BackgroundBannerTexture, function() end, function() playerBanner.BackgroundBannerTexture:SetSize(63, 129) end) - detailsFramework:CreateAnimation(backgroundBannerTextureScaleAnimation, "Scale", 1, 0.25, 1, 0, 1, 1, "top", 0, 0) - playerBanner.BackgroundBannerTextureScaleAnimation = backgroundBannerTextureScaleAnimation + local onPlayAnim = function(self) + if (Details.mythic_plus.finished_run_frame_options.grow_direction == "left") then + self.ScaleAnim:SetOrigin("RIGHT", 0, 0) + + elseif (Details.mythic_plus.finished_run_frame_options.grow_direction == "right") then + self.ScaleAnim:SetOrigin("LEFT", 0, 0) + end + end + + playerBannerSettings.trans_anim_duration = 0.5 + + local backgroundShowAnim = detailsFramework:CreateAnimationHub(playerBanner.BackgroundBannerTexture, onPlayAnim, function() playerBanner.BackgroundBannerTexture:SetSize(playerBannerSettings.background_width, playerBannerSettings.background_height) end) + backgroundShowAnim.ScaleAnim = detailsFramework:CreateAnimation(backgroundShowAnim, "Scale", 1, playerBannerSettings.trans_anim_duration, 0, 1, 1, 1, "RIGHT") + playerBanner.BackgroundShowAnim = backgroundShowAnim + + local dungeonBackdropTextureAnim = detailsFramework:CreateAnimationHub(playerBanner.DungeonBackdropTexture, onPlayAnim, function() playerBanner.DungeonBackdropTexture:SetSize(playerBannerSettings.background_width, 61) end) + dungeonBackdropTextureAnim.ScaleAnim = detailsFramework:CreateAnimation(dungeonBackdropTextureAnim, "Scale", 1, playerBannerSettings.trans_anim_duration, 0, 1, 1, 1, "RIGHT") + dungeonBackdropTextureAnim.AlphaAnim = detailsFramework:CreateAnimation(dungeonBackdropTextureAnim, "Alpha", 1, playerBannerSettings.trans_anim_duration+0.1, 0, playerBanner.DungeonBackdropTexture:GetAlpha()) + playerBanner.DungeonBackdropShowAnim = dungeonBackdropTextureAnim + + --create the same animations for the texture playerBanner.BackgroundGradient + local backgroundGradientAnim = detailsFramework:CreateAnimationHub(playerBanner.BackgroundBannerGradient, onPlayAnim, function() playerBanner.BackgroundBannerGradient:SetSize(playerBannerSettings.background_width, playerBannerSettings.background_height) end) + backgroundGradientAnim.ScaleAnim = detailsFramework:CreateAnimation(backgroundGradientAnim, "Scale", 1, playerBannerSettings.trans_anim_duration, 0, 1, 1, 1, "RIGHT") + backgroundGradientAnim.ScaleAnim:SetStartDelay(0.05) + playerBanner.BackgroundGradientAnim = backgroundGradientAnim end - local backgroundBannerBorderTexture = playerBanner:CreateTexture("$parentBannerBorderTexture", "highlight", nil, -1) - backgroundBannerBorderTexture:SetAtlas("UI-Achievement-Guild-Flag-Outline") - backgroundBannerBorderTexture:SetSize(63, 129) - backgroundBannerBorderTexture:SetPoint("topleft", playerBanner, "bottomleft", -5, playerBanner:GetHeight()/2) - backgroundBannerBorderTexture:SetPoint("topright", playerBanner, "bottomright", 4, playerBanner:GetHeight()/2) - - local dungeonTexture = playerBanner:CreateTexture("$parentDungeonTexture", "artwork") - dungeonTexture:SetTexCoord(25/512, 360/512, 50/512, 290/512) - dungeonTexture:SetSize(50, 39) - dungeonTexture:SetPoint("top", playerBanner,"bottom", 0, -16) - dungeonTexture:SetAlpha(0.9934) - playerBanner.DungeonTexture = dungeonTexture + local keyStoneDungeonTexture = playerBanner:CreateTexture("$parentDungeonTexture", "artwork") + keyStoneDungeonTexture:SetTexCoord(36/512, 375/512, 50/512, 290/512) + keyStoneDungeonTexture:SetSize(playerBannerSettings.dungeon_texture_width, playerBannerSettings.dungeon_texture_height) + keyStoneDungeonTexture:SetPoint("right", playerBanner,"left", -16, 0) + keyStoneDungeonTexture:SetAlpha(0.9934) + detailsFramework:SetMask(keyStoneDungeonTexture, [[Interface\FrameGeneral\UIFrameIconMask]]) + playerBanner.KeyStoneDungeonTexture = keyStoneDungeonTexture local dungeonBorderTexture = playerBanner:CreateTexture("$parentDungeonBorder", "border") - dungeonBorderTexture:SetTexture([[Interface\BUTTONS\UI-EmptySlot]]) + dungeonBorderTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + dungeonBorderTexture:SetTexCoord(441/512, 511/512, 81/512, 151/512) dungeonBorderTexture:SetDrawLayer("border", 0) dungeonBorderTexture:ClearAllPoints() - dungeonBorderTexture:SetPoint("topleft", dungeonTexture,"topleft", -17, 15) - dungeonBorderTexture:SetPoint("bottomright", dungeonTexture,"bottomright", 18, -15) + dungeonBorderTexture:SetSize(playerBannerSettings.dungeon_texture_width+2, playerBannerSettings.dungeon_texture_height+2) + dungeonBorderTexture:SetPoint("center", keyStoneDungeonTexture, "center", 0, 0) dungeonBorderTexture:SetAlpha(1) + dungeonBorderTexture:SetVertexColor(0, 0, 0) playerBanner.DungeonBorderTexture = dungeonBorderTexture --animation for the key leveling up local levelUpFrame = CreateFrame("frame", "$LevelUpFrame", playerBanner, "GarrisonFollowerLevelUpTemplate") - levelUpFrame:SetPoint("top", dungeonTexture, "bottom", 0, 44) + levelUpFrame:SetPoint("top", keyStoneDungeonTexture, "bottom", 0, 44) levelUpFrame:SetScale(0.9) levelUpFrame.Text:SetText("") playerBanner.LevelUpFrame = levelUpFrame levelUpFrame:SetFrameLevel(playerBanner:GetFrameLevel()+1) local levelUpTextFrame = CreateFrame("frame", "$LevelUpTextFrame", playerBanner) - levelUpTextFrame:SetPoint("top", dungeonTexture, "bottom", -1, -14) + levelUpTextFrame:SetPoint("top", keyStoneDungeonTexture, "bottom", -1, 0) levelUpTextFrame:SetFrameLevel(playerBanner:GetFrameLevel()+2) levelUpTextFrame:SetSize(1, 1) playerBanner.LevelUpTextFrame = levelUpTextFrame @@ -402,12 +562,19 @@ local createPlayerBanner = function(parent, name) local shakeAnimation2 = detailsFramework:CreateFrameShake(levelUpTextFrame, 0.5, 1, 200, false, false, 0, 1, 0, 0) local levelFontString = levelUpTextFrame:CreateFontString("$parentLVLText", "artwork", "GameFontNormal") + levelFontString:SetPoint("bottom", keyStoneDungeonTexture, "bottom", 0, -4) levelFontString:SetTextColor(1, 1, 1) - levelFontString:SetPoint("center", levelUpTextFrame, "center", 0, 0) - detailsFramework:SetFontSize(levelFontString, 20) + detailsFramework:SetFontSize(levelFontString, 11) levelFontString:SetText("") playerBanner.LevelFontString = levelFontString + local levelFontStringBackgroundTexture = levelUpTextFrame:CreateTexture("$parentItemLevelBackgroundTexture", "artwork", nil, 6) + levelFontStringBackgroundTexture:SetTexture([[Interface\Cooldown\LoC-ShadowBG]]) + levelFontStringBackgroundTexture:SetPoint("bottomleft", keyStoneDungeonTexture, "bottomleft", -7, -3) + levelFontStringBackgroundTexture:SetPoint("bottomright", keyStoneDungeonTexture, "bottomright", 7, -15) + levelFontStringBackgroundTexture:SetHeight(10) + levelUpTextFrame.LevelFontStringBackgroundTexture = levelFontStringBackgroundTexture + --> animations for levelFontString local animationGroup = levelFontString:CreateAnimationGroup("DetailsMythicLevelTextAnimationGroup") animationGroup:SetLooping("NONE") @@ -477,16 +644,61 @@ local createPlayerBanner = function(parent, name) detailsFramework:CreateFlashAnimation(flashTexture) --flashTexture:Flash(0.1, 0.5, 0.01) + local rantingLabel = detailsFramework:CreateLabel(playerBanner, "", 16, "green") + rantingLabel:SetPoint("right", playerBanner, "left", -154, 0) + playerBanner.RantingLabel = rantingLabel + + local waitingForLootDotsAnimationLabel = detailsFramework:CreateLabel(playerBanner, "...", 20, "silver") --~dots + waitingForLootDotsAnimationLabel:SetDrawLayer("overlay", 6) + waitingForLootDotsAnimationLabel:SetAlpha(0.5) + waitingForLootDotsAnimationLabel:SetPoint("right", keyStoneDungeonTexture, "left", -12, 0) + waitingForLootDotsAnimationLabel:Hide() + playerBanner.WaitingForLootLabel = waitingForLootDotsAnimationLabel + + --make a text dot animation, which will show no dots at start and then "." then ".." then "..." and back to "" and so on + function playerBanner:StartTextDotAnimation() + --update the Waiting for Loot labels + local dotsString = self.WaitingForLootLabel + dotsString:Show() + + local dotsCount = 0 + local maxDots = 3 + local maxLoops = 200 + + local dotsTimer = C_Timer.NewTicker(0.5+RandomFloatInRange(-0.003, 0.003), function() + dotsCount = dotsCount + 1 + + if (dotsCount > maxDots) then + dotsCount = 0 + end + + local dotsText = "" + for i = 1, dotsCount do + dotsText = dotsText .. "." + end + + dotsString:SetText(dotsText) + end, maxLoops) + + dotsString.dotsTimer = dotsTimer + end + + function playerBanner:StopTextDotAnimation() + local dotsString = self.WaitingForLootLabel + dotsString:Hide() + if (dotsString.dotsTimer) then + dotsString.dotsTimer:Cancel() + end + end + playerBanner.LootSquares = {} - local lootSquareAmount = 2 - - for i = 1, lootSquareAmount do + for i = 1, playerBannerSettings.loot_square_amount do local lootSquare = createLootSquare(playerBanner, name, parent, i) if (i == 1) then - lootSquare:SetPoint("top", playerBanner, "bottom", 0, -90) + lootSquare:SetPoint("right", playerBanner, "left", -90, 0) else - lootSquare:SetPoint("top", playerBanner.LootSquares[i-1], "bottom", 0, -2) + lootSquare:SetPoint("right", playerBanner.LootSquares[i-1], "left", -2, 0) end playerBanner.LootSquares[i] = lootSquare playerBanner["lootSquare" .. i] = lootSquare @@ -514,6 +726,161 @@ local createPlayerBanner = function(parent, name) return playerBanner end +--changes the orientation of the player banners to horizontal or vertical, following the current settings +---@param readyFrame details_mplus_endframe +local setOrientation = function(readyFrame, mythicDungeonInfo, overallMythicDungeonCombat) + local settingsTable = Details.mythic_plus.finished_run_frame_options + local orientation = settingsTable.orientation + local growDirection = settingsTable.grow_direction + + readyFrame:SetFrameStrata("FULLSCREEN") + + ---@type details_instanceinfo + local instanceInfo = Details:GetInstanceInfo(mythicDungeonInfo.MapID) or Details:GetInstanceInfo(Details:GetCurrentCombat().mapId) + + if (orientation == "horizontal") then + readyFrame:SetSize(256, 430) + + if (growDirection == "left") then + --when the grow direction if to the left, the readyFrame is anchored to the right side of the ui parent + --header texture + readyFrame.HeaderTexture:ClearAllPoints() + readyFrame.HeaderTexture:SetPoint("topright", readyFrame, "topright", -7, -36) + readyFrame.HeaderTexture:SetTexCoord(257/512, 1, 234/512, 298/512) + readyFrame.HeaderTexture:SetSize(296, 64) + + readyFrame.AutoCloseTimeBar:SetSize(readyFrame.HeaderTexture:GetWidth(), 25) + readyFrame.AutoCloseTimeBar:ClearAllPoints() + readyFrame.AutoCloseTimeBar:SetPoint("topright", readyFrame.HeaderTexture, "topright", 0, -22) + readyFrame.AutoCloseTimeBar:SetTimer(40, true) + readyFrame.AutoCloseTimeBar:SetColor(1, 0.7, 0.0, 0.9) + readyFrame.AutoCloseTimeBar:SetDirection("left") + readyFrame.AutoCloseTimeBar:SetFrameLevel(readyFrame:GetFrameLevel()+1) + readyFrame.AutoCloseTimeBar:ShowSpark(false) + readyFrame.AutoCloseTimeBar:SetAlpha(0.7) + readyFrame.AutoCloseTimeBar:ShowTimer(false) + + local buttonSize = 14 + + readyFrame.ElapsedTimeIcon:ClearAllPoints() + readyFrame.OutOfCombatIcon:ClearAllPoints() + readyFrame.ElapsedTimeIcon:SetSize(buttonSize, buttonSize) + readyFrame.OutOfCombatIcon:SetSize(buttonSize, buttonSize) + readyFrame.ElapsedTimeIcon:SetPoint("topleft", readyFrame.HeaderTexture, "topleft", 51, -5) + readyFrame.OutOfCombatIcon:SetPoint("left", readyFrame.ElapsedTimeIcon, "right", 45, 0) + + readyFrame.ShowChartButton:ClearAllPoints() + PixelUtil.SetPoint(readyFrame.ShowChartButton, "right", readyFrame.ElapsedTimeIcon, "left", -3, 0) + PixelUtil.SetSize(readyFrame.ShowChartButton, 50, 32) + + readyFrame.SandTimeIcon:ClearAllPoints() + readyFrame.SandTimeIcon:SetSize(buttonSize, buttonSize) --original size is 32x60, need to adjust to the correct size + readyFrame.SandTimeIcon:SetPoint("left", readyFrame.OutOfCombatIcon, "right", 45, 0) + + readyFrame.StrongArmIcon:ClearAllPoints() + readyFrame.StrongArmIcon:SetSize(buttonSize, buttonSize) + readyFrame.StrongArmIcon:SetPoint("left", readyFrame.SandTimeIcon, "right", 18, 0) + + readyFrame.CloseButton:ClearAllPoints() + readyFrame.CloseButton:SetPoint("topright", readyFrame.HeaderTexture, "topright", -5, -5) + + readyFrame.ConfigButton:ClearAllPoints() + readyFrame.ConfigButton:SetPoint("right", readyFrame.CloseButton, "left", -3, 0) + readyFrame.ConfigButton.widget:GetNormalTexture():Show() + + local okay = pcall(function() + local objTracker = _G["ObjectiveTrackerFrame"] + if (objTracker) then + objTracker.Header.MinimizeButton:Click() + end + end) + if (not okay) then + Details:Msg("failed 0x8660") + end + + --widgets are anchored to the left side of the player banner and the player banner has its right side anchored to the right side of the readyFrame + for i = 1, #readyFrame.PlayerBanners do + --player banner + local playerBanner = readyFrame.PlayerBanners[i] + playerBanner:StartTextDotAnimation() + + playerBanner:ClearAllPoints() + if (i == 1) then + playerBanner:SetPoint("topright", readyFrame, "topright", -5, -i*playerBanner:GetHeight()) + else + playerBanner:SetPoint("topright", readyFrame.PlayerBanners[i-1], "bottomright", 0, -10) + end + + if (instanceInfo) then + playerBanner.DungeonBackdropTexture:SetTexture(instanceInfo.iconLore) + else + playerBanner.DungeonBackdropTexture:SetTexture(overallMythicDungeonCombat.is_mythic_dungeon.DungeonTexture) + end + + playerBanner.RantingLabel:ClearAllPoints() + playerBanner.RantingLabel:SetPoint("right", playerBanner, "left", -154, 0) + + --background texture + --playerBanner.BackgroundBannerTexture:ClearAllPoints() + --playerBanner.BackgroundBannerTexture:SetPoint("topright", playerBanner, "topleft", playerBanner:GetHeight()/2, 0) + --playerBanner.BackgroundBannerTexture:SetPoint("bottomright", playerBanner, "bottomleft", playerBanner:GetHeight()/2, 0) + --playerBanner.BackgroundBannerTexture:SetSize(playerBannerSettings.background_width, playerBannerSettings.background_height) + --playerBanner.BackgroundBannerTexture:SetTexCoord(256/512, 1, 0, 68/512) + + --dungeon texture is the small square icon showing a picture to identify the dungeon + playerBanner.KeyStoneDungeonTexture:ClearAllPoints() + playerBanner.KeyStoneDungeonTexture:SetPoint("right", playerBanner, "left", -8, 0) --right side attach to the left side of the player banner, growing to the left + playerBanner.KeyStoneDungeonTexture:SetSize(playerBannerSettings.dungeon_texture_width, playerBannerSettings.dungeon_texture_height) + + --loot squares + for j = 1, playerBannerSettings.loot_square_amount do + local lootSquare = playerBanner.LootSquares[j] + lootSquare:SetSize(playerBannerSettings.dungeon_texture_width, playerBannerSettings.dungeon_texture_height) + lootSquare:ClearAllPoints() + if (j == 1) then + lootSquare:SetPoint("right", playerBanner.KeyStoneDungeonTexture, "left", -2, 0) + else + lootSquare:SetPoint("right", playerBanner.LootSquares[j-1], "left", -2, 0) + end + end + + --role icon + playerBanner.RoleIcon:ClearAllPoints() + playerBanner.RoleIcon:SetPoint("center", playerBanner, "left", 4, 0) + playerBanner.RoleIcon:SetSize(18, 18) + playerBanner.RoleIcon:SetAlpha(0.834) + end + + elseif (growDirection == "right") then + --when the grow direction if to the right, the readyFrame is anchored to the left side of the ui parent + --widgets are anchored to the right side of the player banner and the player banner has its left side anchored to the left side of the readyFrame + for i = 1, #readyFrame.PlayerBanners do + + end + end + end +end + +local updateRatingLevel = function(playerBanner, unitId) + local ratingSummary = C_PlayerInfo.GetPlayerMythicPlusRatingSummary(unitId) + if (ratingSummary) then + local rating = ratingSummary.currentSeasonScore or 0 + local color = C_ChallengeMode.GetDungeonScoreRarityColor(rating) + if (not color) then + color = _G["HIGHLIGHT_FONT_COLOR"] + end + + local oldRatingLevel = Details.PlayerRatings[Details:GetFullName(unitId)] + local diff = 0 + if (oldRatingLevel) then + diff = rating - oldRatingLevel + end + + local s = "%s" + playerBanner.RantingLabel:SetText(s:format(color:WrapTextInColorCode(_G["CHALLENGE_COMPLETE_DUNGEON_SCORE_FORMAT_TEXT"]:format(rating, diff)))) + end +end + local updatPlayerBanner = function(unitId, bannerIndex) if (CONST_DEBUG_MODE) then --print("updating player banner for unit:", unitId, "bannerIndex:", bannerIndex) @@ -534,8 +901,15 @@ local updatPlayerBanner = function(unitId, bannerIndex) playerBanner.unitName = unitName playerBanner:Show() - playerBanner.FadeInAnimation:Play() - playerBanner.BackgroundBannerTextureScaleAnimation:Play() + --update the border to match the class color + local classColor = RAID_CLASS_COLORS[select(2, UnitClass(unitId))] or defaultColor + playerBanner.Border:SetVertexColor(classColor.r, classColor.g, classColor.b) + + playerBanner.BackgroundShowAnim:Play() + playerBanner.DungeonBackdropShowAnim:Play() + playerBanner.BackgroundGradientAnim:Play() + + playerBanner.FadeInAnimation:Play() --fade in the whole player banner playerBanner.BackgroundBannerFlashTextureColorAnimation:Play() playerBanner.BackgroundBannerTexture:SetFrameShakeSettings(playerBanner.BackgroundBannerTexture.BounceFrameShake, playerBanner.BackgroundBannerTexture:CreateRandomBounceSettings()) @@ -563,15 +937,21 @@ local updatPlayerBanner = function(unitId, bannerIndex) playerBanner.LevelFontString:SetText(playerKeystoneInfo.level or "") if (instanceInfo) then - playerBanner.DungeonTexture:SetTexture(instanceInfo.iconLore) + playerBanner.KeyStoneDungeonTexture:SetTexture(instanceInfo.iconLore) else - playerBanner.DungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) + playerBanner.KeyStoneDungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) end else - playerBanner.DungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) + playerBanner.KeyStoneDungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) playerBanner.LevelFontString:SetText("") end + updateRatingLevel(playerBanner, unitId) + + C_Timer.After(3, function() + updateRatingLevel(playerBanner, unitId) + end) + lootFrame.UpdateUnitLoot(playerBanner) return true end @@ -580,6 +960,7 @@ end local updateKeysStoneLevel = function() --update the player banners local libOpenRaid = LibStub("LibOpenRaid-1.0", true) + ---@type details_mplus_endframe local readyFrame = DetailsMythicDungeonReadyFrame for bannerIndex = 1, #readyFrame.PlayerBanners do @@ -593,7 +974,7 @@ local updateKeysStoneLevel = function() --if (instanceInfo) then -- ---@type details_instanceinfo -- local thisInstanceInfo = Details:GetInstanceInfo(unitKeystoneInfo.mapID) - -- unitBanner.DungeonTexture:SetTexture(thisInstanceInfo.iconLore) + -- unitBanner.KeyStoneDungeonTexture:SetTexture(thisInstanceInfo.iconLore) --end --unitBanner.LevelFontString:SetText(unitKeystoneInfo.level) @@ -611,9 +992,9 @@ local updateKeysStoneLevel = function() local instanceInfo = Details:GetInstanceInfo(unitKeystoneInfo.mapID) if (instanceInfo) then - unitBanner.DungeonTexture:SetTexture(instanceInfo.iconLore) + unitBanner.KeyStoneDungeonTexture:SetTexture(instanceInfo.iconLore) else - unitBanner.DungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) + unitBanner.KeyStoneDungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) end --this character had its keystone upgraded @@ -625,7 +1006,7 @@ local updateKeysStoneLevel = function() --print("keystone level updated for", Details:GetFullName(unitId), unitKeystoneInfo.level) else - unitBanner.DungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) + unitBanner.KeyStoneDungeonTexture:SetTexture([[Interface\ICONS\INV_Misc_QuestionMark]]) unitBanner.LevelFontString:SetText("") end end @@ -660,6 +1041,7 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() local textColor = {1, 0.8196, 0, 1} local textSize = 11 + ---@type details_mplus_endframe mythicDungeonFrames.ReadyFrame = CreateFrame("frame", "DetailsMythicDungeonReadyFrame", UIParent, "BackdropTemplate") local readyFrame = mythicDungeonFrames.ReadyFrame readyFrame:SetSize(355, 390) @@ -686,11 +1068,75 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() color = {.1, .1, .1, 0.5}, --border_color = {.05, .05, .05, 0.834}, } - detailsFramework:AddRoundedCornersToFrame(readyFrame, roundedCornerTemplate) + --detailsFramework:AddRoundedCornersToFrame(readyFrame, roundedCornerTemplate) end readyFrame.entryAnimationDuration = 0.1 + local headerTexture = readyFrame:CreateTexture("$parentHeaderTexture", "artwork", nil, 1) + headerTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + headerTexture:SetTexCoord(320/512, 498/512, 161/512, 192/512) + headerTexture:SetSize(178, 31) + headerTexture:SetVertexColor(0.251, 0.251, 0.251, 0.823) + readyFrame.HeaderTexture = headerTexture + + local headerFadeInAnimation = detailsFramework:CreateAnimationHub(headerTexture, function()headerTexture:SetAlpha(0)end, function()headerTexture:SetAlpha(0.823)end) + local headerAnimFadeIn = detailsFramework:CreateAnimation(headerFadeInAnimation, "Alpha", 1, 0.3, 0, 1) + headerAnimFadeIn:SetStartDelay(0.8) + + readyFrame.HeaderFadeInAnimation = headerFadeInAnimation + + --clock texture and icon to show the total time elapsed + local elapsedTimeIcon = readyFrame:CreateTexture("$parentClockIcon", "artwork", nil, 2) + elapsedTimeIcon:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + elapsedTimeIcon:SetTexCoord(172/512, 235/512, 84/512, 147/512) + readyFrame.ElapsedTimeIcon = elapsedTimeIcon + + local elapsedTimeText = readyFrame:CreateFontString("$parentClockText", "artwork", "GameFontNormal") + elapsedTimeText:SetTextColor(1, 1, 1) + detailsFramework:SetFontSize(elapsedTimeText, 11) + elapsedTimeText:SetText("00:00") + elapsedTimeText:SetPoint("left", elapsedTimeIcon, "right", 3, 0) + readyFrame.ElapsedTimeText = elapsedTimeText + + --another clock texture and icon to show the wasted time (time out of combat) + local outOfCombatIcon = readyFrame:CreateTexture("$parentClockIcon2", "artwork", nil, 2) + outOfCombatIcon:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + outOfCombatIcon:SetTexCoord(172/512, 235/512, 84/512, 147/512) + outOfCombatIcon:SetVertexColor(detailsFramework:ParseColors("orangered")) + readyFrame.OutOfCombatIcon = outOfCombatIcon + + local outOfCombatText = readyFrame:CreateFontString("$parentClockText2", "artwork", "GameFontNormal") + outOfCombatText:SetTextColor(1, 1, 1) + detailsFramework:SetFontSize(outOfCombatText, 11) + detailsFramework:SetFontColor(outOfCombatText, "orangered") + outOfCombatText:SetText("00:00") + outOfCombatText:SetPoint("left", outOfCombatIcon, "right", 3, 0) + readyFrame.OutOfCombatText = outOfCombatText + + --create the sandtime icon and a text to show the keystone level + local sandTimeIcon = readyFrame:CreateTexture("$parentSandTimeIcon", "artwork", nil, 2) + sandTimeIcon:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + sandTimeIcon:SetTexCoord(81/512, 137/512, 83/512, 143/512) + readyFrame.SandTimeIcon = sandTimeIcon + + local sandTimeText = readyFrame:CreateFontString("$parentSandTimeText", "artwork", "GameFontNormal") + sandTimeText:SetTextColor(1, 1, 1) + detailsFramework:SetFontSize(sandTimeText, 11) + sandTimeText:SetText("0") + sandTimeText:SetPoint("left", sandTimeIcon, "right", 1, 0) + readyFrame.KeylevelText = sandTimeText + + --create a strong arm texture and a text to show the ranting of the player + local strongArmIcon = readyFrame:CreateTexture("$parentStrongArmIcon", "artwork", nil, 2) + strongArmIcon:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + strongArmIcon:SetTexCoord(84/512, 145/512, 151/512, 215/512) + readyFrame.StrongArmIcon = strongArmIcon + + local rantingLabel = detailsFramework:CreateLabel(readyFrame, "", textSize, textColor) + rantingLabel:SetPoint("left", strongArmIcon, "right", 3, 0) + readyFrame.RantingLabel = rantingLabel + --this frame is required due to the animation, the readyFrame and the contentFrame has their own animations mythicDungeonFrames.ReadyFrameTop = CreateFrame("frame", "DetailsMythicDungeonReadyTopFrame", UIParent, "BackdropTemplate") mythicDungeonFrames.ReadyFrameTop:SetPoint("bottomleft", readyFrame, "topleft", 0, 0) @@ -699,36 +1145,10 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() readyFrame.TopFrame = mythicDungeonFrames.ReadyFrameTop local openingAnimationHub = detailsFramework:CreateAnimationHub(readyFrame, function() end, function() readyFrame:SetWidth(355); end) - detailsFramework:CreateAnimation(openingAnimationHub, "Scale", 1, readyFrame.entryAnimationDuration, 0, 1, 1, 1, "center", 0, 0) readyFrame.OpeningAnimation = openingAnimationHub + detailsFramework:CreateAnimation(openingAnimationHub, "Scale", 1, readyFrame.entryAnimationDuration, 0, 1, 1, 1, "center", 0, 0) - do --backdrop textures - --backdrop gradient from bottom to top - ---@type df_gradienttable - local gradientTable = {gradient = "vertical", fromColor = {0, 0, 0, 0.8}, toColor = "transparent"} - local gradientBelowTheLine = detailsFramework:CreateTexture(readyFrame, gradientTable, 1, readyFrame:GetHeight()/3, "artwork", {0, 1, 0, 1}, "backgroundGradient") - gradientBelowTheLine:SetPoint("bottoms", 0, 0) - local dungeonBackdropTexture = readyFrame:CreateTexture("$parentDungeonBackdropTexture", "artwork", nil, -2) - dungeonBackdropTexture:SetTexCoord(0.05, 0.70, 0.1, 0.82) - dungeonBackdropTexture:SetVertexColor(0.2, 0.2, 0.2, 0.8) - dungeonBackdropTexture:SetDesaturated(true) - dungeonBackdropTexture:SetAlpha(0.834) - dungeonBackdropTexture:SetAllPoints() - readyFrame.DungeonBackdropTexture = dungeonBackdropTexture - - local anotherBackdropTexture = readyFrame:CreateTexture("$parentAnotherBackdropTexture", "artwork", nil, -3) - anotherBackdropTexture:SetTexture([[Interface\GLUES\Models\UI_HighmountainTauren\7HM_RapidSimpleMask]]) - anotherBackdropTexture:SetAllPoints() - anotherBackdropTexture:SetVertexColor(0.467, 0.416, 0.639, 1) - readyFrame.AnotherBackdropTexture = anotherBackdropTexture - end - - --frame to place all texture that goes behind the readyFrame - local backgroundFrame = CreateFrame("frame", "DetailsMythicDungeonBackgroundFrame", readyFrame) - backgroundFrame:SetAllPoints() - backgroundFrame:SetFrameLevel(readyFrame:GetFrameLevel()-1) - readyFrame.BackgroundFrame = backgroundFrame --frame to place all texture that goes in front of the readyFrame, doing this, we call fade in this frame making all texts gently show up local contentFrame = CreateFrame("frame", "$parentContentFrame", readyFrame) @@ -746,92 +1166,109 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() spikes:SetAtlas("ChallengeMode-SpikeyStar") spikes:SetAlpha(1) readyFrame.YellowSpikeCircle = spikes + spikes:Hide() local yellowFlash = mythicDungeonFrames.ReadyFrameTop:CreateTexture("$parentYellowFlash", "artwork") yellowFlash:SetSize(120, 120) yellowFlash:SetPoint("center", readyFrame, "top", 0, 30) - yellowFlash:SetAtlas("BossBanner-RedFlash") + --yellowFlash:SetAtlas("BossBanner-RedFlash") yellowFlash:SetAlpha(0) yellowFlash:SetBlendMode("ADD") readyFrame.YellowFlash = yellowFlash - readyFrame.Level = mythicDungeonFrames.ReadyFrameTop:CreateFontString("$parentLevelText", "overlay", "SystemFont_OutlineThick_WTF") - readyFrame.Level:SetPoint("center", readyFrame.YellowSpikeCircle, "center", 0, 0) - readyFrame.Level:SetText("") + readyFrame.Level = mythicDungeonFrames.ReadyFrameTop:CreateFontString("$parentLevelText", "overlay", "GameFontNormalWTF2Outline") + --readyFrame.Level:SetPoint("center", readyFrame.YellowSpikeCircle, "center", 0, 0) + --readyFrame.Level:SetText("") --create the animation for the yellow flash local flashAnimHub = detailsFramework:CreateAnimationHub(yellowFlash, function() yellowFlash:SetAlpha(0) end, function() yellowFlash:SetAlpha(0) end) local flashAnim1 = detailsFramework:CreateAnimation(flashAnimHub, "Alpha", 1, 0.5, 0, 1) local flashAnim2 = detailsFramework:CreateAnimation(flashAnimHub, "Alpha", 2, 0.5, 1, 0) - readyFrame.YellowSpikeCircle.OnShowAnimation = flashAnimHub + --create the animation for the yellow spike circle + local spikeCircleAnimHub = detailsFramework:CreateAnimationHub(spikes, function() spikes:SetAlpha(0); spikes:SetScale(1) end, function() flashAnimHub:Play(); spikes:SetSize(100, 100); spikes:SetScale(1); spikes:SetAlpha(1) end) + local alphaAnim1 = detailsFramework:CreateAnimation(spikeCircleAnimHub, "Alpha", 1, 0.2960000038147, 0, 1) + local scaleAnim1 = detailsFramework:CreateAnimation(spikeCircleAnimHub, "Scale", 1, 0.21599999070168, 5, 5, 1, 1, "center", 0, 0) + --readyFrame.YellowSpikeCircle.OnShowAnimation = spikeCircleAnimHub end - readyFrame.leftFiligree = contentFrame:CreateTexture("$parentLeftFiligree", "artwork") - readyFrame.leftFiligree:SetAtlas("BossBanner-LeftFillagree") - readyFrame.leftFiligree:SetSize(72, 43) - readyFrame.leftFiligree:SetPoint("bottom", readyFrame, "top", -50, 2) + do + readyFrame.leftFiligree = contentFrame:CreateTexture("$parentLeftFiligree", "artwork") + readyFrame.leftFiligree:SetAtlas("BossBanner-LeftFillagree") + readyFrame.leftFiligree:SetSize(72, 43) + readyFrame.leftFiligree:SetPoint("bottom", readyFrame, "top", -50, 2) + readyFrame.leftFiligree:Hide() - readyFrame.rightFiligree = contentFrame:CreateTexture("$parentRightFiligree", "artwork") - readyFrame.rightFiligree:SetAtlas("BossBanner-RightFillagree") - readyFrame.rightFiligree:SetSize(72, 43) - readyFrame.rightFiligree:SetPoint("bottom", readyFrame, "top", 50, 2) + readyFrame.rightFiligree = contentFrame:CreateTexture("$parentRightFiligree", "artwork") + readyFrame.rightFiligree:SetAtlas("BossBanner-RightFillagree") + readyFrame.rightFiligree:SetSize(72, 43) + readyFrame.rightFiligree:SetPoint("bottom", readyFrame, "top", 50, 2) + readyFrame.rightFiligree:Hide() - --create the bottom filligree using BossBanner-BottomFillagree atlas - readyFrame.bottomFiligree = contentFrame:CreateTexture("$parentBottomFiligree", "artwork") - readyFrame.bottomFiligree:SetAtlas("BossBanner-BottomFillagree") - readyFrame.bottomFiligree:SetSize(66, 28) - readyFrame.bottomFiligree:SetPoint("bottom", readyFrame, "bottom", 0, -19) + --create the bottom filligree using BossBanner-BottomFillagree atlas + readyFrame.bottomFiligree = contentFrame:CreateTexture("$parentBottomFiligree", "artwork") + readyFrame.bottomFiligree:SetAtlas("BossBanner-BottomFillagree") + readyFrame.bottomFiligree:SetSize(66, 28) + readyFrame.bottomFiligree:SetPoint("bottom", readyFrame, "bottom", 0, -19) + readyFrame.bottomFiligree:Hide() + end local titleLabel = detailsFramework:CreateLabel(contentFrame, "Details! Mythic Run Completed!", 12, "yellow") titleLabel:SetPoint("top", readyFrame, "top", 0, -7) + titleLabel:Hide() titleLabel.textcolor = textColor ---@type df_closebutton local closeButton = detailsFramework:CreateCloseButton(contentFrame, "$parentCloseButton") closeButton:SetPoint("topright", readyFrame, "topright", -2, -2) - closeButton:SetScale(1.4) + closeButton:SetScale(1.0) closeButton:SetAlpha(0.823) closeButton:SetScript("OnClick", function(self) readyFrame:Hide() end) + readyFrame.CloseButton = closeButton - --warning footer - local warningFooter = detailsFramework:CreateLabel(contentFrame, "Under development", 9, "orange") - warningFooter:SetPoint("bottomright", readyFrame, "bottomright", -5, 5) - warningFooter:SetAlpha(0.5) + local configButtonOnClick = function() + Details:OpenOptionsWindow(Details:GetInstance(1), false, 18) + end + readyFrame.ConfigButton = detailsFramework:CreateButton(contentFrame, configButtonOnClick, 32, 32, "") + readyFrame.ConfigButton:SetAlpha(0.823) + readyFrame.ConfigButton:SetSize(closeButton:GetSize()) - ---@type texture - local topRedLineTexture = backgroundFrame:CreateTexture("$parentBannerTop", "border") - topRedLineTexture:SetAtlas("BossBanner-BgBanner-Top") - topRedLineTexture:SetPoint("top", backgroundFrame, "top", 0, 34) - local topTextureAnimGroup = detailsFramework:CreateAnimationHub(topRedLineTexture, function()end, function() topRedLineTexture:SetSize(388, 112) end) - topRedLineTexture.Animation = topTextureAnimGroup - local animDuration = 0.3 - detailsFramework:CreateAnimation(topTextureAnimGroup, "Scale", 1, animDuration, 0, 1, 1, 1, "center", 0, 0) - readyFrame.TopRedLineTexture = topRedLineTexture + local normalTexture = readyFrame.ConfigButton:CreateTexture(nil, "overlay") + normalTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + normalTexture:SetTexCoord(79/512, 113/512, 0/512, 36/512) + normalTexture:SetDesaturated(true) - local bottomRedLineTexture = backgroundFrame:CreateTexture("$parentBannerBottom", "border") - bottomRedLineTexture:SetAtlas("BossBanner-BgBanner-Bottom") - bottomRedLineTexture:SetPoint("bottom", backgroundFrame, "bottom", 0, -25) - local bottomTextureAnimGroup = detailsFramework:CreateAnimationHub(bottomRedLineTexture, function()end, function() bottomRedLineTexture:SetSize(388, 112) end) - bottomRedLineTexture.Animation = bottomTextureAnimGroup - detailsFramework:CreateAnimation(bottomTextureAnimGroup, "Scale", 1, animDuration, 0, 1, 0.5, 1, "center", 0, 0) - readyFrame.BottomRedLineTexture = bottomRedLineTexture + local pushedTexture = readyFrame.ConfigButton:CreateTexture(nil, "overlay") + pushedTexture:SetTexture([[Interface\AddOns\Details\images\end_of_mplus]], nil, nil, "TRILINEAR") + pushedTexture:SetTexCoord(114/512, 148/512, 0/512, 36/512) + pushedTexture:SetDesaturated(true) - --local leftRedLineTexture = backgroundFrame:CreateTexture("$parentBannerLeft", "border") - --leftRedLineTexture:SetAtlas("BossBanner-BgBanner-Top") - --leftRedLineTexture:SetPoint("topleft", backgroundFrame, "topleft", 0, 0) - --leftRedLineTexture:SetPoint("bottomleft", backgroundFrame, "bottomleft", 0, 0) - --leftRedLineTexture:SetWidth(388) - --leftRedLineTexture:SetRotation(-1.5708) + local highlightTexture = readyFrame.ConfigButton:CreateTexture(nil, "highlight") + highlightTexture:SetTexture([[Interface\BUTTONS\redbutton2x]], nil, nil, "TRILINEAR") + highlightTexture:SetTexCoord(116/256, 150/256, 0, 39/128) + highlightTexture:SetDesaturated(true) - --local centerGradient = backgroundFrame:CreateTexture("$parentCenterGradient", "artwork") - --centerGradient:SetAtlas("BossBanner-BgBanner-Mid") - --centerGradient:SetPoint("center", backgroundFrame, "center", 0, 0) - --centerGradient:SetSize(355, 390) + readyFrame.ConfigButton:SetTexture(normalTexture, highlightTexture, pushedTexture, normalTexture) + + --waiting for loot label + local waitingForLootLabel = detailsFramework:CreateLabel(contentFrame, "Waiting for loot", 12, "silver") + waitingForLootLabel:SetPoint("bottom", readyFrame, "bottom", 0, 54) + waitingForLootLabel:Hide() + + --auto close time bar + local autoCloseTimeBar = detailsFramework:CreateTimeBar(contentFrame, [[Interface\AddOns\Details\images\bar_serenity]]) + autoCloseTimeBar:SetHook("OnTimerEnd", function() + readyFrame:Hide() + end) + readyFrame.AutoCloseTimeBar = autoCloseTimeBar readyFrame:SetScript("OnHide", function(self) + --hide the dotString on all player banners + for i = 1, #readyFrame.PlayerBanners do + readyFrame.PlayerBanners[i]:StopTextDotAnimation() + end mythicDungeonFrames.ReadyFrameTop:Hide() end) @@ -847,6 +1284,7 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() local showBreakdownFunc = function() mPlus.ShowSummary() end + ---@type df_button readyFrame.ShowBreakdownButton = detailsFramework:CreateButton(contentFrame, showBreakdownFunc, 145, 30, "Show Breakdown") PixelUtil.SetPoint(readyFrame.ShowBreakdownButton, "topleft", readyFrame, "topleft", 31, -30) @@ -857,12 +1295,14 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() detailsFramework:AddRoundedCornersToFrame(readyFrame.ShowBreakdownButton.widget, roundedCornerPreset) leftAnchor = readyFrame.ShowBreakdownButton readyFrame.ShowBreakdownButton:Disable() + readyFrame.ShowBreakdownButton:Hide() --show graphic button local showChartFunc = function(self) mythicDungeonCharts.ShowChart() readyFrame:Hide() end + ---@type df_button readyFrame.ShowChartButton = detailsFramework:CreateButton(contentFrame, showChartFunc, 145, 30, "Show Damage Graphic") PixelUtil.SetPoint(readyFrame.ShowChartButton, "left", readyFrame.ShowBreakdownButton, "right", 6, 0) @@ -870,82 +1310,27 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() readyFrame.ShowChartButton:SetBackdrop(nil) readyFrame.ShowChartButton:SetIcon([[Interface\AddOns\Details\images\icons2]], 16, 16, "overlay", {42/512, 75/512, 153/512, 187/512}, {.7, .7, .7, 1}, nil, 0, 0) readyFrame.ShowChartButton.textcolor = textColor - detailsFramework:AddRoundedCornersToFrame(readyFrame.ShowChartButton.widget, roundedCornerPreset) - - --disable feature check box (dont show this again) - local on_switch_enable = function(self, _, value) - Details.mythic_plus.show_damage_graphic = not value - end + --detailsFramework:AddRoundedCornersToFrame(readyFrame.ShowChartButton.widget, roundedCornerPreset) local elapsedTimeLabel = detailsFramework:CreateLabel(contentFrame, "Run Time:", textSize, textColor) - --elapsedTimeLabel:SetPoint("topleft", leftAnchor, "bottomleft", 0, -8) elapsedTimeLabel:SetPoint("topleft", readyFrame, "topleft", 5, -70) local elapsedTimeAmount = detailsFramework:CreateLabel(contentFrame, "00:00", textSize, textColor) elapsedTimeAmount:SetPoint("left", elapsedTimeLabel, "left", 130, 0) + elapsedTimeLabel:Hide() + elapsedTimeAmount:Hide() local timeNotInCombatLabel = detailsFramework:CreateLabel(contentFrame, "Time not in combat:", textSize, "orangered") timeNotInCombatLabel:SetPoint("topleft", elapsedTimeLabel, "bottomleft", 0, -5) local timeNotInCombatAmount = detailsFramework:CreateLabel(contentFrame, "00:00", textSize, "orangered") timeNotInCombatAmount:SetPoint("left", timeNotInCombatLabel, "left", 130, 0) - - local youBeatTheTimerLabel = detailsFramework:CreateLabel(contentFrame, "", textSize, "white") - youBeatTheTimerLabel:SetPoint("topleft", timeNotInCombatLabel, "bottomleft", 0, -5) - - --local keystoneUpgradeLabel = detailsFramework:CreateLabel(readyFrame, "Keystone Upgrade:", textSize, "white") - --keystoneUpgradeLabel:SetPoint("topleft", youBeatTheTimerLabel, "bottomleft", 0, -5) - - local rantingLabel = detailsFramework:CreateLabel(contentFrame, "", textSize, textColor) - --rantingLabel:SetPoint("topleft", keystoneUpgradeLabel, "bottomleft", 0, -5) - rantingLabel:SetPoint("topleft", youBeatTheTimerLabel, "bottomleft", 0, -5) + timeNotInCombatLabel:Hide() + timeNotInCombatAmount:Hide() readyFrame.PlayerBanners = {} for i = 1, 5 do - local playerBanner = createPlayerBanner(readyFrame, "$parentPlayerBanner" .. i) + local playerBanner = createPlayerBanner(readyFrame, "$parentPlayerBanner" .. i, i) readyFrame.PlayerBanners[#readyFrame.PlayerBanners+1] = playerBanner - if (i == 1) then - playerBanner:SetPoint("topleft", rantingLabel.widget, "bottomleft", 0, -22) - else - playerBanner:SetPoint("topleft", readyFrame.PlayerBanners[i-1], "topright", 10, 0) - end end - - --here was the loot frame events ~loot - - --[=[ - Details222.MythicPlus.MapID = mapID - Details222.MythicPlus.Level = level --level of the key just finished - Details222.MythicPlus.OnTime = onTime - Details222.MythicPlus.KeystoneUpgradeLevels = keystoneUpgradeLevels - Details222.MythicPlus.PracticeRun = practiceRun - Details222.MythicPlus.OldDungeonScore = oldDungeonScore - Details222.MythicPlus.NewDungeonScore = newDungeonScore - Details222.MythicPlus.IsAffixRecord = isAffixRecord - Details222.MythicPlus.IsMapRecord = isMapRecord - Details222.MythicPlus.PrimaryAffix = primaryAffix - Details222.MythicPlus.IsEligibleForScore = isEligibleForScore - Details222.MythicPlus.UpgradeMembers = upgradeMembers - Details222.MythicPlus.DungeonName = dungeonName - Details222.MythicPlus.DungeonID = id - Details222.MythicPlus.TimeLimit = timeLimit - Details222.MythicPlus.Texture = texture - Details222.MythicPlus.BackgroundTexture = backgroundTexture - --]=] - - local notAgainSwitch, notAgainLabel = detailsFramework:CreateSwitch(contentFrame, on_switch_enable, not Details.mythic_plus.show_damage_graphic, _, _, _, _, _, _, _, _, _, Loc ["STRING_MINITUTORIAL_BOOKMARK4"], detailsFramework:GetTemplate("switch", "OPTIONS_CHECKBOX_BRIGHT_TEMPLATE"), "GameFontHighlightLeft") - notAgainLabel.textcolor = "orange" - notAgainSwitch:ClearAllPoints() - notAgainLabel:SetPoint("left", notAgainSwitch, "right", 2, 0) - notAgainSwitch:SetPoint("bottomleft", readyFrame, "bottomleft", 5, 5) - notAgainSwitch:SetAsCheckBox() - notAgainSwitch:SetSize(12, 12) - notAgainSwitch:SetAlpha(0.5) - notAgainLabel.textsize = 9 - - readyFrame.TimeNotInCombatAmountLabel = timeNotInCombatAmount - readyFrame.ElapsedTimeAmountLabel = elapsedTimeAmount - readyFrame.YouBeatTheTimerLabel = youBeatTheTimerLabel - readyFrame.KeystoneUpgradeLabel = keystoneUpgradeLabel - readyFrame.RantingLabel = rantingLabel end --end of creating of the readyFrame --< end of mythic+ end of run frame creation >-- @@ -955,12 +1340,12 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() readyFrame:Show() readyFrame.TopFrame:Show() - readyFrame.YellowSpikeCircle.OnShowAnimation:Play() + --readyFrame.YellowSpikeCircle.OnShowAnimation:Play() - readyFrame.TopRedLineTexture:Hide() - readyFrame.BottomRedLineTexture:Hide() + readyFrame.ContentFrame:SetAlpha(0) - readyFrame.Level:SetText(Details222.MythicPlus.Level or "") + --readyFrame.Level:SetText(Details222.MythicPlus.Level or "") + readyFrame.KeylevelText:SetText(Details222.MythicPlus.Level or "") --hide the lootSquare for i = 1, #readyFrame.PlayerBanners do @@ -976,16 +1361,15 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() end) C_Timer.After(readyFrame.entryAnimationDuration+0.05, function() - readyFrame.TopRedLineTexture:Show() - readyFrame.BottomRedLineTexture:Show() - readyFrame.TopRedLineTexture.Animation:Play() - readyFrame.BottomRedLineTexture.Animation:Play() - C_Timer.After(0.3, function() readyFrame.ContentFrameFadeInAnimation:Play() end) end) + --readyFrame.HeaderFadeInAnimation + readyFrame.HeaderTexture:SetAlpha(0) + readyFrame.HeaderFadeInAnimation:Play() + --fin the overall mythic dungeon combat, starting with the current combat ---@type combat local overallMythicDungeonCombat = Details:GetCurrentCombat() @@ -1005,14 +1389,14 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() --update the run time and time not in combat local elapsedTime = Details222.MythicPlus.time or 1507 - readyFrame.ElapsedTimeAmountLabel.text = detailsFramework:IntegerToTimer(elapsedTime) + readyFrame.ElapsedTimeText:SetText(detailsFramework:IntegerToTimer(elapsedTime)) if (overallMythicDungeonCombat:GetCombatType() == DETAILS_SEGMENTTYPE_MYTHICDUNGEON_OVERALL) then local combatTime = overallMythicDungeonCombat:GetCombatTime() local notInCombat = elapsedTime - combatTime - readyFrame.TimeNotInCombatAmountLabel.text = detailsFramework:IntegerToTimer(notInCombat) .. " (" .. math.floor(notInCombat / elapsedTime * 100) .. "%)" + readyFrame.OutOfCombatText:SetText(detailsFramework:IntegerToTimer(notInCombat)) else - readyFrame.TimeNotInCombatAmountLabel.text = "Unknown for this run" + readyFrame.OutOfCombatText:SetText("00:00") end local mythicDungeonInfo = overallMythicDungeonCombat:GetMythicDungeonInfo() @@ -1021,39 +1405,40 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() return end - ---@type details_instanceinfo - local instanceInfo = Details:GetInstanceInfo(mythicDungeonInfo.MapID) or Details:GetInstanceInfo(Details:GetCurrentCombat().mapId) - - if (instanceInfo) then - readyFrame.DungeonBackdropTexture:SetTexture(instanceInfo.iconLore) - else - readyFrame.DungeonBackdropTexture:SetTexture(overallMythicDungeonCombat.is_mythic_dungeon.DungeonTexture) - end + setOrientation(readyFrame, mythicDungeonInfo, overallMythicDungeonCombat) wipe(readyFrame.unitCacheByName) if (Details222.MythicPlus.OnTime) then - readyFrame.YouBeatTheTimerLabel:SetFormattedText(MYTHIC_PLUS_COMPLETE_BEAT_TIMER .. " | " .. MYTHIC_PLUS_COMPLETE_KEYSTONE_UPGRADED, Details222.MythicPlus.KeystoneUpgradeLevels) --"You beat the timer!" - readyFrame.YouBeatTheTimerLabel.textcolor = "limegreen" - --readyFrame.KeystoneUpgradeLabel:SetFormattedText(CHALLENGE_MODE_COMPLETE_KEYSTONE_UPGRADED, Details222.MythicPlus.KeystoneUpgradeLevels) + --beat the timer PlaySound(SOUNDKIT.UI_CHALLENGEMODE_NEWRECORD) C_Timer.After(0.020, function() --PlaySoundFile([[Interface\AddOns\Details\sounds\bassdrop2.mp3]]) end) else readyFrame.YouBeatTheTimerLabel.textcolor = "white" - readyFrame.YouBeatTheTimerLabel.text = MYTHIC_PLUS_COMPLETE_TIME_EXPIRED --"Time expired!" - --readyFrame.KeystoneUpgradeLabel.text = MYTHIC_PLUS_COMPLETE_TRY_AGAIN --"Try again! Beat the timer to upgrade your keystone!" + readyFrame.YouBeatTheTimerLabel.text = CHALLENGE_MODE_COMPLETE_TIME_EXPIRED --"Time expired!" + --readyFrame.KeystoneUpgradeLabel.text = CHALLENGE_MODE_COMPLETE_TRY_AGAIN --"Try again! Beat the timer to upgrade your keystone!" PlaySound(SOUNDKIT.UI_CHALLENGEMODE_WARNING) end - readyFrame.RantingLabel.text = "" + if (Details222.MythicPlus.NewDungeonScore and Details222.MythicPlus.OldDungeonScore) then + local gainedScore = Details222.MythicPlus.NewDungeonScore - Details222.MythicPlus.OldDungeonScore + local color = C_ChallengeMode.GetDungeonScoreRarityColor(Details222.MythicPlus.NewDungeonScore) + if (not color) then + color = HIGHLIGHT_FONT_COLOR + end + readyFrame.RantingLabel.text = CHALLENGE_COMPLETE_DUNGEON_SCORE:format(color:WrapTextInColorCode(CHALLENGE_COMPLETE_DUNGEON_SCORE_FORMAT_TEXT:format(Details222.MythicPlus.NewDungeonScore, gainedScore))) + readyFrame.RantingLabel.textcolor = "limegreen" + else + readyFrame.RantingLabel.text = "" + end C_Timer.After(0.6, function() local playersFound = 0 local playerBannerIndex = 1 do --update the player banner - C_Timer.After(RandomFloatInRange(0.1, 0.15), function() + C_Timer.After(0.1, function() if (updatPlayerBanner("player", playerBannerIndex)) then playersFound = playersFound + 1 end @@ -1061,13 +1446,15 @@ function mythicDungeonFrames.ShowEndOfMythicPlusPanel() end local unitCount = 1 + local delay = 0.3 for bannerIndex = 2, #readyFrame.PlayerBanners do - C_Timer.After(RandomFloatInRange(bannerIndex/5-0.075, bannerIndex/5+0.075), function() + C_Timer.After(delay, function() --RandomFloatInRange(bannerIndex/5-0.075, bannerIndex/5+0.075) if (updatPlayerBanner("party"..unitCount, bannerIndex)) then playersFound = playersFound + 1 end unitCount = unitCount + 1 end) + delay = delay + 0.3 end end) diff --git a/functions/profiles.lua b/functions/profiles.lua index 3c3a0c99..feeaff78 100644 --- a/functions/profiles.lua +++ b/functions/profiles.lua @@ -1450,7 +1450,11 @@ local default_global_data = { last_mythicrun_chart = {}, mythicrun_chart_frame = {}, mythicrun_chart_frame_minimized = {}, - finished_run_frame = {}, --end of mythic+ panel + finished_run_frame = {}, + finished_run_frame_options = { + orientation = "horizontal", + grow_direction = "left", + }, mythicrun_time_type = 1, --1: combat time (the amount of time the player is in combat) 2: run time (the amount of time it took to finish the mythic+ run) }, --implementar esse time_type quando estiver dando refresh na janela diff --git a/images/end_of_mplus.png b/images/end_of_mplus.png new file mode 100644 index 0000000000000000000000000000000000000000..a71554d04e117208eb3600a7a446600a6d154847 GIT binary patch literal 58760 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 QCb$c24gHs;YUW@62PnKVkN^Mx literal 0 HcmV?d00001 diff --git a/images/end_of_mplus_banner_mask.png b/images/end_of_mplus_banner_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f49504ea6c32800b0737816b024aa8bd8875d5 GIT binary patch literal 3249 zcma)9dpMM78=o2JfJ97oqfCP>S!T>QWHJgv4h@r>Mys04JG_{~&J1RxjP}xUwIwV$ zlx$gtMGligIn9>RwnQuvLZyRaC{zyLJEK*7eY@B9$2-^iJip(4-@oU6?)!ePiKV#g zS*x&70S1Gub#x%P!C-Lc6}EfzO6d2EZ(c17Ca(^ek$ zX1Pniq4@;?eAE%ZA7l~H<5hKN6i6qcy-dkiGRGDO03E`)!2WO-cfar;KMOi~mz9F0 zfB-pQ0(=@uzzk;b2m&Hn=9d7yOSds-l#Id;BBH&?6qGHS3!qGmOpUN;D+QD#m(C!# zk?cPhLn|UWfY0X;Fqp8gFrzTM5u59eF}AR8v0@!>uFM$0I zCd=>_hJXP4c!tlyNRcn-e30=EbSd%$9m+MC{O_k=GCw;Ak8c+WCE`O;KNIx#xq0qF z4uEk3c>u=qN%TtLwXwp@V52T0IEMB}hH6GQ9{L#(+w4o|?D5lo?XJQll5 zCbQ`vL-@bQCOCpA9wHMyWkcmbr}1h34}Uq~1Sxw-6jYl3s_3JQEjyUag~UN;z{|vb zgdA;cDO@%K42CeC+a4Q~qn)jpv4xqLA)e& znx8a25zXPU=^=gqT_yo?w}kT841O4m3t0O@HAF;PGZ-MGREVNTy9tFu;h?@>x`1Vq zumC`s&Ce?9(_+DVkcN?V_21eoF<+>+lo37|1CZU)9yDH{IfYpsRM291)B!B0h`7)o z6KAdKjuT^5~54sPLP%b-Ni9DmlRauPwQ!Ctxf`)v(k@;)9s??9*xg^?WyaP z)Rh_Y2M)wNT@&kbb||g>=ND-|eQTGq82LW%*Ol*jhZeAZSv{O~-YPI35YxNISC!8T zSGTX@WEeaz8oAk$|Kiuuj*?a6VNUqIj)ZPbIlOSj{>I)2=k~`L?g|-kzZfe=8RQ<~ z9Gw<7-`}(?Xwu_cu~^ZUTMJGosx|pbOrMF^b_ST%z3ejqqn3*X(SM^=>EG59N1?`iI+VI#mZ*^c^OpJG4=7U)c@2smOs+zZK z>TQZA@>CSJph%P>Xa%3VZ5ym~w_NMdt9W0M(pUMUw933{CqA_+;^0{&^NPB+O*iK^ z*NO4gwz`G(^?HsCq64DQ__+EMx-o4&H;H(>uBhJZ8~wz{uF-(oqfLo3mSE^}hy5L* zkxIMJO&xB>tM>MFI(G8EORKpOnZYt)PCTD`sN@uE;F_*|AeEPK@dv8a&a^|R&15H? z%;r~wIR&jNLvcr46O`@ig^rOhbCaZ^)YD~(ufxetbwMI>;arp)y^M09@JzdZhrOTM zpB0^<46w`S-0h7m`i!ns$mC<0Uhm=R*B90yE^35kuHD)^;#4MA(C)KZuaH!~yOk`_ zKb#ocTXvpPjL!LkF9 z;tly(qU@vd6?#O)>ZGD8f$tff*8*AcF5-o%gfaOu@oVMY37+*8G;Jt?DTo(3<+ir9 zM)wuWZ;wTcxQZ7po<%Oq(c~B%@Kc^JA+6be{#aEJ^~XkExH@D0kUZ-k|0tuQ`r0#? z?r2=hse}x~Bj@~iVx)L1g}t8AS{w5shq~B&=lWvCEPgoo+E%z%+awdxOkid!prYsB zy=<79_+xB3J>;)~=$YN&Ee-D_qu_<{Y5Z_*D^iqIW-=V9EpEzwkd}VT<$fj{|Pibek?~xr}2QYf~8!4R47_HNL>2oq}Ht^|b8-{0ktE>FI zT~o#!&4sk0uGR-)goEE=nCQLc18sZ|C3TBLQG$Qj0B6lNCEG)*aixX@Mmg!O65MLV zLAX(N^Ui5@x$gAHJ!gW7s)p_{xt{2{H~!^HIbB}!E!`$S}Fr~|gI7Hn5 zL=Eyd2v@&^h)f{b2P?X+PAhXW^n0z2d=L}!MB+@}2VW=G=#$-HqyLgZI-m4fjk7}P z;!?SV=#nD%_L$H0SLtOAqrH}H z;wRHQBxPWxCjL#^fZQKA?xME3Q0V6m(#Ec4{=aW?HP(eRV2no$%J|@UF3`v{zO4O@J%ym3+C<(y*pF YuZq_LFrh9*`cK2r&V_Wv=J1LC0J$Pgu>b%7 literal 0 HcmV?d00001