WoWInterface SVN MacroSequence

[/] [trunk/] [MacroSequence/] [MacroSequence.lua] - Rev 4

Compare with Previous | Blame | View Log

-- Adapted from code appearing in World of Warcraft Programming: A Guide and
-- Reference for Creating WoW Addons. http://wowprogramming.com

MacroSequence = MacroSequence or {}
local L = MacroSequenceLocals
local print = CogsUtils:GetPrint(L.PRINT_HEADER)



-- Initialize saved variable from Sequences.lua
if MacroSequence.sequences then
        MacroSequenceSequences = MacroSequence.sequences
        print(L.IMPORTED)
end



function MacroSequence:Initialize()
        if not MacroSequenceSequences then
                MacroSequenceSequences = {}
        end

        for name, data in pairs(MacroSequenceSequences) do
                if _G[name] then
                        print(format(L.NAME_EXISTS, name))
                else
                        self:CreateSequence(name, data)
                end
        end
end



-- No global variable of name should exist
function MacroSequence:CreateSequence(name, data)
        local button = self:NewButton(name)

        if #data == 0 then
                return
        end
        
        self:ApplySettings(button, data)
end



function MacroSequence:ApplySettings(button, data)
        -- Add entries to the sequence
        self:CreateSequenceEntries(button, data)
        
        -- Only add states and reset conditions if there are multiple entries
        if #data > 1 then
                -- Set up a newstate attribute to cycle through each possible state
                button:SetAttribute("newstate", "0-"..(#data - 1))
                
                -- Set up reset conditions for the sequence
                self:ApplyResetConditions(button, data)
        end
end



function MacroSequence:RemoveSettings(button)
        button:GetParent():SetAttribute("state", 0)
        for attribute in pairs(button.attributes) do
                button:SetAttribute(attribute, nil)
        end
end



MacroSequence.freeHeaders = {}
MacroSequence.usedHeaders = {}
MacroSequence.namedButtons = {}
function MacroSequence:NewButton(name)
        local header = tremove(self.freeHeaders) or CreateFrame(
                "Frame",
                nil,
                nil,
                "SecureStateHeaderTemplate"
        )
        self.usedHeaders[name] = header -- Add the header to the used pool
        
        local button = self.namedButtons[name]
        if not button then
                button = CreateFrame(
                        "Button",
                        name,
                        header,
                        "SecureActionButtonTemplate"
                )
                self.namedButtons[name] = button
                
                button:SetAttribute("type", "macro")
                
                -- Store attributes for possible removal
                button.attributes = {}
                button:SetScript("OnAttributeChanged", function(button, name, value)
                        if value == nil then
                                button.attributes[name] = nil
                                if name == "_combatreset" then
                                        button:UnregisterEvent("PLAYER_REGEN_ENABLED")
                                end
                        else
                                button.attributes[name] = true
                                if name == "_combatreset" then
                                        button:RegisterEvent("PLAYER_REGEN_ENABLED")
                                end
                        end
                end)
                
                -- Used for combat reset, if present
                button:SetScript("OnEvent", function(button)
                        header:SetAttribute("state", 0)
                end)
        end
        header:SetAttribute("addchild", button)
        header.button = button
        _G[name] = button                                               -- Make a global reference to the button
        
        return header.button
end



-- Unused child buttons are parented to this header to satisfy assumptions made
-- by the state header code
local dummyHeader = CreateFrame(
        "Frame",
        "MacroSequenceDummyHeader",
        UIParent,
        "SecureStateHeaderTemplate"
)
function MacroSequence:RemoveSequence(name)
        local header = self.usedHeaders[name]
        local button = header.button
        
        -- Clear the button's attributes
        self:RemoveSettings(button)
        button:SetParent(dummyHeader)
        
        -- Remove the sequence from various tables
        MacroSequenceSequences[name] = nil
        _G[name] = nil
        self.usedHeaders[name] = nil
        
        -- Put the header into the free pool
        tinsert(self.freeHeaders, header)
end



local function deepCopy(inTable)
        local outTable = {}
        for k, v in pairs(inTable) do
                if type(v) == "table" then
                        outTable[k] = deepCopy(v)
                else
                        outTable[k] = v
                end
        end
        return outTable
end



-- There must not be a global variable named the same as new
function MacroSequence:CopySequence(old, new)
        MacroSequenceSequences[new] = deepCopy(MacroSequenceSequences[old])
        self:CreateSequence(new, MacroSequenceSequences[new])
        
        local oldHeader = self.usedHeaders[old]
        local newHeader = self.usedHeaders[new]
        local oldButton = oldHeader.button
        local newButton = newHeader.button
        
        -- Copy attributes between buttons
        for name in pairs(oldButton.attributes) do
                newButton:SetAttribute(name, oldButton:GetAttribute(name))
        end
        
        -- Copy state from header
        newHeader:SetAttribute("state", oldHeader:GetAttribute("state"))
end



function MacroSequence:RenameSequence(old, new)
        self:CopySequence(old, new)
        self:RemoveSequence(old)
end



function MacroSequence:CreateSequenceEntries(button, data)
        local statebutton = ""
        for state, macro in ipairs(data) do
                state = state - 1
                -- Add a statebutton entry for the current macro in the form "0:b0;"
                statebutton = statebutton..format("%d:b%1$d;", state)
                -- Create a macrotext attribute using the new statebutton
                button:SetAttribute("*macrotext-b"..state, macro)
        end
        button:SetAttribute("statebutton", statebutton)
end



-- This table describes which prefixes to use for each reset modifier's newstate
-- attribute. For the sake of simplicity, ApplyModifierResets will rewrite
-- certain attributes if multiple modifier resets are used on a single sequence.
local modifierPrefixes = {
        alt = {
                "alt",
                "alt-ctrl",
                "alt-shift",
                "alt-ctrl-shift"
        },
        ctrl = {
                "ctrl",
                "alt-ctrl",
                "ctrl-shift",
                "alt-ctrl-shift"
        },
        shift = {
                "shift",
                "alt-shift",
                "ctrl-shift",
                "alt-ctrl-shift"
        }
}



local resetFuncs = {
        -- Combat
        function(button, data)
                if data.reset.combat then
                        button:SetAttribute("_combatreset", true)
                end
        end,
        
        
        
        -- Timer
        function(button, data)
                local seconds = data.reset.seconds
                if seconds and seconds > 0 then
                        -- Switch to state 0 after the specified delay unless the state changes by
                        -- other means first
                        button:SetAttribute("delaystate", "0")
                        button:SetAttribute("delaytime", seconds)
                end
        end,
        
        
        
        -- Modifiers
        function(button, data)
                for modifier, prefixes in pairs(modifierPrefixes) do
                        if data.reset[modifier] then
                                for _, prefix in ipairs(prefixes) do
                                        -- Make the modified click run the first macro in the sequence
                                        button:SetAttribute(prefix.."-statebutton*", "b0")
                                        -- And then move on to the next
                                        button:SetAttribute(prefix.."-newstate*", "1")
                                end
                        end
                end
        end,
        
        
        
        -- Cycle
        function(button, data)
                local cycle = tonumber(data.reset.cycle)
                if cycle and cycle < #data then
                        -- This converts a newstate attribute from, e.g. "0-5" to "5:3;0-5"
                        button:SetAttribute("newstate", format(
                                "%d:%d;%s",
                                #data - 1,
                                #data - cycle,
                                button:GetAttribute("newstate")
                        ))
                end
        end
}


function MacroSequence:ApplyResetConditions(button, data)
        if not data.reset then
                return
        end
        for _, func in ipairs(resetFuncs) do
                func(button, data)
        end
end



function MacroSequence:ShowGUI()
        if not IsAddOnLoaded("MacroSequenceGUI") then
                LoadAddOn("MacroSequenceGUI")
        end
        MacroSequenceGUI:Show()
end



local events = {
        ["VARIABLES_LOADED"] = function()
                MacroSequence:Initialize()
        end,
        ["PLAYER_DEAD"] = function()
                for _, header in ipairs(MacroSequence.usedHeaders) do
                        header:SetAttribute("state", 0)
                end
        end,
}
local handler = CreateFrame("Frame")
for name, func in pairs(events) do
        handler:RegisterEvent(name)
end
handler:SetScript("OnEvent", function(self, event, ...)
        local func = events[event]
        if func then
                func(event, ...)
        end
end)



SLASH_MACROSEQUENCE1 = "/macrosequence"
SLASH_MACROSEQUENCE2 = "/sequence"
SLASH_MACROSEQUENCE3 = "/seq"
SlashCmdList["MACROSEQUENCE"] = function(msg)
        MacroSequence:ShowGUI()
end

Compare with Previous | Blame