local SharedMedia = LibStub('LibSharedMedia-3.0'); local AceGUI = LibStub('AceGUI-3.0'); MaxDps.Specs = { [1] = { [1] = 'Arms', [2] = 'Fury', [3] = 'Protection', }, [2] = { [1] = 'Holy', [2] = 'Protection', [3] = 'Retribution', }, [3] = { [1] = 'BeastMastery', [2] = 'Marksmanship', [3] = 'Survival', }, [4] = { [1] = 'Assassination', [2] = 'Outlaw', [3] = 'Subtlety', }, [5] = { [1] = 'Discipline', [2] = 'Holy', [3] = 'Shadow', }, [6] = { [1] = 'Blood', [2] = 'Frost', [3] = 'Unholy', }, [7] = { [1] = 'Elemental', [2] = 'Enhancement', [3] = 'Restoration', }, [8] = { [1] = 'Arcane', [2] = 'Fire', [3] = 'Frost', }, [9] = { [1] = 'Affliction', [2] = 'Demonology', [3] = 'Destruction', }, [10] = { [1] = 'Brewmaster', [2] = 'Mistweaver', [3] = 'Windwalker', }, [11] = { [1] = 'Balance', [2] = 'Feral', [3] = 'Guardian', [4] = 'Restoration', }, [12] = { [1] = 'Havoc', [2] = 'Vengeance', }, } MaxDps.CustomRotations = {}; StaticPopupDialogs['REMOVE_MAXDPS_ROTATION'] = { text = 'Are you sure?', button1 = 'Yes', button2 = 'No', OnAccept = function() MaxDps:RemoveCustomRotation(); end, OnCancel = function (_,reason) end, whileDead = true, hideOnEscape = true, } AceGUI:RegisterLayout('2Columns3', function(content, children) if children[1] then children[1]:SetWidth(200) children[1].frame:SetPoint('TOPLEFT', content, 'TOPLEFT', 0, 0) children[1].frame:SetPoint('BOTTOMLEFT', content, 'BOTTOMLEFT', 0, 0) children[1].frame:Show(); if children[1].DoLayout then children[1]:DoLayout() end end if children[2] then children[2].frame:SetPoint('TOPLEFT', children[1].frame, 'TOPRIGHT', 0, 0) children[2].frame:SetPoint('RIGHT', content, 'RIGHT', 0, 0) children[2]:SetHeight(100) children[2].frame:Show(); if children[2].DoLayout then children[2]:DoLayout() end end if children[3] then children[3].frame:SetPoint('TOPLEFT', children[2].frame, 'BOTTOMLEFT', 0, 0) children[3].frame:SetPoint('BOTTOMRIGHT', content, 'BOTTOMRIGHT', 0, 0) children[3].frame:Show(); if children[3].DoLayout then children[3]:DoLayout() end end if(content.obj.LayoutFinished) then content.obj:LayoutFinished(content.obj, nil, nil); end end) function MaxDps:ShowCustomWindow() if not self.CustomWindow then self.CustomWindow = AceGUI:Create('Window'); self.CustomWindow:SetTitle('MaxDps Custom Rotations'); self.CustomWindow.frame:SetFrameStrata('DIALOG'); self.CustomWindow:SetLayout('2Columns3'); self.CustomWindow:SetWidth(700); self.CustomWindow:SetHeight(550); self.CustomWindow:EnableResize(true); self.CustomWindow:SetCallback('OnClose', function(widget) MaxDps:LoadCustomRotations(); end) local scrollLeft = AceGUI:Create('ScrollFrame'); scrollLeft:SetLayout('Flow'); self.CustomWindow.scrollLeft = scrollLeft; self.CustomWindow:AddChild(scrollLeft); local scrollRight = AceGUI:Create('ScrollFrame'); scrollRight:SetLayout('Flow'); self.CustomWindow:AddChild(scrollRight); -- Rotation Name local rotationName = AceGUI:Create('EditBox'); rotationName:SetLabel('Rotation Name'); rotationName:SetCallback('OnTextChanged', function(self, event, text) if not MaxDps.CurrentEditRotation then return end; MaxDps.CurrentEditRotation.name = text; MaxDps:UpdateCustomRotationButtons(); end); scrollRight:AddChild(rotationName); self.CustomWindow.rotationName = rotationName; -- Rotation Class local rotationClass = AceGUI:Create('Dropdown'); rotationClass:SetLabel('Class'); rotationClass:SetList(MaxDps.Classes); rotationClass:SetCallback('OnValueChanged', function(self, event, key) if not MaxDps.CurrentEditRotation then return end; MaxDps.CurrentEditRotation.class = key; local specs = MaxDps.Specs[key]; if specs then MaxDps.CustomWindow.rotationSpec:SetList(specs); end end); scrollRight:AddChild(rotationClass); self.CustomWindow.rotationClass = rotationClass; -- Rotation Spec local rotationSpec = AceGUI:Create('Dropdown'); rotationSpec:SetLabel('Spec'); rotationSpec:SetCallback('OnValueChanged', function(self, event, key) if not MaxDps.CurrentEditRotation then return end; MaxDps.CurrentEditRotation.spec = key; end); scrollRight:AddChild(rotationSpec); self.CustomWindow.rotationSpec = rotationSpec; -- Rotation Enabled local rotationEnabled = AceGUI:Create('CheckBox'); rotationEnabled:SetLabel('Enabled'); rotationEnabled:SetCallback('OnValueChanged', function(self, event, val) if not MaxDps.CurrentEditRotation then return end; MaxDps.CurrentEditRotation.enabled = val; end); scrollRight:AddChild(rotationEnabled); self.CustomWindow.rotationEnabled = rotationEnabled; -- Rotation Delete local rotationDelete = AceGUI:Create('Button'); rotationDelete:SetText('Remove'); rotationDelete:SetCallback('OnClick', function() if not MaxDps.CurrentEditRotation then return end; StaticPopup_Show('REMOVE_MAXDPS_ROTATION'); end); scrollRight:AddChild(rotationDelete); -- Editor local editor = AceGUI:Create('MultiLineEditBox'); editor:SetLabel('Custom Rotation'); editor.button:Hide(); local fontPath = SharedMedia:Fetch('font', 'Fira Mono Medium'); if(fontPath) then editor.editBox:SetFont(fontPath, 12); end editor:SetCallback('OnTextChanged', function(self, event, value) if not MaxDps.CurrentEditRotation then return end; value = IndentationLib.decode(value); if MaxDps.CurrentEditRotation then MaxDps.CurrentEditRotation.fn = value; end end); self.CustomWindow:AddChild(editor); self.CustomWindow.editor = editor; IndentationLib.enable(editor.editBox, nil, 4); self:UpdateCustomRotationButtons(); self:EnableDisableCustomFields(true, true); end self:DisableRotation(); self.CustomWindow:Show(); end function MaxDps:UpdateCustomRotationButtons() self.CustomWindow.scrollLeft:ReleaseChildren(); local btn = AceGUI:Create('Button'); btn:SetFullWidth(true); btn:SetText('Add Rotation'); btn:SetHeight(40); btn.text:SetTextColor(1, 0, 0); btn:SetCallback('OnClick', function() MaxDps:AddCustomRotation(); end); self.CustomWindow.scrollLeft:AddChild(btn); for k, rotation in pairs(self.db.global.customRotations) do local btn = AceGUI:Create('Button'); btn:SetFullWidth(true); btn:SetText(rotation.name); btn:SetHeight(40); btn:SetCallback('OnClick', function(self, event) for k, btn in pairs(MaxDps.CustomWindow.scrollLeft.children) do if k > 1 then btn.text:SetTextColor(1, 1, 1); end end self.text:SetTextColor(0, 1, 0); MaxDps:EditRotation(rotation); end); if self.CurrentEditRotation == rotation then btn.text:SetTextColor(0, 1, 0); else btn.text:SetTextColor(1, 1, 1); end self.CustomWindow.scrollLeft:AddChild(btn); end self.CustomWindow.scrollLeft:DoLayout(); end function MaxDps:AddCustomRotation() local customRotation = { name = 'New Rotation', enabled = false, class = nil, spec = nil, fn = "function(_, timeShift, currentSpell, gcd, talents)\n \nend", }; tinsert(self.db.global.customRotations, customRotation); self:UpdateCustomRotationButtons(); MaxDps:EditRotation(customRotation); end function MaxDps:RemoveCustomRotation() for k, rotation in pairs(self.db.global.customRotations) do if rotation == MaxDps.CurrentEditRotation then self.db.global.customRotations[k] = nil; end end self.CurrentEditRotation = nil; self:UpdateCustomRotationButtons(); self:EnableDisableCustomFields(true, true); end function MaxDps:EditRotation(rotation) self.CurrentEditRotation = rotation; self.CustomWindow.rotationName:SetText(rotation.name); self.CustomWindow.rotationEnabled:SetValue(rotation.enabled); self.CustomWindow.rotationClass:SetValue(rotation.class); local specs = MaxDps.Specs[rotation.class]; if specs then self.CustomWindow.rotationSpec:SetList(specs); else self.CustomWindow.rotationSpec:SetList({}); end self.CustomWindow.rotationSpec:SetValue(rotation.spec); self.CustomWindow.editor:SetText(IndentationLib.encode(rotation.fn)); self:EnableDisableCustomFields(false); end function MaxDps:EnableDisableCustomFields(flag, clear) clear = clear or false; self.CustomWindow.rotationName:SetDisabled(flag); self.CustomWindow.rotationEnabled:SetDisabled(flag); self.CustomWindow.rotationClass:SetDisabled(flag); self.CustomWindow.rotationSpec:SetDisabled(flag); self.CustomWindow.editor:SetDisabled(flag); if clear then self.CustomWindow.rotationName:SetText(''); self.CustomWindow.rotationEnabled:SetValue(false); self.CustomWindow.rotationClass:SetValue(nil); self.CustomWindow.rotationSpec:SetValue(nil); self.CustomWindow.editor:SetText(''); end end function MaxDps:LoadCustomRotations() for k,v in pairs(self.CustomRotations) do self.CustomRotations[k] = nil; end for k, rotation in pairs(self.db.global.customRotations) do if rotation.enabled then local fn = MaxDps.LoadFunction(rotation.fn); if not self.CustomRotations[rotation.class] then self.CustomRotations[rotation.class] = {} end self.CustomRotations[rotation.class][rotation.spec] = { name = rotation.name, fn = fn } end end self:Print(self.Colors.Info .. 'Custom Rotations Loaded!'); end --[[ Borrowed from WeakAuras This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. For more information see WeakAuras License ]] local blockedFunctions = { getfenv = true, setfenv = true, loadstring = true, pcall = true, SendMail = true, SetTradeMoney = true, AddTradeMoney = true, PickupTradeMoney = true, PickupPlayerMoney = true, TradeFrame = true, MailFrame = true, EnumerateFrames = true, RunScript = true, AcceptTrade = true, SetSendMailMoney = true, EditMacro = true, SlashCmdList = true, DevTools_DumpCommand = true, hash_SlashCmdList = true, CreateMacro = true, SetBindingMacro = true, } local function forbidden() print('|cffffff00A MaxDps just tried to use a forbidden function but has been blocked from doing so.|r'); end local env_getglobal; local exec_env = setmetatable({}, { __index = function(t, k) if k == '_G' then return t; elseif k == 'getglobal' then return env_getglobal; elseif blockedFunctions[k] then return forbidden; else return _G[k]; end end }); local function_cache = {}; function MaxDps.LoadFunction(string) if function_cache[string] then return function_cache[string]; else local loadedFunction, errorString = loadstring('return ' .. string); if errorString then print(errorString); else setfenv(loadedFunction, exec_env); local success, func = pcall(assert(loadedFunction)); if success then function_cache[string] = func; return func; end end end end