-- UI.lua -- Dynamic UI Elements local addon, ns = ... local Hekili = _G[addon] local class = Hekili.Class local state = Hekili.State local FindUnitBuffByID, FindUnitDebuffByID = ns.FindUnitBuffByID, ns.FindUnitDebuffByID -- Atlas/Textures local AddTexString, GetTexString, AtlasToString, GetAtlasFile, GetAtlasCoords = ns.AddTexString, ns.GetTexString, ns.AtlasToString, ns.GetAtlasFile, ns.GetAtlasCoords local frameStratas = ns.FrameStratas local getInverseDirection = ns.getInverseDirection local multiUnpack = ns.multiUnpack local orderedPairs = ns.orderedPairs local round = ns.round local format, insert = string.format, table.insert local HasVehicleActionBar, HasOverrideActionBar, IsInPetBattle, UnitHasVehicleUI, UnitOnTaxi = HasVehicleActionBar, HasOverrideActionBar, C_PetBattles.IsInBattle, UnitHasVehicleUI, UnitOnTaxi local Masque, MasqueGroup local _ function Hekili:GetScale() return PixelUtil.GetNearestPixelSize( 1, PixelUtil.GetPixelToUIUnitFactor(), 1 ) --[[ local monitorIndex = (tonumber(GetCVar("gxMonitor")) or 0) + 1 local resolutions = {GetScreenResolutions()} local resolution = resolutions[GetCurrentResolution()] or GetCVar("gxWindowedResolution") return (GetCVar("UseUIScale") == "1" and (GetScreenHeight() / resolution:match("%d+x(%d+)")) or 1) ]] end local movementData = {} local function startScreenMovement(frame) _, _, _, movementData.origX, movementData.origY = frame:GetPoint() frame:StartMoving() _, _, _, movementData.fromX, movementData.fromY = frame:GetPoint() frame.Moving = true end local function stopScreenMovement(frame) local monitor = (tonumber(GetCVar("gxMonitor")) or 0) + 1 local resolutions = {GetScreenResolutions()} local resolution = resolutions[GetCurrentResolution()] or GetCVar("gxWindowedResolution") local scrW, scrH = resolution:match("(%d+)x(%d+)") local scale, pScale = Hekili:GetScale(), UIParent:GetScale() scrW = scrW / ( scale * pScale ) scrH = scrH / ( scale * pScale ) local limitX = (scrW - frame:GetWidth() ) / 2 local limitY = (scrH - frame:GetHeight()) / 2 _, _, _, movementData.toX, movementData.toY = frame:GetPoint() frame:StopMovingOrSizing() frame.Moving = false frame:ClearAllPoints() frame:SetPoint( "CENTER", nil, "CENTER", max(-limitX, min(limitX, movementData.origX + (movementData.toX - movementData.fromX))), max(-limitY, min(limitY, movementData.origY + (movementData.toY - movementData.fromY))) ) Hekili:SaveCoordinates() end local function Mover_OnMouseUp(self, btn) local obj = self.moveObj or self if (btn == "LeftButton" and obj.Moving) then stopScreenMovement(obj) Hekili:SaveCoordinates() elseif btn == "RightButton" then if obj:GetName() == "HekiliNotification" then LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", "nPanel" ) return elseif obj and obj.id then LibStub( "AceConfigDialog-3.0" ):SelectGroup( "Hekili", "displays", obj.id, obj.id ) return end end end local function Mover_OnMouseDown( self, btn ) local obj = self.moveObj or self if Hekili.Config and btn == "LeftButton" and not obj.Moving then startScreenMovement(obj) end end local function Button_OnMouseUp( self, btn ) local display = self.display local mover = _G[ "HekiliDisplay" .. display ] if (btn == "LeftButton" and mover.Moving) then stopScreenMovement(mover) elseif (btn == "RightButton") then if mover.Moving then stopScreenMovement(mover) end local mouseInteract = Hekili.Pause or Hekili.Config for i = 1, #ns.UI.Buttons do for j = 1, #ns.UI.Buttons[i] do ns.UI.Buttons[i][j]:EnableMouse(mouseInteract) end end ns.UI.Notification:EnableMouse( Hekili.Config ) -- Hekili:SetOption( { "locked" }, true ) GameTooltip:Hide() end Hekili:SaveCoordinates() end local function Button_OnMouseDown(self, btn) local display = self.display local mover = _G[ "HekiliDisplay" .. display ] if Hekili.Config and btn == "LeftButton" and not mover.Moving then startScreenMovement(mover) end end function ns.StartConfiguration( external ) Hekili.Config = true local scaleFactor = Hekili:GetScale() local ccolor = RAID_CLASS_COLORS[select(2, UnitClass("player"))] -- Notification Panel ns.UI.Notification.Mover = ns.UI.Notification.Mover or CreateFrame( "Frame", "HekiliNotificationMover", ns.UI.Notification, "BackdropTemplate" ) ns.UI.Notification.Mover:SetAllPoints(HekiliNotification) ns.UI.Notification.Mover:SetBackdrop( { bgFile = "Interface/Buttons/WHITE8X8", edgeFile = "Interface/Buttons/WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } } ) ns.UI.Notification.Mover:SetBackdropColor( 0, 0, 0, .8 ) ns.UI.Notification.Mover:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 ) ns.UI.Notification:EnableMouse( true ) ns.UI.Notification:SetMovable( true ) ns.UI.Notification.Mover:Show() local f = ns.UI.Notification.Mover if not f.Header then f.Header = f:CreateFontString( "HekiliNotificationHeader", "OVERLAY", "GameFontNormal" ) local path, size = f.Header:GetFont() f.Header:SetFont( path, size, "OUTLINE" ) end f.Header:SetAllPoints( HekiliNotificationMover ) f.Header:SetText( "Notifications" ) f.Header:SetJustifyH( "CENTER" ) f.Header:Show() HekiliNotification:SetScript( "OnMouseDown", Mover_OnMouseDown ) HekiliNotification:SetScript( "OnMouseUp", Mover_OnMouseUp ) HekiliNotification:SetScript( "OnEnter", function( self ) local H = Hekili if not H.Pause and H.Config then GameTooltip:SetOwner( self, "ANCHOR_TOPRIGHT" ) GameTooltip:SetText( "Hekili: Notifications" ) GameTooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 ) GameTooltip:AddLine( "Right-click to open Notification panel settings.", 1, 1, 1 ) GameTooltip:Show() end end ) HekiliNotification:SetScript( "OnLeave", function(self) GameTooltip:Hide() end ) Hekili:ProfileFrame( "NotificationFrame", HekiliNotification ) for i, v in pairs( ns.UI.Displays ) do if v.Backdrop then v.Backdrop:Hide() end if v.Header then v.Header:Hide() end if ns.UI.Buttons[ i ][ 1 ] and Hekili.DB.profile.displays[ i ] then -- if not Hekili:IsDisplayActive( i ) then v:Show() end v.Backdrop = v.Backdrop or CreateFrame( "Frame", v:GetName().. "_Backdrop", UIParent, "BackdropTemplate" ) v.Backdrop:ClearAllPoints() if not v:IsAnchoringRestricted() then v:EnableMouse( true ) v:SetMovable( true ) local left, right, top, bottom = v:GetPerimeterButtons() if left and right and top and bottom then v.Backdrop:SetPoint( "LEFT", left, "LEFT", -2, 0 ) v.Backdrop:SetPoint( "RIGHT", right, "RIGHT", 2, 0 ) v.Backdrop:SetPoint( "TOP", top, "TOP", 0, 2 ) v.Backdrop:SetPoint( "BOTTOM", bottom, "BOTTOM", 0, -2 ) else v.Backdrop:SetWidth( v:GetWidth() + 2 ) v.Backdrop:SetHeight( v:GetHeight() + 2 ) v.Backdrop:SetPoint( "CENTER", v, "CENTER" ) end end v.Backdrop:SetFrameStrata( v:GetFrameStrata() ) v.Backdrop:SetFrameLevel( v:GetFrameLevel() + 1 ) v.Backdrop.moveObj = v v.Backdrop:SetBackdrop( { bgFile = "Interface/Buttons/WHITE8X8", edgeFile = "Interface/Buttons/WHITE8X8", tile = false, tileSize = 0, edgeSize = 1, insets = { left = 0, right = 0, top = 0, bottom = 0 } } ) local ccolor = RAID_CLASS_COLORS[ select(2, UnitClass("player")) ] if Hekili:IsDisplayActive( v.id, true ) then v.Backdrop:SetBackdropBorderColor( ccolor.r, ccolor.g, ccolor.b, 1 ) else v.Backdrop:SetBackdropBorderColor( 0.5, 0.5, 0.5, 0.5 ) end v.Backdrop:SetBackdropColor( 0, 0, 0, 0.8 ) v.Backdrop:Show() v.Backdrop:SetScript( "OnMouseDown", Mover_OnMouseDown ) v.Backdrop:SetScript( "OnMouseUp", Mover_OnMouseUp ) v.Backdrop:SetScript( "OnEnter", function( self ) local H = Hekili if not H.Pause and H.Config then GameTooltip:SetOwner( self, "ANCHOR_TOPRIGHT" ) GameTooltip:SetText( "Hekili: " .. i ) GameTooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 ) GameTooltip:AddLine( "Right-click to open " .. i .. " display settings.", 1, 1, 1 ) if not H:IsDisplayActive( i, true, "OnEnter" ) then GameTooltip:AddLine( "This display is not currently active.", 0.5, 0.5, 0.5 ) end GameTooltip:Show() end end ) v.Backdrop:SetScript( "OnLeave", function( self ) GameTooltip:Hide() end ) v:Show() if not v.Header then v.Header = v.Backdrop:CreateFontString( "HekiliDisplay" .. i .. "Header", "OVERLAY", "GameFontNormal" ) local path, size = v.Header:GetFont() v.Header:SetFont( path, size, "OUTLINE" ) end v.Header:ClearAllPoints() v.Header:SetAllPoints( v.Backdrop ) if i == "Defensives" then v.Header:SetText( AtlasToString( "nameplates-InterruptShield", 20, 20 ) ) elseif i == "Interrupts" then v.Header:SetText( AtlasToString( "communities-icon-redx", 20, 20 ) ) elseif i == "Cooldowns" then v.Header:SetText( "CD" ) else v.Header:SetText( i ) end v.Header:SetJustifyH("CENTER") v.Header:Show() else v:Hide() end end -- HekiliNotification:EnableMouse(true) -- HekiliNotification:SetMovable(true) if not external then local ACD = LibStub( "AceConfigDialog-3.0" ) ACD:SetDefaultSize( "Hekili", 800, 608 ) ACD:Open( "Hekili" ) local oFrame = ACD.OpenFrames["Hekili"].frame oFrame:SetMinResize( 800,608 ) ns.OnHideFrame = ns.OnHideFrame or CreateFrame( "Frame" ) ns.OnHideFrame:SetParent( oFrame ) ns.OnHideFrame:SetScript( "OnHide", function(self) ns.StopConfiguration() self:SetScript( "OnHide", nil ) collectgarbage() Hekili:UpdateDisplayVisibility() end ) Hekili:ProfileFrame( "CloseOptionsFrame", ns.OnHideFrame ) end Hekili:UpdateDisplayVisibility() end function Hekili:OpenConfiguration() ns.StartConfiguration() end function ns.StopConfiguration() Hekili.Config = false local scaleFactor = Hekili:GetScale() local mouseInteract = Hekili.Pause for i, v in ipairs( ns.UI.Buttons ) do for j, btn in ipairs( v ) do btn:EnableMouse( mouseInteract ) btn:SetMovable( false ) end end HekiliNotification:EnableMouse( false ) HekiliNotification:SetMovable( false ) HekiliNotification.Mover:Hide() -- HekiliNotification.Mover.Header:Hide() for i, v in pairs( ns.UI.Displays ) do v:EnableMouse( false ) if not v:IsAnchoringRestricted() then v:SetMovable( true ) end -- v:SetBackdrop( nil ) if v.Header then v.Header:Hide() end if v.Backdrop then v.Backdrop:Hide() end end Hekili.MakeDefaults = false end local function MasqueUpdate( Addon, Group, SkinID, Gloss, Backdrop, Colors, Disabled ) if Disabled then for dispID, display in ipairs( ns.UI.Buttons ) do for btnID, button in ipairs( display ) do button.__MSQ_NormalTexture:Hide() button.Texture:SetAllPoints( button ) end end end end do ns.UI.Menu = ns.UI.Menu or CreateFrame( "Frame", "HekiliMenu", UIParent, "UIDropDownMenuTemplate" ) local menu = ns.UI.Menu menu.info = {} menu.AddButton = UIDropDownMenu_AddButton menu.AddSeparator = UIDropDownMenu_AddSeparator local function SetDisplayMode( mode ) Hekili.DB.profile.toggles.mode.value = mode if WeakAuras and WeakAuras.ScanEvents then WeakAuras.ScanEvents( "HEKILI_TOGGLE", "mode", mode ) end if ns.UI.Minimap then ns.UI.Minimap:RefreshDataText() end Hekili:UpdateDisplayVisibility() Hekili:ForceUpdate( "HEKILI_TOGGLE", true ) end local function IsDisplayMode( p, mode ) return Hekili.DB.profile.toggles.mode.value == mode end local menuData = { { isTitle = 1, text = "Hekili", notCheckable = 1, }, { text = "Enable", func = function () Hekili:Toggle() end, checked = function () return Hekili.DB.profile.enabled end, }, { text = "Pause", func = function () return Hekili:TogglePause() end, checked = function () return Hekili.Pause end, }, { isSeparator = 1, }, { isTitle = 1, text = "Display Mode", notCheckable = 1, }, { text = "Auto", func = function () SetDisplayMode( "automatic" ) end, checked = function () return IsDisplayMode( p, "automatic" ) end, }, { text = "Single", func = function () SetDisplayMode( "single" ) end, checked = function () return IsDisplayMode( p, "single" ) end, }, { text = "AOE", func = function () SetDisplayMode( "aoe" ) end, checked = function () return IsDisplayMode( p, "aoe" ) end, }, { text = "Dual", func = function () SetDisplayMode( "dual" ) end, checked = function () return IsDisplayMode( p, "dual" ) end, }, { text = "Reactive", func = function () SetDisplayMode( "reactive" ) end, checked = function () return IsDisplayMode( p, "reactive" ) end, }, { isSeparator = 1, }, { isTitle = 1, text = "Toggles", notCheckable = 1, }, { text = "Cooldowns", func = function() Hekili:FireToggle( "cooldowns" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.cooldowns.value end, }, { text = "Covenants", func = function() Hekili:FireToggle( "essences" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.essences.value end, }, { text = "Interrupts", func = function() Hekili:FireToggle( "interrupts" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.interrupts.value end, }, { text = "Defensives", func = function() Hekili:FireToggle( "defensives" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.defensives.value end, }, { text = "Potions", func = function() Hekili:FireToggle( "potions" ); ns.UI.Minimap:RefreshDataText() end, checked = function () return Hekili.DB.profile.toggles.potions.value end, }, } local specsParsed = false menu.args = {} function menu:initialize( level, list ) if not level and not list then return end if level == 1 then if not specsParsed then -- Add specialization toggles where applicable. for i, spec in pairs( Hekili.Class.specs ) do if i > 0 then local titled = false -- Check for Toggles. for n, setting in pairs( spec.settings ) do if setting.info.type == "toggle" then if not titled then insert( menuData, { isSeparator = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) insert( menuData, { isTitle = 1, text = spec.name, notCheckable = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) titled = true end insert( menuData, { text = setting.info.name, func = function () menu.args[1] = setting.name setting.info.set( menu.args, not setting.info.get( menu.args ) ) if Hekili.DB.profile.notifications.enabled then Hekili:Notify( setting.info.name .. ": " .. ( setting.info.get( menu.args ) and "ON" or "OFF" ) ) else self:Print( setting.info.name .. ": " .. ( setting.info.get( menu.args ) and " |cFF00FF00ENABLED|r." or " |cFFFF0000DISABLED|r." ) ) end end, checked = function () menu.args[1] = setting.name return setting.info.get( menu.args ) end, hidden = function () return Hekili.State.spec.id ~= i end, } ) elseif setting.info.type == "select" then if not titled then insert( menuData, { isSeparator = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) insert( menuData, { isTitle = 1, text = spec.name, notCheckable = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) titled = true end local submenu = { text = setting.info.name, hasArrow = true, menuList = {}, notCheckable = true, hidden = function () return Hekili.State.spec.id ~= i end, } local values = setting.info.values if type( values ) == "function" then values = values() end if values then for k, v in orderedPairs( values ) do insert( submenu.menuList, { text = v, func = function () menu.args[1] = setting.name setting.info.set( menu.args, k ) for k, v in pairs( Hekili.DisplayPool ) do v:OnEvent( "HEKILI_MENU" ) end end, checked = function () menu.args[1] = setting.name return setting.info.get( menu.args ) == k end, hidden = function () return Hekili.State.spec.id ~= i end, } ) end end insert( menuData, submenu ) elseif setting.info.type == "range" and setting.info.step == 1 and ( ( setting.info.max or 999 ) - ( setting.info.min or -999 ) ) < 30 then if not titled then insert( menuData, { isSeparator = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) insert( menuData, { isTitle = 1, text = spec.name, notCheckable = 1, hidden = function () return Hekili.State.spec.id ~= i end, } ) titled = true end local submenu = { text = setting.info.name, hasArrow = true, menuList = {}, notCheckable = true, hidden = function () return Hekili.State.spec.id ~= i end, } --[[ for j = setting.info.min, setting.info.max do insert( submenu.menuList, { text = tostring( j ), func = function () menu.args[1] = setting.name setting.info.set( menu.args, j ) end, checked = function () menu.args[1] = setting.name return setting.info.get( menu.args ) == j end, hidden = function () return Hekili.State.spec.id ~= i end, } ) end ]] insert( menuData, submenu ) end end end end specsParsed = true end end local use = list or menuData local classic = Hekili.IsClassic() for i, data in ipairs( use ) do data.classicChecks = classic if not data.hidden or ( type( data.hidden ) == 'function' and not data.hidden() ) then if data.isSeparator then menu.AddSeparator( level ) else menu.AddButton( data, level ) end end end end end do ns.UI.Displays = ns.UI.Displays or {} local dPool = ns.UI.Displays Hekili.DisplayPool = dPool local alphaUpdateEvents = { PET_BATTLE_OPENING_START = 1, PET_BATTLE_CLOSE = 1, BARBER_SHOP_OPEN = 1, BARBER_SHOP_CLOSE = 1, PLAYER_GAINS_VEHICLE_DATA = 1, PLAYER_LOSES_VEHICLE_DATA = 1, UNIT_ENTERING_VEHICLE = 1, UNIT_ENTERED_VEHICLE = 1, UNIT_EXITED_VEHICLE = 1, UNIT_EXITING_VEHICLE = 1, VEHICLE_ANGLE_SHOW = 1, VEHICLE_UPDATE = 1, UPDATE_VEHICLE_ACTIONBAR = 1, UNIT_FLAGS = 1, PLAYER_TARGET_CHANGED = 1, PLAYER_ENTERING_WORLD = 1, PLAYER_REGEN_ENABLED = 1, PLAYER_REGEN_DISABLED = 1, ACTIVE_TALENT_GROUP_CHANGED = 1, ZONE_CHANGED = 1, ZONE_CHANGED_INDOORS = 1, ZONE_CHANGED_NEW_AREA = 1, PLAYER_CONTROL_LOST = 1, PLAYER_CONTROL_GAINED = 1, PLAYER_MOUNT_DISPLAY_CHANGED = 1, UPDATE_ALL_UI_WIDGETS = 1, } local function CalculateAlpha( id ) if IsInPetBattle() or Hekili.Barber or UnitHasVehicleUI("player") or HasVehicleActionBar() or HasOverrideActionBar() or UnitOnTaxi("player") or not Hekili:IsDisplayActive( id ) then return 0 end local prof = Hekili.DB.profile local conf, mode = prof.displays[ id ], prof.toggles.mode.value local _, zoneType = IsInInstance() -- Switch Type: -- 0 = Auto - AOE -- 1 = ST - AOE if ( not conf.enabled ) or ( not conf.visibility.mode[ mode ] ) then return 0 elseif zoneType == "pvp" or zoneType == "arena" then if not conf.visibility.advanced then return conf.visibility.pvp.alpha end if conf.visibility.pvp.hideMounted and IsMounted() then return 0 end if conf.visibility.pvp.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then return conf.visibility.pvp.combatTarget elseif conf.visibility.pvp.combat > 0 and state.combat > 0 then return conf.visibility.pvp.combat elseif conf.visibility.pvp.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then return conf.visibility.pvp.target elseif conf.visibility.pvp.always > 0 then return conf.visibility.pvp.always end return 0 end if not conf.visibility.advanced then return conf.visibility.pve.alpha end if conf.visibility.pve.hideMounted and IsMounted() then return 0 end if conf.visibility.pve.combatTarget > 0 and state.combat > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then return conf.visibility.pve.combatTarget elseif conf.visibility.pve.combat > 0 and state.combat > 0 then return conf.visibility.pve.combat elseif conf.visibility.pve.target > 0 and UnitExists( "target" ) and not UnitIsDead( "target" ) and UnitCanAttack( "player", "target" ) then return conf.visibility.pve.target elseif conf.visibility.pve.always > 0 then return conf.visibility.pve.always end return 0 end local kbEvents = { ACTIONBAR_SLOT_CHANGED = 1, ACTIONBAR_PAGE_CHANGED = 1, ACTIONBAR_UPDATE_STATE = 1, SPELLS_CHANGED = 1, UPDATE_SHAPESHIFT_FORM = 1 } local function Display_UpdateKeybindings( self ) local conf = Hekili.DB.profile.displays[ self.id ] if conf.keybindings and conf.keybindings.enabled then for i, b in ipairs( self.Buttons ) do local r = self.Recommendations[i] if r then local a = r.actionName if a then r.keybind, r.keybindFrom = Hekili:GetBindingForAction( r.actionName, conf, i ) end if i == 1 or conf.keybindings.queued then b.Keybinding:SetText( r.keybind ) else b.Keybinding:SetText( nil ) end end end end end local pulseAuras = 0.1 local pulseDelay = 0.05 local pulseGlow = 0.25 local pulseTargets = 0.1 local pulseRange = TOOLTIP_UPDATE_TIME local pulseFlash = 0.5 local oocRefresh = 1 local icRefresh = { Primary = 0.25, AOE = 0.25, Interrupts = 0.25, Defensives = 0.5, Cooldowns = 0.25 } local LRC = LibStub("LibRangeCheck-2.0") local LSF = SpellFlashCore local LSR = LibStub("SpellRange-1.0") local Glower = LibStub("LibCustomGlow-1.0") local function Display_OnUpdate( self, elapsed ) if not self.Recommendations or not Hekili.PLAYER_ENTERING_WORLD then return end local profile = Hekili.DB.profile local conf = profile.displays[ self.id ] self.alphaCheck = self.alphaCheck - elapsed if self.alpha == 0 then if self.alphaCheck <= 0 then self.alphaCheck = 0.5 self:UpdateAlpha() end return end if Hekili.Pause then if not self.paused then self.Buttons[ 1 ].Overlay:Show() self.paused = true end elseif self.paused then self.Buttons[ 1 ].Overlay:Hide() self.paused = false end local now = GetTime() self.recTimer = self.recTimer - elapsed if self.NewRecommendations or self.recTimer < 0 then local alpha = self.alpha for i, b in ipairs( self.Buttons ) do local rec = self.Recommendations[ i ] local action = rec.actionName local caption = rec.caption local indicator = rec.indicator local keybind = rec.keybind local ability = class.abilities[ action ] if ability then if ( conf.flash.enabled and conf.flash.suppress ) then b:Hide() else b:Show() end if action ~= b.lastAction or self.NewRecommendations then b.Texture:SetTexture( rec.texture or ability.texture or GetSpellTexture( ability.id ) ) b.Texture:SetTexCoord( unpack( b.texCoords ) ) b.lastAction = action end b.Texture:Show() if conf.indicators.enabled and indicator then if indicator == "cycle" then b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cycle") end if indicator == "cancel" then b.Icon:SetTexture("Interface\\Addons\\Hekili\\Textures\\Cancel") end b.Icon:Show() else b.Icon:Hide() end if ( conf.captions.enabled or ability.caption ) and ( i == 1 or conf.captions.queued ) then b.Caption:SetText( caption ) else b.Caption:SetText(nil) end if conf.keybindings.enabled and ( i == 1 or conf.keybindings.queued ) then b.Keybinding:SetText( keybind ) else b.Keybinding:SetText(nil) end if conf.glow.enabled and ( i == 1 or conf.glow.queued ) and IsSpellOverlayed( ability.id ) then b.glowColor = b.glowColor or {} if conf.glow.coloring == "class" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA() elseif conf.glow.coloring == "custom" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color) else b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1 end if conf.glow.mode == "default" then Glower.ButtonGlow_Start( b, b.glowColor ) b.glowStop = Glower.ButtonGlow_Stop elseif conf.glow.mode == "autocast" then Glower.AutoCastGlow_Start( b, b.glowColor ) b.glowStop = Glower.AutoCastGlow_Stop elseif conf.glow.mode == "pixel" then Glower.PixelGlow_Start( b, b.glowColor ) b.glowStop = Glower.PixelGlow_Stop end b.glowing = true elseif b.glowing then if b.glowStop then b:glowStop() end b.glowing = false end else b:Hide() end end -- Force glow, range, SpellFlash updates. self.glowTimer = -1 self.rangeTimer = -1 self.flashTimer = -1 self.delayTimer = -1 self.recTimer = 0.1 self.alphaCheck = 0.5 self:RefreshCooldowns() self.NewRecommendations = false end self.refreshTimer = self.refreshTimer - elapsed if Hekili.freshFrame and not Hekili.Pause then local spec = Hekili.DB.profile.specs[ state.spec.id ] local throttle = spec.throttleRefresh and ( 1 / spec.maxRefresh ) or 0.25 if self.refreshTimer < 0 or ( self.superUpdate and ( self.id == "Primary" or self.id == "AOE" ) ) or self.criticalUpdate and ( now - self.lastUpdate >= throttle ) then Hekili:ProcessHooks( self.id ) self.lastUpdate = now self.criticalUpdate = false self.superUpdate = false local refreshRate = max( throttle, state.combat == 0 and oocRefresh or icRefresh[ self.id ] ) if UnitChannelInfo( "player" ) then refreshRate = refreshRate * 2 end self.refreshTimer = refreshRate table.wipe( self.eventsTriggered ) Hekili.freshFrame = false end end self.glowTimer = self.glowTimer - elapsed if self.glowTimer < 0 then if conf.glow.enabled then for i, b in ipairs( self.Buttons ) do local r = self.Recommendations[ i ] if not r.actionName then break end local a = class.abilities[ r.actionName ] if i == 1 or conf.glow.queued then local glowing = not a.item and IsSpellOverlayed( a.id ) if glowing and not b.glowing then b.glowColor = b.glowColor or {} if conf.glow.coloring == "class" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA() elseif conf.glow.coloring == "custom" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color) else b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1 end if conf.glow.mode == "default" then Glower.ButtonGlow_Start( b, b.glowColor ) b.glowStop = Glower.ButtonGlow_Stop elseif conf.glow.mode == "autocast" then Glower.AutoCastGlow_Start( b, b.glowColor ) b.glowStop = Glower.AutoCastGlow_Stop elseif conf.glow.mode == "pixel" then Glower.PixelGlow_Start( b, b.glowColor ) b.glowStop = Glower.PixelGlow_Stop end b.glowing = true elseif not glowing and b.glowing then b:glowStop() b.glowing = false end else if b.glowing then b:glowStop() b.glowing = false end end end end end self.rangeTimer = self.rangeTimer - elapsed if self.rangeTimer < 0 then for i, b in ipairs( self.Buttons ) do local r = self.Recommendations[ i ] local a = class.abilities[ r.actionName ] if a and a.id then local outOfRange = false if conf.range.enabled then if conf.range.type == "melee" and UnitExists( "target" ) then outOfRange = ( LRC:GetRange( "target" ) or 50 ) > 7 elseif conf.range.type == "ability" and UnitExists( "target" ) and UnitCanAttack( "player", "target" ) then if a.item then outOfRange = IsItemInRange( a.itemCd or a.item, "target" ) == false else local name = a.rangeSpell or a.actualName or a.name if name then outOfRange = LSR.IsSpellInRange( a.rangeSpell or a.actualName or a.name, "target" ) == 0 end end end end if outOfRange and not b.outOfRange then b.Texture:SetDesaturated(true) b.Texture:SetVertexColor(1.0, 0.0, 0.0, 1.0) b.outOfRange = true elseif b.outOfRange and not outOfRange then b.Texture:SetDesaturated(false) b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0) b.outOfRange = false end if not b.outOfRange then local _, unusable if a.itemCd or a.item then unusable = not IsUsableItem( a.itemCd or a.item ) else _, unusable = IsUsableSpell( a.actualName or a.name ) end if i == 1 and conf.delays.fade then local delay = r.exact_time - now local moment = 0 local start, duration = 0, 0 if a.gcd ~= "off" then start, duration = GetSpellCooldown( 61304 ) if start > 0 then moment = start + duration - now end end local rStart, rDuration if a.item then rStart, rDuration = GetItemCooldown( a.item ) else rStart, rDuration = GetSpellCooldown( a.id ) end if rStart > 0 then moment = max( moment, rStart + rDuration - now ) end _, _, _, start, duration = UnitCastingInfo( "player" ) if start and start > 0 then moment = max( ( start / 1000 ) + ( duration / 1000 ) - now, moment ) end if delay > moment + 0.05 then unusable = true end end if unusable and not b.unusable then b.Texture:SetVertexColor(0.4, 0.4, 0.4, 1.0) b.unusable = true elseif b.unusable and not unusable then b.Texture:SetVertexColor(1.0, 1.0, 1.0, 1.0) b.unusable = false end end end end self.rangeTimer = pulseRange end if conf.flash.enabled and LSF then self.flashTimer = self.flashTimer - elapsed if self.flashTimer < 0 then local a = self.Recommendations and self.Recommendations[ 1 ] and self.Recommendations[ 1 ].actionName if a then local ability = class.abilities[ a ] self.flashColor = self.flashColor or {} self.flashColor.r, self.flashColor.g, self.flashColor.b = unpack( conf.flash.color ) if self.lastFlash ~= a or now - self.lastFlashTime > 0.5 then if ability.item then local iname = LSF.ItemName( ability.item ) LSF.FlashItem( iname, self.flashColor ) else if ability.flash then LSF.FlashAction( ability.flash, self.flashColor ) else local id = ability.known if id == nil or type( id ) ~= "number" then id = ability.id end local sname = LSF.SpellName( id ) LSF.FlashAction( sname, self.flashColor ) end end self.lastFlash = a self.lastFlashTime = now end end end self.flashTimer = pulseFlash end self.targetTimer = self.targetTimer - elapsed if self.targetTimer < 0 then local b = self.Buttons[ 1] if conf.targets.enabled then local tMin, tMax = 0, 0 local mode = profile.toggles.mode.value local spec = state.spec.id and profile.specs[ state.spec.id ] if self.id == 'Primary' then if ( mode == 'dual' or mode == 'single' or mode == 'reactive' ) then tMax = 1 elseif mode == 'aoe' then tMin = spec and spec.aoe or 3 end elseif self.id == 'AOE' then tMin = spec and spec.aoe or 3 end local detected = ns.getNumberTargets() local shown = detected if tMin > 0 then shown = max(tMin, shown) end if tMax > 0 then shown = min(tMax, shown) end if tMax == 1 or shown > 1 then local color = detected < shown and "|cFFFF0000" or ( shown < detected and "|cFF00C0FF" or "" ) b.Targets:SetText( color .. shown .. "|r") b.targetShown = true else b.Targets:SetText(nil) b.targetShown = false end elseif b.targetShown then b.Targets:SetText(nil) end self.targetTimer = pulseTargets end local rec = self.Recommendations[ 1 ] self.delayTimer = self.delayTimer - elapsed if rec.exact_time and self.delayTimer < 0 then local b = self.Buttons[ 1 ] local a = class.abilities[ rec.actionName ] local delay = rec.exact_time - now local moment = 0 if delay > 0 then local start, duration = 0, 0 if a.gcd ~= "off" then start, duration = GetSpellCooldown( 61304 ) if start > 0 then moment = start + duration - now end end _, _, _, start, duration = UnitCastingInfo( "player" ) if start and start > 0 then moment = max( ( start / 1000 ) + ( duration / 1000 ) - now, moment ) end local rStart, rDuration = 0, 0 if a.item then rStart, rDuration = GetItemCooldown( a.item ) else if a.cooldown > 0 or a.spendType ~= "runes" then rStart, rDuration = GetSpellCooldown( a.id ) end end if rStart > 0 then moment = max( moment, rStart + rDuration - now ) end end if conf.delays.type == "TEXT" then if self.delayIconShown then b.DelayIcon:Hide() self.delayIconShown = false end if delay > moment + 0.05 then b.DelayText:SetText( format( "%.1f", delay ) ) self.delayTextShown = true else b.DelayText:SetText( nil ) self.delayTextShown = false end elseif conf.delays.type == "ICON" then if self.delayTextShown then b.DelayText:SetText(nil) self.delayTextShown = false end if delay > moment + 0.05 then b.DelayIcon:Show() b.DelayIcon:SetAlpha( self.alpha ) self.delayIconShown = true if delay < 0.5 then b.DelayIcon:SetVertexColor( 0.0, 1.0, 0.0, 1.0 ) elseif delay < 1.5 then b.DelayIcon:SetVertexColor( 1.0, 1.0, 0.0, 1.0 ) else b.DelayIcon:SetVertexColor( 1.0, 0.0, 0.0, 1.0) end else b.DelayIcon:Hide() b.delayIconShown = false end else if self.delayTextShown then b.DelayText:SetText( nil ) self.delayTextShown = false end if self.delayIconShown then b.DelayIcon:Hide() self.delayIconShown = false end end self.delayTimer = pulseDelay end end ns.cpuProfile.Display_OnUpdate = Display_OnUpdate local function Display_UpdateAlpha( self ) if self.Backdrop then --[[ if not Hekili:IsDisplayActive( self.id, true ) then self.Backdrop:SetBackdropBorderColor( 0.5, 0.5, 0.5, 0.5 ) else self.Backdrop:SetBackdropBorderColor( RAID_CLASS_COLORS[ class.file ]:GetRGBA() ) end ]] end if not self.Active then self:SetAlpha(0) self:Hide() self.alpha = 0 return end local preAlpha = self.alpha or 0 local newAlpha = CalculateAlpha( self.id ) if preAlpha > 0 and newAlpha == 0 then -- self:Deactivate() self:SetAlpha( 0 ) self.alphaCheck = 0.5 else if preAlpha == 0 and newAlpha > 0 then Hekili:ForceUpdate( "DISPLAY_ALPHA_CHANGED" ) end self:SetAlpha( newAlpha ) self:Show() end self.alpha = newAlpha end local function Display_RefreshCooldowns( self ) local gStart, gDuration = GetSpellCooldown( 61304 ) local gExpires = gStart + gDuration local now = GetTime() local conf = Hekili.DB.profile.displays[ self.id ] for i, rec in ipairs( self.Recommendations ) do if not rec.actionName then break end local ability = class.abilities[ rec.actionName ] local cd = self.Buttons[ i ].Cooldown if ability then local start, duration = 0, 0 if ability.item then start, duration = GetItemCooldown( ability.item ) else if ability.cooldown > 0 or ability.spendType ~= "runes" then start, duration = GetSpellCooldown( ability.id ) end end if ability.gcd ~= "off" and start + duration < gExpires then start = gStart duration = gDuration end if i == 1 and conf.delays.extend and rec.time > 0 and rec.exact_time > max( now, start + duration ) then if rec.interrupt and rec.startCast then start = rec.startCast duration = rec.exact_time - start else start = start > 0 and start or state.gcd.lastStart duration = rec.exact_time - start end end if cd.lastStart ~= start or cd.lastDuration ~= duration then cd:SetCooldown( start, duration ) cd.lastStart = start cd.lastDuration = duration end end end end local function Display_OnEvent(self, event, ...) if not self.Recommendations then return end local conf = Hekili.DB.profile.displays[ self.id ] -- Update the CDs. if event == "SPELL_UPDATE_USABLE" or event == "SPELL_UPDATE_COOLDOWN" or event == "ACTIONBAR_UPDATE_USABLE" or event == "ACTIONBAR_UPDATE_COOLDOWN" then self:RefreshCooldowns() elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" then if conf.glow.enabled then for i, r in ipairs( self.Recommendations ) do if i > 1 and not conf.glow.queued then break end if not r.actionName then break end local b = self.Buttons[ i ] local a = class.abilities[ r.actionName ] if not b.glowing and a and not a.item and IsSpellOverlayed( a.id ) then b.glowColor = b.glowColor or {} if conf.glow.coloring == "class" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = RAID_CLASS_COLORS[ class.file ]:GetRGBA() elseif conf.glow.coloring == "custom" then b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = unpack(conf.glow.color) else b.glowColor[1], b.glowColor[2], b.glowColor[3], b.glowColor[4] = 0.95, 0.95, 0.32, 1 end if conf.glow.mode == "default" then Glower.ButtonGlow_Start( b, b.glowColor ) b.glowStop = Glower.ButtonGlow_Stop elseif conf.glow.mode == "autocast" then Glower.AutoCastGlow_Start( b, b.glowColor ) b.glowStop = Glower.AutoCastGlow_Stop elseif conf.glow.mode == "pixel" then Glower.PixelGlow_Start( b, b.glowColor ) b.glowStop = Glower.PixelGlow_Stop end b.glowing = true end end end elseif event == "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" then if conf.glow.enabled then for i, r in ipairs(self.Recommendations) do if i > 1 and not conf.glow.queued then break end if not r.actionName then break end local b = self.Buttons[ i ] local a = class.abilities[ r.actionName ] if b.glowing and ( not a or a.item or not IsSpellOverlayed( a.id ) ) then b:glowStop() b.glowing = false end end end elseif kbEvents[ event ] then self:UpdateKeybindings() elseif alphaUpdateEvents[ event ] then self:UpdateAlpha() elseif event == "SPELLS_CHANGED" then for i, rec in ipairs( self.Recommendations ) do rec.texture = nil end self.NewRecommendations = true end end ns.cpuProfile.Display_OnEvent = Display_OnEvent local function Display_Activate( self ) if not self.Active then self.Active = true self.Recommendations = self.Recommendations or ( ns.queue and ns.queue[ self.id ] ) self.NewRecommendations = true self.alphaCheck = 0 self.auraTimer = 0 self.delayTimer = 0 self.flashTimer = 0 self.lastFlashTime = 0 self.glowTimer = 0 self.rangeTimer = 0 self.recTimer = 0 self.refreshTimer = 0 self.targetTimer = 0 self.lastUpdate = 0 self:SetScript( "OnUpdate", Display_OnUpdate ) self:SetScript( "OnEvent", Display_OnEvent ) if not self.Initialized then -- Update Cooldown Wheels. self:RegisterEvent( "ACTIONBAR_UPDATE_USABLE" ) self:RegisterEvent( "ACTIONBAR_UPDATE_COOLDOWN" ) self:RegisterEvent( "SPELL_UPDATE_COOLDOWN" ) self:RegisterEvent( "SPELL_UPDATE_USABLE" ) -- Show/Hide Overlay Glows. self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_SHOW" ) self:RegisterEvent( "SPELL_ACTIVATION_OVERLAY_GLOW_HIDE" ) -- Recalculate Alpha/Visibility. for e in pairs( alphaUpdateEvents ) do self:RegisterEvent( e ) end -- Recheck spell displays if spells have changed. self:RegisterEvent( "SPELLS_CHANGED" ) -- Update keybindings. for k in pairs( kbEvents ) do self:RegisterEvent( k ) end self.Initialized = true end Hekili:ProcessHooks( self.id ) end end local function Display_Deactivate( self ) self.Active = false self:SetScript( "OnUpdate", nil ) self:SetScript( "OnEvent", nil ) for i, b in ipairs( self.Buttons ) do b:Hide() end end local function Display_GetPerimeterButtons( self ) local left, right, top, bottom local lPos, rPos, tPos, bPos for i = 1, self.numIcons do local button = self.Buttons[ i ] if i == 1 then lPos = button:GetLeft() rPos = button:GetRight() tPos = button:GetTop() bPos = button:GetBottom() left = button right = button top = button bottom = button else if button:GetLeft() < lPos then lPos = button:GetLeft() left = button end if button:GetRight() > rPos then rPos = button:GetRight() right = button end if button:GetTop() > tPos then tPos = button:GetTop() top = button end if button:GetBottom() < bPos then bPos = button:GetBottom() bottom = button end end end return left, right, top, bottom end local function Display_UpdatePerformance( self, now, used, newRecs ) if used == nil then return end used = used / 1000 -- ms to sec. if self.combatTime.samples == 0 then self.combatTime.fastest = used self.combatTime.slowest = used self.combatTime.average = used self.combatTime.samples = 1 else if used < self.combatTime.fastest then self.combatTime.fastest = used end if used > self.combatTime.slowest then self.combatTime.slowest = used end self.combatTime.average = ( ( self.combatTime.average * self.combatTime.samples ) + used ) / ( self.combatTime.samples + 1 ) self.combatTime.samples = self.combatTime.samples + 1 end if self.combatUpdates.samples == 0 then if self.combatUpdates.last == 0 then self.combatUpdates.last = now else local interval = now - self.combatUpdates.last self.combatUpdates.last = now self.combatUpdates.shortest = interval self.combatUpdates.longest = interval self.combatUpdates.average = interval self.combatUpdates.samples = 1 end else local interval = now - self.combatUpdates.last self.combatUpdates.last = now if interval < self.combatUpdates.shortest then self.combatUpdates.shortest = interval self.combatUpdates.shortEvents = nil local e = 0 for k in pairs( self.eventsTriggered ) do if e == 0 then self.combatUpdates.shortEvents = k; e = 1 else self.combatUpdates.shortEvents = self.combatUpdates.shortEvents .. "|" .. k end end end if interval > self.combatUpdates.longest then self.combatUpdates.longest = interval self.combatUpdates.longEvents = nil local e = 0 for k in pairs( self.eventsTriggered ) do if e == 0 then self.combatUpdates.longEvents = k; e = 1 else self.combatUpdates.longEvents = self.combatUpdates.longEvents .. "|" .. k end end end self.combatUpdates.average = ( ( self.combatUpdates.average * self.combatUpdates.samples ) + interval ) / ( self.combatUpdates.samples + 1 ) self.combatUpdates.samples = self.combatUpdates.samples + 1 end self.successEvents = self.successEvents or {} self.failEvents = self.failEvents or {} local events = newRecs and self.successEvents or self.failEvents for k in pairs( self.eventsTriggered ) do if events[ k ] then events[ k ] = events[ k ] + 1 else events[ k ] = 1 end end end local numDisplays = 0 function Hekili:CreateDisplay( id ) local conf = rawget( self.DB.profile.displays, id ) if not conf then return end if not dPool[ id ] then numDisplays = numDisplays + 1 dPool[ id ] = CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent ) dPool[ id ].index = numDisplays Hekili:ProfileFrame( "HekiliDisplay" .. id, dPool[ id ] ) end local d = dPool[ id ] d.id = id d.alpha = 0 d.numIcons = conf.numIcons d.firstForce = 0 local scale = self:GetScale() local border = 2 d:SetSize( scale * ( border + ( conf.primaryWidth or 50 ) ), scale * ( border + ( conf.primaryHeight or 50 ) ) ) --[[ d:SetIgnoreParentScale( true ) d:SetScale( UIParent:GetScale() ) ]] d:ClearAllPoints() local frame --[[ if conf.relativeTo == "CUSTOM" then frame = _G[ conf.customFrame ] elseif conf.relativeTo == "PERSONAL" then frame = C_NamePlate.GetNamePlateForUnit( "player" ) end ]] if not frame then frame = UIParent end d:SetPoint( "CENTER", frame, "CENTER", conf.x or 0, conf.y or -225 ) d:SetParent( frame ) d:SetFrameStrata( conf.frameStrata or "MEDIUM" ) d:SetFrameLevel( conf.frameLevel or ( 10 * d.index ) ) if not d:IsAnchoringRestricted() then d:SetClampedToScreen( true ) d:EnableMouse( false ) d:SetMovable( true ) end d.Activate = Display_Activate d.Deactivate = Display_Deactivate d.GetPerimeterButtons = Display_GetPerimeterButtons d.UpdatePerformance = Display_UpdatePerformance d.RefreshCooldowns = Display_RefreshCooldowns d.UpdateAlpha = Display_UpdateAlpha d.UpdateKeybindings = Display_UpdateKeybindings ns.queue[id] = ns.queue[id] or {} d.Recommendations = ns.queue[id] ns.UI.Buttons[id] = ns.UI.Buttons[id] or {} d.Buttons = ns.UI.Buttons[id] for i = 1, 10 do d.Buttons[ i ] = self:CreateButton( id, i ) d.Buttons[ i ]:Hide() if conf.enabled and self:IsDisplayActive( id ) and i <= conf.numIcons then if d.Recommendations[ i ] and d.Recommendations[ i ].actionName then d.Buttons[ i ]:Show() end end if MasqueGroup then MasqueGroup:AddButton( d.Buttons[i], { Icon = d.Buttons[ i ].Texture, Cooldown = d.Buttons[ i ].Cooldown } ) end end if d.forceElvUpdate then local E = _G.ElvUI and ElvUI[1] E:UpdateCooldownOverride( 'global' ) d.forceElvUpdate = nil end -- Performance Information -- Time Spent d.combatTime = { fastest = 0, slowest = 0, average = 0, samples = 0 } -- Time Between Updates d.combatUpdates = { last = 0, longest = 0, shortest = 0, average = 0, samples = 0, } d.eventsTriggered = {} end function Hekili:CreateCustomDisplay( id ) local conf = rawget( self.DB.profile.displays, id ) if not conf then return end dPool[ id ] = dPool[ id ] or CreateFrame( "Frame", "HekiliDisplay" .. id, UIParent ) local d = dPool[ id ] d.id = id local scale = self:GetScale() local border = 2 d:SetSize( scale * ( border + conf.primaryWidth ), scale * (border + conf.primaryHeight ) ) d:SetPoint( "CENTER", nil, "CENTER", conf.x, conf.y ) d:SetFrameStrata( "MEDIUM" ) d:SetClampedToScreen( true ) d:EnableMouse( false ) d:SetMovable( true ) d.Activate = Display_Activate d.Deactivate = Display_Deactivate d.RefreshCooldowns = Display_RefreshCooldowns d.UpdateAlpha = Display_UpdateAlpha d.UpdateKeybindings = Display_UpdateKeybindings ns.queue[id] = ns.queue[id] or {} d.Recommendations = ns.queue[id] ns.UI.Buttons[id] = ns.UI.Buttons[id] or {} d.Buttons = ns.UI.Buttons[id] for i = 1, 10 do d.Buttons[i] = self:CreateButton(id, i) d.Buttons[i]:Hide() if self.DB.profile.enabled and self:IsDisplayActive(id) and i <= conf.numIcons then if d.Recommendations[i] and d.Recommendations[i].actionName then d.Buttons[i]:Show() end end if MasqueGroup then MasqueGroup:AddButton(d.Buttons[i], {Icon = d.Buttons[i].Texture, Cooldown = d.Buttons[i].Cooldown}) end end end local dispActive = {} local listActive = {} local actsActive = {} function Hekili:UpdateDisplayVisibility() local profile = self.DB.profile local displays = ns.UI.Displays for key in pairs( dispActive ) do dispActive[ key ] = nil end for list in pairs( listActive ) do listActive[ list ] = nil end for a in pairs( actsActive ) do actsActive[ a ] = nil end local specEnabled = GetSpecialization() specEnabled = specEnabled and GetSpecializationInfo( specEnabled ) specEnabled = specEnabled and profile.specs[ specEnabled ] specEnabled = specEnabled and specEnabled.enabled or false if profile.enabled and specEnabled then for i, display in pairs( profile.displays ) do if display.enabled then if i == 'AOE' then dispActive[i] = ( profile.toggles.mode.value == 'dual' or profile.toggles.mode.value == "reactive" ) and 1 or nil elseif i == 'Interrupts' then dispActive[i] = ( profile.toggles.interrupts.value and profile.toggles.interrupts.separate ) and 1 or nil elseif i == 'Defensives' then dispActive[i] = ( profile.toggles.defensives.value and profile.toggles.defensives.separate ) and 1 or nil elseif i == 'Cooldowns' then dispActive[i] = ( profile.toggles.cooldowns.value and profile.toggles.cooldowns.separate ) and 1 or nil else dispActive[i] = 1 end if dispActive[i] == nil and self.Config then dispActive[i] = 2 end if dispActive[i] and displays[i] then if not displays[i].Active then displays[i]:Activate() end displays[i].NewRecommendations = true end else if displays[i] and displays[i].Active then displays[i]:Deactivate() end end end for packName, pack in pairs( profile.packs ) do if pack.spec == 0 or pack.spec == state.spec.id then for listName, list in pairs( pack.lists ) do listActive[ packName .. ":" .. listName ] = true -- NYI: We can cache if abilities are disabled here as well to reduce checking in ProcessHooks. for a, entry in ipairs( list ) do if entry.enabled and entry.action then actsActive[ packName .. ":" .. listName .. ":" .. a ] = true end end end end end end for i, d in pairs(ns.UI.Displays) do d:UpdateAlpha() end end function Hekili:ReviewPacks() local profile = self.DB.profile for list in pairs( listActive ) do listActive[ list ] = nil end for a in pairs( actsActive ) do actsActive[ a ] = nil end for packName, pack in pairs( profile.packs ) do if pack.spec == 0 or pack.spec == state.spec.id then for listName, list in pairs( pack.lists ) do listActive[ packName .. ":" .. listName ] = true -- NYI: We can cache if abilities are disabled here as well to reduce checking in ProcessHooks. for a, entry in ipairs( list ) do if entry.enabled and entry.action and class.abilities[ entry.action ] then actsActive[ packName .. ":" .. listName .. ":" .. a ] = true end end end end end end function Hekili:IsDisplayActive( display, config ) if config then return dispActive[ display ] == 1 end return dispActive[display] ~= nil end function Hekili:IsListActive( pack, list ) return pack == "UseItems" or ( listActive[ pack .. ":" .. list ] == true ) end function Hekili:IsActionActive( pack, list, action ) return pack == "UseItems" or ( actsActive[ pack .. ":" .. list .. ":" .. action ] == true ) end function Hekili:DumpActionActive() DevTools_Dump( actsActive ) end local firstForceRequest = 0 function Hekili:ForceUpdate( event, super ) Hekili.freshFrame = false for i, d in pairs( ns.UI.Displays ) do d.criticalUpdate = true if super then d.superUpdate = true end if d.firstForce == 0 then d.firstForce = GetTime() end if event then d.eventsTriggered[ event ] = true end end end local LSM = LibStub("LibSharedMedia-3.0", true) local LRC = LibStub("LibRangeCheck-2.0") local LSR = LibStub("SpellRange-1.0") function Hekili:CreateButton( dispID, id ) local d = dPool[ dispID ] if not d then return end local conf = rawget( self.DB.profile.displays, dispID ) if not conf then return end ns.queue[ dispID ][ id ] = ns.queue[ dispID ][ id ] or {} local bName = "Hekili_" .. dispID .. "_B" .. id local b = d.Buttons[ id ] or CreateFrame( "Button", bName, d ) b.display = dispID b.index = id local scale = self:GetScale() local borderOffset = 0 if conf.border.enabled and conf.border.fit then borderOffset = 2 end if id == 1 then b:SetHeight( scale * ( ( conf.primaryHeight or 50 ) - borderOffset ) ) b:SetWidth( scale * ( ( conf.primaryWidth or 50 ) - borderOffset ) ) else b:SetHeight( scale * ( ( conf.queue.height or 30 ) - borderOffset ) ) b:SetWidth( scale * ( ( conf.queue.width or 50 ) - borderOffset ) ) end -- Texture if not b.Texture then b.Texture = b:CreateTexture( nil, "ARTWORK" ) b.Texture:SetTexture( "Interface\\ICONS\\Spell_Nature_BloodLust" ) b.Texture:SetAllPoints( b ) end b.texCoords = b.texCoords or {} local zoom = 1 - ( ( conf.zoom or 0) / 200 ) if conf.keepAspectRatio then local biggest = id == 1 and max( conf.primaryHeight, conf.primaryWidth ) or max( conf.queue.height, conf.queue.width ) local height = 0.5 * zoom * ( id == 1 and conf.primaryHeight or conf.queue.height ) / biggest local width = 0.5 * zoom * ( id == 1 and conf.primaryWidth or conf.queue.width ) / biggest b.texCoords[1] = 0.5 - width b.texCoords[2] = 0.5 + width b.texCoords[3] = 0.5 - height b.texCoords[4] = 0.5 + height b.Texture:SetTexCoord( unpack( b.texCoords ) ) else local zoom = zoom / 2 b.texCoords[1] = 0.5 - zoom b.texCoords[2] = 0.5 + zoom b.texCoords[3] = 0.5 - zoom b.texCoords[4] = 0.5 + zoom b.Texture:SetTexCoord( unpack( b.texCoords ) ) end -- Initialize glow/noop if button has not yet been glowed. b.glowing = b.glowing or false b.glowStop = b.glowStop or function () end -- Indicator Icons. b.Icon = b.Icon or b:CreateTexture( nil, "OVERLAY" ) b.Icon: SetSize( max( 10, b:GetWidth() / 3 ), max( 10, b:GetHeight() / 3 ) ) if conf.keepAspectRatio and b.Icon:GetHeight() ~= b.Icon:GetWidth() then local biggest = max( b.Icon:GetHeight(), b.Icon:GetWidth() ) local height = 0.5 * b.Icon:GetHeight() / biggest local width = 0.5 * b.Icon:GetWidth() / biggest b.Icon:SetTexCoord( 0.5 - width, 0.5 + width, 0.5 - height, 0.5 + height ) else b.Icon:SetTexCoord( 0, 1, 0, 1 ) end local iconAnchor = conf.indicators.anchor or "RIGHT" b.Icon:ClearAllPoints() b.Icon:SetPoint( iconAnchor, b, iconAnchor, conf.indicators.x or 0, conf.indicators.y or 0 ) b.Icon:Hide() -- Caption Text. b.Caption = b.Caption or b:CreateFontString( bName .. "_Caption", "OVERLAY" ) local captionFont = conf.captions.font or conf.font b.Caption:SetFont( LSM:Fetch("font", captionFont), conf.captions.fontSize or 12, conf.captions.fontStyle or "OUTLINE" ) local capAnchor = conf.captions.anchor or "BOTTOM" b.Caption:ClearAllPoints() b.Caption:SetPoint( capAnchor, b, capAnchor, conf.captions.x or 0, conf.captions.y or 0 ) b.Caption:SetSize( b:GetWidth(), max( 12, b:GetHeight() / 2 ) ) b.Caption:SetJustifyV( capAnchor ) b.Caption:SetJustifyH( conf.captions.align or "CENTER" ) b.Caption:SetTextColor( unpack( conf.captions.color ) ) local capText = b.Caption:GetText() b.Caption:SetText( nil ) b.Caption:SetText( capText ) -- Keybinding Text b.Keybinding = b.Keybinding or b:CreateFontString(bName .. "_KB", "OVERLAY") local queued = id > 1 and conf.keybindings.separateQueueStyle local kbFont = queued and conf.keybindings.queuedFont or conf.keybindings.font or conf.font b.Keybinding:SetFont( LSM:Fetch("font", kbFont), queued and conf.keybindings.queuedFontSize or conf.keybindings.fontSize or 12, queued and conf.keybindings.queuedFontStyle or conf.keybindings.fontStyle or "OUTLINE" ) local kbAnchor = conf.keybindings.anchor or "TOPRIGHT" b.Keybinding:ClearAllPoints() b.Keybinding:SetPoint( kbAnchor, b, kbAnchor, conf.keybindings.x or 0, conf.keybindings.y or 0 ) b.Keybinding:SetSize( 0, 0 ) b.Keybinding:SetTextColor( unpack( queued and conf.keybindings.queuedColor or conf.keybindings.color ) ) local kbText = b.Keybinding:GetText() b.Keybinding:SetText( nil ) b.Keybinding:SetText( kbText ) -- Cooldown Wheel b.Cooldown = b.Cooldown or CreateFrame( "Cooldown", bName .. "_Cooldown", b, "CooldownFrameTemplate" ) b.Cooldown:ClearAllPoints() b.Cooldown:SetAllPoints( b ) -- b.Cooldown:SetFrameStrata( "MEDIUM" ) -- b.Cooldown:SetFrameLevel( 50 ) b.Cooldown:SetDrawBling( false ) b.Cooldown:SetDrawEdge( false ) if _G["ElvUI"] and ( ( id == 1 and conf.elvuiCooldown ) or ( id > 1 and conf.queue.elvuiCooldown ) ) then local E = unpack( ElvUI ) local cd = b.Cooldown.CooldownSettings or {} cd.font = E.Libs.LSM:Fetch( "font", E.db.cooldown.fonts.font ) cd.fontSize = E.db.cooldown.fonts.fontSize cd.fontOutline = E.db.cooldown.fonts.fontOutline b.Cooldown.CooldownSettings = cd if not b.Cooldown.elvRegistered then E:RegisterCooldown( b.Cooldown ) b.Cooldown.elvRegistered = true end d.forceElvUpdate = true end -- Backdrop (for borders) b.Backdrop = b.Backdrop or Mixin( CreateFrame("Frame", bName .. "_Backdrop", b ), BackdropTemplateMixin ) b.Backdrop:ClearAllPoints() b.Backdrop:SetWidth( b:GetWidth() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) ) b.Backdrop:SetHeight( b:GetHeight() + ( conf.border.thickness and ( 2 * conf.border.thickness ) or 2 ) ) local framelevel = b:GetFrameLevel() if framelevel > 0 then -- b.Backdrop:SetFrameStrata( "MEDIUM" ) b.Backdrop:SetFrameLevel( framelevel - 1 ) else local lowerStrata = frameStratas[ b:GetFrameStrata() ] lowerStrata = frameStratas[ lowerStrata - 1 ] b.Backdrop:SetFrameStrata( lowerStrata or "LOW" ) end b.Backdrop:SetPoint( "CENTER", b, "CENTER" ) b.Backdrop:Hide() if conf.border.enabled then b.Backdrop:SetBackdrop( { bgFile = nil, edgeFile = "Interface\\Buttons\\WHITE8X8", tile = false, tileSize = 0, edgeSize = conf.border.thickness or 1, insets = { left = -1, right = -1, top = -1, bottom = -1 } } ) if conf.border.coloring == 'custom' then b.Backdrop:SetBackdropBorderColor( unpack( conf.border.color ) ) else b.Backdrop:SetBackdropBorderColor( RAID_CLASS_COLORS[ class.file ]:GetRGBA() ) end b.Backdrop:Show() else b.Backdrop:SetBackdrop( nil ) b.Backdrop:SetBackdropColor( 0, 0, 0, 0 ) b.Backdrop:Hide() end -- Primary Icon Stuff if id == 1 then -- Anchoring stuff for the queue. b:ClearAllPoints() b:SetPoint( "CENTER", d, "CENTER" ) -- Target Counter b.Targets = b.Targets or b:CreateFontString( bName .. "_Targets", "OVERLAY" ) local tarFont = conf.targets.font or conf.font b.Targets:SetFont( LSM:Fetch( "font", tarFont ), conf.targets.fontSize or 12, conf.targets.fontStyle or "OUTLINE" ) local tarAnchor = conf.targets.anchor or "BOTTOM" b.Targets:ClearAllPoints() b.Targets:SetPoint( tarAnchor, b, tarAnchor, conf.targets.x or 0, conf.targets.y or 0 ) b.Targets:SetSize( b:GetWidth(), b:GetHeight() / 2 ) b.Targets:SetJustifyH( tarAnchor:match("RIGHT") and "RIGHT" or ( tarAnchor:match( "LEFT" ) and "LEFT" or "CENTER" ) ) b.Targets:SetJustifyV( tarAnchor:match("TOP") and "TOP" or ( tarAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE" ) ) b.Targets:SetTextColor( unpack( conf.targets.color ) ) local tText = b.Targets:GetText() b.Targets:SetText( nil ) b.Targets:SetText( tText ) -- Aura Counter -- Disabled for Now --[[ b.Auras = b.Auras or b:CreateFontString(bName .. "_Auras", "OVERLAY") local auraFont = conf.auraFont or (ElvUI and "PT Sans Narrow" or "Arial Narrow") b.Auras:SetFont(LSM:Fetch("font", auraFont), conf.auraFontSize or 12, conf.auraFontStyle or "OUTLINE") b.Auras:SetSize(b:GetWidth(), b:GetHeight() / 2) local auraAnchor = conf.auraAnchor or "BOTTOM" b.Auras:ClearAllPoints() b.Auras:SetPoint(auraAnchor, b, auraAnchor, conf.xOffsetAuras or 0, conf.yOffsetAuras or 0) b.Auras:SetJustifyH( auraAnchor:match("RIGHT") and "RIGHT" or (auraAnchor:match("LEFT") and "LEFT" or "CENTER") ) b.Auras:SetJustifyV( auraAnchor:match("TOP") and "TOP" or (auraAnchor:match("BOTTOM") and "BOTTOM" or "MIDDLE") ) b.Auras:SetTextColor(1, 1, 1, 1) ]] -- Delay Counter b.DelayText = b.DelayText or b:CreateFontString( bName .. "_DelayText", "OVERLAY" ) local delayFont = conf.delays.font or conf.font b.DelayText:SetFont( LSM:Fetch("font", delayFont), conf.delays.fontSize or 12, conf.delays.fontStyle or "OUTLINE" ) local delayAnchor = conf.delays.anchor or "TOPLEFT" b.DelayText:ClearAllPoints() b.DelayText:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x, conf.delays.y or 0 ) b.DelayText:SetSize( b:GetWidth(), b:GetHeight() / 2 ) b.DelayText:SetJustifyH( delayAnchor:match( "RIGHT" ) and "RIGHT" or ( delayAnchor:match( "LEFT" ) and "LEFT" or "CENTER") ) b.DelayText:SetJustifyV( delayAnchor:match( "TOP" ) and "TOP" or ( delayAnchor:match( "BOTTOM" ) and "BOTTOM" or "MIDDLE") ) b.DelayText:SetTextColor( unpack( conf.delays.color ) ) local dText = b.DelayText:GetText() b.DelayText:SetText( nil ) b.DelayText:SetText( dText ) -- Delay Icon b.DelayIcon = b.DelayIcon or b:CreateTexture( bName .. "_DelayIcon", "OVERLAY" ) b.DelayIcon:SetSize( min( 20, max( 10, b:GetSize() / 3 ) ), min( 20, max( 10, b:GetSize() / 3 ) ) ) b.DelayIcon:SetTexture( "Interface\\FriendsFrame\\StatusIcon-Online" ) b.DelayIcon:SetDesaturated( true ) b.DelayIcon:SetVertexColor( 1, 0, 0, 1 ) b.DelayIcon:ClearAllPoints() b.DelayIcon:SetPoint( delayAnchor, b, delayAnchor, conf.delays.x or 0, conf.delays.y or 0 ) b.DelayIcon:Hide() -- Overlay (for Pause) b.Overlay = b.Overlay or b:CreateTexture( nil, "OVERLAY" ) b.Overlay:SetAllPoints( b ) b.Overlay:SetTexture( "Interface\\Addons\\Hekili\\Textures\\Pause.blp" ) b.Overlay:SetTexCoord( unpack( b.texCoords ) ) b.Overlay:Hide() elseif id == 2 then -- Anchoring for the remainder. local queueAnchor = conf.queue.anchor or "RIGHT" local qOffsetX = ( conf.queue.offsetX or 5 ) local qOffsetY = ( conf.queue.offsetY or 0 ) b:ClearAllPoints() if queueAnchor:sub( 1, 5 ) == "RIGHT" then local dir, align = "RIGHT", queueAnchor:sub(6) b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, ( borderOffset + qOffsetX ) * scale, qOffsetY * scale ) elseif queueAnchor:sub( 1, 4 ) == "LEFT" then local dir, align = "LEFT", queueAnchor:sub(5) b:SetPoint( align .. getInverseDirection(dir), "Hekili_" .. dispID .. "_B1", align .. dir, -1 * ( borderOffset + qOffsetX ) * scale, qOffsetY * scale ) elseif queueAnchor:sub( 1, 3) == "TOP" then local dir, align = "TOP", queueAnchor:sub(4) b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, ( borderOffset + qOffsetY ) * scale ) else -- BOTTOM local dir, align = "BOTTOM", queueAnchor:sub(7) b:SetPoint( getInverseDirection(dir) .. align, "Hekili_" .. dispID .. "_B1", dir .. align, 0, -1 * ( borderOffset + qOffsetY ) * scale ) end else local queueDirection = conf.queue.direction or "RIGHT" local btnSpacing = borderOffset + ( conf.queue.spacing or 5 ) b:ClearAllPoints() if queueDirection == "RIGHT" then b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, btnSpacing * scale, 0 ) elseif queueDirection == "LEFT" then b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, -1 * btnSpacing * scale, 0 ) elseif queueDirection == "TOP" then b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, btnSpacing * scale ) else -- BOTTOM b:SetPoint( getInverseDirection(queueDirection), "Hekili_" .. dispID .. "_B" .. id - 1, queueDirection, 0, -1 * btnSpacing * scale ) end end -- Mover Stuff. b:SetScript("OnMouseDown", Button_OnMouseDown) b:SetScript("OnMouseUp", Button_OnMouseUp) b:SetScript( "OnEnter", function( self ) local H = Hekili --[[ if H.Config then GameTooltip:SetOwner( self, "ANCHOR_TOPRIGHT" ) GameTooltip:SetBackdropColor( 0, 0, 0, 0.8 ) GameTooltip:SetText( "Hekili: " .. dispID ) GameTooltip:AddLine( "Left-click and hold to move.", 1, 1, 1 ) GameTooltip:Show() self:SetMovable( true ) else ]] if ( H.Pause and ns.queue[ dispID ] and ns.queue[ dispID ][ id ] ) then H:ShowDiagnosticTooltip( ns.queue[ dispID ][ id ] ) end end ) b:SetScript( "OnLeave", function(self) GameTooltip:Hide() end ) Hekili:ProfileFrame( bName, b ) b:EnableMouse( false ) b:SetMovable( false ) return b end end -- Builds and maintains the visible UI elements. -- Buttons (as frames) are never deleted, but should get reused effectively. local builtIns = { "Primary", "AOE", "Cooldowns", "Interrupts", "Defensives" } function Hekili:BuildUI() if not Masque then Masque = LibStub( "Masque", true ) if Masque then Masque:Register( addon, MasqueUpdate, self ) MasqueGroup = Masque:Group( addon ) end end local LSM = LibStub( "LibSharedMedia-3.0" ) ns.UI.Keyhandler = ns.UI.Keyhandler or CreateFrame( "Button", "Hekili_Keyhandler", UIParent ) ns.UI.Keyhandler:RegisterForClicks( "AnyDown" ) ns.UI.Keyhandler:SetScript( "OnClick", function( self, button, down ) Hekili:FireToggle( button ) end ) Hekili:ProfileFrame( "KeyhandlerFrame", ns.UI.Keyhandler ) local scaleFactor = self:GetScale() local mouseInteract = self.Pause -- Notification Panel local notif = self.DB.profile.notifications local f = ns.UI.Notification or CreateFrame( "Frame", "HekiliNotification", UIParent ) f:SetSize( notif.width * scaleFactor, notif.height * scaleFactor ) f:SetClampedToScreen( true ) f:ClearAllPoints() f:SetPoint("CENTER", nil, "CENTER", notif.x, notif.y ) f.Text = f.Text or f:CreateFontString( "HekiliNotificationText", "OVERLAY" ) f.Text:SetAllPoints( f ) f.Text:SetFont( LSM:Fetch( "font", notif.font ), notif.fontSize * scaleFactor, notif.fontStyle ) f.Text:SetJustifyV("MIDDLE") f.Text:SetJustifyH("CENTER") f.Text:SetTextColor(1, 1, 1, 1) if not notif.enabled then f:Hide() else f.Text:SetText(nil); f:Show() end ns.UI.Notification = f -- End Notification Panel -- Dropdown Menu. ns.UI.Menu = ns.UI.Menu or CreateFrame("Frame", "HekiliMenu", UIParent, "UIDropDownMenuTemplate") -- Displays for disp in pairs( self.DB.profile.displays ) do self:CreateDisplay( disp ) end self:UpdateDisplayVisibility() --if Hekili.Config then ns.StartConfiguration() end if MasqueGroup then MasqueGroup:ReSkin() end -- Check for a display that has been removed. for display, buttons in ipairs(ns.UI.Buttons) do if not Hekili.DB.profile.displays[display] then for i, _ in ipairs(buttons) do buttons[i]:Hide() end end end if Hekili.Config then ns.StartConfiguration(true) end end local T = ns.lib.Format.Tokens local SyntaxColors = {} function ns.primeTooltipColors() T = ns.lib.Format.Tokens --- Assigns a color to multiple tokens at once. local function Color(Code, ...) for Index = 1, select("#", ...) do SyntaxColors[select(Index, ...)] = Code end end Color( "|cffB266FF", T.KEYWORD ) -- Reserved Words Color( "|cffffffff", T.LEFTCURLY, T.RIGHTCURLY, T.LEFTBRACKET, T.RIGHTBRACKET, T.LEFTPAREN, T.RIGHTPAREN ) Color( "|cffFF66FF", T.UNKNOWN, T.ADD, T.SUBTRACT, T.MULTIPLY, T.DIVIDE, T.POWER, T.MODULUS, T.CONCAT, T.VARARG, T.ASSIGNMENT, T.PERIOD, T.COMMA, T.SEMICOLON, T.COLON, T.SIZE, T.EQUALITY, T.NOTEQUAL, T.LT, T.LTE, T.GT, T.GTE ) Color( "|cFFB2FF66", multiUnpack(ns.keys, ns.attr) ) Color( "|cffFFFF00", T.NUMBER ) Color( "|cff888888", T.STRING, T.STRING_LONG ) Color( "|cff55cc55", T.COMMENT_SHORT, T.COMMENT_LONG ) Color( "|cff55ddcc", -- Minimal standard Lua functions "assert", "error", "ipairs", "next", "pairs", "pcall", "print", "select", "tonumber", "tostring", "type", "unpack", -- Libraries "bit", "coroutine", "math", "string", "table" ) Color( "|cffddaaff", -- Some of WoW's aliases for standard Lua functions -- math "abs", "ceil", "floor", "max", "min", -- string "format", "gsub", "strbyte", "strchar", "strconcat", "strfind", "strjoin", "strlower", "strmatch", "strrep", "strrev", "strsplit", "strsub", "strtrim", "strupper", "tostringall", -- table "sort", "tinsert", "tremove", "wipe" ) end local SpaceLeft = {"(%()"} local SpaceRight = {"(%))"} local DoubleSpace = {"(!=)", "(~=)", "(>=*)", "(<=*)", "(&)", "(||)", "(+)", "(*)", "(-)", "(/)"} local function Format(Code) for Index = 1, #SpaceLeft do Code = Code:gsub("%s-" .. SpaceLeft[Index] .. "%s-", " %1") end for Index = 1, #SpaceRight do Code = Code:gsub("%s-" .. SpaceRight[Index] .. "%s-", "%1 ") end for Index = 1, #DoubleSpace do Code = Code:gsub("%s-" .. DoubleSpace[Index] .. "%s-", " %1 ") end Code = Code:gsub("([^<>~!])(=+)", "%1 %2 ") Code = Code:gsub("%s+", " "):trim() return Code end local key_cache = setmetatable( {}, { __index = function( t, k ) t[k] = k:gsub( "(%S+)%[(%d+)]", "%1.%2" ) return t[k] end }) function Hekili:ShowDiagnosticTooltip( q ) local tt = GameTooltip local fmt = ns.lib.Format -- Grab the default backdrop and copy it with a solid background. local backdrop = GameTooltip:GetBackdrop() if backdrop then backdrop.bgFile = [[Interface\Buttons\WHITE8X8]] --[[ tt:SetBackdrop(backdrop) tt:SetBackdropColor(0, 0, 0, 1) ]] end tt:SetOwner(UIParent, "ANCHOR_CURSOR") tt:SetText(class.abilities[q.actionName].name) tt:AddDoubleLine(q.listName .. " #" .. q.action, "+" .. ns.formatValue(round(q.time or 0, 2)), 1, 1, 1, 1, 1, 1) if q.resources and q.resources[q.resource_type] then tt:AddDoubleLine(q.resource_type, ns.formatValue(q.resources[q.resource_type]), 1, 1, 1, 1, 1, 1) end if q.HookHeader or (q.HookScript and q.HookScript ~= "") then if q.HookHeader then tt:AddLine(" ") tt:AddLine(q.HookHeader) else tt:AddLine(" ") tt:AddLine("Hook Criteria") end if q.HookScript and q.HookScript ~= "" then local Text = Format(q.HookScript) tt:AddLine(fmt.FormatCode(Text, 0, SyntaxColors), 1, 1, 1, 1) end if q.HookElements then local applied = false for k, v in orderedPairs(q.HookElements) do if not applied then tt:AddLine(" ") tt:AddLine("Values") applied = true end if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1) end end end end if q.ReadyScript and q.ReadyScript ~= "" then tt:AddLine(" ") tt:AddLine("Time Script") local Text = Format(q.ReadyScript) tt:AddLine(fmt.FormatCode(Text, 0, SyntaxColors), 1, 1, 1, 1) if q.ReadyElements then tt:AddLine("Values") for k, v in orderedPairs(q.ReadyElements) do if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1) end end end end if q.ActScript and q.ActScript ~= "" then tt:AddLine(" ") tt:AddLine("Action Criteria") local Text = Format(q.ActScript) tt:AddLine(fmt.FormatCode(Text, 0, SyntaxColors), 1, 1, 1, 1) if q.ActElements then tt:AddLine(" ") tt:AddLine("Values") for k, v in orderedPairs(q.ActElements) do if not key_cache[k]:find( "safebool" ) and not key_cache[k]:find( "safenum" ) and not key_cache[k]:find( "ceil" ) and not key_cache[k]:find( "floor" ) then tt:AddDoubleLine( key_cache[ k ], ns.formatValue(v), 1, 1, 1, 1, 1, 1) end end end end if q.pack and q.listName and q.action then local entry = rawget( self.DB.profile.packs, q.pack ) entry = entry and entry.lists[ q.listName ] entry = entry and entry[ q.action ] if entry and entry.description and entry.description:len() > 0 then tt:AddLine( " " ) tt:AddLine( entry.description, 0, 0.7, 1, true ) end end tt:Show() end function Hekili:SaveCoordinates() for i in pairs(Hekili.DB.profile.displays) do local _, _, rel, x, y = ns.UI.Displays[i]:GetPoint() self.DB.profile.displays[i].rel = "CENTER" self.DB.profile.displays[i].x = x self.DB.profile.displays[i].y = y end _, _, _, self.DB.profile.notifications.x, self.DB.profile.notifications.y = HekiliNotification:GetPoint() end