--- ============================ HEADER ============================ --- ======= LOCALIZE ======= -- Addon local addonName, HR = ...; -- HeroLib local HL = HeroLib; local Cache = HeroCache; local Unit = HL.Unit; local Player = Unit.Player; local Target = Unit.Target; local Spell = HL.Spell; local Item = HL.Item; local GUI = HL.GUI; local CreatePanelOption = GUI.CreatePanelOption; -- Lua local mathmax = math.max; local mathmin = math.min; local pairs = pairs; local select = select; -- File Locals local Masque; -- , MasqueGroupLeft; local MasqueGroups = {}; local UIFrames; local MasqueFrameList; local PrevResult, CurrResult; local function ModMasque(Frame, Disabled) if Disabled then Frame.__MSQ_Normal:Hide(); Frame.Texture:SetAllPoints( Frame ); if Frame.CooldownFrame then Frame.CooldownFrame:SetAllPoints( Frame ); end if Frame.Backdrop then Frame.Backdrop:Show(); Frame.Backdrop:SetFrameLevel(mathmin(Frame.Backdrop:GetFrameLevel(), 7)); end else if Frame.Backdrop then Frame.Backdrop:Hide(); end end end local function MasqueUpdate( Addon, Group, SkinID, Backdrop, Shadow, Gloss, Colors, Disabled ) if Addon==HR and MasqueGroups and MasqueFrameList then local k = MasqueFrameList[Group]; if k then if type(k.Icon) == "table" then for _, tblIcon in pairs(k.Icon) do ModMasque(tblIcon, Disabled) end else ModMasque(k, Disabled) end end end end --- ============================ CONTENT ============================ --- ======= BINDINGS ======= BINDING_HEADER_HEROROTATION = "HeroRotation"; BINDING_NAME_HEROROTATION_CDS = "Toggle CDs"; BINDING_NAME_HEROROTATION_AOE = "Toggle AoE"; BINDING_NAME_HEROROTATION_TOGGLE = "Toggle On/Off"; BINDING_NAME_HEROROTATION_UNLOCK = "Unlock the addon to move icons"; BINDING_NAME_HEROROTATION_LOCK = "Lock the addon in place"; --- ======= MAIN FRAME ======= HR.MainFrame = CreateFrame("Frame", "HeroRotation_MainFrame", UIParent); HR.MainFrame:SetFrameStrata(HR.GUISettings.General.MainFrameStrata); HR.MainFrame:SetFrameLevel(10); HR.MainFrame:SetWidth(112); HR.MainFrame:SetHeight(96); HR.MainFrame:SetClampedToScreen(true); -- Resize function HR.MainFrame:ResizeUI (Multiplier) local FramesToResize = { -- TODO: Put the Size in one Array in UI.lua and pull it out here {HR.MainFrame, 112, 96}, {HR.MainIconFrame, 64, 64}, {HR.SmallIconFrame, 64, 32}, {HR.SmallIconFrame.Icon[1], HR.GUISettings.General.BlackBorderIcon and 30 or 32, HR.GUISettings.General.BlackBorderIcon and 30 or 32}, {HR.SmallIconFrame.Icon[2], HR.GUISettings.General.BlackBorderIcon and 30 or 32, HR.GUISettings.General.BlackBorderIcon and 30 or 32}, {HR.LeftIconFrame, 48, 48}, {HR.SuggestedIconFrame, 32, 32}, {HR.RightSuggestedIconFrame, 32, 32}, {HR.MainIconPartOverlayFrame, 64, 64}, }; for _, Value in pairs(FramesToResize) do Value[1]:SetWidth(Value[2]*Multiplier); Value[1]:SetHeight(Value[3]*Multiplier); end for i = 1, HR.MaxQueuedCasts do HR.MainIconFrame.Part[i]:SetWidth(64*Multiplier); HR.MainIconFrame.Part[i]:SetHeight(64*Multiplier); end HR.SuggestedIconFrame:SetPoint("BOTTOM", HR.MainIconFrame, "LEFT", -HR.LeftIconFrame:GetWidth()/2, HR.LeftIconFrame:GetHeight()/2+(HR.GUISettings.General.BlackBorderIcon and 3*Multiplier or 4*Multiplier)); HR.RightSuggestedIconFrame:SetPoint("BOTTOM", HR.MainIconFrame, "RIGHT", HR.LeftIconFrame:GetWidth()/2, HR.LeftIconFrame:GetHeight()/2 + (HR.GUISettings.General.BlackBorderIcon and 3*Multiplier or 4*Multiplier)); -- todo matt fix this location HeroRotationDB.GUISettings["Scaling.ScaleUI"] = Multiplier; end function HR.MainFrame:ResizeButtons (Multiplier) local FramesToResize = { -- TODO: Put the Size in one Array in UI.lua and pull it out here {HR.ToggleIconFrame, 64, 20}, {HR.ToggleIconFrame.Button[1], 20, 20}, {HR.ToggleIconFrame.Button[2], 20, 20}, {HR.ToggleIconFrame.Button[3], 20, 20} }; for Key, Value in pairs(FramesToResize) do Value[1]:SetWidth(Value[2]*Multiplier); Value[1]:SetHeight(Value[3]*Multiplier); end for i = 1, 3 do HR.ToggleIconFrame.Button[i]:SetPoint("LEFT", HR.ToggleIconFrame, "LEFT", HR.ToggleIconFrame.Button[i]:GetWidth()*(i-1)+i, 0); end HeroRotationDB.GUISettings["Scaling.ScaleButtons"] = Multiplier; end -- Lock/Unlock local LockSpell = Spell(18282); function HR.MainFrame:Unlock () -- Grey Texture HR.ResetIcons(); HR.Cast(LockSpell); -- Main Icon HR.Cast(LockSpell, {true}); -- Small Icon 1 HR.Cast(LockSpell, {true}); -- Small Icon 2 HR.CastLeft(LockSpell); -- Left Icon HR.CastSuggested(LockSpell); -- Suggested Icon HR.CastRightSuggested(LockSpell); -- Right Suggested Icon -- Unlock the UI for Key, Value in pairs(UIFrames) do Value:EnableMouse(true); end HR.MainFrame:SetMovable(true); HR.ToggleIconFrame:SetMovable(true); HeroRotationDB.Locked = false; end function HR.MainFrame:Lock () for Key, Value in pairs(UIFrames) do Value:EnableMouse(false); end HR.MainFrame:SetMovable(false); HR.ToggleIconFrame:SetMovable(false); HeroRotationDB.Locked = true; end function HR.MainFrame:ToggleLock () if HeroRotationDB.Locked then HR.MainFrame:Unlock (); HR.Print("HeroRotation UI is now |cff00ff00unlocked|r."); else HR.MainFrame:Lock (); HR.Print("HeroRotation UI is now |cffff0000locked|r."); end end -- Start Move local function StartMove (self) self:StartMoving(); end HR.MainFrame:SetScript("OnMouseDown", StartMove); -- Stop Move local function StopMove (self) self:StopMovingOrSizing(); if not HeroRotationDB then HeroRotationDB = {}; end local point, relativeTo, relativePoint, xOffset, yOffset, relativeToName; point, relativeTo, relativePoint, xOffset, yOffset = self:GetPoint(); if not relativeTo then relativeToName = "UIParent"; else relativeToName = relativeTo:GetName(); end HeroRotationDB.IconFramePos = { point, relativeToName, relativePoint, xOffset, yOffset }; end HR.MainFrame:SetScript("OnMouseUp", StopMove); HR.MainFrame:SetScript("OnHide", StopMove); -- AddonLoaded HR.MainFrame:RegisterEvent("ADDON_LOADED"); HR.MainFrame:SetScript("OnEvent", function (self, Event, Arg1) if Event == "ADDON_LOADED" then if Arg1 == "HeroRotation" then MasqueFrameList = { ["Main Icon"] = HR.MainIconFrame, ["Top Icons"] = HR.SmallIconFrame, ["Left Icon"] = HR.LeftIconFrame, ["Suggested Icon"] = HR.SuggestedIconFrame, ["Right Suggested Icon"] = HR.RightSuggestedIconFrame, ["Part Overlay"] = HR.MainIconPartOverlayFrame, }; if not Masque then Masque = LibStub( "Masque", true ) if Masque then Masque:Register( "HeroRotation", MasqueUpdate, HR ) for FrameName, Frame in pairs(MasqueFrameList) do MasqueGroups[Frame] = Masque:Group( addonName, FrameName) end end end -- Panels if type(HeroRotationDB) ~= "table" then HeroRotationDB = {}; end if type(HeroRotationCharDB) ~= "table" then HeroRotationCharDB = {}; end if type(HeroRotationDB.GUISettings) ~= "table" then HeroRotationDB.GUISettings = {}; end if type(HeroRotationCharDB.GUISettings) ~= "table" then HeroRotationCharDB.GUISettings = {}; end HR.GUI.LoadSettingsRecursively(HR.GUISettings); HR.GUI.CorePanelSettingsInit(); -- UI if HeroRotationDB and type(HeroRotationDB.IconFramePos) == "table" and #HeroRotationDB.IconFramePos == 5 then HR.MainFrame:SetPoint(HeroRotationDB.IconFramePos[1], _G[HeroRotationDB.IconFramePos[2]], HeroRotationDB.IconFramePos[3], HeroRotationDB.IconFramePos[4], HeroRotationDB.IconFramePos[5]); else HR.MainFrame:SetPoint("CENTER", UIParent, "CENTER", -200, 0); end HR.MainFrame:SetFrameStrata(HR.GUISettings.General.MainFrameStrata); HR.MainFrame:Show(); HR.MainIconFrame:Init(); HR.SmallIconFrame:Init(); HR.LeftIconFrame:Init(); HR.SuggestedIconFrame:Init(); HR.RightSuggestedIconFrame:Init(); HR.ToggleIconFrame:Init(); if HeroRotationDB.GUISettings["Scaling.ScaleUI"] then HR.MainFrame:ResizeUI(HeroRotationDB.GUISettings["Scaling.ScaleUI"]); end if HeroRotationDB.GUISettings["Scaling.ScaleButtons"] then HR.MainFrame:ResizeButtons(HeroRotationDB.GUISettings["Scaling.ScaleButtons"]); end for k, v in pairs(MasqueFrameList) do if type(v.Icon) == "table" then for _, tblIcon in pairs(v.Icon) do tblIcon.GetNormalTexture = function(self) return nil end; tblIcon.SetNormalTexture = function(self, Texture) self.Texture = Texture end; end else v.GetNormalTexture = function(self) return nil end; v.SetNormalTexture = function(self, Texture) self.Texture = Texture end; end end if MasqueGroups then for k, v in pairs(MasqueGroups) do if type(k.Icon) == "table" then for _, tblIcon in pairs(k.Icon) do if v then v:AddButton( tblIcon, { Icon = tblIcon.Texture, Cooldown = (tblIcon.CooldownFrame or nil) } ) end; end else if v then v:AddButton( k, { Icon = k.Texture, Cooldown = k.CooldownFrame } ) end; end end for k, v in pairs(MasqueGroups) do if v then v:ReSkin() end end end UIFrames = { HR.MainFrame, HR.MainIconFrame, HR.MainIconPartOverlayFrame, HR.MainIconFrame.Part[1], HR.MainIconFrame.Part[2], HR.MainIconFrame.Part[3], HR.SmallIconFrame, HR.SmallIconFrame.Icon[1], HR.SmallIconFrame.Icon[2], HR.LeftIconFrame, HR.SuggestedIconFrame, HR.RightSuggestedIconFrame, HR.ToggleIconFrame }; -- Load additionnal settings local CP_General = GUI.GetPanelByName("General") if CP_General then CreatePanelOption("Slider", CP_General, "General.SetAlpha", {0, 1, 0.05}, "Addon Alpha", "Change the addon's alpha setting."); CreatePanelOption("Button", CP_General, "ButtonMove", "Lock/Unlock", "Enable the moving of the frames.", function() HR.MainFrame:ToggleLock(); end); CreatePanelOption("Button", CP_General, "ButtonReset", "Reset Buttons", "Resets the anchor of buttons.", function() HR.ToggleIconFrame:ResetAnchor(); end); end local CP_Scaling = GUI.GetPanelByName("Scaling") if CP_Scaling then CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleUI", {0.5, 5, 0.1}, "UI Scale", "Scale of the Icons.", function(value) HR.MainFrame:ResizeUI(value); end); CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleButtons", {0.5, 5, 0.1}, "Buttons Scale", "Scale of the Buttons.", function(value) HR.MainFrame:ResizeButtons(value); end); CreatePanelOption("Slider", CP_Scaling, "Scaling.ScaleHotkey", {0.5, 5, 0.1}, "Hotkey Scale", "Scale of the Hotkeys."); end -- Modules C_Timer.After(2, function () HR.MainFrame:UnregisterEvent("ADDON_LOADED"); HR.PulsePreInit(); HR.PulseInit(); end ); end end end ); --- ======= MAIN ======= function HR.PulsePreInit () HR.MainFrame:Lock(); end local EnabledRotation = { -- Death Knight [250] = "HeroRotation_DeathKnight", -- Blood [251] = "HeroRotation_DeathKnight", -- Frost [252] = "HeroRotation_DeathKnight", -- Unholy -- Demon Hunter [577] = "HeroRotation_DemonHunter", -- Havoc [581] = "HeroRotation_DemonHunter", -- Vengeance -- Druid [102] = "HeroRotation_Druid", -- Balance [103] = "HeroRotation_Druid", -- Feral [104] = "HeroRotation_Druid", -- Guardian --[105] = "HeroRotation_Druid", -- Restoration -- Evoker [1467] = "HeroRotation_Evoker", -- Devastation --[1468] = "HeroRotation_Evoker", -- Preservation [1473] = "HeroRotation_Evoker", -- Augmentation -- Hunter [253] = "HeroRotation_Hunter", -- Beast Mastery [254] = "HeroRotation_Hunter", -- Marksmanship [255] = "HeroRotation_Hunter", -- Survival -- Mage [62] = "HeroRotation_Mage", -- Arcane [63] = "HeroRotation_Mage", -- Fire [64] = "HeroRotation_Mage", -- Frost -- Monk [268] = "HeroRotation_Monk", -- Brewmaster [269] = "HeroRotation_Monk", -- Windwalker --[270] = "HeroRotation_Monk", -- Mistweaver -- Paladin --[65] = "HeroRotation_Paladin", -- Holy [66] = "HeroRotation_Paladin", -- Protection [70] = "HeroRotation_Paladin", -- Retribution -- Priest --[256] = "HeroRotation_Priest", -- Discipline --[257] = "HeroRotation_Priest", -- Holy [258] = "HeroRotation_Priest", -- Shadow -- Rogue [259] = "HeroRotation_Rogue", -- Assassination [260] = "HeroRotation_Rogue", -- Outlaw [261] = "HeroRotation_Rogue", -- Subtlety -- Shaman [262] = "HeroRotation_Shaman", -- Elemental [263] = "HeroRotation_Shaman", -- Enhancement --[264] = "HeroRotation_Shaman", -- Restoration -- Warlock [265] = "HeroRotation_Warlock", -- Affliction [266] = "HeroRotation_Warlock", -- Demonology [267] = "HeroRotation_Warlock", -- Destruction -- Warrior [71] = "HeroRotation_Warrior", -- Arms [72] = "HeroRotation_Warrior", -- Fury [73] = "HeroRotation_Warrior" -- Protection }; local LatestSpecIDChecked = 0; function HR.PulseInit () local Spec = GetSpecialization(); -- Delay by 1 second until the WoW API returns a valid value. if Spec == nil then HL.PulseInitialized = false; C_Timer.After(1, function () HR.PulseInit(); end ); else -- Force a refresh from the Core -- TODO: Make it a function instead of copy/paste from Core Events.lua Cache.Persistent.Player.Spec = {GetSpecializationInfo(Spec)}; local SpecID = Cache.Persistent.Player.Spec[1]; -- Delay by 1 second until the WoW API returns a valid value. if SpecID == nil then HL.PulseInitialized = false; C_Timer.After(1, function () HR.PulseInit(); end ); else -- Load the Class Module if it's possible and not already loaded if EnabledRotation[SpecID] and not IsAddOnLoaded(EnabledRotation[SpecID]) then LoadAddOn(EnabledRotation[SpecID]); HL.LoadOverrides(SpecID) end -- Check if there is a Rotation for this Spec if LatestSpecIDChecked ~= SpecID then if EnabledRotation[SpecID] and HR.APLs[SpecID] then for Key, Value in pairs(UIFrames) do Value:Show(); end HR.MainFrame:SetScript("OnUpdate", HR.Pulse); -- Spec Registers -- Spells Player:RegisterListenedSpells(SpecID); HL.UnregisterAuraTracking(); -- Enums Filters Player:FilterTriggerGCD(SpecID); Spell:FilterProjectileSpeed(SpecID); -- Module Init Function if HR.APLInits[SpecID] then HR.APLInits[SpecID](); end -- Special Checks if GetCVar("nameplateShowEnemies") ~= "1" then HR.Print("It looks like enemy nameplates are disabled. This may cause the addon to operate incorrectly, leading to incorrect or missing spell suggestions."); end else HR.Print("No Rotation found for this class/spec (SpecID: ".. SpecID .. "), addon disabled. This is likely due to the rotation being unsupported at this time. Please check supported rotations here: https://github.com/herotc/hero-rotation#supported-rotations"); for Key, Value in pairs(UIFrames) do Value:Hide(); end HR.MainFrame:SetScript("OnUpdate", nil); end LatestSpecIDChecked = SpecID; end if not HL.PulseInitialized then HL.PulseInitialized = true; end end end end HR.Timer = { Pulse = 0 }; function HR.Pulse () if GetTime() > HR.Timer.Pulse and HR.Locked() then HR.Timer.Pulse = GetTime() + HL.Timer.PulseOffset; HR.ResetIcons(); -- Check if the current spec is available (might not always be the case) -- Especially when switching from area (open world -> instance) local SpecID = Cache.Persistent.Player.Spec[1]; if SpecID then -- Check if we are ready to cast something to save FPS. if HR.ON() and HR.Ready() then HL.CacheHasBeenReset = false; Cache.Reset(); -- Rotational Debug Output if HR.DebugON() then CurrResult = HR.APLs[SpecID](); if CurrResult and CurrResult ~= PrevResult then HR.Print(CurrResult); PrevResult = CurrResult; end else HR.APLs[SpecID](); end end if MasqueGroups then for k, v in pairs(MasqueGroups) do if v then v:ReSkin() end end end end end end -- Is the player ready ? function HR.Ready () local AreWeReady if HR.GUISettings.General.ShowWhileMounted then AreWeReady = not Player:IsDeadOrGhost() and not Player:IsInVehicle() and not C_PetBattles.IsInBattle(); else AreWeReady = not Player:IsDeadOrGhost() and not Player:IsMounted() and not Player:IsInVehicle() and not C_PetBattles.IsInBattle(); end return AreWeReady end -- Used to force a short/long pulse wait, it also resets the icons. function HR.ChangePulseTimer (Offset) HR.ResetIcons(); HR.Timer.Pulse = GetTime() + Offset; end