diff --git a/Libs/AceAddon-3.0/AceAddon-3.0.lua b/Libs/AceAddon-3.0/AceAddon-3.0.lua new file mode 100644 index 0000000..a7f7279 --- /dev/null +++ b/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -0,0 +1,674 @@ +--- **AceAddon-3.0** provides a template for creating addon objects. +-- It'll provide you with a set of callback functions that allow you to simplify the loading +-- process of your addon.\\ +-- Callbacks provided are:\\ +-- * **OnInitialize**, which is called directly after the addon is fully loaded. +-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. +-- * **OnDisable**, which is only called when your addon is manually being disabled. +-- @usage +-- -- A small (but complete) addon, that doesn't do anything, +-- -- but shows usage of the callbacks. +-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- +-- function MyAddon:OnInitialize() +-- -- do init tasks here, like loading the Saved Variables, +-- -- or setting up slash commands. +-- end +-- +-- function MyAddon:OnEnable() +-- -- Do more initialization here, that really enables the use of your addon. +-- -- Register Events, Hook functions, Create Frames, Get information from +-- -- the game that wasn't available in OnInitialize +-- end +-- +-- function MyAddon:OnDisable() +-- -- Unhook, Unregister Events, Hide frames that you created. +-- -- You would probably only use an OnDisable if you want to +-- -- build a "standby" mode, or be able to toggle modules on/off. +-- end +-- @class file +-- @name AceAddon-3.0.lua +-- @release $Id: AceAddon-3.0.lua 1084 2013-04-27 20:14:11Z nevcairiel $ + +local MAJOR, MINOR = "AceAddon-3.0", 12 +local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceAddon then return end -- No Upgrade needed. + +AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame +AceAddon.addons = AceAddon.addons or {} -- addons in general +AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. +AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized +AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled +AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon + +-- Lua APIs +local tinsert, tconcat, tremove = table.insert, table.concat, table.remove +local fmt, tostring = string.format, tostring +local select, pairs, next, type, unpack = select, pairs, next, type, unpack +local loadstring, assert, error = loadstring, assert, error +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler + +--[[ + xpcall safecall implementation +]] +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end + +local function safecall(func, ...) + -- we check to see if the func is passed is actually a function here and don't error when it isn't + -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not + -- present execution should continue without hinderance + if type(func) == "function" then + return Dispatchers[select('#', ...)](func, ...) + end +end + +-- local functions that will be implemented further down +local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype + +-- used in the addon metatable +local function addontostring( self ) return self.name end + +-- Check if the addon is queued for initialization +local function queuedForInitialization(addon) + for i = 1, #AceAddon.initializequeue do + if AceAddon.initializequeue[i] == addon then + return true + end + end + return false +end + +--- Create a new AceAddon-3.0 addon. +-- Any libraries you specified will be embeded, and the addon will be scheduled for +-- its OnInitialize and OnEnable callbacks. +-- The final addon object, with all libraries embeded, will be returned. +-- @paramsig [object ,]name[, lib, ...] +-- @param object Table to use as a base for the addon (optional) +-- @param name Name of the addon object to create +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a simple addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") +-- +-- -- Create a Addon object based on the table of a frame +-- local MyFrame = CreateFrame("Frame") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0") +function AceAddon:NewAddon(objectorname, ...) + local object,name + local i=1 + if type(objectorname)=="table" then + object=objectorname + name=... + i=2 + else + name=objectorname + end + if type(name)~="string" then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) + end + if self.addons[name] then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) + end + + object = object or {} + object.name = name + + local addonmeta = {} + local oldmeta = getmetatable(object) + if oldmeta then + for k, v in pairs(oldmeta) do addonmeta[k] = v end + end + addonmeta.__tostring = addontostring + + setmetatable( object, addonmeta ) + self.addons[name] = object + object.modules = {} + object.orderedModules = {} + object.defaultModuleLibraries = {} + Embed( object ) -- embed NewModule, GetModule methods + self:EmbedLibraries(object, select(i,...)) + + -- add to queue of addons to be initialized upon ADDON_LOADED + tinsert(self.initializequeue, object) + return object +end + + +--- Get the addon object by its name from the internal AceAddon registry. +-- Throws an error if the addon object cannot be found (except if silent is set). +-- @param name unique name of the addon object +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +function AceAddon:GetAddon(name, silent) + if not silent and not self.addons[name] then + error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) + end + return self.addons[name] +end + +-- - Embed a list of libraries into the specified addon. +-- This function will try to embed all of the listed libraries into the addon +-- and error if a single one fails. +-- +-- **Note:** This function is for internal use by :NewAddon/:NewModule +-- @paramsig addon, [lib, ...] +-- @param addon addon object to embed the libs in +-- @param lib List of libraries to embed into the addon +function AceAddon:EmbedLibraries(addon, ...) + for i=1,select("#", ... ) do + local libname = select(i, ...) + self:EmbedLibrary(addon, libname, false, 4) + end +end + +-- - Embed a library into the addon object. +-- This function will check if the specified library is registered with LibStub +-- and if it has a :Embed function to call. It'll error if any of those conditions +-- fails. +-- +-- **Note:** This function is for internal use by :EmbedLibraries +-- @paramsig addon, libname[, silent[, offset]] +-- @param addon addon object to embed the library in +-- @param libname name of the library to embed +-- @param silent marks an embed to fail silently if the library doesn't exist (optional) +-- @param offset will push the error messages back to said offset, defaults to 2 (optional) +function AceAddon:EmbedLibrary(addon, libname, silent, offset) + local lib = LibStub:GetLibrary(libname, true) + if not lib and not silent then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) + elseif lib and type(lib.Embed) == "function" then + lib:Embed(addon) + tinsert(self.embeds[addon], libname) + return true + elseif lib then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) + end +end + +--- Return the specified module from an addon object. +-- Throws an error if the addon object cannot be found (except if silent is set) +-- @name //addon//:GetModule +-- @paramsig name[, silent] +-- @param name unique name of the module +-- @param silent if true, the module is optional, silently return nil if its not found (optional) +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- -- Get the Module +-- MyModule = MyAddon:GetModule("MyModule") +function GetModule(self, name, silent) + if not self.modules[name] and not silent then + error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) + end + return self.modules[name] +end + +local function IsModuleTrue(self) return true end + +--- Create a new module for the addon. +-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\ +-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as +-- an addon object. +-- @name //addon//:NewModule +-- @paramsig name[, prototype|lib[, lib, ...]] +-- @param name unique name of the module +-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a module with some embeded libraries +-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") +-- +-- -- Create a module with a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") +function NewModule(self, name, prototype, ...) + if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end + if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end + + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end + + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. + -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. + local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) + + module.IsModule = IsModuleTrue + module:SetEnabledState(self.defaultModuleState) + module.moduleName = name + + if type(prototype) == "string" then + AceAddon:EmbedLibraries(module, prototype, ...) + else + AceAddon:EmbedLibraries(module, ...) + end + AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) + + if not prototype or type(prototype) == "string" then + prototype = self.defaultModulePrototype or nil + end + + if type(prototype) == "table" then + local mt = getmetatable(module) + mt.__index = prototype + setmetatable(module, mt) -- More of a Base class type feel. + end + + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. + self.modules[name] = module + tinsert(self.orderedModules, module) + + return module +end + +--- Returns the real name of the addon or module, without any prefix. +-- @name //addon//:GetName +-- @paramsig +-- @usage +-- print(MyAddon:GetName()) +-- -- prints "MyAddon" +function GetName(self) + return self.moduleName or self.name +end + +--- Enables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback +-- and enabling all modules of the addon (unless explicitly disabled).\\ +-- :Enable() also sets the internal `enableState` variable to true +-- @name //addon//:Enable +-- @paramsig +-- @usage +-- -- Enable MyModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +function Enable(self) + self:SetEnabledState(true) + + -- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still + -- it'll be enabled after the init process + if not queuedForInitialization(self) then + return AceAddon:EnableAddon(self) + end +end + +--- Disables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback +-- and disabling all modules of the addon.\\ +-- :Disable() also sets the internal `enableState` variable to false +-- @name //addon//:Disable +-- @paramsig +-- @usage +-- -- Disable MyAddon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:Disable() +function Disable(self) + self:SetEnabledState(false) + return AceAddon:DisableAddon(self) +end + +--- Enables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. +-- @name //addon//:EnableModule +-- @paramsig name +-- @usage +-- -- Enable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +-- +-- -- Enable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:EnableModule("MyModule") +function EnableModule(self, name) + local module = self:GetModule( name ) + return module:Enable() +end + +--- Disables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. +-- @name //addon//:DisableModule +-- @paramsig name +-- @usage +-- -- Disable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Disable() +-- +-- -- Disable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:DisableModule("MyModule") +function DisableModule(self, name) + local module = self:GetModule( name ) + return module:Disable() +end + +--- Set the default libraries to be mixed into all modules created by this object. +-- Note that you can only change the default module libraries before any module is created. +-- @name //addon//:SetDefaultModuleLibraries +-- @paramsig lib[, lib, ...] +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Configure default libraries for modules (all modules need AceEvent-3.0) +-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0") +-- -- Create a module +-- MyModule = MyAddon:NewModule("MyModule") +function SetDefaultModuleLibraries(self, ...) + if next(self.modules) then + error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleLibraries = {...} +end + +--- Set the default state in which new modules are being created. +-- Note that you can only change the default state before any module is created. +-- @name //addon//:SetDefaultModuleState +-- @paramsig state +-- @param state Default state for new modules, true for enabled, false for disabled +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Set the default state to "disabled" +-- MyAddon:SetDefaultModuleState(false) +-- -- Create a module and explicilty enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +function SetDefaultModuleState(self, state) + if next(self.modules) then + error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleState = state +end + +--- Set the default prototype to use for new modules on creation. +-- Note that you can only change the default prototype before any module is created. +-- @name //addon//:SetDefaultModulePrototype +-- @paramsig prototype +-- @param prototype Default prototype for the new modules (table) +-- @usage +-- -- Define a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- -- Set the default prototype +-- MyAddon:SetDefaultModulePrototype(prototype) +-- -- Create a module and explicitly Enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +-- -- should print "OnEnable called!" now +-- @see NewModule +function SetDefaultModulePrototype(self, prototype) + if next(self.modules) then + error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) + end + if type(prototype) ~= "table" then + error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) + end + self.defaultModulePrototype = prototype +end + +--- Set the state of an addon or module +-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize. +-- @name //addon//:SetEnabledState +-- @paramsig state +-- @param state the state of an addon or module (enabled=true, disabled=false) +function SetEnabledState(self, state) + self.enabledState = state +end + + +--- Return an iterator of all modules associated to the addon. +-- @name //addon//:IterateModules +-- @paramsig +-- @usage +-- -- Enable all modules +-- for name, module in MyAddon:IterateModules() do +-- module:Enable() +-- end +local function IterateModules(self) return pairs(self.modules) end + +-- Returns an iterator of all embeds in the addon +-- @name //addon//:IterateEmbeds +-- @paramsig +local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end + +--- Query the enabledState of an addon. +-- @name //addon//:IsEnabled +-- @paramsig +-- @usage +-- if MyAddon:IsEnabled() then +-- MyAddon:Disable() +-- end +local function IsEnabled(self) return self.enabledState end +local mixins = { + NewModule = NewModule, + GetModule = GetModule, + Enable = Enable, + Disable = Disable, + EnableModule = EnableModule, + DisableModule = DisableModule, + IsEnabled = IsEnabled, + SetDefaultModuleLibraries = SetDefaultModuleLibraries, + SetDefaultModuleState = SetDefaultModuleState, + SetDefaultModulePrototype = SetDefaultModulePrototype, + SetEnabledState = SetEnabledState, + IterateModules = IterateModules, + IterateEmbeds = IterateEmbeds, + GetName = GetName, +} +local function IsModule(self) return false end +local pmixins = { + defaultModuleState = true, + enabledState = true, + IsModule = IsModule, +} +-- Embed( target ) +-- target (object) - target object to embed aceaddon in +-- +-- this is a local function specifically since it's meant to be only called internally +function Embed(target, skipPMixins) + for k, v in pairs(mixins) do + target[k] = v + end + if not skipPMixins then + for k, v in pairs(pmixins) do + target[k] = target[k] or v + end + end +end + + +-- - Initialize the addon after creation. +-- This function is only used internally during the ADDON_LOADED event +-- It will call the **OnInitialize** function on the addon object (if present), +-- and the **OnEmbedInitialize** function on all embeded libraries. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- @param addon addon object to intialize +function AceAddon:InitializeAddon(addon) + safecall(addon.OnInitialize, addon) + + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedInitialize, lib, addon) end + end + + -- we don't call InitializeAddon on modules specifically, this is handled + -- from the event handler and only done _once_ +end + +-- - Enable the addon after creation. +-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, +-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. +-- It will call the **OnEnable** function on the addon object (if present), +-- and the **OnEmbedEnable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Enable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:EnableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if self.statuses[addon.name] or not addon.enabledState then return false end + + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. + self.statuses[addon.name] = true + + safecall(addon.OnEnable, addon) + + -- make sure we're still enabled before continueing + if self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedEnable, lib, addon) end + end + + -- enable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:EnableAddon(modules[i]) + end + end + return self.statuses[addon.name] -- return true if we're disabled +end + +-- - Disable the addon +-- Note: This function is only used internally. +-- It will call the **OnDisable** function on the addon object (if present), +-- and the **OnEmbedDisable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Disable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:DisableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if not self.statuses[addon.name] then return false end + + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. + self.statuses[addon.name] = false + + safecall( addon.OnDisable, addon ) + + -- make sure we're still disabling... + if not self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedDisable, lib, addon) end + end + -- disable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:DisableAddon(modules[i]) + end + end + + return not self.statuses[addon.name] -- return true if we're disabled +end + +--- Get an iterator over all registered addons. +-- @usage +-- -- Print a list of all installed AceAddon's +-- for name, addon in AceAddon:IterateAddons() do +-- print("Addon: " .. name) +-- end +function AceAddon:IterateAddons() return pairs(self.addons) end + +--- Get an iterator over the internal status registry. +-- @usage +-- -- Print a list of all enabled addons +-- for name, status in AceAddon:IterateAddonStatus() do +-- if status then +-- print("EnabledAddon: " .. name) +-- end +-- end +function AceAddon:IterateAddonStatus() return pairs(self.statuses) end + +-- Following Iterators are deprecated, and their addon specific versions should be used +-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon) +function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end +function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end + +-- Event Handling +local function onEvent(this, event, arg1) + -- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up + if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then + -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration + while(#AceAddon.initializequeue > 0) do + local addon = tremove(AceAddon.initializequeue, 1) + -- this might be an issue with recursion - TODO: validate + if event == "ADDON_LOADED" then addon.baseName = arg1 end + AceAddon:InitializeAddon(addon) + tinsert(AceAddon.enablequeue, addon) + end + + if IsLoggedIn() then + while(#AceAddon.enablequeue > 0) do + local addon = tremove(AceAddon.enablequeue, 1) + AceAddon:EnableAddon(addon) + end + end + end +end + +AceAddon.frame:RegisterEvent("ADDON_LOADED") +AceAddon.frame:RegisterEvent("PLAYER_LOGIN") +AceAddon.frame:SetScript("OnEvent", onEvent) + +-- upgrade embeded +for name, addon in pairs(AceAddon.addons) do + Embed(addon, true) +end + +-- 2010-10-27 nevcairiel - add new "orderedModules" table +if oldminor and oldminor < 10 then + for name, addon in pairs(AceAddon.addons) do + addon.orderedModules = {} + for module_name, module in pairs(addon.modules) do + tinsert(addon.orderedModules, module) + end + end +end diff --git a/Libs/AceAddon-3.0/AceAddon-3.0.xml b/Libs/AceAddon-3.0/AceAddon-3.0.xml new file mode 100644 index 0000000..e6ad639 --- /dev/null +++ b/Libs/AceAddon-3.0/AceAddon-3.0.xml @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceAddon-3.0.lua"/> +</Ui> \ No newline at end of file diff --git a/Libs/AceConsole-3.0/AceConsole-3.0.lua b/Libs/AceConsole-3.0/AceConsole-3.0.lua new file mode 100644 index 0000000..c001123 --- /dev/null +++ b/Libs/AceConsole-3.0/AceConsole-3.0.lua @@ -0,0 +1,250 @@ +--- **AceConsole-3.0** provides registration facilities for slash commands. +-- You can register slash commands to your custom functions and use the `GetArgs` function to parse them +-- to your addons individual needs. +-- +-- **AceConsole-3.0** can be embeded into your addon, either explicitly by calling AceConsole:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceConsole itself.\\ +-- It is recommended to embed AceConsole, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceConsole. +-- @class file +-- @name AceConsole-3.0 +-- @release $Id: AceConsole-3.0.lua 878 2009-11-02 18:51:58Z nevcairiel $ +local MAJOR,MINOR = "AceConsole-3.0", 7 + +local AceConsole, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceConsole then return end -- No upgrade needed + +AceConsole.embeds = AceConsole.embeds or {} -- table containing objects AceConsole is embedded in. +AceConsole.commands = AceConsole.commands or {} -- table containing commands registered +AceConsole.weakcommands = AceConsole.weakcommands or {} -- table containing self, command => func references for weak commands that don't persist through enable/disable + +-- Lua APIs +local tconcat, tostring, select = table.concat, tostring, select +local type, pairs, error = type, pairs, error +local format, strfind, strsub = string.format, string.find, string.sub +local max = math.max + +-- WoW APIs +local _G = _G + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: DEFAULT_CHAT_FRAME, SlashCmdList, hash_SlashCmdList + +local tmp={} +local function Print(self,frame,...) + local n=0 + if self ~= AceConsole then + n=n+1 + tmp[n] = "|cff33ff99"..tostring( self ).."|r:" + end + for i=1, select("#", ...) do + n=n+1 + tmp[n] = tostring(select(i, ...)) + end + frame:AddMessage( tconcat(tmp," ",1,n) ) +end + +--- Print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function) +-- @paramsig [chatframe ,] ... +-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function) +-- @param ... List of any values to be printed +function AceConsole:Print(...) + local frame = ... + if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member? + return Print(self, frame, select(2,...)) + else + return Print(self, DEFAULT_CHAT_FRAME, ...) + end +end + + +--- Formatted (using format()) print to DEFAULT_CHAT_FRAME or given ChatFrame (anything with an .AddMessage function) +-- @paramsig [chatframe ,] "format"[, ...] +-- @param chatframe Custom ChatFrame to print to (or any frame with an .AddMessage function) +-- @param format Format string - same syntax as standard Lua format() +-- @param ... Arguments to the format string +function AceConsole:Printf(...) + local frame = ... + if type(frame) == "table" and frame.AddMessage then -- Is first argument something with an .AddMessage member? + return Print(self, frame, format(select(2,...))) + else + return Print(self, DEFAULT_CHAT_FRAME, format(...)) + end +end + + + + +--- Register a simple chat command +-- @param command Chat command to be registered WITHOUT leading "/" +-- @param func Function to call when the slash command is being used (funcref or methodname) +-- @param persist if false, the command will be soft disabled/enabled when aceconsole is used as a mixin (default: true) +function AceConsole:RegisterChatCommand( command, func, persist ) + if type(command)~="string" then error([[Usage: AceConsole:RegisterChatCommand( "command", func[, persist ]): 'command' - expected a string]], 2) end + + if persist==nil then persist=true end -- I'd rather have my addon's "/addon enable" around if the author screws up. Having some extra slash regged when it shouldnt be isn't as destructive. True is a better default. /Mikk + + local name = "ACECONSOLE_"..command:upper() + + if type( func ) == "string" then + SlashCmdList[name] = function(input, editBox) + self[func](self, input, editBox) + end + else + SlashCmdList[name] = func + end + _G["SLASH_"..name.."1"] = "/"..command:lower() + AceConsole.commands[command] = name + -- non-persisting commands are registered for enabling disabling + if not persist then + if not AceConsole.weakcommands[self] then AceConsole.weakcommands[self] = {} end + AceConsole.weakcommands[self][command] = func + end + return true +end + +--- Unregister a chatcommand +-- @param command Chat command to be unregistered WITHOUT leading "/" +function AceConsole:UnregisterChatCommand( command ) + local name = AceConsole.commands[command] + if name then + SlashCmdList[name] = nil + _G["SLASH_" .. name .. "1"] = nil + hash_SlashCmdList["/" .. command:upper()] = nil + AceConsole.commands[command] = nil + end +end + +--- Get an iterator over all Chat Commands registered with AceConsole +-- @return Iterator (pairs) over all commands +function AceConsole:IterateChatCommands() return pairs(AceConsole.commands) end + + +local function nils(n, ...) + if n>1 then + return nil, nils(n-1, ...) + elseif n==1 then + return nil, ... + else + return ... + end +end + + +--- Retreive one or more space-separated arguments from a string. +-- Treats quoted strings and itemlinks as non-spaced. +-- @param string The raw argument string +-- @param numargs How many arguments to get (default 1) +-- @param startpos Where in the string to start scanning (default 1) +-- @return Returns arg1, arg2, ..., nextposition\\ +-- Missing arguments will be returned as nils. 'nextposition' is returned as 1e9 at the end of the string. +function AceConsole:GetArgs(str, numargs, startpos) + numargs = numargs or 1 + startpos = max(startpos or 1, 1) + + local pos=startpos + + -- find start of new arg + pos = strfind(str, "[^ ]", pos) + if not pos then -- whoops, end of string + return nils(numargs, 1e9) + end + + if numargs<1 then + return pos + end + + -- quoted or space separated? find out which pattern to use + local delim_or_pipe + local ch = strsub(str, pos, pos) + if ch=='"' then + pos = pos + 1 + delim_or_pipe='([|"])' + elseif ch=="'" then + pos = pos + 1 + delim_or_pipe="([|'])" + else + delim_or_pipe="([| ])" + end + + startpos = pos + + while true do + -- find delimiter or hyperlink + local ch,_ + pos,_,ch = strfind(str, delim_or_pipe, pos) + + if not pos then break end + + if ch=="|" then + -- some kind of escape + + if strsub(str,pos,pos+1)=="|H" then + -- It's a |H....|hhyper link!|h + pos=strfind(str, "|h", pos+2) -- first |h + if not pos then break end + + pos=strfind(str, "|h", pos+2) -- second |h + if not pos then break end + elseif strsub(str,pos, pos+1) == "|T" then + -- It's a |T....|t texture + pos=strfind(str, "|t", pos+2) + if not pos then break end + end + + pos=pos+2 -- skip past this escape (last |h if it was a hyperlink) + + else + -- found delimiter, done with this arg + return strsub(str, startpos, pos-1), AceConsole:GetArgs(str, numargs-1, pos+1) + end + + end + + -- search aborted, we hit end of string. return it all as one argument. (yes, even if it's an unterminated quote or hyperlink) + return strsub(str, startpos), nils(numargs-1, 1e9) +end + + +--- embedding and embed handling + +local mixins = { + "Print", + "Printf", + "RegisterChatCommand", + "UnregisterChatCommand", + "GetArgs", +} + +-- Embeds AceConsole into the target object making the functions from the mixins list available on target:.. +-- @param target target object to embed AceBucket in +function AceConsole:Embed( target ) + for k, v in pairs( mixins ) do + target[v] = self[v] + end + self.embeds[target] = true + return target +end + +function AceConsole:OnEmbedEnable( target ) + if AceConsole.weakcommands[target] then + for command, func in pairs( AceConsole.weakcommands[target] ) do + target:RegisterChatCommand( command, func, false, true ) -- nonpersisting and silent registry + end + end +end + +function AceConsole:OnEmbedDisable( target ) + if AceConsole.weakcommands[target] then + for command, func in pairs( AceConsole.weakcommands[target] ) do + target:UnregisterChatCommand( command ) -- TODO: this could potentially unregister a command from another application in case of command conflicts. Do we care? + end + end +end + +for addon in pairs(AceConsole.embeds) do + AceConsole:Embed(addon) +end diff --git a/Libs/AceConsole-3.0/AceConsole-3.0.xml b/Libs/AceConsole-3.0/AceConsole-3.0.xml new file mode 100644 index 0000000..be9f47c --- /dev/null +++ b/Libs/AceConsole-3.0/AceConsole-3.0.xml @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceConsole-3.0.lua"/> +</Ui> \ No newline at end of file diff --git a/Libs/AceDB-3.0/AceDB-3.0.lua b/Libs/AceDB-3.0/AceDB-3.0.lua new file mode 100644 index 0000000..a17c6f1 --- /dev/null +++ b/Libs/AceDB-3.0/AceDB-3.0.lua @@ -0,0 +1,745 @@ +--- **AceDB-3.0** manages the SavedVariables of your addon. +-- It offers profile management, smart defaults and namespaces for modules.\\ +-- Data can be saved in different data-types, depending on its intended usage. +-- The most common data-type is the `profile` type, which allows the user to choose +-- the active profile, and manage the profiles of all of his characters.\\ +-- The following data types are available: +-- * **char** Character-specific data. Every character has its own database. +-- * **realm** Realm-specific data. All of the players characters on the same realm share this database. +-- * **class** Class-specific data. All of the players characters of the same class share this database. +-- * **race** Race-specific data. All of the players characters of the same race share this database. +-- * **faction** Faction-specific data. All of the players characters of the same faction share this database. +-- * **factionrealm** Faction and realm specific data. All of the players characters on the same realm and of the same faction share this database. +-- * **global** Global Data. All characters on the same account share this database. +-- * **profile** Profile-specific data. All characters using the same profile share this database. The user can control which profile should be used. +-- +-- Creating a new Database using the `:New` function will return a new DBObject. A database will inherit all functions +-- of the DBObjectLib listed here. \\ +-- If you create a new namespaced child-database (`:RegisterNamespace`), you'll get a DBObject as well, but note +-- that the child-databases cannot individually change their profile, and are linked to their parents profile - and because of that, +-- the profile related APIs are not available. Only `:RegisterDefaults` and `:ResetProfile` are available on child-databases. +-- +-- For more details on how to use AceDB-3.0, see the [[AceDB-3.0 Tutorial]]. +-- +-- You may also be interested in [[libdualspec-1-0|LibDualSpec-1.0]] to do profile switching automatically when switching specs. +-- +-- @usage +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("DBExample") +-- +-- -- declare defaults to be used in the DB +-- local defaults = { +-- profile = { +-- setting = true, +-- } +-- } +-- +-- function MyAddon:OnInitialize() +-- -- Assuming the .toc says ## SavedVariables: MyAddonDB +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) +-- end +-- @class file +-- @name AceDB-3.0.lua +-- @release $Id: AceDB-3.0.lua 1124 2014-10-27 21:00:07Z funkydude $ +local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 26 +local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR) + +if not AceDB then return end -- No upgrade needed + +-- Lua APIs +local type, pairs, next, error = type, pairs, next, error +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget + +-- WoW APIs +local _G = _G + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub + +AceDB.db_registry = AceDB.db_registry or {} +AceDB.frame = AceDB.frame or CreateFrame("Frame") + +local CallbackHandler +local CallbackDummy = { Fire = function() end } + +local DBObjectLib = {} + +--[[------------------------------------------------------------------------- + AceDB Utility Functions +---------------------------------------------------------------------------]] + +-- Simple shallow copy for copying defaults +local function copyTable(src, dest) + if type(dest) ~= "table" then dest = {} end + if type(src) == "table" then + for k,v in pairs(src) do + if type(v) == "table" then + -- try to index the key first so that the metatable creates the defaults, if set, and use that table + v = copyTable(v, dest[k]) + end + dest[k] = v + end + end + return dest +end + +-- Called to add defaults to a section of the database +-- +-- When a ["*"] default section is indexed with a new key, a table is returned +-- and set in the host table. These tables must be cleaned up by removeDefaults +-- in order to ensure we don't write empty default tables. +local function copyDefaults(dest, src) + -- this happens if some value in the SV overwrites our default value with a non-table + --if type(dest) ~= "table" then return end + for k, v in pairs(src) do + if k == "*" or k == "**" then + if type(v) == "table" then + -- This is a metatable used for table defaults + local mt = { + -- This handles the lookup and creation of new subtables + __index = function(t,k) + if k == nil then return nil end + local tbl = {} + copyDefaults(tbl, v) + rawset(t, k, tbl) + return tbl + end, + } + setmetatable(dest, mt) + -- handle already existing tables in the SV + for dk, dv in pairs(dest) do + if not rawget(src, dk) and type(dv) == "table" then + copyDefaults(dv, v) + end + end + else + -- Values are not tables, so this is just a simple return + local mt = {__index = function(t,k) return k~=nil and v or nil end} + setmetatable(dest, mt) + end + elseif type(v) == "table" then + if not rawget(dest, k) then rawset(dest, k, {}) end + if type(dest[k]) == "table" then + copyDefaults(dest[k], v) + if src['**'] then + copyDefaults(dest[k], src['**']) + end + end + else + if rawget(dest, k) == nil then + rawset(dest, k, v) + end + end + end +end + +-- Called to remove all defaults in the default table from the database +local function removeDefaults(db, defaults, blocker) + -- remove all metatables from the db, so we don't accidentally create new sub-tables through them + setmetatable(db, nil) + -- loop through the defaults and remove their content + for k,v in pairs(defaults) do + if k == "*" or k == "**" then + if type(v) == "table" then + -- Loop through all the actual k,v pairs and remove + for key, value in pairs(db) do + if type(value) == "table" then + -- if the key was not explicitly specified in the defaults table, just strip everything from * and ** tables + if defaults[key] == nil and (not blocker or blocker[key] == nil) then + removeDefaults(value, v) + -- if the table is empty afterwards, remove it + if next(value) == nil then + db[key] = nil + end + -- if it was specified, only strip ** content, but block values which were set in the key table + elseif k == "**" then + removeDefaults(value, v, defaults[key]) + end + end + end + elseif k == "*" then + -- check for non-table default + for key, value in pairs(db) do + if defaults[key] == nil and v == value then + db[key] = nil + end + end + end + elseif type(v) == "table" and type(db[k]) == "table" then + -- if a blocker was set, dive into it, to allow multi-level defaults + removeDefaults(db[k], v, blocker and blocker[k]) + if next(db[k]) == nil then + db[k] = nil + end + else + -- check if the current value matches the default, and that its not blocked by another defaults table + if db[k] == defaults[k] and (not blocker or blocker[k] == nil) then + db[k] = nil + end + end + end +end + +-- This is called when a table section is first accessed, to set up the defaults +local function initSection(db, section, svstore, key, defaults) + local sv = rawget(db, "sv") + + local tableCreated + if not sv[svstore] then sv[svstore] = {} end + if not sv[svstore][key] then + sv[svstore][key] = {} + tableCreated = true + end + + local tbl = sv[svstore][key] + + if defaults then + copyDefaults(tbl, defaults) + end + rawset(db, section, tbl) + + return tableCreated, tbl +end + +-- Metatable to handle the dynamic creation of sections and copying of sections. +local dbmt = { + __index = function(t, section) + local keys = rawget(t, "keys") + local key = keys[section] + if key then + local defaultTbl = rawget(t, "defaults") + local defaults = defaultTbl and defaultTbl[section] + + if section == "profile" then + local new = initSection(t, section, "profiles", key, defaults) + if new then + -- Callback: OnNewProfile, database, newProfileKey + t.callbacks:Fire("OnNewProfile", t, key) + end + elseif section == "profiles" then + local sv = rawget(t, "sv") + if not sv.profiles then sv.profiles = {} end + rawset(t, "profiles", sv.profiles) + elseif section == "global" then + local sv = rawget(t, "sv") + if not sv.global then sv.global = {} end + if defaults then + copyDefaults(sv.global, defaults) + end + rawset(t, section, sv.global) + else + initSection(t, section, section, key, defaults) + end + end + + return rawget(t, section) + end +} + +local function validateDefaults(defaults, keyTbl, offset) + if not defaults then return end + offset = offset or 0 + for k in pairs(defaults) do + if not keyTbl[k] or k == "profiles" then + error(("Usage: AceDBObject:RegisterDefaults(defaults): '%s' is not a valid datatype."):format(k), 3 + offset) + end + end +end + +local preserve_keys = { + ["callbacks"] = true, + ["RegisterCallback"] = true, + ["UnregisterCallback"] = true, + ["UnregisterAllCallbacks"] = true, + ["children"] = true, +} + +local realmKey = GetRealmName() +local charKey = UnitName("player") .. " - " .. realmKey +local _, classKey = UnitClass("player") +local _, raceKey = UnitRace("player") +local factionKey = UnitFactionGroup("player") +local factionrealmKey = factionKey .. " - " .. realmKey +local localeKey = GetLocale():lower() + +local regionTable = { "US", "KR", "EU", "TW", "CN" } +local regionKey = regionTable[GetCurrentRegion()] +local factionrealmregionKey = factionrealmKey .. " - " .. regionKey + +-- Actual database initialization function +local function initdb(sv, defaults, defaultProfile, olddb, parent) + -- Generate the database keys for each section + + -- map "true" to our "Default" profile + if defaultProfile == true then defaultProfile = "Default" end + + local profileKey + if not parent then + -- Make a container for profile keys + if not sv.profileKeys then sv.profileKeys = {} end + + -- Try to get the profile selected from the char db + profileKey = sv.profileKeys[charKey] or defaultProfile or charKey + + -- save the selected profile for later + sv.profileKeys[charKey] = profileKey + else + -- Use the profile of the parents DB + profileKey = parent.keys.profile or defaultProfile or charKey + + -- clear the profileKeys in the DB, namespaces don't need to store them + sv.profileKeys = nil + end + + -- This table contains keys that enable the dynamic creation + -- of each section of the table. The 'global' and 'profiles' + -- have a key of true, since they are handled in a special case + local keyTbl= { + ["char"] = charKey, + ["realm"] = realmKey, + ["class"] = classKey, + ["race"] = raceKey, + ["faction"] = factionKey, + ["factionrealm"] = factionrealmKey, + ["factionrealmregion"] = factionrealmregionKey, + ["profile"] = profileKey, + ["locale"] = localeKey, + ["global"] = true, + ["profiles"] = true, + } + + validateDefaults(defaults, keyTbl, 1) + + -- This allows us to use this function to reset an entire database + -- Clear out the old database + if olddb then + for k,v in pairs(olddb) do if not preserve_keys[k] then olddb[k] = nil end end + end + + -- Give this database the metatable so it initializes dynamically + local db = setmetatable(olddb or {}, dbmt) + + if not rawget(db, "callbacks") then + -- try to load CallbackHandler-1.0 if it loaded after our library + if not CallbackHandler then CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0", true) end + db.callbacks = CallbackHandler and CallbackHandler:New(db) or CallbackDummy + end + + -- Copy methods locally into the database object, to avoid hitting + -- the metatable when calling methods + + if not parent then + for name, func in pairs(DBObjectLib) do + db[name] = func + end + else + -- hack this one in + db.RegisterDefaults = DBObjectLib.RegisterDefaults + db.ResetProfile = DBObjectLib.ResetProfile + end + + -- Set some properties in the database object + db.profiles = sv.profiles + db.keys = keyTbl + db.sv = sv + --db.sv_name = name + db.defaults = defaults + db.parent = parent + + -- store the DB in the registry + AceDB.db_registry[db] = true + + return db +end + +-- handle PLAYER_LOGOUT +-- strip all defaults from all databases +-- and cleans up empty sections +local function logoutHandler(frame, event) + if event == "PLAYER_LOGOUT" then + for db in pairs(AceDB.db_registry) do + db.callbacks:Fire("OnDatabaseShutdown", db) + db:RegisterDefaults(nil) + + -- cleanup sections that are empty without defaults + local sv = rawget(db, "sv") + for section in pairs(db.keys) do + if rawget(sv, section) then + -- global is special, all other sections have sub-entrys + -- also don't delete empty profiles on main dbs, only on namespaces + if section ~= "global" and (section ~= "profiles" or rawget(db, "parent")) then + for key in pairs(sv[section]) do + if not next(sv[section][key]) then + sv[section][key] = nil + end + end + end + if not next(sv[section]) then + sv[section] = nil + end + end + end + end + end +end + +AceDB.frame:RegisterEvent("PLAYER_LOGOUT") +AceDB.frame:SetScript("OnEvent", logoutHandler) + + +--[[------------------------------------------------------------------------- + AceDB Object Method Definitions +---------------------------------------------------------------------------]] + +--- Sets the defaults table for the given database object by clearing any +-- that are currently set, and then setting the new defaults. +-- @param defaults A table of defaults for this database +function DBObjectLib:RegisterDefaults(defaults) + if defaults and type(defaults) ~= "table" then + error("Usage: AceDBObject:RegisterDefaults(defaults): 'defaults' - table or nil expected.", 2) + end + + validateDefaults(defaults, self.keys) + + -- Remove any currently set defaults + if self.defaults then + for section,key in pairs(self.keys) do + if self.defaults[section] and rawget(self, section) then + removeDefaults(self[section], self.defaults[section]) + end + end + end + + -- Set the DBObject.defaults table + self.defaults = defaults + + -- Copy in any defaults, only touching those sections already created + if defaults then + for section,key in pairs(self.keys) do + if defaults[section] and rawget(self, section) then + copyDefaults(self[section], defaults[section]) + end + end + end +end + +--- Changes the profile of the database and all of it's namespaces to the +-- supplied named profile +-- @param name The name of the profile to set as the current profile +function DBObjectLib:SetProfile(name) + if type(name) ~= "string" then + error("Usage: AceDBObject:SetProfile(name): 'name' - string expected.", 2) + end + + -- changing to the same profile, dont do anything + if name == self.keys.profile then return end + + local oldProfile = self.profile + local defaults = self.defaults and self.defaults.profile + + -- Callback: OnProfileShutdown, database + self.callbacks:Fire("OnProfileShutdown", self) + + if oldProfile and defaults then + -- Remove the defaults from the old profile + removeDefaults(oldProfile, defaults) + end + + self.profile = nil + self.keys["profile"] = name + + -- if the storage exists, save the new profile + -- this won't exist on namespaces. + if self.sv.profileKeys then + self.sv.profileKeys[charKey] = name + end + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.SetProfile(db, name) + end + end + + -- Callback: OnProfileChanged, database, newProfileKey + self.callbacks:Fire("OnProfileChanged", self, name) +end + +--- Returns a table with the names of the existing profiles in the database. +-- You can optionally supply a table to re-use for this purpose. +-- @param tbl A table to store the profile names in (optional) +function DBObjectLib:GetProfiles(tbl) + if tbl and type(tbl) ~= "table" then + error("Usage: AceDBObject:GetProfiles(tbl): 'tbl' - table or nil expected.", 2) + end + + -- Clear the container table + if tbl then + for k,v in pairs(tbl) do tbl[k] = nil end + else + tbl = {} + end + + local curProfile = self.keys.profile + + local i = 0 + for profileKey in pairs(self.profiles) do + i = i + 1 + tbl[i] = profileKey + if curProfile and profileKey == curProfile then curProfile = nil end + end + + -- Add the current profile, if it hasn't been created yet + if curProfile then + i = i + 1 + tbl[i] = curProfile + end + + return tbl, i +end + +--- Returns the current profile name used by the database +function DBObjectLib:GetCurrentProfile() + return self.keys.profile +end + +--- Deletes a named profile. This profile must not be the active profile. +-- @param name The name of the profile to be deleted +-- @param silent If true, do not raise an error when the profile does not exist +function DBObjectLib:DeleteProfile(name, silent) + if type(name) ~= "string" then + error("Usage: AceDBObject:DeleteProfile(name): 'name' - string expected.", 2) + end + + if self.keys.profile == name then + error("Cannot delete the active profile in an AceDBObject.", 2) + end + + if not rawget(self.profiles, name) and not silent then + error("Cannot delete profile '" .. name .. "'. It does not exist.", 2) + end + + self.profiles[name] = nil + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.DeleteProfile(db, name, true) + end + end + + -- switch all characters that use this profile back to the default + if self.sv.profileKeys then + for key, profile in pairs(self.sv.profileKeys) do + if profile == name then + self.sv.profileKeys[key] = nil + end + end + end + + -- Callback: OnProfileDeleted, database, profileKey + self.callbacks:Fire("OnProfileDeleted", self, name) +end + +--- Copies a named profile into the current profile, overwriting any conflicting +-- settings. +-- @param name The name of the profile to be copied into the current profile +-- @param silent If true, do not raise an error when the profile does not exist +function DBObjectLib:CopyProfile(name, silent) + if type(name) ~= "string" then + error("Usage: AceDBObject:CopyProfile(name): 'name' - string expected.", 2) + end + + if name == self.keys.profile then + error("Cannot have the same source and destination profiles.", 2) + end + + if not rawget(self.profiles, name) and not silent then + error("Cannot copy profile '" .. name .. "'. It does not exist.", 2) + end + + -- Reset the profile before copying + DBObjectLib.ResetProfile(self, nil, true) + + local profile = self.profile + local source = self.profiles[name] + + copyTable(source, profile) + + -- populate to child namespaces + if self.children then + for _, db in pairs(self.children) do + DBObjectLib.CopyProfile(db, name, true) + end + end + + -- Callback: OnProfileCopied, database, sourceProfileKey + self.callbacks:Fire("OnProfileCopied", self, name) +end + +--- Resets the current profile to the default values (if specified). +-- @param noChildren if set to true, the reset will not be populated to the child namespaces of this DB object +-- @param noCallbacks if set to true, won't fire the OnProfileReset callback +function DBObjectLib:ResetProfile(noChildren, noCallbacks) + local profile = self.profile + + for k,v in pairs(profile) do + profile[k] = nil + end + + local defaults = self.defaults and self.defaults.profile + if defaults then + copyDefaults(profile, defaults) + end + + -- populate to child namespaces + if self.children and not noChildren then + for _, db in pairs(self.children) do + DBObjectLib.ResetProfile(db, nil, noCallbacks) + end + end + + -- Callback: OnProfileReset, database + if not noCallbacks then + self.callbacks:Fire("OnProfileReset", self) + end +end + +--- Resets the entire database, using the string defaultProfile as the new default +-- profile. +-- @param defaultProfile The profile name to use as the default +function DBObjectLib:ResetDB(defaultProfile) + if defaultProfile and type(defaultProfile) ~= "string" then + error("Usage: AceDBObject:ResetDB(defaultProfile): 'defaultProfile' - string or nil expected.", 2) + end + + local sv = self.sv + for k,v in pairs(sv) do + sv[k] = nil + end + + local parent = self.parent + + initdb(sv, self.defaults, defaultProfile, self) + + -- fix the child namespaces + if self.children then + if not sv.namespaces then sv.namespaces = {} end + for name, db in pairs(self.children) do + if not sv.namespaces[name] then sv.namespaces[name] = {} end + initdb(sv.namespaces[name], db.defaults, self.keys.profile, db, self) + end + end + + -- Callback: OnDatabaseReset, database + self.callbacks:Fire("OnDatabaseReset", self) + -- Callback: OnProfileChanged, database, profileKey + self.callbacks:Fire("OnProfileChanged", self, self.keys["profile"]) + + return self +end + +--- Creates a new database namespace, directly tied to the database. This +-- is a full scale database in it's own rights other than the fact that +-- it cannot control its profile individually +-- @param name The name of the new namespace +-- @param defaults A table of values to use as defaults +function DBObjectLib:RegisterNamespace(name, defaults) + if type(name) ~= "string" then + error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - string expected.", 2) + end + if defaults and type(defaults) ~= "table" then + error("Usage: AceDBObject:RegisterNamespace(name, defaults): 'defaults' - table or nil expected.", 2) + end + if self.children and self.children[name] then + error ("Usage: AceDBObject:RegisterNamespace(name, defaults): 'name' - a namespace with that name already exists.", 2) + end + + local sv = self.sv + if not sv.namespaces then sv.namespaces = {} end + if not sv.namespaces[name] then + sv.namespaces[name] = {} + end + + local newDB = initdb(sv.namespaces[name], defaults, self.keys.profile, nil, self) + + if not self.children then self.children = {} end + self.children[name] = newDB + return newDB +end + +--- Returns an already existing namespace from the database object. +-- @param name The name of the new namespace +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- local namespace = self.db:GetNamespace('namespace') +-- @return the namespace object if found +function DBObjectLib:GetNamespace(name, silent) + if type(name) ~= "string" then + error("Usage: AceDBObject:GetNamespace(name): 'name' - string expected.", 2) + end + if not silent and not (self.children and self.children[name]) then + error ("Usage: AceDBObject:GetNamespace(name): 'name' - namespace does not exist.", 2) + end + if not self.children then self.children = {} end + return self.children[name] +end + +--[[------------------------------------------------------------------------- + AceDB Exposed Methods +---------------------------------------------------------------------------]] + +--- Creates a new database object that can be used to handle database settings and profiles. +-- By default, an empty DB is created, using a character specific profile. +-- +-- You can override the default profile used by passing any profile name as the third argument, +-- or by passing //true// as the third argument to use a globally shared profile called "Default". +-- +-- Note that there is no token replacement in the default profile name, passing a defaultProfile as "char" +-- will use a profile named "char", and not a character-specific profile. +-- @param tbl The name of variable, or table to use for the database +-- @param defaults A table of database defaults +-- @param defaultProfile The name of the default profile. If not set, a character specific profile will be used as the default. +-- You can also pass //true// to use a shared global profile called "Default". +-- @usage +-- -- Create an empty DB using a character-specific default profile. +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB") +-- @usage +-- -- Create a DB using defaults and using a shared default profile +-- self.db = LibStub("AceDB-3.0"):New("MyAddonDB", defaults, true) +function AceDB:New(tbl, defaults, defaultProfile) + if type(tbl) == "string" then + local name = tbl + tbl = _G[name] + if not tbl then + tbl = {} + _G[name] = tbl + end + end + + if type(tbl) ~= "table" then + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'tbl' - table expected.", 2) + end + + if defaults and type(defaults) ~= "table" then + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaults' - table expected.", 2) + end + + if defaultProfile and type(defaultProfile) ~= "string" and defaultProfile ~= true then + error("Usage: AceDB:New(tbl, defaults, defaultProfile): 'defaultProfile' - string or true expected.", 2) + end + + return initdb(tbl, defaults, defaultProfile) +end + +-- upgrade existing databases +for db in pairs(AceDB.db_registry) do + if not db.parent then + for name,func in pairs(DBObjectLib) do + db[name] = func + end + else + db.RegisterDefaults = DBObjectLib.RegisterDefaults + db.ResetProfile = DBObjectLib.ResetProfile + end +end diff --git a/Libs/AceDB-3.0/AceDB-3.0.xml b/Libs/AceDB-3.0/AceDB-3.0.xml new file mode 100644 index 0000000..46b20ba --- /dev/null +++ b/Libs/AceDB-3.0/AceDB-3.0.xml @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceDB-3.0.lua"/> +</Ui> \ No newline at end of file diff --git a/Libs/AceEvent-3.0/AceEvent-3.0.lua b/Libs/AceEvent-3.0/AceEvent-3.0.lua new file mode 100644 index 0000000..578ae25 --- /dev/null +++ b/Libs/AceEvent-3.0/AceEvent-3.0.lua @@ -0,0 +1,126 @@ +--- AceEvent-3.0 provides event registration and secure dispatching. +-- All dispatching is done using **CallbackHandler-1.0**. AceEvent is a simple wrapper around +-- CallbackHandler, and dispatches all game events or addon message to the registrees. +-- +-- **AceEvent-3.0** can be embeded into your addon, either explicitly by calling AceEvent:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceEvent itself.\\ +-- It is recommended to embed AceEvent, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceEvent. +-- @class file +-- @name AceEvent-3.0 +-- @release $Id: AceEvent-3.0.lua 975 2010-10-23 11:26:18Z nevcairiel $ +local MAJOR, MINOR = "AceEvent-3.0", 3 +local AceEvent = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceEvent then return end + +-- Lua APIs +local pairs = pairs + +local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") + +AceEvent.frame = AceEvent.frame or CreateFrame("Frame", "AceEvent30Frame") -- our event frame +AceEvent.embeds = AceEvent.embeds or {} -- what objects embed this lib + +-- APIs and registry for blizzard events, using CallbackHandler lib +if not AceEvent.events then + AceEvent.events = CallbackHandler:New(AceEvent, + "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents") +end + +function AceEvent.events:OnUsed(target, eventname) + AceEvent.frame:RegisterEvent(eventname) +end + +function AceEvent.events:OnUnused(target, eventname) + AceEvent.frame:UnregisterEvent(eventname) +end + + +-- APIs and registry for IPC messages, using CallbackHandler lib +if not AceEvent.messages then + AceEvent.messages = CallbackHandler:New(AceEvent, + "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages" + ) + AceEvent.SendMessage = AceEvent.messages.Fire +end + +--- embedding and embed handling +local mixins = { + "RegisterEvent", "UnregisterEvent", + "RegisterMessage", "UnregisterMessage", + "SendMessage", + "UnregisterAllEvents", "UnregisterAllMessages", +} + +--- Register for a Blizzard Event. +-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) +-- Any arguments to the event will be passed on after that. +-- @name AceEvent:RegisterEvent +-- @class function +-- @paramsig event[, callback [, arg]] +-- @param event The event to register for +-- @param callback The callback function to call when the event is triggered (funcref or method, defaults to a method with the event name) +-- @param arg An optional argument to pass to the callback function + +--- Unregister an event. +-- @name AceEvent:UnregisterEvent +-- @class function +-- @paramsig event +-- @param event The event to unregister + +--- Register for a custom AceEvent-internal message. +-- The callback will be called with the optional `arg` as the first argument (if supplied), and the event name as the second (or first, if no arg was supplied) +-- Any arguments to the event will be passed on after that. +-- @name AceEvent:RegisterMessage +-- @class function +-- @paramsig message[, callback [, arg]] +-- @param message The message to register for +-- @param callback The callback function to call when the message is triggered (funcref or method, defaults to a method with the event name) +-- @param arg An optional argument to pass to the callback function + +--- Unregister a message +-- @name AceEvent:UnregisterMessage +-- @class function +-- @paramsig message +-- @param message The message to unregister + +--- Send a message over the AceEvent-3.0 internal message system to other addons registered for this message. +-- @name AceEvent:SendMessage +-- @class function +-- @paramsig message, ... +-- @param message The message to send +-- @param ... Any arguments to the message + + +-- Embeds AceEvent into the target object making the functions from the mixins list available on target:.. +-- @param target target object to embed AceEvent in +function AceEvent:Embed(target) + for k, v in pairs(mixins) do + target[v] = self[v] + end + self.embeds[target] = true + return target +end + +-- AceEvent:OnEmbedDisable( target ) +-- target (object) - target object that is being disabled +-- +-- Unregister all events messages etc when the target disables. +-- this method should be called by the target manually or by an addon framework +function AceEvent:OnEmbedDisable(target) + target:UnregisterAllEvents() + target:UnregisterAllMessages() +end + +-- Script to fire blizzard events into the event listeners +local events = AceEvent.events +AceEvent.frame:SetScript("OnEvent", function(this, event, ...) + events:Fire(event, ...) +end) + +--- Finally: upgrade our old embeds +for target, v in pairs(AceEvent.embeds) do + AceEvent:Embed(target) +end diff --git a/Libs/AceEvent-3.0/AceEvent-3.0.xml b/Libs/AceEvent-3.0/AceEvent-3.0.xml new file mode 100644 index 0000000..313ef4d --- /dev/null +++ b/Libs/AceEvent-3.0/AceEvent-3.0.xml @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceEvent-3.0.lua"/> +</Ui> \ No newline at end of file diff --git a/Libs/AceTimer-3.0/AceTimer-3.0.lua b/Libs/AceTimer-3.0/AceTimer-3.0.lua new file mode 100644 index 0000000..8ba6b3c --- /dev/null +++ b/Libs/AceTimer-3.0/AceTimer-3.0.lua @@ -0,0 +1,276 @@ +--- **AceTimer-3.0** provides a central facility for registering timers. +-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient +-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered +-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ +-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API +-- restricts us to. +-- +-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you +-- need to cancel the timer you just registered. +-- +-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceTimer itself.\\ +-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceTimer. +-- @class file +-- @name AceTimer-3.0 +-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $ + +local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes +local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceTimer then return end -- No upgrade needed +AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list +local activeTimers = AceTimer.activeTimers -- Upvalue our private data + +-- Lua APIs +local type, unpack, next, error, select = type, unpack, next, error, select +-- WoW APIs +local GetTime, C_TimerAfter = GetTime, C_Timer.After + +local function new(self, loop, func, delay, ...) + if delay < 0.01 then + delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us + end + + local timer = {...} + timer.object = self + timer.func = func + timer.looping = loop + timer.argsCount = select("#", ...) + timer.delay = delay + timer.ends = GetTime() + delay + + activeTimers[timer] = timer + + -- Create new timer closure to wrap the "timer" object + timer.callback = function() + if not timer.cancelled then + if type(timer.func) == "string" then + -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil + -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. + timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) + else + timer.func(unpack(timer, 1, timer.argsCount)) + end + + if timer.looping and not timer.cancelled then + -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly + -- due to fps differences + local time = GetTime() + local delay = timer.delay - (time - timer.ends) + -- Ensure the delay doesn't go below the threshold + if delay < 0.01 then delay = 0.01 end + C_TimerAfter(delay, timer.callback) + timer.ends = time + delay + else + activeTimers[timer.handle or timer] = nil + end + end + end + + C_TimerAfter(delay, timer.callback) + return timer +end + +--- Schedule a new one-shot timer. +-- The timer will fire once in `delay` seconds, unless canceled before. +-- @param callback Callback function for the timer pulse (funcref or method name). +-- @param delay Delay for the timer, in seconds. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @usage +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- +-- function MyAddOn:OnEnable() +-- self:ScheduleTimer("TimerFeedback", 5) +-- end +-- +-- function MyAddOn:TimerFeedback() +-- print("5 seconds passed") +-- end +function AceTimer:ScheduleTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, nil, func, delay, ...) +end + +--- Schedule a repeating timer. +-- The timer will fire every `delay` seconds, until canceled. +-- @param callback Callback function for the timer pulse (funcref or method name). +-- @param delay Delay for the timer, in seconds. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @usage +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- +-- function MyAddOn:OnEnable() +-- self.timerCount = 0 +-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) +-- end +-- +-- function MyAddOn:TimerFeedback() +-- self.timerCount = self.timerCount + 1 +-- print(("%d seconds passed"):format(5 * self.timerCount)) +-- -- run 30 seconds in total +-- if self.timerCount == 6 then +-- self:CancelTimer(self.testTimer) +-- end +-- end +function AceTimer:ScheduleRepeatingTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, true, func, delay, ...) +end + +--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` +-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid +-- and the timer has not fired yet or was canceled before. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +function AceTimer:CancelTimer(id) + local timer = activeTimers[id] + + if not timer then + return false + else + timer.cancelled = true + activeTimers[id] = nil + return true + end +end + +--- Cancels all timers registered to the current addon object ('self') +function AceTimer:CancelAllTimers() + for k,v in pairs(activeTimers) do + if v.object == self then + AceTimer.CancelTimer(self, k) + end + end +end + +--- Returns the time left for a timer with the given id, registered by the current addon object ('self'). +-- This function will return 0 when the id is invalid. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +-- @return The time left on the timer. +function AceTimer:TimeLeft(id) + local timer = activeTimers[id] + if not timer then + return 0 + else + return timer.ends - GetTime() + end +end + + +-- --------------------------------------------------------------------- +-- Upgrading + +-- Upgrade from old hash-bucket based timers to C_Timer.After timers. +if oldminor and oldminor < 10 then + -- disable old timer logic + AceTimer.frame:SetScript("OnUpdate", nil) + AceTimer.frame:SetScript("OnEvent", nil) + AceTimer.frame:UnregisterAllEvents() + -- convert timers + for object,timers in pairs(AceTimer.selfs) do + for handle,timer in pairs(timers) do + if type(timer) == "table" and timer.callback then + local newTimer + if timer.delay then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle + end + end + end + AceTimer.selfs = nil + AceTimer.hash = nil + AceTimer.debug = nil +elseif oldminor and oldminor < 17 then + -- Upgrade from old animation based timers to C_Timer.After timers. + AceTimer.inactiveTimers = nil + AceTimer.frame = nil + local oldTimers = AceTimer.activeTimers + -- Clear old timer table and update upvalue + AceTimer.activeTimers = {} + activeTimers = AceTimer.activeTimers + for handle, timer in pairs(oldTimers) do + local newTimer + -- Stop the old timer animation + local duration, elapsed = timer:GetDuration(), timer:GetElapsed() + timer:GetParent():Stop() + if timer.looping then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle + end + + -- Migrate transitional handles + if oldminor < 13 and AceTimer.hashCompatTable then + for handle, id in pairs(AceTimer.hashCompatTable) do + local t = activeTimers[id] + if t then + activeTimers[id] = nil + activeTimers[handle] = t + t.handle = handle + end + end + AceTimer.hashCompatTable = nil + end +end + +-- --------------------------------------------------------------------- +-- Embed handling + +AceTimer.embeds = AceTimer.embeds or {} + +local mixins = { + "ScheduleTimer", "ScheduleRepeatingTimer", + "CancelTimer", "CancelAllTimers", + "TimeLeft" +} + +function AceTimer:Embed(target) + AceTimer.embeds[target] = true + for _,v in pairs(mixins) do + target[v] = AceTimer[v] + end + return target +end + +-- AceTimer:OnEmbedDisable(target) +-- target (object) - target object that AceTimer is embedded in. +-- +-- cancel all timers registered for the object +function AceTimer:OnEmbedDisable(target) + target:CancelAllTimers() +end + +for addon in pairs(AceTimer.embeds) do + AceTimer:Embed(addon) +end diff --git a/Libs/AceTimer-3.0/AceTimer-3.0.xml b/Libs/AceTimer-3.0/AceTimer-3.0.xml new file mode 100644 index 0000000..38e9021 --- /dev/null +++ b/Libs/AceTimer-3.0/AceTimer-3.0.xml @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceTimer-3.0.lua"/> +</Ui> \ No newline at end of file diff --git a/Libs/BigLibTimer/BigLibTimer.lua b/Libs/BigLibTimer/BigLibTimer.lua deleted file mode 100644 index 3727a09..0000000 --- a/Libs/BigLibTimer/BigLibTimer.lua +++ /dev/null @@ -1,253 +0,0 @@ - -local MajorVersion = "BigLibTimer6" -local BigLibTimer = LibStub:NewLibrary(MajorVersion, tonumber("20150826224730") or tonumber(date("%Y%m%d%H%M%S"))) -if not BigLibTimer then return end - -BigLibTimer.API = BigLibTimer.API or {} - -function BigLibTimer:Register(handler) - if type(handler) ~= "table" then - handler = {} - elseif handler[MajorVersion] then - return - end - handler[MajorVersion] = {} - handler[MajorVersion].RECYCLE_TABLES = setmetatable({}, {__mode = "k"}) - handler[MajorVersion].TimerFrame = CreateFrame("Frame") - handler[MajorVersion].TimerFrame:Hide() - handler[MajorVersion].TIMER = {} - handler[MajorVersion].OnUpdate = function() BigLibTimer.OnUpdate(handler) end - handler[MajorVersion].TimerFrame:SetScript("OnUpdate", handler[MajorVersion].OnUpdate) - for key in pairs(BigLibTimer.API) do - handler[key] = function(...) return BigLibTimer.API[key](...) end - end - return handler -end - -function BigLibTimer.OnUpdate(handler) - local TIMER = handler[MajorVersion].TIMER - if next(TIMER) then - if not handler[MajorVersion].Running then - handler[MajorVersion].Running = 1 - for Name in pairs(TIMER) do - if TIMER and TIMER[Name] and not TIMER[Name].Running and TIMER[Name].Seconds <= GetTime() then - if TIMER[Name].Function then - TIMER[Name].Function(unpack(TIMER[Name].Args)) - if TIMER and TIMER[Name] and TIMER[Name].Seconds <= GetTime() then - if TIMER[Name].RepeatSeconds > 0 then - TIMER[Name].Seconds = GetTime() + TIMER[Name].RepeatSeconds - else - TIMER[Name].Args = handler:RecycleTable(TIMER[Name].Args) - TIMER[Name] = handler:RecycleTable(TIMER[Name]) - end - end - elseif TIMER[Name].RepeatSeconds > 0 then - TIMER[Name].Seconds = GetTime() + TIMER[Name].RepeatSeconds - else - TIMER[Name].Args = handler:RecycleTable(TIMER[Name].Args) - TIMER[Name] = handler:RecycleTable(TIMER[Name]) - end - end - end - if not next(TIMER) then - handler[MajorVersion].TimerFrame:Hide() - end - handler[MajorVersion].Running = nil - end - elseif not handler[MajorVersion].Running then - handler[MajorVersion].TimerFrame:Hide() - end -end - -function BigLibTimer.API:SetTimer(Name, Seconds, RepeatSeconds, Function, ...) - local TIMER = self[MajorVersion].TIMER - if type(Name) == "string" and TIMER then - if TIMER[Name] then - TIMER[Name].Args = self:RecycleTable(TIMER[Name].Args) - end - TIMER[Name] = self:CreateTable(TIMER[Name]) - TIMER[Name].Running = 1 - if type(Seconds) == "number" and Seconds > 0 then - TIMER[Name].Seconds = GetTime() + Seconds - else - TIMER[Name].Seconds = 0 - end - if type(RepeatSeconds) == "number" and RepeatSeconds > 0 then - TIMER[Name].RepeatSeconds = RepeatSeconds - else - TIMER[Name].RepeatSeconds = 0 - end - if type(Function) == "function" then - TIMER[Name].Function = Function - TIMER[Name].Args = self:CreateTable(TIMER[Name].Args) - local n = select("#", ...) - if n > 0 then - for i = 1, n do - TIMER[Name].Args[i] = select(i, ...) - end - end - end - if TIMER[Name].Seconds == 0 and TIMER[Name].Function then - Function(...) - if TIMER and TIMER[Name] and TIMER[Name].Seconds <= GetTime() then - if TIMER[Name].RepeatSeconds > 0 then - TIMER[Name].Seconds = GetTime() + TIMER[Name].RepeatSeconds - else - TIMER[Name].Args = self:RecycleTable(TIMER[Name].Args) - TIMER[Name] = self:RecycleTable(TIMER[Name]) - end - end - end - if TIMER and TIMER[Name] then - TIMER[Name].Running = nil - self[MajorVersion].TimerFrame:Show() - end - end -end - -function BigLibTimer.API:ReplaceTimer(Name, Seconds, RepeatSeconds, Function, ...) - local TIMER = self[MajorVersion].TIMER - if type(Name) == "string" and TIMER[Name] then - if type(Seconds) == "number" and Seconds > 0 then - TIMER[Name].Seconds = GetTime() + Seconds - elseif Seconds ~= nil then - TIMER[Name].Seconds = 0 - end - if type(RepeatSeconds) == "number" and RepeatSeconds > 0 then - TIMER[Name].RepeatSeconds = RepeatSeconds - elseif RepeatSeconds ~= nil then - TIMER[Name].RepeatSeconds = 0 - end - if type(Function) == "function" then - TIMER[Name].Function = Function - TIMER[Name].Args = self:CreateTable(TIMER[Name].Args) - local n = select("#", ...) - if n > 0 then - for i = 1, n do - TIMER[Name].Args[i] = select(i, ...) - end - end - elseif Function ~= nil then - TIMER[Name].Function = nil - end - return true - end - return false -end - -function BigLibTimer.API:ClearTimer(Name, Search) - local TIMER = self[MajorVersion].TIMER - local found = nil - if type(Name) == "string" then - if Search then - for key in pairs(TIMER) do - if key:match(Name) and ( TIMER[key].RepeatSeconds > 0 or TIMER[key].Seconds - GetTime() > 0 ) then - TIMER[key].Args = self:RecycleTable(TIMER[key].Args) - TIMER[key] = self:RecycleTable(TIMER[key]) - found = true - end - end - elseif TIMER[Name] and ( TIMER[Name].RepeatSeconds > 0 or TIMER[Name].Seconds - GetTime() > 0 ) then - TIMER[Name].Args = self:RecycleTable(TIMER[Name].Args) - TIMER[Name] = self:RecycleTable(TIMER[Name]) - return true - end - end - return found -end - -function BigLibTimer.API:ClearAllTimers() - wipe(self[MajorVersion].TIMER) -end - -function BigLibTimer.API:IsTimer(Name, Search) - local TIMER = self[MajorVersion].TIMER - if type(Name) == "string" then - if Search then - for key in pairs(TIMER) do - if key:match(Name) and ( TIMER[key].RepeatSeconds > 0 or TIMER[key].Seconds - GetTime() > 0 ) then - return true - end - end - elseif TIMER[Name] and ( TIMER[Name].RepeatSeconds > 0 or TIMER[Name].Seconds - GetTime() > 0 ) then - return true - end - end - return false -end - -function BigLibTimer.API:IsRepeatTimer(Name, Search) - local TIMER = self[MajorVersion].TIMER - if type(Name) == "string" then - if Search then - for key in pairs(TIMER) do - if key:match(Name) and TIMER[key].RepeatSeconds > 0 then - return true - end - end - elseif TIMER[Name] and TIMER[Name].RepeatSeconds > 0 then - return true - end - end - return false -end - -function BigLibTimer.API:GetTimer(Name) - local TIMER = self[MajorVersion].TIMER - if type(Name) == "string" and TIMER[Name] then - local TimeRemaining = TIMER[Name].Seconds - GetTime() - if TimeRemaining > 0 then - return TimeRemaining - end - end - return 0 -end - -function BigLibTimer.API:CreateTable(Table, All) - if type(Table) == "table" and type(Table[0]) ~= "userdata" then - if All then - self:RecycleTable(Table, All) - else - wipe(Table) - return Table - end - end - local t = next(self[MajorVersion].RECYCLE_TABLES) - if t then - self[MajorVersion].RECYCLE_TABLES[t] = nil - if next(t) then - return self:CreateTable() - end - return t - end - return {} -end - -function BigLibTimer.RecycleAllTables(self, Table, CompareList) - if not CompareList[Table] then - CompareList[Table] = 1 - for k, v in pairs(Table) do - if type(v) == "table" and type(v[0]) ~= "userdata" then - BigLibTimer.RecycleAllTables(self, v, CompareList) - end - if type(k) == "table" and type(k[0]) ~= "userdata" then - BigLibTimer.RecycleAllTables(self, k, CompareList) - end - end - self:RecycleTable(Table) - end -end - -function BigLibTimer.API:RecycleTable(Table, All) - if type(Table) == "table" and type(Table[0]) ~= "userdata" then - if All then - local CompareList = self:CreateTable() - BigLibTimer.RecycleAllTables(self, Table, CompareList) - self:RecycleTable(CompareList) - else - wipe(Table) - self[MajorVersion].RECYCLE_TABLES[Table] = 1 - end - end - return nil -end diff --git a/MaxDps.toc b/MaxDps.toc new file mode 100644 index 0000000..b832c94 --- /dev/null +++ b/MaxDps.toc @@ -0,0 +1,22 @@ +## Title: MaxDps +## Notes: Rotation helper framework. +## Version: 7.1.0 +## Author: Kaminaris +## Interface: 70100 +## SavedVariables: MaxDpsOptions +## OptionalDependencies: Bartender4, ElvUI, ButtonForge, SVUI_ActionBars + +Libs\LibStub\LibStub.lua +Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml +Libs\LibSharedMedia-3.0\lib.xml +Libs\AceAddon-3.0\AceAddon-3.0.xml +Libs\AceTimer-3.0\AceTimer-3.0.xml +Libs\AceEvent-3.0\AceEvent-3.0.xml +Libs\AceDB-3.0\AceDB-3.0.xml +Libs\AceGUI-3.0\AceGUI-3.0.xml +Libs\AceGUI-3.0-SharedMediaWidgets\widget.xml +Libs\AceConfig-3.0\AceConfig-3.0.xml + +core.lua +buttons.lua +helper.lua \ No newline at end of file diff --git a/Modules/module.lua.template b/Modules/module.lua.template deleted file mode 100644 index 60ebc96..0000000 --- a/Modules/module.lua.template +++ /dev/null @@ -1,65 +0,0 @@ --- Author : Kaminari --- Create Date : 13:03 2015-04-20 - --- Spells -local _Spell = 23881; - --- Auras -local _Aura = 12880; - --- Talents -local _isTalent = false; - ----------------------------------------------- --- Pre enable, checking talents ----------------------------------------------- -TDDps_[Class]_CheckTalents = function() - _isTalent = TD_TalentEnabled('Talent Name'); - -- other checking functions -end - ----------------------------------------------- --- Enabling Addon ----------------------------------------------- -function TDDps_[Class]_EnableAddon(mode) - mode = mode or 1; - TDDps.Description = "TD [Class] DPS supports: [Spec1], [Spec2], [Spec3]"; - TDDps.OnEnable = TDDps_[Class]_CheckTalents; - if mode == 1 then - TDDps.NextSpell = TDDps_[Class]_[Spec1]; - end; - if mode == 2 then - TDDps.NextSpell = TDDps_[Class]_[Spec2]; - end; - if mode == 3 then - TDDps.NextSpell = TDDps_[Class]_[Spec3]; - end; - TDDps:EnableAddon(); -end - ----------------------------------------------- --- Main rotation: [Spec1] ----------------------------------------------- -TDDps_[Class]_[Spec1] = function() - local timeShift, currentSpell = TD_EndCast(); - - return _Spell; -end - ----------------------------------------------- --- Main rotation: [Spec2] ----------------------------------------------- -TDDps_[Class]_[Spec2] = function() - local timeShift, currentSpell = TD_EndCast(); - - return _Spell; -end - ----------------------------------------------- --- Main rotation: [Spec3] ----------------------------------------------- -TDDps_[Class]_[Spec3] = function() - local timeShift, currentSpell = TD_EndCast(); - - return _Spell; -end \ No newline at end of file diff --git a/Modules/module.template.lua b/Modules/module.template.lua new file mode 100644 index 0000000..b41c801 --- /dev/null +++ b/Modules/module.template.lua @@ -0,0 +1,46 @@ +-- Replace all occurances of Class with Class Name ex. Warrior +-- Replace Spec1 - Spec3 with specialization names ex. Protection +-- Spells +local _Spell = 23881; + +-- Talents +local _isTalent = false; +MaxDps.Class = {}; + +MaxDps.Class.CheckTalents = function() + _isTalent = MaxDps:TalentEnabled('Talent Name'); + -- other checking functions +end + +function MaxDps:EnableRotationModule(mode) + mode = mode or 1; + MaxDps.Description = "Class Module [Spec1, Spec2, Spec3]"; + MaxDps.ModuleOnEnable = MaxDps.Class.CheckTalents; + if mode == 1 then + MaxDps.NextSpell = MaxDps.Class.Spec1; + end; + if mode == 2 then + MaxDps.NextSpell = MaxDps.Class.Spec2; + end; + if mode == 3 then + MaxDps.NextSpell = MaxDps.Class.Spec3; + end; +end + +function MaxDps.Class.Spec1() + local timeShift, currentSpell, gcd = MaxDps:EndCast(); + + return _Spell; +end + +function MaxDps.Class.Spec2() + local timeShift, currentSpell, gcd = MaxDps:EndCast(); + + return _Spell; +end + +function MaxDps.Class.Spec3() + local timeShift, currentSpell, gcd = MaxDps:EndCast(); + + return _Spell; +end \ No newline at end of file diff --git a/TDButtons.lua b/TDButtons.lua deleted file mode 100644 index 24f9aa5..0000000 --- a/TDButtons.lua +++ /dev/null @@ -1,415 +0,0 @@ -TDButton = {}; -TDButton.Spells = {}; -TDButton.Flags = {}; -TDButton.SpellsGlowing = {}; -TDButton.FramePool = {}; -TDButton.Frames = {}; - -function TDButton.CreateOverlay(parent, id, texture, r, g, b) - local frame = tremove(TDButton.FramePool); - if not frame then - frame = CreateFrame('Frame', 'TDButton_Overlay_' .. id, parent); - else --- frame:SetAttribute('name', 'TDButton_Overlay_' .. id); - end - - frame:SetParent(parent); - frame:SetFrameStrata('HIGH'); - frame:SetPoint('CENTER', 0, 0); - frame:SetWidth(parent:GetWidth() * 1.4); - frame:SetHeight(parent:GetHeight() * 1.4); - - local t = frame.texture; - if not t then - t = frame:CreateTexture('GlowOverlay', 'OVERLAY'); - t:SetTexture(texture or TDDps_Options_GetTexture()); - t:SetBlendMode('ADD'); - frame.texture = t; - end - - t:SetAllPoints(frame); - t:SetVertexColor( - r or TDDps_Options.highlightColor.r, - g or TDDps_Options.highlightColor.g, - b or TDDps_Options.highlightColor.b, - TDDps_Options.highlightColor.a - ); - - tinsert(TDButton.Frames, frame); - return frame; -end - -function TDButton.DestroyAllOverlays() - local frame; - for key, frame in pairs(TDButton.Frames) do - frame:GetParent().tdOverlays = nil; - frame:ClearAllPoints(); - frame:Hide(); - frame:SetParent(UIParent); - frame.width = nil; - frame.height = nil; - end - for key, frame in pairs(TDButton.Frames) do - tinsert(TDButton.FramePool, frame); - TDButton.Frames[key] = nil; - end -end - -function TDButton.UpdateButtonGlow() - local LAB; - local LBG; - local origShow; - local noFunction = function() end; - - if IsAddOnLoaded('ElvUI') then - LAB = LibStub:GetLibrary('LibActionButton-1.0-ElvUI'); - LBG = LibStub:GetLibrary('LibButtonGlow-1.0'); - origShow = LBG.ShowOverlayGlow; - elseif IsAddOnLoaded('Bartender4') then - LAB = LibStub:GetLibrary('LibActionButton-1.0'); - end - - if TDDps_Options.disableButtonGlow then - ActionBarActionEventsFrame:UnregisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); - if LAB then - LAB.eventFrame:UnregisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); - end - - if LBG then - LBG.ShowOverlayGlow = noFunction; - end - else - ActionBarActionEventsFrame:RegisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); - if LAB then - LAB.eventFrame:RegisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); - end - - if LBG then - LBG.ShowOverlayGlow = origShow; - end - end -end - ----------------------------------------------- --- Show Overlay on button ----------------------------------------------- -function TDButton.Glow(button, id, r, g, b, texture) - if button.tdOverlays and button.tdOverlays[id] then - button.tdOverlays[id]:Show(); - else - if not button.tdOverlays then - button.tdOverlays = {}; - end - - button.tdOverlays[id] = TDButton.CreateOverlay(button, id, texture, r, g, b); - button.tdOverlays[id]:Show(); - end -end - ----------------------------------------------- --- Hide Overlay on button ----------------------------------------------- -function TDButton.HideGlow(button, id) - if button.tdOverlays and button.tdOverlays[id] then - button.tdOverlays[id]:Hide(); - end -end - ----------------------------------------------- --- Fetch button spells ----------------------------------------------- -function TDButton.Fetch() - local origEna = TDDps.rotationEnabled; - TDDps.rotationEnabled = false; - TDDps.Spell = nil; - - TDButton.GlowClear(); - TDButton.Spells = {}; - TDButton.Flags = {}; - TDButton.SpellsGlowing = {}; - local isBartender = IsAddOnLoaded('Bartender4'); - local isElv = IsAddOnLoaded('ElvUI'); - local isSv = IsAddOnLoaded('SVUI_ActionBars'); - - if (isBartender) then - TDButton.FetchBartender4(); - elseif (isElv) then - TDButton.FetchElvUI(); - elseif (isSv) then - TDButton.FetchSuperVillain(); - else - TDButton.FetchBlizzard(); - end - - -- It does not alter original button frames so it needs to be fetched too - if IsAddOnLoaded('ButtonForge') then - TDButton.FetchButtonForge(); - end - - TDDps.rotationEnabled = origEna; - --TDDps:Print(_tdInfo, 'Fetched action bars!'); - -- after fetching invoke spell check - if TDDps.rotationEnabled then - TDDps:InvokeNextSpell(); - end -end - ----------------------------------------------- --- Button spells on original blizzard UI ----------------------------------------------- -function TDButton.FetchBlizzard() - local TDActionBarsBlizzard = {'Action', 'MultiBarBottomLeft', 'MultiBarBottomRight', 'MultiBarRight', 'MultiBarLeft'}; - for _, barName in pairs(TDActionBarsBlizzard) do - for i = 1, 12 do - local button = _G[barName .. 'Button' .. i]; - local slot = ActionButton_GetPagedID(button) or ActionButton_CalculateAction(button) or button:GetAttribute('action') or 0; - if HasAction(slot) then - local actionName, _; - local actionType, id = GetActionInfo(slot); - if actionType == 'macro' then _, _ , id = GetMacroSpell(id) end - if actionType == 'item' then - actionName = GetItemInfo(id); - elseif actionType == 'spell' or (actionType == 'macro' and id) then - actionName = GetSpellInfo(id); - end - if actionName then - if TDButton.Spells[actionName] == nil then - TDButton.Spells[actionName] = {}; - end - - tinsert(TDButton.Spells[actionName], button); - end - end - end - end -end - ----------------------------------------------- --- Button spells on original button forge ----------------------------------------------- -function TDButton.FetchButtonForge() - local i = 1; - while true do - local button = _G['ButtonForge' .. i]; - if not button then - break; - end - i = i + 1; - - local type = button:GetAttribute('type'); - if type then - local actionType = button:GetAttribute(type); - local id; - local actionName; - if type == 'macro' then - local id = GetMacroSpell(actionType); - if id then - actionName = GetSpellInfo(id); - end - elseif type == 'item' then - actionName = GetItemInfo(actionType); - elseif type == 'spell' then - actionName = GetSpellInfo(actionType); - end - if actionName then - if TDButton.Spells[actionName] == nil then - TDButton.Spells[actionName] = {}; - end - - tinsert(TDButton.Spells[actionName], button); - end - end - end -end - ----------------------------------------------- --- Button spells on ElvUI ----------------------------------------------- -function TDButton.FetchElvUI() - local ret = false; - for x = 1, 10 do - for i = 1, 12 do - local button = _G['ElvUI_Bar' .. x .. 'Button' .. i]; - if button then - local spellId = button:GetSpellId(); - if spellId then - local actionName, _ = GetSpellInfo(spellId); - if actionName then - if TDButton.Spells[actionName] == nil then - TDButton.Spells[actionName] = {}; - end - ret = true; - tinsert(TDButton.Spells[actionName], button); - end - end - end - end - end - return ret; -end - ----------------------------------------------- --- Button spells on SuperVillain ----------------------------------------------- -function TDButton.FetchSuperVillain() - local ret = false; - for x = 1, 10 do - for i = 1, 12 do - local button = _G['SVUI_ActionBar' .. x .. 'Button' .. i]; - if button then - local spellId = button:GetSpellId(); - if spellId then - local actionName, _ = GetSpellInfo(spellId); - if actionName then - if TDButton.Spells[actionName] == nil then - TDButton.Spells[actionName] = {}; - end - ret = true; - tinsert(TDButton.Spells[actionName], button); - end - end - end - end - end - return ret; -end - ----------------------------------------------- --- Button spells on Bartender4 ----------------------------------------------- -function TDButton.FetchBartender4() - local ret = false; - for i = 1, 120 do - local button = _G['BT4Button' .. i]; - if button then - local spellId = button:GetSpellId(); - if spellId then - local actionName, _ = GetSpellInfo(spellId); - if actionName then - if TDButton.Spells[actionName] == nil then - TDButton.Spells[actionName] = {}; - end - ret = true; - tinsert(TDButton.Spells[actionName], button); - end - end - end - end - return ret; -end - ----------------------------------------------- --- Dump spells for debug ----------------------------------------------- -function TDButton.Dump() - local s = ''; - for k, v in pairs(TDButton.Spells) do - s = s .. ', ' .. k; - end - print(s); -end - ----------------------------------------------- --- Find button on action bars ----------------------------------------------- -function TDButton.FindSpell(spellName) - local name = GetSpellInfo(spellName) or spellName; - return TDButton.Spells[name]; -end - ----------------------------------------------- --- Glow independent button by spell name ----------------------------------------------- -function TDButton.GlowIndependent(spellName, id, r, g, b, texture) - local name = GetSpellInfo(spellName) or spellName; - if TDButton.Spells[name] ~= nil then - for k, button in pairs(TDButton.Spells[name]) do - TDButton.Glow(button, id, r, g, b, texture); - end - end -end - ----------------------------------------------- --- Clear glow independent button by spell name ----------------------------------------------- -function TDButton.ClearGlowIndependent(spellName, id) - local name = GetSpellInfo(spellName) or spellName; - if TDButton.Spells[name] ~= nil then - for k, button in pairs(TDButton.Spells[name]) do - TDButton.HideGlow(button, id); - end - end -end - ----------------------------------------------- --- Glow cooldown ----------------------------------------------- -function TDButton.GlowCooldown(spell, condition) - if TDButton.Flags[spell] == nil then - TDButton.Flags[spell] = false; - end - if condition and not TDButton.Flags[spell] then - TDButton.Flags[spell] = true; - TDButton.GlowIndependent(spell, spell, 0, 1, 0); - end - if not condition and TDButton.Flags[spell] then - TDButton.Flags[spell] = false; - TDButton.ClearGlowIndependent(spell, spell); - end -end - -function TDButton_GlowCooldown(spell, condition) - TDButton.GlowCooldown(spell, condition); -end ----------------------------------------------- --- Glow spell by name ----------------------------------------------- -function TDButton.GlowSpell(spellName) - if TDButton.Spells[spellName] ~= nil then - for k, button in pairs(TDButton.Spells[spellName]) do - TDButton.Glow(button, 'next'); - end - TDButton.SpellsGlowing[spellName] = 1; - else - TDDps:Print(_tdError, 'Spell not found on action bars: ' .. spellName); - end -end - ----------------------------------------------- --- Glow spell by id ----------------------------------------------- -function TDButton.GlowSpellId(spellId) - local name = GetSpellInfo(spellId); - TDButton.GlowSpell(name); -end - ----------------------------------------------- --- Glow next spell by name ----------------------------------------------- -function TDButton.GlowNextSpell(spellName) - TDButton.GlowClear(); - TDButton.GlowSpell(spellName); -end - ----------------------------------------------- --- Glow next spell by id ----------------------------------------------- -function TDButton.GlowNextSpellId(spellId) - local spellName = GetSpellInfo(spellId); - TDButton.GlowClear(); - TDButton.GlowSpell(spellName); -end - ----------------------------------------------- --- Clear next spell glows ----------------------------------------------- -function TDButton.GlowClear() - for spellName, v in pairs(TDButton.SpellsGlowing) do - if v == 1 then - for k, button in pairs(TDButton.Spells[spellName]) do - TDButton.HideGlow(button, 'next'); - end - TDButton.SpellsGlowing[spellName] = 0; - end - end -end \ No newline at end of file diff --git a/TDDps.lua b/TDDps.lua deleted file mode 100644 index 58d7c77..0000000 --- a/TDDps.lua +++ /dev/null @@ -1,241 +0,0 @@ -_TD = _TD or {}; -- depreciated - -local timer = LibStub:GetLibrary("BigLibTimer6"):Register(timer); - -local TDDps = CreateFrame('Frame', 'TDDps'); -TDDps.AddonEnabled = false; -TDDps.rotationEnabled = false; -TDDps.ModuleOnEnable = nil; -TDDps.NextSpell = nil; -TDDps.Spell = nil; -TDDps.Description = nil; -TDDps.Time = 0; - -TDDps.Classes = { - [1] = 'Warrior', - [2] = 'Paladin', - [3] = 'Hunter', - [4] = 'Rogue', - [5] = 'Priest', - [6] = 'DeathKnight', - [7] = 'Shaman', - [8] = 'Mage', - [9] = 'Warlock', - [10] = 'Monk', - [11] = 'Druid', - [12] = 'DemonHunter', -} - --- Name and colors -TDDpsName = 'TDDPS'; -_tdInfo = '|cFF1394CC'; -_tdError = '|cFFF0563D'; -_tdSuccess = '|cFFBCCF02'; - --- Globals for time to die -TDDps_TargetGuid = nil; -TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; - -function TDDps:Print(color, message, force) - if force or TDDps_Options.debugMode or (not TDDps_Options.disabledInfo and color == _tdError) then - print(color .. TDDpsName .. ': ' .. message); - end -end - ----------------------------------------------- --- Disable dps addon functionality ----------------------------------------------- -function TDDps:DisableAddon() - if not TDDps.AddonEnabled then - return; - end - - TDButton.DestroyAllOverlays(); - TDDps:Print(_tdInfo, 'Disabling', true); - TDDps:SetScript('OnUpdate', nil); - TDDps.Spell = nil; - TDDps.rotationEnabled = false; - TDDps.AddonEnabled = false; -end - ----------------------------------------------- --- Initialize dps addon functionality ----------------------------------------------- -function TDDps:InitAddon() - TDDps:Show(); - - TDDps:RegisterEvent('PLAYER_TARGET_CHANGED'); - TDDps:RegisterEvent('PLAYER_TALENT_UPDATE'); - TDDps:RegisterEvent('ACTIONBAR_SLOT_CHANGED'); - TDDps:RegisterEvent('PLAYER_REGEN_DISABLED'); - TDDps:RegisterEvent('PLAYER_ENTERING_WORLD'); - - TDDps:RegisterEvent('ACTIONBAR_HIDEGRID'); - TDDps:RegisterEvent('ACTIONBAR_PAGE_CHANGED'); - TDDps:RegisterEvent('LEARNED_SPELL_IN_TAB'); - TDDps:RegisterEvent('CHARACTER_POINTS_CHANGED'); - TDDps:RegisterEvent('ACTIVE_TALENT_GROUP_CHANGED'); - TDDps:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED'); - TDDps:RegisterEvent('UPDATE_MACROS'); - TDDps:RegisterEvent('VEHICLE_UPDATE'); --- TDDps:RegisterEvent('PLAYER_REGEN_ENABLED'); - - TDDps:SetScript('OnEvent', self.OnEvent); - - TDDps:Print(_tdInfo, 'Initialized'); -end - ----------------------------------------------- --- Enable dps addon functionality ----------------------------------------------- -function TDDps:EnableAddon() - TDDps:Print(_tdInfo, 'Enabling'); - - if TDDps.NextSpell == nil or TDDps.AddonEnabled then - TDDps:Print(_tdError, 'Failed to enable addon!', true); - return; - end - TDDps:Print(_tdInfo, 'Fetching'); - TDButton.Fetch(); - - if TDDps.ModuleOnEnable then - TDDps.ModuleOnEnable(); - end - - TDDps:SetScript('OnUpdate', TDDps.OnUpdate); - - TDDps.AddonEnabled = true; - TDDps:Print(_tdSuccess, 'Enabled', true); -end - -function TDDps_EnableAddon() - -- backwards compatibility, don't load it until we say so -end - ----------------------------------------------- --- Event Script, Target Change, Specializaton Change ----------------------------------------------- -function TDDps:InvokeNextSpell() - -- invoke spell check - local oldSkill = TDDps.Spell; - - TDDps.Spell = TDDps.NextSpell(); - - if (oldSkill ~= TDDps.Spell or oldSkill == nil) and TDDps.Spell ~= nil then - TDButton.GlowNextSpellId(TDDps.Spell); - end - if TDDps.Spell == nil and oldSkill ~= nil then - TDButton.GlowClear(); - end -end - ----------------------------------------------- --- Event Script, Target Change, Specializaton Change ----------------------------------------------- -function TDDps.OnEvent(self, event) - if event == 'PLAYER_TALENT_UPDATE' then - TDDps:DisableAddon(); - elseif event == 'PLAYER_ENTERING_WORLD' then - TDButton.UpdateButtonGlow(); - elseif event == 'ACTIONBAR_SLOT_CHANGED' or - event == 'ACTIONBAR_HIDEGRID' or - event == 'ACTIONBAR_PAGE_CHANGED' or - event == 'LEARNED_SPELL_IN_TAB' or - event == 'CHARACTER_POINTS_CHANGED' or - event == 'ACTIVE_TALENT_GROUP_CHANGED' or - event == 'PLAYER_SPECIALIZATION_CHANGED' or - event == 'UPDATE_MACROS' or - event == 'VEHICLE_UPDATE' then - if TDDps.rotationEnabled then - timer:SetTimer("TDButton_Fetch", 0.5, 0, TDButton.Fetch); - end - return; - end - if event == 'PLAYER_TARGET_CHANGED' then - TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; - - if UnitExists('target') and not UnitIsFriend('player', 'target') then - TDDps_TargetGuid = UnitGUID('target'); - else - TDDps_TargetGuid = nil; - end - end - if TDDps.rotationEnabled then - if event == 'PLAYER_TARGET_CHANGED' then - if (UnitIsFriend('player', 'target')) then - return; - else - TDDps:InvokeNextSpell(); - end - end - end - if event == 'PLAYER_REGEN_DISABLED' and TDDps_Options.onCombatEnter and not TDDps.rotationEnabled then - TDDps:Print(_tdSuccess, 'Auto enable on combat!'); - TDDps.rotationEnabled = true; - TDDps:LoadModule(); - end --- if event == 'PLAYER_REGEN_ENABLED' then --- TDDps:Print(_tdSuccess, 'Auto disable on combat!'); --- TDDps.rotationEnabled = false; --- TDDps:DisableAddon(); --- end -end - ----------------------------------------------- --- Update script (timer) ----------------------------------------------- -function TDDps.OnUpdate(self, elapsed) - TDDps.Time = TDDps.Time + elapsed; - if TDDps.Time >= TDDps_Options.interval then - TDDps.Time = 0; - TDDps:InvokeNextSpell(); - end -end - ----------------------------------------------- --- Load appropriate addon for class ----------------------------------------------- -function TDDps:LoadModule() - TDDps.rotationEnabled = true; - - TDDps:Print(_tdInfo, 'Loading class module'); - local _, _, classId = UnitClass('player'); - if TDDps.Classes[classId] == nil then - TDDps:Print(_tdError, 'Invalid player class, please contact author of addon.', true); - return; - end - - local module = 'TDDps_' .. TDDps.Classes[classId]; - - if not IsAddOnLoaded(module) then - LoadAddOn(module); - end - - if not IsAddOnLoaded(module) then - TDDps:Print(_tdError, 'Could not find class module.', true); - return; - end - - local mode = GetSpecialization(); - local init = module .. '_EnableAddon'; - - _G[init](mode); - - -- backward compatiblity - if _TD['DPS_NextSpell'] ~= nil then - TDDps:Print(_tdInfo, 'Backward compatibility mode'); - TDDps.NextSpell = _TD['DPS_NextSpell']; - TDDps.ModuleOnEnable = _TD['DPS_OnEnable']; - TDDps.Description = _TD['DPS_Description']; - end - - TDDps:EnableAddon(); - - if TDDps.NextSpell == nil then - TDDps.rotationEnabled = false; - TDDps:Print(_tdError, 'Specialization is not supported.', true); - end - TDDps:Print(_tdSuccess, 'Finished Loading class module'); -end - -TDDps:InitAddon(); \ No newline at end of file diff --git a/TDDps.toc b/TDDps.toc deleted file mode 100644 index 532179c..0000000 --- a/TDDps.toc +++ /dev/null @@ -1,20 +0,0 @@ -## Title: TDDps -## Notes: Rotation helper framework. -## Version: 2.0 -## Author: Kaminaris -## Interface: 70100 -## SavedVariables: TDDps_Options -## OptionalDependencies: Bartender4, ElvUI, ButtonForge, SVUI_ActionBars - -Libs\LibStub\LibStub.lua -Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml -Libs\LibSharedMedia-3.0\lib.xml -Libs\AceGUI-3.0\AceGUI-3.0.xml -Libs\AceGUI-3.0-SharedMediaWidgets\widget.xml -Libs\AceConfig-3.0\AceConfig-3.0.xml -Libs\BigLibTimer\BigLibTimer.lua - -TDSettings.lua -TDButtons.lua -TDHelper.lua -TDDps.lua \ No newline at end of file diff --git a/TDHelper.lua b/TDHelper.lua deleted file mode 100644 index eb6ecfc..0000000 --- a/TDHelper.lua +++ /dev/null @@ -1,270 +0,0 @@ - --- Global cooldown spell id -_GlobalCooldown = 61304; - --- Bloodlust effects -_Bloodlust = 2825; -_TimeWrap = 80353; -_Heroism = 32182; -_AncientHysteria = 90355; -_Netherwinds = 160452; -_DrumsOfFury = 178207; -_Exhaustion = 57723; - -local INF = 2147483647; - -local _Bloodlusts = {_Bloodlust, _TimeWrap, _Heroism, _AncientHysteria, _Netherwinds, _DrumsOfFury}; - ----------------------------------------------- --- Current Specialisation name ----------------------------------------------- -function TD_SpecName() - local currentSpec = GetSpecialization(); - local currentSpecName = currentSpec and select(2, GetSpecializationInfo(currentSpec)) or 'None'; - return currentSpecName; -end - ----------------------------------------------- --- Is talent enabled ----------------------------------------------- -function TD_TalentEnabled(talent) - local found = false; - for i=1,7 do - for j=1,3 do - local id, n, x, sel = GetTalentInfo(i,j,GetActiveSpecGroup()); - if (id == talent or n == talent) and sel then - found = true; - end - end - end - return found; -end - ----------------------------------------------- --- Is aura on player ----------------------------------------------- -function TD_PersistentAura(name) - local spellName = GetSpellInfo(name); - local aura, _, _, count = UnitAura('player', spellName); - if aura then - return true, count; - end - return false, 0; -end - ----------------------------------------------- --- Is aura on player ----------------------------------------------- -function TD_Aura(name, timeShift) - timeShift = timeShift or 0.2; - local spellName = GetSpellInfo(name); - local _, _, _, count, _, _, expirationTime = UnitAura('player', spellName); - if expirationTime ~= nil and (expirationTime - GetTime()) > timeShift then - return true, count; - end - return false, 0; -end - ----------------------------------------------- --- Is aura on specific unit ----------------------------------------------- -function TD_UnitAura(name, timeShift, unit) - timeShift = timeShift or 0.2; - local spellName = GetSpellInfo(name); - local _, _, _, count, _, _, expirationTime = UnitAura(unit, spellName); - if expirationTime ~= nil and (expirationTime - GetTime()) > timeShift then - return true, count; - end - return false, 0; -end - ----------------------------------------------- --- Is aura on target ----------------------------------------------- -function TD_TargetAura(name, timeShift) - timeShift = timeShift or 0; - local spellName = GetSpellInfo(name) or name; - local _, _, _, _, _, _, expirationTime = UnitAura('target', spellName, nil, 'PLAYER|HARMFUL'); - if expirationTime ~= nil and (expirationTime - GetTime()) > timeShift then - local cd = expirationTime - GetTime() - (timeShift or 0); - return true, cd; - end - return false, 0; -end - ----------------------------------------------- --- When current cast will end ----------------------------------------------- -function TD_EndCast(target) - local t = GetTime(); - local c = t * 1000; - local spell, _, _, _, _, endTime = UnitCastingInfo(target or 'player'); - local gstart, gduration = GetSpellCooldown(_GlobalCooldown); - local gcd = gduration - (t - gstart); - if gcd < 0 then gcd = 0; end; - if endTime == nil then - return gcd, '', gcd; - end - local timeShift = (endTime - c) / 1000; - if gcd > timeShift then - timeShift = gcd; - end - return timeShift, spell, gcd; -end - ----------------------------------------------- --- Target Percent Health ----------------------------------------------- -function TD_TargetPercentHealth() - local health = UnitHealth('target'); - if health <= 0 then - return 0; - end; - local healthMax = UnitHealthMax('target'); - if healthMax <= 0 then - return 0; - end; - return health/healthMax; -end - ----------------------------------------------- --- Simple calculation of global cooldown ----------------------------------------------- -function TD_GlobalCooldown() - local haste = UnitSpellHaste('player'); - local gcd = 1.5 / ((haste / 100) + 1); - if gcd < 1 then - gcd = 1; - end - return gcd; -end - - ----------------------------------------------- --- Stacked spell CD, charges and max charges ----------------------------------------------- -function TD_SpellCharges(spell, timeShift) - local currentCharges, maxCharges, cooldownStart, cooldownDuration = GetSpellCharges(spell); - if currentCharges == nil then - local cd = TD_Cooldown(spell, timeShift); - if cd <= 0 then - return 0, 1, 0; - else - return cd, 0, 1; - end - end - local cd = cooldownDuration - (GetTime() - cooldownStart) - (timeShift or 0); - if cd > cooldownDuration then - cd = 0; - end - return cd, currentCharges, maxCharges; -end - ----------------------------------------------- --- Is Spell Available ----------------------------------------------- -function TD_SpellAvailable(spell, timeShift) - local cd = TD_Cooldown(spell, timeShift); - return cd <= 0, cd; -end - ----------------------------------------------- --- Extract tooltip number ----------------------------------------------- -function TD_ExtractTooltip(spell, pattern) - local _pattern = gsub(pattern, "%%s", "([%%d%.,]+)"); - - if not TDSpellTooltip then - CreateFrame('GameTooltip', 'TDSpellTooltip', UIParent, 'GameTooltipTemplate'); - TDSpellTooltip:SetOwner(UIParent, "ANCHOR_NONE") - end - TDSpellTooltip:SetSpellByID(spell); - - for i = 2, 4 do - local line = _G['TDSpellTooltipTextLeft' .. i]; - local text = line:GetText(); - - if text then - local cost = strmatch(text, _pattern); - if cost then - cost = cost and tonumber((gsub(cost, "%D", ""))); - return cost; - end - end - end - - return 0; -end - ----------------------------------------------- --- Spell Cooldown ----------------------------------------------- -function TD_Cooldown(spell, timeShift) - local start, duration, enabled = GetSpellCooldown(spell); - if enabled and duration == 0 and start == 0 then - return 0; - elseif enabled then - return (duration - (GetTime() - start) - (timeShift or 0)); - else - return 100000; - end; -end - ----------------------------------------------- --- Time to die - NOT YET WORKING ----------------------------------------------- ---TD_Hp0, TD_T0, TD_Hpm, TD_Tm -function TD_TimeToDie(health) - local unit = UnitGUID('target'); - if unit ~= TDDps_TargetGuid then - --print('phial'); - return INF; - end - - health = health or UnitHealth('target'); - - if health == UnitHealthMax('target') then - TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; - --print('phial2'); - return INF; - end - - local time = GetTime(); - - if not TD_Hp0 then - TD_Hp0, TD_T0 = health, time; - TD_Hpm, TD_Tm = health, time; - --print('phial3'); - return INF; - end - - TD_Hpm = (TD_Hpm + health) * .5; - TD_Tm = (TD_Tm + time) * .5; - - if TD_Hpm >= TD_Hp0 then - TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; - else - return health * (TD_T0 - TD_Tm) / (TD_Hpm - TD_Hp0); - end -end - ----------------------------------------------- --- Current or Future Mana Percent ----------------------------------------------- -function TD_Mana(minus, timeShift) - local _, casting = GetManaRegen(); - local mana = UnitPower('player', 0) - minus + (casting * timeShift); - return mana / UnitPowerMax('player', 0), mana; -end - ----------------------------------------------- --- Is bloodlust or similar effect ----------------------------------------------- -function TD_Bloodlust(timeShift) - -- @TODO: detect exhausted/seated debuff instead of 6 auras - for k, v in pairs (_Bloodlusts) do - if TD_Aura(v, timeShift or 0) then return true; end - end - - return false; -end diff --git a/TDSettings.lua b/TDSettings.lua deleted file mode 100644 index 6835a44..0000000 --- a/TDSettings.lua +++ /dev/null @@ -1,147 +0,0 @@ -local AceGUI = LibStub('AceGUI-3.0'); -local lsm = LibStub('AceGUISharedMediaWidgets-1.0'); -local media = LibStub('LibSharedMedia-3.0'); - -TDDps_textures = { - ['Ping'] = 'Interface\\Cooldown\\ping4', - ['Star'] = 'Interface\\Cooldown\\star4', - ['Starburst'] = 'Interface\\Cooldown\\starburst', -}; - -TDDps_Options = { - enabled = true, - disabledInfo = false, - debugMode = false, - disableButtonGlow = false, - onCombatEnter = true, - texture = '', - customTexture = '', - highlightColor = { - r = 1, g = 1, b = 1, a = 1 - }, - interval = 0.15 -} - -TDDps_Temp = {}; - -function TDDps_Options_GetTexture() - if TDDps_Options.customTexture ~= '' and TDDps_Options.customTexture ~= nil then - TDDps_Temp.finalTexture = TDDps_Options.customTexture; - return TDDps_Temp.finalTexture; - end - - TDDps_Temp.finalTexture = TDDps_textures[TDDps_Options.texture]; - if TDDps_Temp.finalTexture == '' or TDDps_Temp.finalTexture == nil then - TDDps_Temp.finalTexture = 'Interface\\Cooldown\\ping4'; - end - - return TDDps_Temp.finalTexture; -end - -local options = { - type = 'group', - name = 'TD Dps Options', - inline = false, - args = { - enable = { - name = 'Enable', - desc = 'Enables / disables the addon', - type = 'toggle', - width = 'full', - set = function(info, val) - TDDps_Options.enabled = val; - end, - get = function(info) return TDDps_Options.enabled end - }, - disabledInfo = { - name = 'Disable info messages', - desc = 'Enables / disables info messages, if you have issues with addon, make sure to deselect this.', - type = 'toggle', - width = 'full', - set = function(info, val) - TDDps_Options.disabledInfo = val; - end, - get = function(info) return TDDps_Options.disabledInfo end - }, - debugMode = { - name = 'Enable debug mode', - desc = 'Enables spammy chat messages (use this when addon does not work for you)', - type = 'toggle', - width = 'full', - set = function(info, val) - TDDps_Options.debugMode = val; - end, - get = function(info) return TDDps_Options.debugMode end - }, - disableButtonGlow = { - name = 'Dissable blizzard button glow (experimental)', - desc = 'Disables original blizzard button glow', - type = 'toggle', - width = 'full', - set = function(info, val) - TDDps_Options.disableButtonGlow = val; - TDButton_UpdateButtonGlow(); - end, - get = function(info) return TDDps_Options.disableButtonGlow end - }, - onCombatEnter = { - name = 'Enable upon entering combat', - desc = 'Automatically enables helper upon entering combat', - type = 'toggle', - width = 'full', - set = function(info, val) - TDDps_Options.onCombatEnter = val; - end, - get = function(info) return TDDps_Options.onCombatEnter end - }, - customTexture = { - name = 'Custom Texture', - desc = 'Sets Highlight texture, has priority over selected one (changing this requires UI Reload)', - type = 'input', - set = function(info, val) TDDps_Options.customTexture = strtrim(val or ''); end, - get = function(info) return strtrim(TDDps_Options.customTexture or '') end - }, - texture = { - type = 'select', - dialogControl = 'LSM30_Background', - name = 'Texture', - desc = 'Sets Highlight texture (changing this requires UI Reload)', - values = function() - return TDDps_textures; - end, - get = function() - return TDDps_Options.texture; - end, - set = function(self, val) - TDDps_Options.texture = val; - end, - }, - highlightColor = { - name = 'Highlight color', - desc = 'Sets Highlight color', - type = 'color', - set = function(info, r, g, b, a) - TDDps_Options.highlightColor.r = r; - TDDps_Options.highlightColor.g = g; - TDDps_Options.highlightColor.b = b; - TDDps_Options.highlightColor.a = a; - end, - get = function(info) - return TDDps_Options.highlightColor.r, TDDps_Options.highlightColor.g, TDDps_Options.highlightColor.b, TDDps_Options.highlightColor.a; - end, - hasAlpha = true - }, - interval = { - name = 'Interval in seconds', - desc = 'Sets how frequent rotation updates will be. Low value will result in fps drops.', - type = 'range', - min = 0.01, - max = 2, - set = function(info,val) TDDps_Options.interval = val end, - get = function(info) return TDDps_Options.interval end - }, - }, -} - -LibStub('AceConfigRegistry-3.0'):RegisterOptionsTable('TDDps_Settings', options) -LibStub('AceConfigDialog-3.0'):AddToBlizOptions('TDDps_Settings', 'TD Dps') diff --git a/buttons.lua b/buttons.lua new file mode 100644 index 0000000..65e0bfb --- /dev/null +++ b/buttons.lua @@ -0,0 +1,355 @@ +MaxDps.Spells = {}; +MaxDps.Flags = {}; +MaxDps.SpellsGlowing = {}; +MaxDps.FramePool = {}; +MaxDps.Frames = {}; + +function MaxDps:CreateOverlay(parent, id, texture, r, g, b) + local frame = tremove(self.FramePool); + if not frame then + frame = CreateFrame('Frame', 'MaxDps_Overlay_' .. id, parent); + end + + frame:SetParent(parent); + frame:SetFrameStrata('HIGH'); + frame:SetPoint('CENTER', 0, 0); + frame:SetWidth(parent:GetWidth() * 1.4); + frame:SetHeight(parent:GetHeight() * 1.4); + + local t = frame.texture; + if not t then + t = frame:CreateTexture('GlowOverlay', 'OVERLAY'); + t:SetTexture(texture or MaxDps:GetTexture()); + t:SetBlendMode('ADD'); + frame.texture = t; + end + + t:SetAllPoints(frame); + t:SetVertexColor( + r or self.db.global.highlightColor.r, + g or self.db.global.highlightColor.g, + b or self.db.global.highlightColor.b, + self.db.global.highlightColor.a + ); + + tinsert(self.Frames, frame); + return frame; +end + +function MaxDps:DestroyAllOverlays() + local frame; + for key, frame in pairs(self.Frames) do + frame:GetParent().MaxDpsOverlays = nil; + frame:ClearAllPoints(); + frame:Hide(); + frame:SetParent(UIParent); + frame.width = nil; + frame.height = nil; + end + for key, frame in pairs(self.Frames) do + tinsert(self.FramePool, frame); + self.Frames[key] = nil; + end +end + +function MaxDps:UpdateButtonGlow() + local LAB; + local LBG; + local origShow; + local noFunction = function() end; + + if IsAddOnLoaded('ElvUI') then + LAB = LibStub:GetLibrary('LibActionButton-1.0-ElvUI'); + LBG = LibStub:GetLibrary('LibButtonGlow-1.0'); + origShow = LBG.ShowOverlayGlow; + elseif IsAddOnLoaded('Bartender4') then + LAB = LibStub:GetLibrary('LibActionButton-1.0'); + end + + if self.db.global.disableButtonGlow then + ActionBarActionEventsFrame:UnregisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); + if LAB then + LAB.eventFrame:UnregisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); + end + + if LBG then + LBG.ShowOverlayGlow = noFunction; + end + else + ActionBarActionEventsFrame:RegisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); + if LAB then + LAB.eventFrame:RegisterEvent('SPELL_ACTIVATION_OVERLAY_GLOW_SHOW'); + end + + if LBG then + LBG.ShowOverlayGlow = origShow; + end + end +end + +function MaxDps:Glow(button, id, r, g, b, texture) + if button.MaxDpsOverlays and button.MaxDpsOverlays[id] then + button.MaxDpsOverlays[id]:Show(); + else + if not button.MaxDpsOverlays then + button.MaxDpsOverlays = {}; + end + + button.MaxDpsOverlays[id] = self:CreateOverlay(button, id, texture, r, g, b); + button.MaxDpsOverlays[id]:Show(); + end +end + +function MaxDps:HideGlow(button, id) + if button.MaxDpsOverlays and button.MaxDpsOverlays[id] then + button.MaxDpsOverlays[id]:Hide(); + end +end + +function MaxDps:Fetch() + self = MaxDps; + if self.rotationEnabled then + self:DisableRotationTimer(); + end + self.Spell = nil; + + self:GlowClear(); + self.Spells = {}; + self.Flags = {}; + self.SpellsGlowing = {}; + local isBartender = IsAddOnLoaded('Bartender4'); + local isElv = IsAddOnLoaded('ElvUI'); + local isSv = IsAddOnLoaded('SVUI_ActionBars'); + + if (isBartender) then + self:FetchBartender4(); + elseif (isElv) then + self:FetchElvUI(); + elseif (isSv) then + self:FetchSuperVillain(); + else + self:FetchBlizzard(); + end + + -- It does not alter original button frames so it needs to be fetched too + if IsAddOnLoaded('ButtonForge') then + self:FetchButtonForge(); + end + + if self.rotationEnabled then + self:EnableRotationTimer(); + self:InvokeNextSpell(); + end +end + +function MaxDps:FetchBlizzard() + local TDActionBarsBlizzard = {'Action', 'MultiBarBottomLeft', 'MultiBarBottomRight', 'MultiBarRight', 'MultiBarLeft'}; + for _, barName in pairs(TDActionBarsBlizzard) do + for i = 1, 12 do + local button = _G[barName .. 'Button' .. i]; + local slot = ActionButton_GetPagedID(button) or ActionButton_CalculateAction(button) or button:GetAttribute('action') or 0; + if HasAction(slot) then + local actionName, _; + local actionType, id = GetActionInfo(slot); + if actionType == 'macro' then _, _ , id = GetMacroSpell(id) end + if actionType == 'item' then + actionName = GetItemInfo(id); + elseif actionType == 'spell' or (actionType == 'macro' and id) then + actionName = GetSpellInfo(id); + end + if actionName then + if self.Spells[actionName] == nil then + self.Spells[actionName] = {}; + end + + tinsert(self.Spells[actionName], button); + end + end + end + end +end + +function MaxDps:FetchButtonForge() + local i = 1; + while true do + local button = _G['ButtonForge' .. i]; + if not button then + break; + end + i = i + 1; + + local type = button:GetAttribute('type'); + if type then + local actionType = button:GetAttribute(type); + local id; + local actionName; + if type == 'macro' then + local id = GetMacroSpell(actionType); + if id then + actionName = GetSpellInfo(id); + end + elseif type == 'item' then + actionName = GetItemInfo(actionType); + elseif type == 'spell' then + actionName = GetSpellInfo(actionType); + end + if actionName then + if self.Spells[actionName] == nil then + self.Spells[actionName] = {}; + end + + tinsert(self.Spells[actionName], button); + end + end + end +end + +function MaxDps:FetchElvUI() + local ret = false; + for x = 1, 10 do + for i = 1, 12 do + local button = _G['ElvUI_Bar' .. x .. 'Button' .. i]; + if button then + local spellId = button:GetSpellId(); + if spellId then + local actionName, _ = GetSpellInfo(spellId); + if actionName then + if self.Spells[actionName] == nil then + self.Spells[actionName] = {}; + end + ret = true; + tinsert(self.Spells[actionName], button); + end + end + end + end + end + return ret; +end + +function MaxDps:FetchSuperVillain() + local ret = false; + for x = 1, 10 do + for i = 1, 12 do + local button = _G['SVUI_ActionBar' .. x .. 'Button' .. i]; + if button then + local spellId = button:GetSpellId(); + if spellId then + local actionName, _ = GetSpellInfo(spellId); + if actionName then + if self.Spells[actionName] == nil then + self.Spells[actionName] = {}; + end + ret = true; + tinsert(self.Spells[actionName], button); + end + end + end + end + end + return ret; +end + +function MaxDps:FetchBartender4() + local ret = false; + for i = 1, 120 do + local button = _G['BT4Button' .. i]; + if button then + local spellId = button:GetSpellId(); + if spellId then + local actionName, _ = GetSpellInfo(spellId); + if actionName then + if self.Spells[actionName] == nil then + self.Spells[actionName] = {}; + end + ret = true; + tinsert(self.Spells[actionName], button); + end + end + end + end + return ret; +end + +function MaxDps:Dump() + local s = ''; + for k, v in pairs(self.Spells) do + s = s .. ', ' .. k; + end + print(s); +end + +function MaxDps:FindSpell(spellName) + local name = GetSpellInfo(spellName) or spellName; + return self.Spells[name]; +end + +function MaxDps:GlowIndependent(spellName, id, r, g, b, texture) + local name = GetSpellInfo(spellName) or spellName; + if self.Spells[name] ~= nil then + for k, button in pairs(self.Spells[name]) do + self:Glow(button, id, r, g, b, texture); + end + end +end + +function MaxDps:ClearGlowIndependent(spellName, id) + local name = GetSpellInfo(spellName) or spellName; + if self.Spells[name] ~= nil then + for k, button in pairs(self.Spells[name]) do + self:HideGlow(button, id); + end + end +end + +function MaxDps:GlowCooldown(spell, condition) + if self.Flags[spell] == nil then + self.Flags[spell] = false; + end + if condition and not self.Flags[spell] then + self.Flags[spell] = true; + self:GlowIndependent(spell, spell, 0, 1, 0); + end + if not condition and self.Flags[spell] then + self.Flags[spell] = false; + self:ClearGlowIndependent(spell, spell); + end +end + +function MaxDps:GlowSpell(spellName) + if self.Spells[spellName] ~= nil then + for k, button in pairs(self.Spells[spellName]) do + self:Glow(button, 'next'); + end + self.SpellsGlowing[spellName] = 1; + else + self:Print(self.Colors.Error .. 'Spell not found on action bars: ' .. spellName); + end +end + +function MaxDps:GlowSpellId(spellId) + local name = GetSpellInfo(spellId); + self:GlowSpell(name); +end + +function MaxDps:GlowNextSpell(spellName) + self:GlowClear(); + self:GlowSpell(spellName); +end + +function MaxDps:GlowNextSpellId(spellId) + local spellName = GetSpellInfo(spellId); + self:GlowClear(); + self:GlowSpell(spellName); +end + +function MaxDps:GlowClear() + for spellName, v in pairs(self.SpellsGlowing) do + if v == 1 then + for k, button in pairs(self.Spells[spellName]) do + self:HideGlow(button, 'next'); + end + self.SpellsGlowing[spellName] = 0; + end + end +end \ No newline at end of file diff --git a/core.lua b/core.lua new file mode 100644 index 0000000..e86b331 --- /dev/null +++ b/core.lua @@ -0,0 +1,329 @@ +local AceGUI = LibStub('AceGUI-3.0'); +local lsm = LibStub('AceGUISharedMediaWidgets-1.0'); +local media = LibStub('LibSharedMedia-3.0'); + +MaxDps = LibStub('AceAddon-3.0'):NewAddon('MaxDps', 'AceConsole-3.0', 'AceEvent-3.0', 'AceTimer-3.0'); + +MaxDps.Textures = { + ['Ping'] = 'Interface\\Cooldown\\ping4', + ['Star'] = 'Interface\\Cooldown\\star4', + ['Starburst'] = 'Interface\\Cooldown\\starburst', +}; +MaxDps.FinalTexture = nil; + +MaxDps.Colors = { + Info = '|cFF1394CC', + Error = '|cFFF0563D', + Success = '|cFFBCCF02', +} + +MaxDps.Classes = { + [1] = 'Warrior', + [2] = 'Paladin', + [3] = 'Hunter', + [4] = 'Rogue', + [5] = 'Priest', + [6] = 'DeathKnight', + [7] = 'Shaman', + [8] = 'Mage', + [9] = 'Warlock', + [10] = 'Monk', + [11] = 'Druid', + [12] = 'DemonHunter', +} + +local defaultOptions = { + global = { + enabled = true, + disabledInfo = false, + debugMode = false, + disableButtonGlow = false, + onCombatEnter = true, + texture = '', + customTexture = '', + highlightColor = { + r = 1, g = 1, b = 1, a = 1 + }, + interval = 0.15 + } +} + +local options = { + type = 'group', + name = 'MaxDps Options', + inline = false, + args = { + enable = { + name = 'Enable', + desc = 'Enables / disables the addon', + type = 'toggle', + width = 'full', + set = function(info, val) + MaxDps.db.global.enabled = val; + end, + get = function(info) return MaxDps.db.global.enabled end + }, + disabledInfo = { + name = 'Disable info messages', + desc = 'Enables / disables info messages, if you have issues with addon, make sure to deselect this.', + type = 'toggle', + width = 'full', + set = function(info, val) + MaxDps.db.global.disabledInfo = val; + end, + get = function(info) return MaxDps.db.global.disabledInfo end + }, + debugMode = { + name = 'Enable debug mode', + desc = 'Enables spammy chat messages (use this when addon does not work for you)', + type = 'toggle', + width = 'full', + set = function(info, val) + MaxDps.db.global.debugMode = val; + end, + get = function(info) return MaxDps.db.global.debugMode end + }, + disableButtonGlow = { + name = 'Dissable blizzard button glow (experimental)', + desc = 'Disables original blizzard button glow', + type = 'toggle', + width = 'full', + set = function(info, val) + MaxDps.db.global.disableButtonGlow = val; + MaxDps:UpdateButtonGlow(); + end, + get = function(info) return MaxDps.db.global.disableButtonGlow end + }, + onCombatEnter = { + name = 'Enable upon entering combat', + desc = 'Automatically enables helper upon entering combat', + type = 'toggle', + width = 'full', + set = function(info, val) + MaxDps.db.global.onCombatEnter = val; + end, + get = function(info) return MaxDps.db.global.onCombatEnter end + }, + customTexture = { + name = 'Custom Texture', + desc = 'Sets Highlight texture, has priority over selected one (changing this requires UI Reload)', + type = 'input', + set = function(info, val) MaxDps.db.global.customTexture = strtrim(val or ''); end, + get = function(info) return strtrim(MaxDps.db.global.customTexture or '') end + }, + texture = { + type = 'select', + dialogControl = 'LSM30_Background', + name = 'Texture', + desc = 'Sets Highlight texture (changing this requires UI Reload)', + values = function() + return MaxDps.Textures; + end, + get = function() + return MaxDps.db.global.texture; + end, + set = function(self, val) + MaxDps.db.global.texture = val; + end, + }, + highlightColor = { + name = 'Highlight color', + desc = 'Sets Highlight color', + type = 'color', + set = function(info, r, g, b, a) + MaxDps.db.global.highlightColor.r = r; + MaxDps.db.global.highlightColor.g = g; + MaxDps.db.global.highlightColor.b = b; + MaxDps.db.global.highlightColor.a = a; + end, + get = function(info) + return MaxDps.db.global.highlightColor.r, MaxDps.db.global.highlightColor.g, MaxDps.db.global.highlightColor.b, MaxDps.db.global.highlightColor.a; + end, + hasAlpha = true + }, + interval = { + name = 'Interval in seconds', + desc = 'Sets how frequent rotation updates will be. Low value will result in fps drops.', + type = 'range', + min = 0.01, + max = 2, + set = function(info,val) MaxDps.db.global.interval = val end, + get = function(info) return MaxDps.db.global.interval end + }, + }, +} + +function MaxDps:GetTexture() + if self.db.global.customTexture ~= '' and self.db.global.customTexture ~= nil then + self.FinalTexture = self.db.global.customTexture; + return self.FinalTexture; + end + + self.FinalTexture = self.Textures[self.db.global.texture]; + if self.FinalTexture == '' or self.FinalTexture == nil then + self.FinalTexture = 'Interface\\Cooldown\\ping4'; + end + + return self.FinalTexture; +end + +function MaxDps:OnInitialize() + LibStub('AceConfig-3.0'):RegisterOptionsTable('MaxDps', options, {'/maxdps'}); + self.db = LibStub('AceDB-3.0'):New('MaxDpsOptions', defaultOptions); + self.optionsFrame = LibStub('AceConfigDialog-3.0'):AddToBlizOptions('MaxDps', 'MaxDps'); +end + +function MaxDps:EnableRotation() + self:Print(self.Colors.Info .. 'Enabling'); + + if self.NextSpell == nil or self.rotationEnabled then + self:Print(self.Colors.Error .. 'Failed to enable addon!'); + return; + end + self:Print(self.Colors.Info .. 'Fetching'); + self.Fetch(); + + if self.ModuleOnEnable then + self.ModuleOnEnable(); + end + + self:EnableRotationTimer(); + + self.rotationEnabled = true; + self:Print(self.Colors.Success .. 'Enabled'); +end + +function MaxDps:EnableRotationTimer() + self.RotationTimer = self:ScheduleRepeatingTimer('InvokeNextSpell', self.db.global.interval); +end + +function MaxDps:DisableRotation() + if not self.rotationEnabled then + return; + end + + self:DisableRotationTimer(); + + self:DestroyAllOverlays(); + self:Print(self.Colors.Info .. 'Disabling'); + + self.Spell = nil; + self.rotationEnabled = false; +end + +function MaxDps:DisableRotationTimer() + if self.RotationTimer then + self:CancelTimer(self.RotationTimer); + end +end + +function MaxDps:OnEnable() + self:RegisterEvent('PLAYER_TARGET_CHANGED'); + self:RegisterEvent('PLAYER_TALENT_UPDATE'); + self:RegisterEvent('ACTIONBAR_SLOT_CHANGED'); + self:RegisterEvent('PLAYER_REGEN_DISABLED'); + self:RegisterEvent('PLAYER_ENTERING_WORLD'); + + self:RegisterEvent('ACTIONBAR_HIDEGRID'); + self:RegisterEvent('ACTIONBAR_PAGE_CHANGED'); + self:RegisterEvent('LEARNED_SPELL_IN_TAB'); + self:RegisterEvent('CHARACTER_POINTS_CHANGED'); + self:RegisterEvent('ACTIVE_TALENT_GROUP_CHANGED'); + self:RegisterEvent('PLAYER_SPECIALIZATION_CHANGED'); + self:RegisterEvent('UPDATE_MACROS'); + self:RegisterEvent('VEHICLE_UPDATE'); + -- self:RegisterEvent('PLAYER_REGEN_ENABLED'); + + self:Print(self.Colors.Info .. 'Initialized'); +end + +function MaxDps:PLAYER_TALENT_UPDATE() + self:DisableRotation(); +end + +function MaxDps:PLAYER_ENTERING_WORLD() + self:UpdateButtonGlow(); +end + +function MaxDps:PLAYER_TARGET_CHANGED() + if self.rotationEnabled then + if (UnitIsFriend('player', 'target')) then + return; + else + self:InvokeNextSpell(); + end + end +end + +function MaxDps:PLAYER_REGEN_DISABLED() + if self.db.global.onCombatEnter and not self.rotationEnabled then + self:Print(self.Colors.Success .. 'Auto enable on combat!'); + self:LoadModule(); + self:EnableRotation(); + end +end + +function MaxDps:ButtonFetch() + if self.rotationEnabled then + if self.fetchTimer then + self:CancelTimer(self.fetchTimer); + end + self.fetchTimer = self:ScheduleTimer('Fetch', 0.5); + end +end + +MaxDps.ACTIONBAR_SLOT_CHANGED = MaxDps.ButtonFetch; +MaxDps.ACTIONBAR_HIDEGRID = MaxDps.ButtonFetch; +MaxDps.ACTIONBAR_PAGE_CHANGED = MaxDps.ButtonFetch; +MaxDps.LEARNED_SPELL_IN_TAB = MaxDps.ButtonFetch; +MaxDps.CHARACTER_POINTS_CHANGED = MaxDps.ButtonFetch; +MaxDps.ACTIVE_TALENT_GROUP_CHANGED = MaxDps.ButtonFetch; +MaxDps.PLAYER_SPECIALIZATION_CHANGED = MaxDps.ButtonFetch; +MaxDps.UPDATE_MACROS = MaxDps.ButtonFetch; +MaxDps.VEHICLE_UPDATE = MaxDps.ButtonFetch; + +function MaxDps:InvokeNextSpell() + -- invoke spell check + local oldSkill = self.Spell; + + self.Spell = self:NextSpell(); + + if (oldSkill ~= self.Spell or oldSkill == nil) and self.Spell ~= nil then + self:GlowNextSpellId(self.Spell); + end + if self.Spell == nil and oldSkill ~= nil then + self:GlowClear(); + end +end + +function MaxDps:LoadModule() + if self.ModuleLoaded then + return; + end + + self:Print(self.Colors.Info .. 'Loading class module'); + local _, _, classId = UnitClass('player'); + if self.Classes[classId] == nil then + self:Print(_tdError, 'Invalid player class, please contact author of addon.'); + return; + end + + local module = 'MaxDps_' .. self.Classes[classId]; + + if not IsAddOnLoaded(module) then + LoadAddOn(module); + end + + if not IsAddOnLoaded(module) then + self:Print(self.Colors.Error .. 'Could not find class module.'); + return; + end + + local mode = GetSpecialization(); + + self:EnableRotationModule(mode); + self:Print(self.Colors.Info .. self.Description); + + self:Print(self.Colors.Info .. 'Finished Loading class module'); + self.ModuleLoaded = true; +end \ No newline at end of file diff --git a/helper.lua b/helper.lua new file mode 100644 index 0000000..30ddfc8 --- /dev/null +++ b/helper.lua @@ -0,0 +1,219 @@ + +-- Global cooldown spell id +_GlobalCooldown = 61304; + +-- Bloodlust effects +_Bloodlust = 2825; +_TimeWrap = 80353; +_Heroism = 32182; +_AncientHysteria = 90355; +_Netherwinds = 160452; +_DrumsOfFury = 178207; +_Exhaustion = 57723; + +local INF = 2147483647; + +local _Bloodlusts = {_Bloodlust, _TimeWrap, _Heroism, _AncientHysteria, _Netherwinds, _DrumsOfFury}; + +function MaxDps:SpecName() + local currentSpec = GetSpecialization(); + local currentSpecName = currentSpec and select(2, GetSpecializationInfo(currentSpec)) or 'None'; + return currentSpecName; +end + +function MaxDps:TalentEnabled(talent) + local found = false; + for i=1,7 do + for j=1,3 do + local id, n, x, sel = GetTalentInfo(i,j,GetActiveSpecGroup()); + if (id == talent or n == talent) and sel then + found = true; + end + end + end + return found; +end + +function MaxDps:PersistentAura(name) + local spellName = GetSpellInfo(name); + local aura, _, _, count = UnitAura('player', spellName); + if aura then + return true, count; + end + return false, 0; +end + +function MaxDps:Aura(name, timeShift) + timeShift = timeShift or 0.2; + local spellName = GetSpellInfo(name); + local _, _, _, count, _, _, expirationTime = UnitAura('player', spellName); + local time = GetTime(); + if expirationTime ~= nil and (expirationTime - time) > timeShift then + return true, count, (expirationTime - time); + end + return false, 0, 0; +end + +function MaxDps:UnitAura(name, timeShift, unit) + timeShift = timeShift or 0.2; + local spellName = GetSpellInfo(name); + local _, _, _, count, _, _, expirationTime = UnitAura(unit, spellName); + if expirationTime ~= nil and (expirationTime - GetTime()) > timeShift then + return true, count; + end + return false, 0; +end + +function MaxDps:TargetAura(name, timeShift) + timeShift = timeShift or 0; + local spellName = GetSpellInfo(name) or name; + local _, _, _, _, _, _, expirationTime = UnitAura('target', spellName, nil, 'PLAYER|HARMFUL'); + if expirationTime ~= nil and (expirationTime - GetTime()) > timeShift then + local cd = expirationTime - GetTime() - (timeShift or 0); + return true, cd; + end + return false, 0; +end + +function MaxDps:EndCast(target) + local t = GetTime(); + local c = t * 1000; + local spell, _, _, _, _, endTime = UnitCastingInfo(target or 'player'); + local gstart, gduration = GetSpellCooldown(_GlobalCooldown); + local gcd = gduration - (t - gstart); + if gcd < 0 then gcd = 0; end; + if endTime == nil then + return gcd, '', gcd; + end + local timeShift = (endTime - c) / 1000; + if gcd > timeShift then + timeShift = gcd; + end + return timeShift, spell, gcd; +end + +function MaxDps:TargetPercentHealth() + local health = UnitHealth('target'); + if health <= 0 then + return 0; + end; + local healthMax = UnitHealthMax('target'); + if healthMax <= 0 then + return 0; + end; + return health/healthMax; +end + +function MaxDps:GlobalCooldown() + local haste = UnitSpellHaste('player'); + local gcd = 1.5 / ((haste / 100) + 1); + if gcd < 1 then + gcd = 1; + end + return gcd; +end + +function MaxDps:SpellCharges(spell, timeShift) + local currentCharges, maxCharges, cooldownStart, cooldownDuration = GetSpellCharges(spell); + if currentCharges == nil then + local cd = MaxDps:Cooldown(spell, timeShift); + if cd <= 0 then + return 0, 1, 0; + else + return cd, 0, 1; + end + end + local cd = cooldownDuration - (GetTime() - cooldownStart) - (timeShift or 0); + if cd > cooldownDuration then + cd = 0; + end + return cd, currentCharges, maxCharges; +end + +function MaxDps:SpellAvailable(spell, timeShift) + local cd = MaxDps:Cooldown(spell, timeShift); + return cd <= 0, cd; +end + +function MaxDps:ExtractTooltip(spell, pattern) + local _pattern = gsub(pattern, "%%s", "([%%d%.,]+)"); + + if not TDSpellTooltip then + CreateFrame('GameTooltip', 'TDSpellTooltip', UIParent, 'GameTooltipTemplate'); + TDSpellTooltip:SetOwner(UIParent, "ANCHOR_NONE") + end + TDSpellTooltip:SetSpellByID(spell); + + for i = 2, 4 do + local line = _G['TDSpellTooltipTextLeft' .. i]; + local text = line:GetText(); + + if text then + local cost = strmatch(text, _pattern); + if cost then + cost = cost and tonumber((gsub(cost, "%D", ""))); + return cost; + end + end + end + + return 0; +end + +function MaxDps:Cooldown(spell, timeShift) + local start, duration, enabled = GetSpellCooldown(spell); + if enabled and duration == 0 and start == 0 then + return 0; + elseif enabled then + return (duration - (GetTime() - start) - (timeShift or 0)); + else + return 100000; + end; +end + +function MaxDps:TimeToDie(health) + local unit = UnitGUID('target'); + if unit ~= TDDps_TargetGuid then + return INF; + end + + health = health or UnitHealth('target'); + + if health == UnitHealthMax('target') then + TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; + return INF; + end + + local time = GetTime(); + + if not TD_Hp0 then + TD_Hp0, TD_T0 = health, time; + TD_Hpm, TD_Tm = health, time; + --print('phial3'); + return INF; + end + + TD_Hpm = (TD_Hpm + health) * .5; + TD_Tm = (TD_Tm + time) * .5; + + if TD_Hpm >= TD_Hp0 then + TD_Hp0, TD_T0, TD_Hpm, TD_Tm = nil, nil, nil, nil; + else + return health * (TD_T0 - TD_Tm) / (TD_Hpm - TD_Hp0); + end +end + +function MaxDps:Mana(minus, timeShift) + local _, casting = GetManaRegen(); + local mana = UnitPower('player', 0) - minus + (casting * timeShift); + return mana / UnitPowerMax('player', 0), mana; +end + +function MaxDps:Bloodlust(timeShift) + -- @TODO: detect exhausted/seated debuff instead of 6 auras + for k, v in pairs (_Bloodlusts) do + if MaxDps:Aura(v, timeShift or 0) then return true; end + end + + return false; +end