diff --git a/.pkgmeta b/.pkgmeta
index 12990f39..867b9d24 100644
--- a/.pkgmeta
+++ b/.pkgmeta
@@ -1,35 +1,18 @@
package-as: Details
-externals:
- Libs/LibStub: https://repos.wowace.com/wow/ace3/trunk/LibStub
- Libs/AceAddon-3.0: https://repos.wowace.com/wow/ace3/trunk/AceAddon-3.0
- Libs/AceComm-3.0: https://repos.wowace.com/wow/ace3/trunk/AceComm-3.0
- Libs/AceLocale-3.0: https://repos.wowace.com/wow/ace3/trunk/AceLocale-3.0
- Libs/AceSerializer-3.0: https://repos.wowace.com/wow/ace3/trunk/AceSerializer-3.0
- Libs/AceTimer-3.0: https://repos.wowace.com/wow/ace3/trunk/AceTimer-3.0
- Libs/CallbackHandler-1.0: https://repos.wowace.com/wow/ace3/trunk/CallbackHandler-1.0
- Libs/LibSharedMedia-3.0: https://repos.wowace.com/wow/libsharedmedia-3-0/trunk/LibSharedMedia-3.0
- Libs/LibGraph-2.0: https://repos.wowace.com/wow/libgraph-2-0/trunk/LibGraph-2.0
- Libs/LibDBIcon-1.0: https://repos.wowace.com/wow/libdbicon-1-0/trunk/LibDBIcon-1.0
- Libs/LibWindow-1.1: https://repos.wowace.com/wow/libwindow-1-1/trunk/LibWindow-1.1
- Libs/LibCompress: https://repos.wowace.com/wow/libcompress/trunk
- Libs/LibGroupInSpecT-1.1: https://repos.wowace.com/wow/libgroupinspect/trunk
- Libs/LibItemUpgradeInfo-1.0: https://repos.wowace.com/wow/libitemupgradeinfo-1-0
- Libs/NickTag-1.0: https://repos.curseforge.com/wow/nicktag
- Libs/LibDataBroker-1.1: https://repos.wowace.com/wow/libdatabroker-1-1
-
move-folders:
- Details/plugins/Details_3DModelsPaths: Details_3DModelsPaths
- Details/plugins/Details_DataStorage: Details_DataStorage
Details/plugins/Details_DmgRank: Details_DmgRank
- Details/plugins/Details_DpsTuning: Details_DpsTuning
Details/plugins/Details_EncounterDetails: Details_EncounterDetails
+ Details/plugins/Details_SpellDetails: Details_SpellDetails
+ Details/plugins/Details_TimeAttack: Details_TimeAttack
+ Details/plugins/Details_TinyThreat: Details_TinyThreat
+ Details/plugins/Details_Vanguard: Details_Vanguard
+ Details/plugins/Details_DataStorage: Details_DataStorage
+ Details/plugins/Details_3DModelsPaths: Details_3DModelsPaths
Details/plugins/Details_RaidCheck: Details_RaidCheck
+ Details/plugins/Details_DpsTuning: Details_DpsTuning
+ Details/plugins/Details_Streamer: Details_Streamer
Details/plugins/Details_RaidInfo-EmeraldNightmare: Details_RaidInfo-EmeraldNightmare
Details/plugins/Details_RaidInfo-Nighthold: Details_RaidInfo-Nighthold
Details/plugins/Details_RaidInfo-TrialOfValor: Details_RaidInfo-TrialOfValor
- Details/plugins/Details_RaidInfo-TombOfSargeras: Details_RaidInfo-TombOfSargeras
- Details/plugins/Details_Streamer: Details_Streamer
- Details/plugins/Details_TimeAttack: Details_TimeAttack
- Details/plugins/Details_TinyThreat: Details_TinyThreat
- Details/plugins/Details_Vanguard: Details_Vanguard
\ No newline at end of file
+ Details/plugins/Details_RaidInfo-TombOfSargeras: Details_RaidInfo-TombOfSargeras
\ No newline at end of file
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 00000000..b338695f
--- /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 00000000..17c568c9
--- /dev/null
+++ b/Libs/AceAddon-3.0/AceAddon-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/AceComm-3.0/AceComm-3.0.lua b/Libs/AceComm-3.0/AceComm-3.0.lua
new file mode 100644
index 00000000..17411ba4
--- /dev/null
+++ b/Libs/AceComm-3.0/AceComm-3.0.lua
@@ -0,0 +1,302 @@
+--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels.
+-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\
+-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server.
+--
+-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm: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 AceComm itself.\\
+-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceComm.
+-- @class file
+-- @name AceComm-3.0
+-- @release $Id: AceComm-3.0.lua 1107 2014-02-19 16:40:32Z nevcairiel $
+
+--[[ AceComm-3.0
+
+TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited.
+
+]]
+
+local MAJOR, MINOR = "AceComm-3.0", 9
+
+local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceComm then return end
+
+local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
+local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib")
+
+-- Lua APIs
+local type, next, pairs, tostring = type, next, pairs, tostring
+local strsub, strfind = string.sub, string.find
+local match = string.match
+local tinsert, tconcat = table.insert, table.concat
+local error, assert = error, assert
+
+-- WoW APIs
+local Ambiguate = Ambiguate
+
+-- 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, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix
+
+AceComm.embeds = AceComm.embeds or {}
+
+-- for my sanity and yours, let's give the message type bytes some names
+local MSG_MULTI_FIRST = "\001"
+local MSG_MULTI_NEXT = "\002"
+local MSG_MULTI_LAST = "\003"
+local MSG_ESCAPE = "\004"
+
+-- remove old structures (pre WoW 4.0)
+AceComm.multipart_origprefixes = nil
+AceComm.multipart_reassemblers = nil
+
+-- the multipart message spool: indexed by a combination of sender+distribution+
+AceComm.multipart_spool = AceComm.multipart_spool or {}
+
+--- Register for Addon Traffic on a specified prefix
+-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters
+-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived"
+function AceComm:RegisterComm(prefix, method)
+ if method == nil then
+ method = "OnCommReceived"
+ end
+
+ if #prefix > 16 then -- TODO: 15?
+ error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters")
+ end
+ RegisterAddonMessagePrefix(prefix)
+
+ return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler
+end
+
+local warnedPrefix=false
+
+--- Send a message over the Addon Channel
+-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent)
+-- @param text Data to send, nils (\000) not allowed. Any length.
+-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API
+-- @param target Destination for some distributions; see SendAddonMessage API
+-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL".
+-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send.
+-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified.
+function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg)
+ prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery!
+ if not( type(prefix)=="string" and
+ type(text)=="string" and
+ type(distribution)=="string" and
+ (target==nil or type(target)=="string") and
+ (prio=="BULK" or prio=="NORMAL" or prio=="ALERT")
+ ) then
+ error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2)
+ end
+
+ local textlen = #text
+ local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327
+ local queueName = prefix..distribution..(target or "")
+
+ local ctlCallback = nil
+ if callbackFn then
+ ctlCallback = function(sent)
+ return callbackFn(callbackArg, sent, textlen)
+ end
+ end
+
+ local forceMultipart
+ if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character
+ -- we need to escape the first character with a \004
+ if textlen+1 > maxtextlen then -- would we go over the size limit?
+ forceMultipart = true -- just make it multipart, no escape problems then
+ else
+ text = "\004" .. text
+ end
+ end
+
+ if not forceMultipart and textlen <= maxtextlen then
+ -- fits all in one message
+ CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen)
+ else
+ maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1)
+
+ -- first part
+ local chunk = strsub(text, 1, maxtextlen)
+ CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen)
+
+ -- continuation
+ local pos = 1+maxtextlen
+
+ while pos+maxtextlen <= textlen do
+ chunk = strsub(text, pos, pos+maxtextlen-1)
+ CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1)
+ pos = pos + maxtextlen
+ end
+
+ -- final part
+ chunk = strsub(text, pos)
+ CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen)
+ end
+end
+
+
+----------------------------------------
+-- Message receiving
+----------------------------------------
+
+do
+ local compost = setmetatable({}, {__mode = "k"})
+ local function new()
+ local t = next(compost)
+ if t then
+ compost[t]=nil
+ for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten
+ t[i]=nil
+ end
+ return t
+ end
+
+ return {}
+ end
+
+ local function lostdatawarning(prefix,sender,where)
+ DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")")
+ end
+
+ function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender)
+ local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
+ local spool = AceComm.multipart_spool
+
+ --[[
+ if spool[key] then
+ lostdatawarning(prefix,sender,"First")
+ -- continue and overwrite
+ end
+ --]]
+
+ spool[key] = message -- plain string for now
+ end
+
+ function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender)
+ local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
+ local spool = AceComm.multipart_spool
+ local olddata = spool[key]
+
+ if not olddata then
+ --lostdatawarning(prefix,sender,"Next")
+ return
+ end
+
+ if type(olddata)~="table" then
+ -- ... but what we have is not a table. So make it one. (Pull a composted one if available)
+ local t = new()
+ t[1] = olddata -- add old data as first string
+ t[2] = message -- and new message as second string
+ spool[key] = t -- and put the table in the spool instead of the old string
+ else
+ tinsert(olddata, message)
+ end
+ end
+
+ function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender)
+ local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender
+ local spool = AceComm.multipart_spool
+ local olddata = spool[key]
+
+ if not olddata then
+ --lostdatawarning(prefix,sender,"End")
+ return
+ end
+
+ spool[key] = nil
+
+ if type(olddata) == "table" then
+ -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat
+ tinsert(olddata, message)
+ AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender)
+ compost[olddata] = true
+ else
+ -- if we've only received a "first", the spooled data will still only be a string
+ AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender)
+ end
+ end
+end
+
+
+
+
+
+
+----------------------------------------
+-- Embed CallbackHandler
+----------------------------------------
+
+if not AceComm.callbacks then
+ AceComm.callbacks = CallbackHandler:New(AceComm,
+ "_RegisterComm",
+ "UnregisterComm",
+ "UnregisterAllComm")
+end
+
+AceComm.callbacks.OnUsed = nil
+AceComm.callbacks.OnUnused = nil
+
+local function OnEvent(self, event, prefix, message, distribution, sender)
+ if event == "CHAT_MSG_ADDON" then
+ sender = Ambiguate(sender, "none")
+ local control, rest = match(message, "^([\001-\009])(.*)")
+ if control then
+ if control==MSG_MULTI_FIRST then
+ AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender)
+ elseif control==MSG_MULTI_NEXT then
+ AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender)
+ elseif control==MSG_MULTI_LAST then
+ AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender)
+ elseif control==MSG_ESCAPE then
+ AceComm.callbacks:Fire(prefix, rest, distribution, sender)
+ else
+ -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!)
+ end
+ else
+ -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not
+ AceComm.callbacks:Fire(prefix, message, distribution, sender)
+ end
+ else
+ assert(false, "Received "..tostring(event).." event?!")
+ end
+end
+
+AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame")
+AceComm.frame:SetScript("OnEvent", OnEvent)
+AceComm.frame:UnregisterAllEvents()
+AceComm.frame:RegisterEvent("CHAT_MSG_ADDON")
+
+
+----------------------------------------
+-- Base library stuff
+----------------------------------------
+
+local mixins = {
+ "RegisterComm",
+ "UnregisterComm",
+ "UnregisterAllComm",
+ "SendCommMessage",
+}
+
+-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:..
+-- @param target target object to embed AceComm-3.0 in
+function AceComm:Embed(target)
+ for k, v in pairs(mixins) do
+ target[v] = self[v]
+ end
+ self.embeds[target] = true
+ return target
+end
+
+function AceComm:OnEmbedDisable(target)
+ target:UnregisterAllComm()
+end
+
+-- Update embeds
+for target, v in pairs(AceComm.embeds) do
+ AceComm:Embed(target)
+end
diff --git a/Libs/AceComm-3.0/AceComm-3.0.xml b/Libs/AceComm-3.0/AceComm-3.0.xml
new file mode 100644
index 00000000..c2e1bdfd
--- /dev/null
+++ b/Libs/AceComm-3.0/AceComm-3.0.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/Libs/AceComm-3.0/ChatThrottleLib.lua b/Libs/AceComm-3.0/ChatThrottleLib.lua
new file mode 100644
index 00000000..3ddeef81
--- /dev/null
+++ b/Libs/AceComm-3.0/ChatThrottleLib.lua
@@ -0,0 +1,524 @@
+--
+-- ChatThrottleLib by Mikk
+--
+-- Manages AddOn chat output to keep player from getting kicked off.
+--
+-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept
+-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage.
+--
+-- Priorities get an equal share of available bandwidth when fully loaded.
+-- Communication channels are separated on extension+chattype+destination and
+-- get round-robinned. (Destination only matters for whispers and channels,
+-- obviously)
+--
+-- Will install hooks for SendChatMessage and SendAddonMessage to measure
+-- bandwidth bypassing the library and use less bandwidth itself.
+--
+--
+-- Fully embeddable library. Just copy this file into your addon directory,
+-- add it to the .toc, and it's done.
+--
+-- Can run as a standalone addon also, but, really, just embed it! :-)
+--
+-- LICENSE: ChatThrottleLib is released into the Public Domain
+--
+
+local CTL_VERSION = 23
+
+local _G = _G
+
+if _G.ChatThrottleLib then
+ if _G.ChatThrottleLib.version >= CTL_VERSION then
+ -- There's already a newer (or same) version loaded. Buh-bye.
+ return
+ elseif not _G.ChatThrottleLib.securelyHooked then
+ print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, =v16) in it!")
+ -- ATTEMPT to unhook; this'll behave badly if someone else has hooked...
+ -- ... and if someone has securehooked, they can kiss that goodbye too... >.<
+ _G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage
+ if _G.ChatThrottleLib.ORIG_SendAddonMessage then
+ _G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage
+ end
+ end
+ _G.ChatThrottleLib.ORIG_SendChatMessage = nil
+ _G.ChatThrottleLib.ORIG_SendAddonMessage = nil
+end
+
+if not _G.ChatThrottleLib then
+ _G.ChatThrottleLib = {}
+end
+
+ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh)
+local ChatThrottleLib = _G.ChatThrottleLib
+
+ChatThrottleLib.version = CTL_VERSION
+
+
+
+------------------ TWEAKABLES -----------------
+
+ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800.
+ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff
+
+ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now.
+
+ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value
+
+
+local setmetatable = setmetatable
+local table_remove = table.remove
+local tostring = tostring
+local GetTime = GetTime
+local math_min = math.min
+local math_max = math.max
+local next = next
+local strlen = string.len
+local GetFramerate = GetFramerate
+local strlower = string.lower
+local unpack,type,pairs,wipe = unpack,type,pairs,wipe
+local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty
+
+
+-----------------------------------------------------------------------
+-- Double-linked ring implementation
+
+local Ring = {}
+local RingMeta = { __index = Ring }
+
+function Ring:New()
+ local ret = {}
+ setmetatable(ret, RingMeta)
+ return ret
+end
+
+function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position)
+ if self.pos then
+ obj.prev = self.pos.prev
+ obj.prev.next = obj
+ obj.next = self.pos
+ obj.next.prev = obj
+ else
+ obj.next = obj
+ obj.prev = obj
+ self.pos = obj
+ end
+end
+
+function Ring:Remove(obj)
+ obj.next.prev = obj.prev
+ obj.prev.next = obj.next
+ if self.pos == obj then
+ self.pos = obj.next
+ if self.pos == obj then
+ self.pos = nil
+ end
+ end
+end
+
+
+
+-----------------------------------------------------------------------
+-- Recycling bin for pipes
+-- A pipe is a plain integer-indexed queue of messages
+-- Pipes normally live in Rings of pipes (3 rings total, one per priority)
+
+ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
+local PipeBin = setmetatable({}, {__mode="k"})
+
+local function DelPipe(pipe)
+ PipeBin[pipe] = true
+end
+
+local function NewPipe()
+ local pipe = next(PipeBin)
+ if pipe then
+ wipe(pipe)
+ PipeBin[pipe] = nil
+ return pipe
+ end
+ return {}
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Recycling bin for messages
+
+ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different
+local MsgBin = setmetatable({}, {__mode="k"})
+
+local function DelMsg(msg)
+ msg[1] = nil
+ -- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them.
+ MsgBin[msg] = true
+end
+
+local function NewMsg()
+ local msg = next(MsgBin)
+ if msg then
+ MsgBin[msg] = nil
+ return msg
+ end
+ return {}
+end
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib:Init
+-- Initialize queues, set up frame for OnUpdate, etc
+
+
+function ChatThrottleLib:Init()
+
+ -- Set up queues
+ if not self.Prio then
+ self.Prio = {}
+ self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+ self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+ self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 }
+ end
+
+ -- v4: total send counters per priority
+ for _, Prio in pairs(self.Prio) do
+ Prio.nTotalSent = Prio.nTotalSent or 0
+ end
+
+ if not self.avail then
+ self.avail = 0 -- v5
+ end
+ if not self.nTotalSent then
+ self.nTotalSent = 0 -- v5
+ end
+
+
+ -- Set up a frame to get OnUpdate events
+ if not self.Frame then
+ self.Frame = CreateFrame("Frame")
+ self.Frame:Hide()
+ end
+ self.Frame:SetScript("OnUpdate", self.OnUpdate)
+ self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds
+ self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD")
+ self.OnUpdateDelay = 0
+ self.LastAvailUpdate = GetTime()
+ self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup
+
+ -- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7)
+ if not self.securelyHooked then
+ -- Use secure hooks as of v16. Old regular hook support yanked out in v21.
+ self.securelyHooked = true
+ --SendChatMessage
+ hooksecurefunc("SendChatMessage", function(...)
+ return ChatThrottleLib.Hook_SendChatMessage(...)
+ end)
+ --SendAddonMessage
+ hooksecurefunc("SendAddonMessage", function(...)
+ return ChatThrottleLib.Hook_SendAddonMessage(...)
+ end)
+ end
+ self.nBypass = 0
+end
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage
+
+local bMyTraffic = false
+
+function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...)
+ if bMyTraffic then
+ return
+ end
+ local self = ChatThrottleLib
+ local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD
+ self.avail = self.avail - size
+ self.nBypass = self.nBypass + size -- just a statistic
+end
+function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...)
+ if bMyTraffic then
+ return
+ end
+ local self = ChatThrottleLib
+ local size = tostring(text or ""):len() + tostring(prefix or ""):len();
+ size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD
+ self.avail = self.avail - size
+ self.nBypass = self.nBypass + size -- just a statistic
+end
+
+
+
+-----------------------------------------------------------------------
+-- ChatThrottleLib:UpdateAvail
+-- Update self.avail with how much bandwidth is currently available
+
+function ChatThrottleLib:UpdateAvail()
+ local now = GetTime()
+ local MAX_CPS = self.MAX_CPS;
+ local newavail = MAX_CPS * (now - self.LastAvailUpdate)
+ local avail = self.avail
+
+ if now - self.HardThrottlingBeginTime < 5 then
+ -- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then
+ avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5)
+ self.bChoking = true
+ elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs
+ avail = math_min(MAX_CPS, avail + newavail*0.5)
+ self.bChoking = true -- just a statistic
+ else
+ avail = math_min(self.BURST, avail + newavail)
+ self.bChoking = false
+ end
+
+ avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can.
+
+ self.avail = avail
+ self.LastAvailUpdate = now
+
+ return avail
+end
+
+
+-----------------------------------------------------------------------
+-- Despooling logic
+-- Reminder:
+-- - We have 3 Priorities, each containing a "Ring" construct ...
+-- - ... made up of N "Pipe"s (1 for each destination/pipename)
+-- - and each pipe contains messages
+
+function ChatThrottleLib:Despool(Prio)
+ local ring = Prio.Ring
+ while ring.pos and Prio.avail > ring.pos[1].nSize do
+ local msg = table_remove(ring.pos, 1)
+ if not ring.pos[1] then -- did we remove last msg in this pipe?
+ local pipe = Prio.Ring.pos
+ Prio.Ring:Remove(pipe)
+ Prio.ByName[pipe.name] = nil
+ DelPipe(pipe)
+ else
+ Prio.Ring.pos = Prio.Ring.pos.next
+ end
+ local didSend=false
+ local lowerDest = strlower(msg[3] or "")
+ if lowerDest == "raid" and not UnitInRaid("player") then
+ -- do nothing
+ elseif lowerDest == "party" and not UnitInParty("player") then
+ -- do nothing
+ else
+ Prio.avail = Prio.avail - msg.nSize
+ bMyTraffic = true
+ msg.f(unpack(msg, 1, msg.n))
+ bMyTraffic = false
+ Prio.nTotalSent = Prio.nTotalSent + msg.nSize
+ DelMsg(msg)
+ didSend = true
+ end
+ -- notify caller of delivery (even if we didn't send it)
+ if msg.callbackFn then
+ msg.callbackFn (msg.callbackArg, didSend)
+ end
+ -- USER CALLBACK MAY ERROR
+ end
+end
+
+
+function ChatThrottleLib.OnEvent(this,event)
+ -- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too.
+ local self = ChatThrottleLib
+ if event == "PLAYER_ENTERING_WORLD" then
+ self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning
+ self.avail = 0
+ end
+end
+
+
+function ChatThrottleLib.OnUpdate(this,delay)
+ local self = ChatThrottleLib
+
+ self.OnUpdateDelay = self.OnUpdateDelay + delay
+ if self.OnUpdateDelay < 0.08 then
+ return
+ end
+ self.OnUpdateDelay = 0
+
+ self:UpdateAvail()
+
+ if self.avail < 0 then
+ return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu.
+ end
+
+ -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop)
+ local n = 0
+ for prioname,Prio in pairs(self.Prio) do
+ if Prio.Ring.pos or Prio.avail < 0 then
+ n = n + 1
+ end
+ end
+
+ -- Anything queued still?
+ if n<1 then
+ -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing
+ for prioname, Prio in pairs(self.Prio) do
+ self.avail = self.avail + Prio.avail
+ Prio.avail = 0
+ end
+ self.bQueueing = false
+ self.Frame:Hide()
+ return
+ end
+
+ -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues
+ local avail = self.avail/n
+ self.avail = 0
+
+ for prioname, Prio in pairs(self.Prio) do
+ if Prio.Ring.pos or Prio.avail < 0 then
+ Prio.avail = Prio.avail + avail
+ if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then
+ self:Despool(Prio)
+ -- Note: We might not get here if the user-supplied callback function errors out! Take care!
+ end
+ end
+ end
+
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Spooling logic
+
+function ChatThrottleLib:Enqueue(prioname, pipename, msg)
+ local Prio = self.Prio[prioname]
+ local pipe = Prio.ByName[pipename]
+ if not pipe then
+ self.Frame:Show()
+ pipe = NewPipe()
+ pipe.name = pipename
+ Prio.ByName[pipename] = pipe
+ Prio.Ring:Add(pipe)
+ end
+
+ pipe[#pipe + 1] = msg
+
+ self.bQueueing = true
+end
+
+function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg)
+ if not self or not prio or not prefix or not text or not self.Prio[prio] then
+ error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2)
+ end
+ if callbackFn and type(callbackFn)~="function" then
+ error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
+ end
+
+ local nSize = text:len()
+
+ if nSize>255 then
+ error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2)
+ end
+
+ nSize = nSize + self.MSG_OVERHEAD
+
+ -- Check if there's room in the global available bandwidth gauge to send directly
+ if not self.bQueueing and nSize < self:UpdateAvail() then
+ self.avail = self.avail - nSize
+ bMyTraffic = true
+ _G.SendChatMessage(text, chattype, language, destination)
+ bMyTraffic = false
+ self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
+ if callbackFn then
+ callbackFn (callbackArg, true)
+ end
+ -- USER CALLBACK MAY ERROR
+ return
+ end
+
+ -- Message needs to be queued
+ local msg = NewMsg()
+ msg.f = _G.SendChatMessage
+ msg[1] = text
+ msg[2] = chattype or "SAY"
+ msg[3] = language
+ msg[4] = destination
+ msg.n = 4
+ msg.nSize = nSize
+ msg.callbackFn = callbackFn
+ msg.callbackArg = callbackArg
+
+ self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg)
+end
+
+
+function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg)
+ if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then
+ error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2)
+ end
+ if callbackFn and type(callbackFn)~="function" then
+ error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2)
+ end
+
+ local nSize = text:len();
+
+ if RegisterAddonMessagePrefix then
+ if nSize>255 then
+ error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2)
+ end
+ else
+ nSize = nSize + prefix:len() + 1
+ if nSize>255 then
+ error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2)
+ end
+ end
+
+ nSize = nSize + self.MSG_OVERHEAD;
+
+ -- Check if there's room in the global available bandwidth gauge to send directly
+ if not self.bQueueing and nSize < self:UpdateAvail() then
+ self.avail = self.avail - nSize
+ bMyTraffic = true
+ _G.SendAddonMessage(prefix, text, chattype, target)
+ bMyTraffic = false
+ self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
+ if callbackFn then
+ callbackFn (callbackArg, true)
+ end
+ -- USER CALLBACK MAY ERROR
+ return
+ end
+
+ -- Message needs to be queued
+ local msg = NewMsg()
+ msg.f = _G.SendAddonMessage
+ msg[1] = prefix
+ msg[2] = text
+ msg[3] = chattype
+ msg[4] = target
+ msg.n = (target~=nil) and 4 or 3;
+ msg.nSize = nSize
+ msg.callbackFn = callbackFn
+ msg.callbackArg = callbackArg
+
+ self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg)
+end
+
+
+
+
+-----------------------------------------------------------------------
+-- Get the ball rolling!
+
+ChatThrottleLib:Init()
+
+--[[ WoWBench debugging snippet
+if(WOWB_VER) then
+ local function SayTimer()
+ print("SAY: "..GetTime().." "..arg1)
+ end
+ ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer)
+ ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY")
+end
+]]
+
+
diff --git a/Libs/AceLocale-3.0/AceLocale-3.0.lua b/Libs/AceLocale-3.0/AceLocale-3.0.lua
new file mode 100644
index 00000000..2ecc0cb8
--- /dev/null
+++ b/Libs/AceLocale-3.0/AceLocale-3.0.lua
@@ -0,0 +1,137 @@
+--- **AceLocale-3.0** manages localization in addons, allowing for multiple locale to be registered with fallback to the base locale for untranslated strings.
+-- @class file
+-- @name AceLocale-3.0
+-- @release $Id: AceLocale-3.0.lua 1035 2011-07-09 03:20:13Z kaelten $
+local MAJOR,MINOR = "AceLocale-3.0", 6
+
+local AceLocale, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceLocale then return end -- no upgrade needed
+
+-- Lua APIs
+local assert, tostring, error = assert, tostring, error
+local getmetatable, setmetatable, rawset, rawget = getmetatable, setmetatable, 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: GAME_LOCALE, geterrorhandler
+
+local gameLocale = GetLocale()
+if gameLocale == "enGB" then
+ gameLocale = "enUS"
+end
+
+AceLocale.apps = AceLocale.apps or {} -- array of ["AppName"]=localetableref
+AceLocale.appnames = AceLocale.appnames or {} -- array of [localetableref]="AppName"
+
+-- This metatable is used on all tables returned from GetLocale
+local readmeta = {
+ __index = function(self, key) -- requesting totally unknown entries: fire off a nonbreaking error and return key
+ rawset(self, key, key) -- only need to see the warning once, really
+ geterrorhandler()(MAJOR..": "..tostring(AceLocale.appnames[self])..": Missing entry for '"..tostring(key).."'")
+ return key
+ end
+}
+
+-- This metatable is used on all tables returned from GetLocale if the silent flag is true, it does not issue a warning on unknown keys
+local readmetasilent = {
+ __index = function(self, key) -- requesting totally unknown entries: return key
+ rawset(self, key, key) -- only need to invoke this function once
+ return key
+ end
+}
+
+-- Remember the locale table being registered right now (it gets set by :NewLocale())
+-- NOTE: Do never try to register 2 locale tables at once and mix their definition.
+local registering
+
+-- local assert false function
+local assertfalse = function() assert(false) end
+
+-- This metatable proxy is used when registering nondefault locales
+local writeproxy = setmetatable({}, {
+ __newindex = function(self, key, value)
+ rawset(registering, key, value == true and key or value) -- assigning values: replace 'true' with key string
+ end,
+ __index = assertfalse
+})
+
+-- This metatable proxy is used when registering the default locale.
+-- It refuses to overwrite existing values
+-- Reason 1: Allows loading locales in any order
+-- Reason 2: If 2 modules have the same string, but only the first one to be
+-- loaded has a translation for the current locale, the translation
+-- doesn't get overwritten.
+--
+local writedefaultproxy = setmetatable({}, {
+ __newindex = function(self, key, value)
+ if not rawget(registering, key) then
+ rawset(registering, key, value == true and key or value)
+ end
+ end,
+ __index = assertfalse
+})
+
+--- Register a new locale (or extend an existing one) for the specified application.
+-- :NewLocale will return a table you can fill your locale into, or nil if the locale isn't needed for the players
+-- game locale.
+-- @paramsig application, locale[, isDefault[, silent]]
+-- @param application Unique name of addon / module
+-- @param locale Name of the locale to register, e.g. "enUS", "deDE", etc.
+-- @param isDefault If this is the default locale being registered (your addon is written in this language, generally enUS)
+-- @param silent If true, the locale will not issue warnings for missing keys. Must be set on the first locale registered. If set to "raw", nils will be returned for unknown keys (no metatable used).
+-- @usage
+-- -- enUS.lua
+-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "enUS", true)
+-- L["string1"] = true
+--
+-- -- deDE.lua
+-- local L = LibStub("AceLocale-3.0"):NewLocale("TestLocale", "deDE")
+-- if not L then return end
+-- L["string1"] = "Zeichenkette1"
+-- @return Locale Table to add localizations to, or nil if the current locale is not required.
+function AceLocale:NewLocale(application, locale, isDefault, silent)
+
+ -- GAME_LOCALE allows translators to test translations of addons without having that wow client installed
+ local gameLocale = GAME_LOCALE or gameLocale
+
+ local app = AceLocale.apps[application]
+
+ if silent and app and getmetatable(app) ~= readmetasilent then
+ geterrorhandler()("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' must be specified for the first locale registered")
+ end
+
+ if not app then
+ if silent=="raw" then
+ app = {}
+ else
+ app = setmetatable({}, silent and readmetasilent or readmeta)
+ end
+ AceLocale.apps[application] = app
+ AceLocale.appnames[app] = application
+ end
+
+ if locale ~= gameLocale and not isDefault then
+ return -- nop, we don't need these translations
+ end
+
+ registering = app -- remember globally for writeproxy and writedefaultproxy
+
+ if isDefault then
+ return writedefaultproxy
+ end
+
+ return writeproxy
+end
+
+--- Returns localizations for the current locale (or default locale if translations are missing).
+-- Errors if nothing is registered (spank developer, not just a missing translation)
+-- @param application Unique name of addon / module
+-- @param silent If true, the locale is optional, silently return nil if it's not found (defaults to false, optional)
+-- @return The locale table for the current language.
+function AceLocale:GetLocale(application, silent)
+ if not silent and not AceLocale.apps[application] then
+ error("Usage: GetLocale(application[, silent]): 'application' - No locales registered for '"..tostring(application).."'", 2)
+ end
+ return AceLocale.apps[application]
+end
diff --git a/Libs/AceLocale-3.0/AceLocale-3.0.xml b/Libs/AceLocale-3.0/AceLocale-3.0.xml
new file mode 100644
index 00000000..d69dbb13
--- /dev/null
+++ b/Libs/AceLocale-3.0/AceLocale-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/AceSerializer-3.0/AceSerializer-3.0.lua b/Libs/AceSerializer-3.0/AceSerializer-3.0.lua
new file mode 100644
index 00000000..cc23a67b
--- /dev/null
+++ b/Libs/AceSerializer-3.0/AceSerializer-3.0.lua
@@ -0,0 +1,287 @@
+--- **AceSerializer-3.0** can serialize any variable (except functions or userdata) into a string format,
+-- that can be send over the addon comm channel. AceSerializer was designed to keep all data intact, especially
+-- very large numbers or floating point numbers, and table structures. The only caveat currently is, that multiple
+-- references to the same table will be send individually.
+--
+-- **AceSerializer-3.0** can be embeded into your addon, either explicitly by calling AceSerializer: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 AceSerializer itself.\\
+-- It is recommended to embed AceSerializer, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceSerializer.
+-- @class file
+-- @name AceSerializer-3.0
+-- @release $Id: AceSerializer-3.0.lua 1135 2015-09-19 20:39:16Z nevcairiel $
+local MAJOR,MINOR = "AceSerializer-3.0", 5
+local AceSerializer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceSerializer then return end
+
+-- Lua APIs
+local strbyte, strchar, gsub, gmatch, format = string.byte, string.char, string.gsub, string.gmatch, string.format
+local assert, error, pcall = assert, error, pcall
+local type, tostring, tonumber = type, tostring, tonumber
+local pairs, select, frexp = pairs, select, math.frexp
+local tconcat = table.concat
+
+-- quick copies of string representations of wonky numbers
+local inf = math.huge
+
+local serNaN -- can't do this in 4.3, see ace3 ticket 268
+local serInf, serInfMac = "1.#INF", "inf"
+local serNegInf, serNegInfMac = "-1.#INF", "-inf"
+
+
+-- Serialization functions
+
+local function SerializeStringHelper(ch) -- Used by SerializeValue for strings
+ -- We use \126 ("~") as an escape character for all nonprints plus a few more
+ local n = strbyte(ch)
+ if n==30 then -- v3 / ticket 115: catch a nonprint that ends up being "~^" when encoded... DOH
+ return "\126\122"
+ elseif n<=32 then -- nonprint + space
+ return "\126"..strchar(n+64)
+ elseif n==94 then -- value separator
+ return "\126\125"
+ elseif n==126 then -- our own escape character
+ return "\126\124"
+ elseif n==127 then -- nonprint (DEL)
+ return "\126\123"
+ else
+ assert(false) -- can't be reached if caller uses a sane regex
+ end
+end
+
+local function SerializeValue(v, res, nres)
+ -- We use "^" as a value separator, followed by one byte for type indicator
+ local t=type(v)
+
+ if t=="string" then -- ^S = string (escaped to remove nonprints, "^"s, etc)
+ res[nres+1] = "^S"
+ res[nres+2] = gsub(v,"[%c \94\126\127]", SerializeStringHelper)
+ nres=nres+2
+
+ elseif t=="number" then -- ^N = number (just tostring()ed) or ^F (float components)
+ local str = tostring(v)
+ if tonumber(str)==v --[[not in 4.3 or str==serNaN]] then
+ -- translates just fine, transmit as-is
+ res[nres+1] = "^N"
+ res[nres+2] = str
+ nres=nres+2
+ elseif v == inf or v == -inf then
+ res[nres+1] = "^N"
+ res[nres+2] = v == inf and serInf or serNegInf
+ nres=nres+2
+ else
+ local m,e = frexp(v)
+ res[nres+1] = "^F"
+ res[nres+2] = format("%.0f",m*2^53) -- force mantissa to become integer (it's originally 0.5--0.9999)
+ res[nres+3] = "^f"
+ res[nres+4] = tostring(e-53) -- adjust exponent to counteract mantissa manipulation
+ nres=nres+4
+ end
+
+ elseif t=="table" then -- ^T...^t = table (list of key,value pairs)
+ nres=nres+1
+ res[nres] = "^T"
+ for k,v in pairs(v) do
+ nres = SerializeValue(k, res, nres)
+ nres = SerializeValue(v, res, nres)
+ end
+ nres=nres+1
+ res[nres] = "^t"
+
+ elseif t=="boolean" then -- ^B = true, ^b = false
+ nres=nres+1
+ if v then
+ res[nres] = "^B" -- true
+ else
+ res[nres] = "^b" -- false
+ end
+
+ elseif t=="nil" then -- ^Z = nil (zero, "N" was taken :P)
+ nres=nres+1
+ res[nres] = "^Z"
+
+ else
+ error(MAJOR..": Cannot serialize a value of type '"..t.."'") -- can't produce error on right level, this is wildly recursive
+ end
+
+ return nres
+end
+
+
+
+local serializeTbl = { "^1" } -- "^1" = Hi, I'm data serialized by AceSerializer protocol rev 1
+
+--- Serialize the data passed into the function.
+-- Takes a list of values (strings, numbers, booleans, nils, tables)
+-- and returns it in serialized form (a string).\\
+-- May throw errors on invalid data types.
+-- @param ... List of values to serialize
+-- @return The data in its serialized form (string)
+function AceSerializer:Serialize(...)
+ local nres = 1
+
+ for i=1,select("#", ...) do
+ local v = select(i, ...)
+ nres = SerializeValue(v, serializeTbl, nres)
+ end
+
+ serializeTbl[nres+1] = "^^" -- "^^" = End of serialized data
+
+ return tconcat(serializeTbl, "", 1, nres+1)
+end
+
+-- Deserialization functions
+local function DeserializeStringHelper(escape)
+ if escape<"~\122" then
+ return strchar(strbyte(escape,2,2)-64)
+ elseif escape=="~\122" then -- v3 / ticket 115: special case encode since 30+64=94 ("^") - OOPS.
+ return "\030"
+ elseif escape=="~\123" then
+ return "\127"
+ elseif escape=="~\124" then
+ return "\126"
+ elseif escape=="~\125" then
+ return "\94"
+ end
+ error("DeserializeStringHelper got called for '"..escape.."'?!?") -- can't be reached unless regex is screwed up
+end
+
+local function DeserializeNumberHelper(number)
+ --[[ not in 4.3 if number == serNaN then
+ return 0/0
+ else]]if number == serNegInf or number == serNegInfMac then
+ return -inf
+ elseif number == serInf or number == serInfMac then
+ return inf
+ else
+ return tonumber(number)
+ end
+end
+
+-- DeserializeValue: worker function for :Deserialize()
+-- It works in two modes:
+-- Main (top-level) mode: Deserialize a list of values and return them all
+-- Recursive (table) mode: Deserialize only a single value (_may_ of course be another table with lots of subvalues in it)
+--
+-- The function _always_ works recursively due to having to build a list of values to return
+--
+-- Callers are expected to pcall(DeserializeValue) to trap errors
+
+local function DeserializeValue(iter,single,ctl,data)
+
+ if not single then
+ ctl,data = iter()
+ end
+
+ if not ctl then
+ error("Supplied data misses AceSerializer terminator ('^^')")
+ end
+
+ if ctl=="^^" then
+ -- ignore extraneous data
+ return
+ end
+
+ local res
+
+ if ctl=="^S" then
+ res = gsub(data, "~.", DeserializeStringHelper)
+ elseif ctl=="^N" then
+ res = DeserializeNumberHelper(data)
+ if not res then
+ error("Invalid serialized number: '"..tostring(data).."'")
+ end
+ elseif ctl=="^F" then -- ^F^f
+ local ctl2,e = iter()
+ if ctl2~="^f" then
+ error("Invalid serialized floating-point number, expected '^f', not '"..tostring(ctl2).."'")
+ end
+ local m=tonumber(data)
+ e=tonumber(e)
+ if not (m and e) then
+ error("Invalid serialized floating-point number, expected mantissa and exponent, got '"..tostring(m).."' and '"..tostring(e).."'")
+ end
+ res = m*(2^e)
+ elseif ctl=="^B" then -- yeah yeah ignore data portion
+ res = true
+ elseif ctl=="^b" then -- yeah yeah ignore data portion
+ res = false
+ elseif ctl=="^Z" then -- yeah yeah ignore data portion
+ res = nil
+ elseif ctl=="^T" then
+ -- ignore ^T's data, future extensibility?
+ res = {}
+ local k,v
+ while true do
+ ctl,data = iter()
+ if ctl=="^t" then break end -- ignore ^t's data
+ k = DeserializeValue(iter,true,ctl,data)
+ if k==nil then
+ error("Invalid AceSerializer table format (no table end marker)")
+ end
+ ctl,data = iter()
+ v = DeserializeValue(iter,true,ctl,data)
+ if v==nil then
+ error("Invalid AceSerializer table format (no table end marker)")
+ end
+ res[k]=v
+ end
+ else
+ error("Invalid AceSerializer control code '"..ctl.."'")
+ end
+
+ if not single then
+ return res,DeserializeValue(iter)
+ else
+ return res
+ end
+end
+
+--- Deserializes the data into its original values.
+-- Accepts serialized data, ignoring all control characters and whitespace.
+-- @param str The serialized data (from :Serialize)
+-- @return true followed by a list of values, OR false followed by an error message
+function AceSerializer:Deserialize(str)
+ str = gsub(str, "[%c ]", "") -- ignore all control characters; nice for embedding in email and stuff
+
+ local iter = gmatch(str, "(^.)([^^]*)") -- Any ^x followed by string of non-^
+ local ctl,data = iter()
+ if not ctl or ctl~="^1" then
+ -- we purposefully ignore the data portion of the start code, it can be used as an extension mechanism
+ return false, "Supplied data is not AceSerializer data (rev 1)"
+ end
+
+ return pcall(DeserializeValue, iter)
+end
+
+
+----------------------------------------
+-- Base library stuff
+----------------------------------------
+
+AceSerializer.internals = { -- for test scripts
+ SerializeValue = SerializeValue,
+ SerializeStringHelper = SerializeStringHelper,
+}
+
+local mixins = {
+ "Serialize",
+ "Deserialize",
+}
+
+AceSerializer.embeds = AceSerializer.embeds or {}
+
+function AceSerializer:Embed(target)
+ for k, v in pairs(mixins) do
+ target[v] = self[v]
+ end
+ self.embeds[target] = true
+ return target
+end
+
+-- Update embeds
+for target, v in pairs(AceSerializer.embeds) do
+ AceSerializer:Embed(target)
+end
\ No newline at end of file
diff --git a/Libs/AceSerializer-3.0/AceSerializer-3.0.xml b/Libs/AceSerializer-3.0/AceSerializer-3.0.xml
new file mode 100644
index 00000000..477ac0b8
--- /dev/null
+++ b/Libs/AceSerializer-3.0/AceSerializer-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ 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 00000000..072ff8ea
--- /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 00000000..7c478a45
--- /dev/null
+++ b/Libs/AceTimer-3.0/AceTimer-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
new file mode 100644
index 00000000..eaa0d5d6
--- /dev/null
+++ b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua
@@ -0,0 +1,238 @@
+--[[ $Id: CallbackHandler-1.0.lua 1131 2015-06-04 07:29:24Z nevcairiel $ ]]
+local MAJOR, MINOR = "CallbackHandler-1.0", 6
+local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not CallbackHandler then return end -- No upgrade needed
+
+local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end}
+
+-- Lua APIs
+local tconcat = table.concat
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local next, select, pairs, type, tostring = next, select, pairs, type, tostring
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: geterrorhandler
+
+local xpcall = xpcall
+
+local function errorhandler(err)
+ return geterrorhandler()(err)
+end
+
+local function CreateDispatcher(argCount)
+ local code = [[
+ local next, xpcall, eh = ...
+
+ local method, ARGS
+ local function call() method(ARGS) end
+
+ local function dispatch(handlers, ...)
+ local index
+ index, method = next(handlers)
+ if not method then return end
+ local OLD_ARGS = ARGS
+ ARGS = ...
+ repeat
+ xpcall(call, eh)
+ index, method = next(handlers, index)
+ until not method
+ ARGS = OLD_ARGS
+ end
+
+ return dispatch
+ ]]
+
+ local ARGS, OLD_ARGS = {}, {}
+ for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end
+ code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", "))
+ return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler)
+end
+
+local Dispatchers = setmetatable({}, {__index=function(self, argCount)
+ local dispatcher = CreateDispatcher(argCount)
+ rawset(self, argCount, dispatcher)
+ return dispatcher
+end})
+
+--------------------------------------------------------------------------
+-- CallbackHandler:New
+--
+-- target - target object to embed public APIs in
+-- RegisterName - name of the callback registration API, default "RegisterCallback"
+-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback"
+-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API.
+
+function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName)
+
+ RegisterName = RegisterName or "RegisterCallback"
+ UnregisterName = UnregisterName or "UnregisterCallback"
+ if UnregisterAllName==nil then -- false is used to indicate "don't want this method"
+ UnregisterAllName = "UnregisterAllCallbacks"
+ end
+
+ -- we declare all objects and exported APIs inside this closure to quickly gain access
+ -- to e.g. function names, the "target" parameter, etc
+
+
+ -- Create the registry object
+ local events = setmetatable({}, meta)
+ local registry = { recurse=0, events=events }
+
+ -- registry:Fire() - fires the given event/message into the registry
+ function registry:Fire(eventname, ...)
+ if not rawget(events, eventname) or not next(events[eventname]) then return end
+ local oldrecurse = registry.recurse
+ registry.recurse = oldrecurse + 1
+
+ Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...)
+
+ registry.recurse = oldrecurse
+
+ if registry.insertQueue and oldrecurse==0 then
+ -- Something in one of our callbacks wanted to register more callbacks; they got queued
+ for eventname,callbacks in pairs(registry.insertQueue) do
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+ for self,func in pairs(callbacks) do
+ events[eventname][self] = func
+ -- fire OnUsed callback?
+ if first and registry.OnUsed then
+ registry.OnUsed(registry, target, eventname)
+ first = nil
+ end
+ end
+ end
+ registry.insertQueue = nil
+ end
+ end
+
+ -- Registration of a callback, handles:
+ -- self["method"], leads to self["method"](self, ...)
+ -- self with function ref, leads to functionref(...)
+ -- "addonId" (instead of self) with function ref, leads to functionref(...)
+ -- all with an optional arg, which, if present, gets passed as first argument (after self if present)
+ target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]])
+ if type(eventname) ~= "string" then
+ error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2)
+ end
+
+ method = method or eventname
+
+ local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten.
+
+ if type(method) ~= "string" and type(method) ~= "function" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2)
+ end
+
+ local regfunc
+
+ if type(method) == "string" then
+ -- self["method"] calling style
+ if type(self) ~= "table" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2)
+ elseif self==target then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2)
+ elseif type(self[method]) ~= "function" then
+ error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2)
+ end
+
+ if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
+ local arg=select(1,...)
+ regfunc = function(...) self[method](self,arg,...) end
+ else
+ regfunc = function(...) self[method](self,...) end
+ end
+ else
+ -- function ref with self=object or self="addonId" or self=thread
+ if type(self)~="table" and type(self)~="string" and type(self)~="thread" then
+ error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2)
+ end
+
+ if select("#",...)>=1 then -- this is not the same as testing for arg==nil!
+ local arg=select(1,...)
+ regfunc = function(...) method(arg,...) end
+ else
+ regfunc = method
+ end
+ end
+
+
+ if events[eventname][self] or registry.recurse<1 then
+ -- if registry.recurse<1 then
+ -- we're overwriting an existing entry, or not currently recursing. just set it.
+ events[eventname][self] = regfunc
+ -- fire OnUsed callback?
+ if registry.OnUsed and first then
+ registry.OnUsed(registry, target, eventname)
+ end
+ else
+ -- we're currently processing a callback in this registry, so delay the registration of this new entry!
+ -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency
+ registry.insertQueue = registry.insertQueue or setmetatable({},meta)
+ registry.insertQueue[eventname][self] = regfunc
+ end
+ end
+
+ -- Unregister a callback
+ target[UnregisterName] = function(self, eventname)
+ if not self or self==target then
+ error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2)
+ end
+ if type(eventname) ~= "string" then
+ error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2)
+ end
+ if rawget(events, eventname) and events[eventname][self] then
+ events[eventname][self] = nil
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(events[eventname]) then
+ registry.OnUnused(registry, target, eventname)
+ end
+ end
+ if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then
+ registry.insertQueue[eventname][self] = nil
+ end
+ end
+
+ -- OPTIONAL: Unregister all callbacks for given selfs/addonIds
+ if UnregisterAllName then
+ target[UnregisterAllName] = function(...)
+ if select("#",...)<1 then
+ error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2)
+ end
+ if select("#",...)==1 and ...==target then
+ error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2)
+ end
+
+
+ for i=1,select("#",...) do
+ local self = select(i,...)
+ if registry.insertQueue then
+ for eventname, callbacks in pairs(registry.insertQueue) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ end
+ end
+ end
+ for eventname, callbacks in pairs(events) do
+ if callbacks[self] then
+ callbacks[self] = nil
+ -- Fire OnUnused callback?
+ if registry.OnUnused and not next(callbacks) then
+ registry.OnUnused(registry, target, eventname)
+ end
+ end
+ end
+ end
+ end
+ end
+
+ return registry
+end
+
+
+-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it
+-- try to upgrade old implicit embeds since the system is selfcontained and
+-- relies on closures to work.
+
diff --git a/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
new file mode 100644
index 00000000..1aad3a2e
--- /dev/null
+++ b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/LibCompress/LibCompress.lua b/Libs/LibCompress/LibCompress.lua
new file mode 100644
index 00000000..4eba26e9
--- /dev/null
+++ b/Libs/LibCompress/LibCompress.lua
@@ -0,0 +1,1259 @@
+----------------------------------------------------------------------------------
+--
+-- LibCompress.lua
+--
+-- Authors: jjsheets and Galmok of European Stormrage (Horde)
+-- Email : sheets.jeff@gmail.com and galmok@gmail.com
+-- Licence: GPL version 2 (General Public License)
+-- Revision: $Revision: 77 $
+-- Date: $Date: 2017-07-13 16:54:12 -0300 (qui, 13 jul 2017) $
+----------------------------------------------------------------------------------
+
+
+local LibCompress = LibStub:NewLibrary("LibCompress", 90000 + tonumber(("$Revision: 77 $"):match("%d+")))
+
+if not LibCompress then return end
+
+-- list of codecs in this file:
+-- \000 - Never used
+-- \001 - Uncompressed
+-- \002 - LZW
+-- \003 - Huffman
+
+
+-- local is faster than global
+local CreateFrame = CreateFrame
+local type = type
+local tostring = tostring
+local select = select
+local next = next
+local loadstring = loadstring
+local setmetatable = setmetatable
+local rawset = rawset
+local assert = assert
+local table_insert = table.insert
+local table_remove = table.remove
+local table_concat = table.concat
+local string_char = string.char
+local string_byte = string.byte
+local string_len = string.len
+local string_sub = string.sub
+local unpack = unpack
+local pairs = pairs
+local math_modf = math.modf
+local bit_band = bit.band
+local bit_bor = bit.bor
+local bit_bxor = bit.bxor
+local bit_bnot = bit.bnot
+local bit_lshift = bit.lshift
+local bit_rshift = bit.rshift
+
+--------------------------------------------------------------------------------
+-- Cleanup
+
+local tables = {} -- tables that may be cleaned have to be kept here
+local tables_to_clean = {} -- list of tables by name (string) that may be reset to {} after a timeout
+
+-- tables that may be erased
+local function cleanup()
+ for k,v in pairs(tables_to_clean) do
+ tables[k] = {}
+ tables_to_clean[k] = nil
+ end
+end
+
+local timeout = -1
+local function onUpdate(frame, elapsed)
+ frame:Hide()
+ timeout = timeout - elapsed
+ if timeout <= 0 then
+ cleanup()
+ end
+end
+
+LibCompress.frame = LibCompress.frame or CreateFrame("frame", nil, UIParent) -- reuse the old frame
+LibCompress.frame:SetScript("OnUpdate", onUpdate)
+LibCompress.frame:Hide()
+
+local function setCleanupTables(...)
+ timeout = 15 -- empty tables after 15 seconds
+ if not LibCompress.frame:IsShown() then
+ LibCompress.frame:Show()
+ end
+ for i = 1, select("#",...) do
+ tables_to_clean[(select(i, ...))] = true
+ end
+end
+
+----------------------------------------------------------------------
+----------------------------------------------------------------------
+--
+-- compression algorithms
+
+--------------------------------------------------------------------------------
+-- LZW codec
+-- implemented by sheets.jeff@gmail.com
+
+-- encode is used to uniquely encode a number into a sequence of bytes that can be decoded using decode()
+-- the bytes returned by this do not contain "\000"
+local bytes = {}
+local function encode(x)
+ for k = 1, #bytes do
+ bytes[k] = nil
+ end
+
+ bytes[#bytes + 1] = x % 255
+ x=math.floor(x/255)
+
+ while x > 0 do
+ bytes[#bytes + 1] = x % 255
+ x=math.floor(x/255)
+ end
+ if #bytes == 1 and bytes[1] > 0 and bytes[1] < 250 then
+ return string_char(bytes[1])
+ else
+ for i = 1, #bytes do
+ bytes[i] = bytes[i] + 1
+ end
+ return string_char(256 - #bytes, unpack(bytes))
+ end
+end
+
+--decode converts a unique character sequence into its equivalent number, from ss, beginning at the ith char.
+-- returns the decoded number and the count of characters used in the decode process.
+local function decode(ss, i)
+ i = i or 1
+ local a = string_byte(ss, i, i)
+ if a > 249 then
+ local r = 0
+ a = 256 - a
+ for n = i + a, i + 1, -1 do
+ r = r * 255 + string_byte(ss, n, n) - 1
+ end
+ return r, a + 1
+ else
+ return a, 1
+ end
+end
+
+-- Compresses the given uncompressed string.
+-- Unless the uncompressed string starts with "\002", this is guaranteed to return a string equal to or smaller than
+-- the passed string.
+-- the returned string will only contain "\000" characters in rare circumstances, and will contain none if the
+-- source string has none.
+local dict = {}
+function LibCompress:CompressLZW(uncompressed)
+ if type(uncompressed) == "string" then
+ local dict_size = 256
+ for k in pairs(dict) do
+ dict[k] = nil
+ end
+
+ local result = {"\002"}
+ local w = ''
+ local ressize = 1
+
+ for i = 0, 255 do
+ dict[string_char(i)] = i
+ end
+
+ for i = 1, #uncompressed do
+ local c = uncompressed:sub(i, i)
+ local wc = w..c
+ if dict[wc] then
+ w = wc
+ else
+ dict[wc] = dict_size
+ dict_size = dict_size + 1
+ local r = encode(dict[w])
+ ressize = ressize + #r
+ result[#result + 1] = r
+ w = c
+ end
+ end
+
+ if w then
+ local r = encode(dict[w])
+ ressize = ressize + #r
+ result[#result + 1] = r
+ end
+
+ if (#uncompressed + 1) > ressize then
+ return table_concat(result)
+ else
+ return string_char(1)..uncompressed
+ end
+ else
+ return nil, "Can only compress strings"
+ end
+end
+
+-- if the passed string is a compressed string, this will decompress it and return the decompressed string.
+-- Otherwise it return an error message
+-- compressed strings are marked by beginning with "\002"
+function LibCompress:DecompressLZW(compressed)
+ if type(compressed) == "string" then
+ if compressed:sub(1, 1) ~= "\002" then
+ return nil, "Can only decompress LZW compressed data ("..tostring(compressed:sub(1, 1))..")"
+ end
+
+ compressed = compressed:sub(2)
+ local dict_size = 256
+
+ for k in pairs(dict) do
+ dict[k] = nil
+ end
+
+ for i = 0, 255 do
+ dict[i] = string_char(i)
+ end
+
+ local result = {}
+ local t = 1
+ local delta, k
+ k, delta = decode(compressed, t)
+ t = t + delta
+ result[#result + 1] = dict[k]
+
+ local w = dict[k]
+ local entry
+ while t <= #compressed do
+ k, delta = decode(compressed, t)
+ t = t + delta
+ entry = dict[k] or (w..w:sub(1, 1))
+ result[#result + 1] = entry
+ dict[dict_size] = w..entry:sub(1, 1)
+ dict_size = dict_size + 1
+ w = entry
+ end
+ return table_concat(result)
+ else
+ return nil, "Can only uncompress strings"
+ end
+end
+
+
+--------------------------------------------------------------------------------
+-- Huffman codec
+-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com
+
+local function addCode(tree, bcode, length)
+ if tree then
+ tree.bcode = bcode
+ tree.blength = length
+ if tree.c1 then
+ addCode(tree.c1, bit_bor(bcode, bit_lshift(1, length)), length + 1)
+ end
+ if tree.c2 then
+ addCode(tree.c2, bcode, length + 1)
+ end
+ end
+end
+
+local function escape_code(code, length)
+ local escaped_code = 0
+ local b
+ local l = 0
+ for i = length -1, 0, - 1 do
+ b = bit_band(code, bit_lshift(1, i)) == 0 and 0 or 1
+ escaped_code = bit_lshift(escaped_code, 1 + b) + b
+ l = l + b
+ end
+ if length + l > 32 then
+ return nil, "escape overflow ("..(length + l)..")"
+ end
+ return escaped_code, length + l
+end
+
+tables.Huffman_compressed = {}
+tables.Huffman_large_compressed = {}
+
+local compressed_size = 0
+local remainder
+local remainder_length
+local function addBits(tbl, code, length)
+ if remainder_length+length >= 32 then
+ -- we have at least 4 bytes to store; bulk it
+ remainder = remainder + bit_lshift(code, remainder_length) -- this overflows! Top part of code is lost (but we handle it below)
+ -- remainder now holds 4 full bytes to store. So lets do it.
+ compressed_size = compressed_size + 1
+ tbl[compressed_size] = string_char(bit_band(remainder, 255)) ..
+ string_char(bit_band(bit_rshift(remainder, 8), 255)) ..
+ string_char(bit_band(bit_rshift(remainder, 16), 255)) ..
+ string_char(bit_band(bit_rshift(remainder, 24), 255))
+ remainder = 0
+ code = bit_rshift(code, 32 - remainder_length)
+ length = remainder_length + length - 32
+ remainder_length = 0
+ end
+ if remainder_length+length >= 16 then
+ -- we have at least 2 bytes to store; bulk it
+ remainder = remainder + bit_lshift(code, remainder_length)
+ remainder_length = length + remainder_length
+ -- remainder now holds at least 2 full bytes to store. So lets do it.
+ compressed_size = compressed_size + 1
+ tbl[compressed_size] = string_char(bit_band(remainder, 255)) .. string_char(bit_band(bit_rshift(remainder, 8), 255))
+ remainder = bit_rshift(remainder, 16)
+ code = remainder
+ length = remainder_length - 16
+ remainder = 0
+ remainder_length = 0
+ end
+ remainder = remainder + bit_lshift(code, remainder_length)
+ remainder_length = length + remainder_length
+ if remainder_length >= 8 then
+ compressed_size = compressed_size + 1
+ tbl[compressed_size] = string_char(bit_band(remainder, 255))
+ remainder = bit_rshift(remainder, 8)
+ remainder_length = remainder_length -8
+ end
+end
+
+-- word size for this huffman algorithm is 8 bits (1 byte). This means the best compression is representing 1 byte with 1 bit, i.e. compress to 0.125 of original size.
+function LibCompress:CompressHuffman(uncompressed)
+ if type(uncompressed) ~= "string" then
+ return nil, "Can only compress strings"
+ end
+ if #uncompressed == 0 then
+ return "\001"
+ end
+
+ -- make histogram
+ local hist = {}
+ local n = 0
+ -- don't have to use all data to make the histogram
+ local uncompressed_size = string_len(uncompressed)
+ local c
+ for i = 1, uncompressed_size do
+ c = string_byte(uncompressed, i)
+ hist[c] = (hist[c] or 0) + 1
+ end
+
+ --Start with as many leaves as there are symbols.
+ local leafs = {}
+ local leaf
+ local symbols = {}
+ for symbol, weight in pairs(hist) do
+ leaf = { symbol=string_char(symbol), weight=weight }
+ symbols[symbol] = leaf
+ table_insert(leafs, leaf)
+ end
+
+ --Enqueue all leaf nodes into the first queue (by probability in increasing order so that the least likely item is in the head of the queue).
+ sort(leafs, function(a, b)
+ if a.weight < b.weight then
+ return true
+ elseif a.weight > b.weight then
+ return false
+ else
+ return nil
+ end
+ end)
+
+ local nLeafs = #leafs
+
+ -- create tree
+ local huff = {}
+ --While there is more than one node in the queues:
+ local length, height, li, hi, leaf1, leaf2
+ local newNode
+ while (#leafs + #huff > 1) do
+ -- Dequeue the two nodes with the lowest weight.
+ -- Dequeue first
+ if not next(huff) then
+ li, leaf1 = next(leafs)
+ table_remove(leafs, li)
+ elseif not next(leafs) then
+ hi, leaf1 = next(huff)
+ table_remove(huff, hi)
+ else
+ li, length = next(leafs)
+ hi, height = next(huff)
+ if length.weight <= height.weight then
+ leaf1 = length
+ table_remove(leafs, li)
+ else
+ leaf1 = height
+ table_remove(huff, hi)
+ end
+ end
+
+ -- Dequeue second
+ if not next(huff) then
+ li, leaf2 = next(leafs)
+ table_remove(leafs, li)
+ elseif not next(leafs) then
+ hi, leaf2 = next(huff)
+ table_remove(huff, hi)
+ else
+ li, length = next(leafs)
+ hi, height = next(huff)
+ if length.weight <= height.weight then
+ leaf2 = length
+ table_remove(leafs, li)
+ else
+ leaf2 = height
+ table_remove(huff, hi)
+ end
+ end
+
+ --Create a new internal node, with the two just-removed nodes as children (either node can be either child) and the sum of their weights as the new weight.
+ newNode = {
+ c1 = leaf1,
+ c2 = leaf2,
+ weight = leaf1.weight + leaf2.weight
+ }
+ table_insert(huff,newNode)
+ end
+
+ if #leafs > 0 then
+ li, length = next(leafs)
+ table_insert(huff, length)
+ table_remove(leafs, li)
+ end
+ huff = huff[1]
+
+ -- assign codes to each symbol
+ -- c1 = "0", c2 = "1"
+ -- As a common convention, bit '0' represents following the left child and bit '1' represents following the right child.
+ -- c1 = left, c2 = right
+
+ addCode(huff, 0, 0)
+ if huff then
+ huff.bcode = 0
+ huff.blength = 1
+ end
+
+ -- READING
+ -- bitfield = 0
+ -- bitfield_len = 0
+ -- read byte1
+ -- bitfield = bitfield + bit_lshift(byte1, bitfield_len)
+ -- bitfield_len = bitfield_len + 8
+ -- read byte2
+ -- bitfield = bitfield + bit_lshift(byte2, bitfield_len)
+ -- bitfield_len = bitfield_len + 8
+ -- (use 5 bits)
+ -- word = bit_band( bitfield, bit_lshift(1,5)-1)
+ -- bitfield = bit_rshift( bitfield, 5)
+ -- bitfield_len = bitfield_len - 5
+ -- read byte3
+ -- bitfield = bitfield + bit_lshift(byte3, bitfield_len)
+ -- bitfield_len = bitfield_len + 8
+
+ -- WRITING
+ remainder = 0
+ remainder_length = 0
+
+ local compressed = tables.Huffman_compressed
+ --compressed_size = 0
+
+ -- first byte is version info. 0 = uncompressed, 1 = 8 - bit word huffman compressed
+ compressed[1] = "\003"
+
+ -- Header: byte 0 = #leafs, bytes 1-3 = size of uncompressed data
+ -- max 2^24 bytes
+ local length = string_len(uncompressed)
+ compressed[2] = string_char(bit_band(nLeafs -1, 255)) -- number of leafs
+ compressed[3] = string_char(bit_band(length, 255)) -- bit 0-7
+ compressed[4] = string_char(bit_band(bit_rshift(length, 8), 255)) -- bit 8-15
+ compressed[5] = string_char(bit_band(bit_rshift(length, 16), 255)) -- bit 16-23
+ compressed_size = 5
+
+ -- create symbol/code map
+ local escaped_code, escaped_code_len, success, msg
+ for symbol, leaf in pairs(symbols) do
+ addBits(compressed, symbol, 8)
+ escaped_code, escaped_code_len = escape_code(leaf.bcode, leaf.blength)
+ if not escaped_code then
+ return nil, escaped_code_len
+ end
+ addBits(compressed, escaped_code, escaped_code_len)
+ addBits(compressed, 3, 2)
+ end
+
+ -- create huffman code
+ local large_compressed = tables.Huffman_large_compressed
+ local large_compressed_size = 0
+ local ulimit
+ for i = 1, length, 200 do
+ ulimit = length < (i + 199) and length or (i + 199)
+
+ for sub_i = i, ulimit do
+ c = string_byte(uncompressed, sub_i)
+ addBits(compressed, symbols[c].bcode, symbols[c].blength)
+ end
+
+ large_compressed_size = large_compressed_size + 1
+ large_compressed[large_compressed_size] = table_concat(compressed, "", 1, compressed_size)
+ compressed_size = 0
+ end
+
+ -- add remaining bits (if any)
+ if remainder_length > 0 then
+ large_compressed_size = large_compressed_size + 1
+ large_compressed[large_compressed_size] = string_char(remainder)
+ end
+ local compressed_string = table_concat(large_compressed, "", 1, large_compressed_size)
+
+ -- is compression worth it? If not, return uncompressed data.
+ if (#uncompressed + 1) <= #compressed_string then
+ return "\001"..uncompressed
+ end
+
+ setCleanupTables("Huffman_compressed", "Huffman_large_compressed")
+ return compressed_string
+end
+
+-- lookuptable (cached between calls)
+local lshiftMask = {}
+setmetatable(lshiftMask, {
+ __index = function (t, k)
+ local v = bit_lshift(1, k)
+ rawset(t, k, v)
+ return v
+ end
+})
+
+-- lookuptable (cached between calls)
+local lshiftMinusOneMask = {}
+setmetatable(lshiftMinusOneMask, {
+ __index = function (t, k)
+ local v = bit_lshift(1, k) - 1
+ rawset(t, k, v)
+ return v
+ end
+})
+
+local function bor64(valueA_high, valueA, valueB_high, valueB)
+ return bit_bor(valueA_high, valueB_high),
+ bit_bor(valueA, valueB)
+end
+
+local function band64(valueA_high, valueA, valueB_high, valueB)
+ return bit_band(valueA_high, valueB_high),
+ bit_band(valueA, valueB)
+end
+
+local function lshift64(value_high, value, lshift_amount)
+ if lshift_amount == 0 then
+ return value_high, value
+ end
+ if lshift_amount >= 64 then
+ return 0, 0
+ end
+ if lshift_amount < 32 then
+ return bit_bor(bit_lshift(value_high, lshift_amount), bit_rshift(value, 32-lshift_amount)),
+ bit_lshift(value, lshift_amount)
+ end
+ -- 32-63 bit shift
+ return bit_lshift(value, lshift_amount), -- builtin modulus 32 on shift amount
+ 0
+end
+
+local function rshift64(value_high, value, rshift_amount)
+ if rshift_amount == 0 then
+ return value_high, value
+ end
+ if rshift_amount >= 64 then
+ return 0, 0
+ end
+ if rshift_amount < 32 then
+ return bit_rshift(value_high, rshift_amount),
+ bit_bor(bit_lshift(value_high, 32-rshift_amount), bit_rshift(value, rshift_amount))
+ end
+ -- 32-63 bit shift
+ return 0,
+ bit_rshift(value_high, rshift_amount)
+end
+
+local function getCode2(bitfield_high, bitfield, field_len)
+ if field_len >= 2 then
+ -- [bitfield_high..bitfield]: bit 0 is right most in bitfield. bit is left most in bitfield_high
+ local b1, b2, remainder_high, remainder
+ for i = 0, field_len - 2 do
+ b1 = i <= 31 and bit_band(bitfield, bit_lshift(1, i)) or bit_band(bitfield_high, bit_lshift(1, i)) -- for shifts, 32 = 0 (5 bit used)
+ b2 = (i+1) <= 31 and bit_band(bitfield, bit_lshift(1, i+1)) or bit_band(bitfield_high, bit_lshift(1, i+1))
+ if not (b1 == 0) and not (b2 == 0) then
+ -- found 2 bits set right after each other (stop bits) with i pointing at the first stop bit
+ -- return the two bitfields separated by the two stopbits (3 values for each: bitfield_high, bitfield, field_len)
+ -- bits left: field_len - (i+2)
+ remainder_high, remainder = rshift64(bitfield_high, bitfield, i+2)
+ -- first bitfield is the lower part
+ return (i-1) >= 32 and bit_band(bitfield_high, bit_lshift(1, i) - 1) or 0,
+ i >= 32 and bitfield or bit_band(bitfield, bit_lshift(1, i) - 1),
+ i,
+ remainder_high,
+ remainder,
+ field_len-(i+2)
+ end
+ end
+ end
+ return nil
+end
+
+local function unescape_code(code, code_len)
+ local unescaped_code = 0
+ local b
+ local l = 0
+ local i = 0
+ while i < code_len do
+ b = bit_band( code, lshiftMask[i])
+ if not (b == 0) then
+ unescaped_code = bit_bor(unescaped_code, lshiftMask[l])
+ i = i + 1
+ end
+ i = i + 1
+ l = l + 1
+ end
+ return unescaped_code, l
+end
+
+tables.Huffman_uncompressed = {}
+tables.Huffman_large_uncompressed = {} -- will always be as big as the largest string ever decompressed. Bad, but clearing it every time takes precious time.
+
+function LibCompress:DecompressHuffman(compressed)
+ if not type(compressed) == "string" then
+ return nil, "Can only uncompress strings"
+ end
+
+ local compressed_size = #compressed
+ --decode header
+ local info_byte = string_byte(compressed)
+ -- is data compressed
+ if info_byte == 1 then
+ return compressed:sub(2) --return uncompressed data
+ end
+ if not (info_byte == 3) then
+ return nil, "Can only decompress Huffman compressed data ("..tostring(info_byte)..")"
+ end
+
+ local num_symbols = string_byte(string_sub(compressed, 2, 2)) + 1
+ local c0 = string_byte(string_sub(compressed, 3, 3))
+ local c1 = string_byte(string_sub(compressed, 4, 4))
+ local c2 = string_byte(string_sub(compressed, 5, 5))
+ local orig_size = c2 * 65536 + c1 * 256 + c0
+ if orig_size == 0 then
+ return ""
+ end
+
+ -- decode code -> symbol map
+ local bitfield = 0
+ local bitfield_high = 0
+ local bitfield_len = 0
+ local map = {} -- only table not reused in Huffman decode.
+ setmetatable(map, {
+ __index = function (t, k)
+ local v = {}
+ rawset(t, k, v)
+ return v
+ end
+ })
+
+ local i = 6 -- byte 1-5 are header bytes
+ local c, cl
+ local minCodeLen = 1000
+ local maxCodeLen = 0
+ local symbol, code_high, code, code_len, temp_high, temp, _bitfield_high, _bitfield, _bitfield_len
+ local n = 0
+ local state = 0 -- 0 = get symbol (8 bits), 1 = get code (varying bits, ends with 2 bits set)
+ while n < num_symbols do
+ if i > compressed_size then
+ return nil, "Cannot decode map"
+ end
+
+ c = string_byte(compressed, i)
+ temp_high, temp = lshift64(0, c, bitfield_len)
+ bitfield_high, bitfield = bor64(bitfield_high, bitfield, temp_high, temp)
+ bitfield_len = bitfield_len + 8
+
+ if state == 0 then
+ symbol = bit_band(bitfield, 255)
+ bitfield_high, bitfield = rshift64(bitfield_high, bitfield, 8)
+ bitfield_len = bitfield_len - 8
+ state = 1 -- search for code now
+ else
+ code_high, code, code_len, _bitfield_high, _bitfield, _bitfield_len = getCode2(bitfield_high, bitfield, bitfield_len)
+ if code_high then
+ bitfield_high, bitfield, bitfield_len = _bitfield_high, _bitfield, _bitfield_len
+ if code_len > 32 then
+ return nil, "Unsupported symbol code length ("..code_len..")"
+ end
+ c, cl = unescape_code(code, code_len)
+ map[cl][c] = string_char(symbol)
+ minCodeLen = cl < minCodeLen and cl or minCodeLen
+ maxCodeLen = cl > maxCodeLen and cl or maxCodeLen
+ --print("symbol: "..string_char(symbol).." code: "..tobinary(c, cl))
+ n = n + 1
+ state = 0 -- search for next symbol (if any)
+ end
+ end
+ i = i + 1
+ end
+
+ -- don't create new subtables for entries not in the map. Waste of space.
+ -- But do return an empty table to prevent runtime errors. (instead of returning nil)
+ local mt = {}
+ setmetatable(map, {
+ __index = function (t, k)
+ return mt
+ end
+ })
+
+ local uncompressed = tables.Huffman_uncompressed
+ local large_uncompressed = tables.Huffman_large_uncompressed
+ local uncompressed_size = 0
+ local large_uncompressed_size = 0
+ local test_code
+ local test_code_len = minCodeLen
+ local symbol
+ local dec_size = 0
+ compressed_size = compressed_size + 1
+ local temp_limit = 200 -- first limit of uncompressed data. large_uncompressed will hold strings of length 200
+ temp_limit = temp_limit > orig_size and orig_size or temp_limit
+
+ while true do
+ if test_code_len <= bitfield_len then
+ test_code = bit_band( bitfield, lshiftMinusOneMask[test_code_len])
+ symbol = map[test_code_len][test_code]
+
+ if symbol then
+ uncompressed_size = uncompressed_size + 1
+ uncompressed[uncompressed_size] = symbol
+ dec_size = dec_size + 1
+ if dec_size >= temp_limit then
+ if dec_size >= orig_size then -- checked here for speed reasons
+ break
+ end
+ -- process compressed bytes in smaller chunks
+ large_uncompressed_size = large_uncompressed_size + 1
+ large_uncompressed[large_uncompressed_size] = table_concat(uncompressed, "", 1, uncompressed_size)
+ uncompressed_size = 0
+ temp_limit = temp_limit + 200 -- repeated chunk size is 200 uncompressed bytes
+ temp_limit = temp_limit > orig_size and orig_size or temp_limit
+ end
+
+ bitfield = bit_rshift(bitfield, test_code_len)
+ bitfield_len = bitfield_len - test_code_len
+ test_code_len = minCodeLen
+ else
+ test_code_len = test_code_len + 1
+ if test_code_len > maxCodeLen then
+ return nil, "Decompression error at "..tostring(i).."/"..tostring(#compressed)
+ end
+ end
+ else
+ c = string_byte(compressed, i)
+ bitfield = bitfield + bit_lshift(c or 0, bitfield_len)
+ bitfield_len = bitfield_len + 8
+ if i > compressed_size then
+ break
+ end
+ i = i + 1
+ end
+ end
+
+ setCleanupTables("Huffman_uncompressed", "Huffman_large_uncompressed")
+ return table_concat(large_uncompressed, "", 1, large_uncompressed_size)..table_concat(uncompressed, "", 1, uncompressed_size)
+end
+
+--------------------------------------------------------------------------------
+-- Generic codec interface
+
+function LibCompress:Store(uncompressed)
+ if type(uncompressed) ~= "string" then
+ return nil, "Can only compress strings"
+ end
+ return "\001"..uncompressed
+end
+
+function LibCompress:DecompressUncompressed(data)
+ if type(data) ~= "string" then
+ return nil, "Can only handle strings"
+ end
+ if string_byte(data) ~= 1 then
+ return nil, "Can only handle uncompressed data"
+ end
+ return data:sub(2)
+end
+
+local compression_methods = {
+ [2] = LibCompress.CompressLZW,
+ [3] = LibCompress.CompressHuffman
+}
+
+local decompression_methods = {
+ [1] = LibCompress.DecompressUncompressed,
+ [2] = LibCompress.DecompressLZW,
+ [3] = LibCompress.DecompressHuffman
+}
+
+-- try all compression codecs and return best result
+function LibCompress:Compress(data)
+ local method = next(compression_methods)
+ local result = compression_methods[method](self, data)
+ local n
+ method = next(compression_methods, method)
+ while method do
+ n = compression_methods[method](self, data)
+ if #n < #result then
+ result = n
+ end
+ method = next(compression_methods, method)
+ end
+ return result
+end
+
+function LibCompress:Decompress(data)
+ local header_info = string_byte(data)
+ if decompression_methods[header_info] then
+ return decompression_methods[header_info](self, data)
+ else
+ return nil, "Unknown compression method ("..tostring(header_info)..")"
+ end
+end
+
+----------------------------------------------------------------------
+----------------------------------------------------------------------
+--
+-- Encoding algorithms
+
+--------------------------------------------------------------------------------
+-- Prefix encoding algorithm
+-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com
+
+--[[
+ Howto: Encode and Decode:
+
+ 3 functions are supplied, 2 of them are variants of the first. They return a table with functions to encode and decode text.
+
+ table, msg = LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars)
+
+ reservedChars: The characters in this string will not appear in the encoded data.
+ escapeChars: A string of characters used as escape-characters (don't supply more than needed). #escapeChars >= 1
+ mapChars: First characters in reservedChars maps to first characters in mapChars. (#mapChars <= #reservedChars)
+
+ return value:
+ table
+ if nil then msg holds an error message, otherwise use like this:
+
+ encoded_message = table:Encode(message)
+ message = table:Decode(encoded_message)
+
+ GetAddonEncodeTable: Sets up encoding for the addon channel (\000 is encoded)
+ GetChatEncodeTable: Sets up encoding for the chat channel (many bytes encoded, see the function for details)
+
+ Except for the mapped characters, all encoding will be with 1 escape character followed by 1 suffix, i.e. 2 bytes.
+]]
+-- to be able to match any requested byte value, the search string must be preprocessed
+-- characters to escape with %:
+-- ( ) . % + - * ? [ ] ^ $
+-- "illegal" byte values:
+-- 0 is replaces %z
+local gsub_escape_table = {
+ ['\000'] = "%z",
+ [('(')] = "%(",
+ [(')')] = "%)",
+ [('.')] = "%.",
+ [('%')] = "%%",
+ [('+')] = "%+",
+ [('-')] = "%-",
+ [('*')] = "%*",
+ [('?')] = "%?",
+ [('[')] = "%[",
+ [(']')] = "%]",
+ [('^')] = "%^",
+ [('$')] = "%$"
+}
+
+local function escape_for_gsub(str)
+ return str:gsub("([%z%(%)%.%%%+%-%*%?%[%]%^%$])", gsub_escape_table)
+end
+
+function LibCompress:GetEncodeTable(reservedChars, escapeChars, mapChars)
+ reservedChars = reservedChars or ""
+ escapeChars = escapeChars or ""
+ mapChars = mapChars or ""
+
+ -- select a default escape character
+ if escapeChars == "" then
+ return nil, "No escape characters supplied"
+ end
+
+ if #reservedChars < #mapChars then
+ return nil, "Number of reserved characters must be at least as many as the number of mapped chars"
+ end
+
+ if reservedChars == "" then
+ return nil, "No characters to encode"
+ end
+
+ -- list of characters that must be encoded
+ local encodeBytes = reservedChars..escapeChars..mapChars
+
+ -- build list of bytes not available as a suffix to a prefix byte
+ local taken = {}
+ for i = 1, string_len(encodeBytes) do
+ taken[string_sub(encodeBytes, i, i)] = true
+ end
+
+ -- allocate a table to hold encode/decode strings/functions
+ local codecTable = {}
+
+ -- the encoding can be a single gsub, but the decoding can require multiple gsubs
+ local decode_func_string = {}
+
+ local encode_search = {}
+ local encode_translate = {}
+ local encode_func
+ local decode_search = {}
+ local decode_translate = {}
+ local decode_func
+ local c, r, i, to, from
+ local escapeCharIndex, escapeChar = 0
+
+ -- map single byte to single byte
+ if #mapChars > 0 then
+ for i = 1, #mapChars do
+ from = string_sub(reservedChars, i, i)
+ to = string_sub(mapChars, i, i)
+ encode_translate[from] = to
+ table_insert(encode_search, from)
+ decode_translate[to] = from
+ table_insert(decode_search, to)
+ end
+ codecTable["decode_search"..tostring(escapeCharIndex)] = "([".. escape_for_gsub(table_concat(decode_search)).."])"
+ codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
+ table_insert(decode_func_string, "str = str:gsub(self.decode_search"..tostring(escapeCharIndex)..", self.decode_translate"..tostring(escapeCharIndex)..");")
+
+ end
+
+ -- map single byte to double-byte
+ escapeCharIndex = escapeCharIndex + 1
+ escapeChar = string_sub(escapeChars, escapeCharIndex, escapeCharIndex)
+ r = 0 -- suffix char value to the escapeChar
+ decode_search = {}
+ decode_translate = {}
+ for i = 1, string_len(encodeBytes) do
+ c = string_sub(encodeBytes, i, i)
+ if not encode_translate[c] then
+ -- this loop will update escapeChar and r
+ while r < 256 and taken[string_char(r)] do
+ r = r + 1
+ if r > 255 then -- switch to next escapeChar
+ if escapeChar == "" then -- we are out of escape chars and we need more!
+ return nil, "Out of escape characters"
+ end
+
+ codecTable["decode_search"..tostring(escapeCharIndex)] = escape_for_gsub(escapeChar).."([".. escape_for_gsub(table_concat(decode_search)).."])"
+ codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
+ table_insert(decode_func_string, "str = str:gsub(self.decode_search"..tostring(escapeCharIndex)..", self.decode_translate"..tostring(escapeCharIndex)..");")
+
+ escapeCharIndex = escapeCharIndex + 1
+ escapeChar = string_sub(escapeChars, escapeCharIndex, escapeCharIndex)
+
+ r = 0
+ decode_search = {}
+ decode_translate = {}
+ end
+ end
+ encode_translate[c] = escapeChar..string_char(r)
+ table_insert(encode_search, c)
+ decode_translate[string_char(r)] = c
+ table_insert(decode_search, string_char(r))
+ r = r + 1
+ end
+ end
+
+ if r > 0 then
+ codecTable["decode_search"..tostring(escapeCharIndex)] = escape_for_gsub(escapeChar).."([".. escape_for_gsub(table_concat(decode_search)).."])"
+ codecTable["decode_translate"..tostring(escapeCharIndex)] = decode_translate
+ table_insert(decode_func_string, "str = str:gsub(self.decode_search"..tostring(escapeCharIndex)..", self.decode_translate"..tostring(escapeCharIndex)..");")
+ end
+
+ -- change last line from "str = ...;" to "return ...;";
+ decode_func_string[#decode_func_string] = decode_func_string[#decode_func_string]:gsub("str = (.*);", "return %1;")
+ decode_func_string = "return function(self, str) "..table_concat(decode_func_string).." end"
+
+ encode_search = "([".. escape_for_gsub(table_concat(encode_search)).."])"
+ decode_search = escape_for_gsub(escapeChars).."([".. escape_for_gsub(table_concat(decode_search)).."])"
+
+ encode_func = assert(loadstring("return function(self, str) return str:gsub(self.encode_search, self.encode_translate); end"))()
+ decode_func = assert(loadstring(decode_func_string))()
+
+ codecTable.encode_search = encode_search
+ codecTable.encode_translate = encode_translate
+ codecTable.Encode = encode_func
+ codecTable.decode_search = decode_search
+ codecTable.decode_translate = decode_translate
+ codecTable.Decode = decode_func
+
+ codecTable.decode_func_string = decode_func_string -- to be deleted
+ return codecTable
+end
+
+-- Addons: Call this only once and reuse the returned table for all encodings/decodings.
+function LibCompress:GetAddonEncodeTable(reservedChars, escapeChars, mapChars )
+ reservedChars = reservedChars or ""
+ escapeChars = escapeChars or ""
+ mapChars = mapChars or ""
+ -- Following byte values are not allowed:
+ -- \000
+ if escapeChars == "" then
+ escapeChars = "\001"
+ end
+ return self:GetEncodeTable( (reservedChars or "").."\000", escapeChars, mapChars)
+end
+
+-- Addons: Call this only once and reuse the returned table for all encodings/decodings.
+function LibCompress:GetChatEncodeTable(reservedChars, escapeChars, mapChars)
+ reservedChars = reservedChars or ""
+ escapeChars = escapeChars or ""
+ mapChars = mapChars or ""
+ -- Following byte values are not allowed:
+ -- \000, s, S, \010, \013, \124, %
+ -- Because SendChatMessage will error if an UTF8 multibyte character is incomplete,
+ -- all character values above 127 have to be encoded to avoid this. This costs quite a bit of bandwidth (about 13-14%)
+ -- Also, because drunken status is unknown for the received, strings used with SendChatMessage should be terminated with
+ -- an identifying byte value, after which the server MAY add "...hic!" or as much as it can fit(!).
+ -- Pass the identifying byte as a reserved character to this function to ensure the encoding doesn't contain that value.
+ -- or use this: local message, match = arg1:gsub("^(.*)\029.-$", "%1")
+ -- arg1 is message from channel, \029 is the string terminator, but may be used in the encoded datastream as well. :-)
+ -- This encoding will expand data anywhere from:
+ -- 0% (average with pure ascii text)
+ -- 53.5% (average with random data valued zero to 255)
+ -- 100% (only encoding data that encodes to two bytes)
+ local i
+ local r = {}
+
+ for i = 128, 255 do
+ table_insert(r, string_char(i))
+ end
+
+ reservedChars = "sS\000\010\013\124%"..table_concat(r)..(reservedChars or "")
+ if escapeChars == "" then
+ escapeChars = "\029\031"
+ end
+
+ if mapChars == "" then
+ mapChars = "\015\020";
+ end
+ return self:GetEncodeTable(reservedChars, escapeChars, mapChars)
+end
+
+--------------------------------------------------------------------------------
+-- 7 bit encoding algorithm
+-- implemented by Galmok of European Stormrage (Horde), galmok@gmail.com
+
+-- The encoded data holds values from 0 to 127 inclusive. Additional encoding may be necessary.
+-- This algorithm isn't exactly fast and be used with care and consideration
+
+tables.encode7bit = {}
+
+function LibCompress:Encode7bit(str)
+ local remainder = 0
+ local remainder_length = 0
+ local tbl = tables.encode7bit
+ local encoded_size = 0
+ local length = #str
+ for i = 1, length do
+ local code = string_byte(str, i)
+ remainder = remainder + bit_lshift(code, remainder_length)
+ remainder_length = 8 + remainder_length
+ while remainder_length >= 7 do
+ encoded_size = encoded_size + 1
+ tbl[encoded_size] = string_char(bit_band(remainder, 127))
+ remainder = bit_rshift(remainder, 7)
+ remainder_length = remainder_length -7
+ end
+ end
+
+ if remainder_length > 0 then
+ encoded_size = encoded_size + 1
+ tbl[encoded_size] = string_char(remainder)
+ end
+ setCleanupTables("encode7bit")
+ return table_concat(tbl, "", 1, encoded_size)
+end
+
+tables.decode8bit = {}
+
+function LibCompress:Decode7bit(str)
+ local bit8 = tables.decode8bit
+ local decoded_size = 0
+ local ch
+ local i = 1
+ local bitfield_len = 0
+ local bitfield = 0
+ local length = #str
+ while true do
+ if bitfield_len >= 8 then
+ decoded_size = decoded_size + 1
+ bit8[decoded_size] = string_char(bit_band(bitfield, 255))
+ bitfield = bit_rshift(bitfield, 8)
+ bitfield_len = bitfield_len - 8
+ end
+ ch = string_byte(str, i)
+ bitfield=bitfield + bit_lshift(ch or 0, bitfield_len)
+ bitfield_len = bitfield_len + 7
+ if i > length then
+ break
+ end
+ i = i + 1
+ end
+ setCleanupTables("decode8bit")
+ return table_concat(bit8, "", 1, decoded_size)
+end
+
+----------------------------------------------------------------------
+----------------------------------------------------------------------
+--
+-- Checksum/hash algorithms
+
+--------------------------------------------------------------------------------
+-- FCS16/32 checksum algorithms
+-- converted from C by Galmok of European Stormrage (Horde), galmok@gmail.com
+-- usage:
+-- code = LibCompress:fcs16init()
+-- code = LibCompress:fcs16update(code, data1)
+-- code = LibCompress:fcs16update(code, data2)
+-- code = LibCompress:fcs16update(code, data...)
+-- code = LibCompress:fcs16final(code)
+--
+-- data = string
+-- fcs16 provides a 16 bit checksum, fcs32 provides a 32 bit checksum.
+
+
+--[[/* The following copyright notice concerns only the FCS hash algorithm
+---------------------------------------------------------------------------
+Copyright (c) 2003, Dominik Reichl , Germany.
+All rights reserved.
+
+Distributed under the terms of the GNU General Public License v2.
+
+This software is provided 'as is' with no explicit or implied warranties
+in respect of its properties, including, but not limited to, correctness
+and/or fitness for purpose.
+---------------------------------------------------------------------------
+*/]]
+--// FCS-16 algorithm implemented as described in RFC 1331
+local FCSINIT16 = 65535
+--// Fast 16 bit FCS lookup table
+local fcs16tab = { [0]=0, 4489, 8978, 12955, 17956, 22445, 25910, 29887,
+ 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735,
+ 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662,
+ 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510,
+ 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949,
+ 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797,
+ 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724,
+ 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572,
+ 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011,
+ 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859,
+ 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786,
+ 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634,
+ 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073,
+ 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921,
+ 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848,
+ 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696,
+ 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623,
+ 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999,
+ 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398,
+ 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774,
+ 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685,
+ 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061,
+ 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460,
+ 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836,
+ 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747,
+ 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123,
+ 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522,
+ 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898,
+ 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809,
+ 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185,
+ 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584,
+ 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 }
+
+function LibCompress:fcs16init()
+ return FCSINIT16
+end
+
+function LibCompress:fcs16update(uFcs16, pBuffer)
+ local i
+ local length = string_len(pBuffer)
+ for i = 1, length do
+ uFcs16 = bit_bxor(bit_rshift(uFcs16,8), fcs16tab[bit_band(bit_bxor(uFcs16, string_byte(pBuffer, i)), 255)])
+ end
+ return uFcs16
+end
+
+function LibCompress:fcs16final(uFcs16)
+ return bit_bxor(uFcs16,65535)
+end
+-- END OF FCS16
+
+--[[/*
+---------------------------------------------------------------------------
+Copyright (c) 2003, Dominik Reichl , Germany.
+All rights reserved.
+
+Distributed under the terms of the GNU General Public License v2.
+
+This software is provided 'as is' with no explicit or implied warranties
+in respect of its properties, including, but not limited to, correctness
+and/or fitness for purpose.
+---------------------------------------------------------------------------
+*/]]
+
+--// FCS-32 algorithm implemented as described in RFC 1331
+
+local FCSINIT32 = -1
+
+--// Fast 32 bit FCS lookup table
+local fcs32tab = { [0] = 0, 1996959894, -301047508, -1727442502, 124634137, 1886057615, -379345611, -1637575261,
+ 249268274, 2044508324, -522852066, -1747789432, 162941995, 2125561021, -407360249, -1866523247,
+ 498536548, 1789927666, -205950648, -2067906082, 450548861, 1843258603, -187386543, -2083289657,
+ 325883990, 1684777152, -43845254, -1973040660, 335633487, 1661365465, -99664541, -1928851979,
+ 997073096, 1281953886, -715111964, -1570279054, 1006888145, 1258607687, -770865667, -1526024853,
+ 901097722, 1119000684, -608450090, -1396901568, 853044451, 1172266101, -589951537, -1412350631,
+ 651767980, 1373503546, -925412992, -1076862698, 565507253, 1454621731, -809855591, -1195530993,
+ 671266974, 1594198024, -972236366, -1324619484, 795835527, 1483230225, -1050600021, -1234817731,
+ 1994146192, 31158534, -1731059524, -271249366, 1907459465, 112637215, -1614814043, -390540237,
+ 2013776290, 251722036, -1777751922, -519137256, 2137656763, 141376813, -1855689577, -429695999,
+ 1802195444, 476864866, -2056965928, -228458418, 1812370925, 453092731, -2113342271, -183516073,
+ 1706088902, 314042704, -1950435094, -54949764, 1658658271, 366619977, -1932296973, -69972891,
+ 1303535960, 984961486, -1547960204, -725929758, 1256170817, 1037604311, -1529756563, -740887301,
+ 1131014506, 879679996, -1385723834, -631195440, 1141124467, 855842277, -1442165665, -586318647,
+ 1342533948, 654459306, -1106571248, -921952122, 1466479909, 544179635, -1184443383, -832445281,
+ 1591671054, 702138776, -1328506846, -942167884, 1504918807, 783551873, -1212326853, -1061524307,
+ -306674912, -1698712650, 62317068, 1957810842, -355121351, -1647151185, 81470997, 1943803523,
+ -480048366, -1805370492, 225274430, 2053790376, -468791541, -1828061283, 167816743, 2097651377,
+ -267414716, -2029476910, 503444072, 1762050814, -144550051, -2140837941, 426522225, 1852507879,
+ -19653770, -1982649376, 282753626, 1742555852, -105259153, -1900089351, 397917763, 1622183637,
+ -690576408, -1580100738, 953729732, 1340076626, -776247311, -1497606297, 1068828381, 1219638859,
+ -670225446, -1358292148, 906185462, 1090812512, -547295293, -1469587627, 829329135, 1181335161,
+ -882789492, -1134132454, 628085408, 1382605366, -871598187, -1156888829, 570562233, 1426400815,
+ -977650754, -1296233688, 733239954, 1555261956, -1026031705, -1244606671, 752459403, 1541320221,
+ -1687895376, -328994266, 1969922972, 40735498, -1677130071, -351390145, 1913087877, 83908371,
+ -1782625662, -491226604, 2075208622, 213261112, -1831694693, -438977011, 2094854071, 198958881,
+ -2032938284, -237706686, 1759359992, 534414190, -2118248755, -155638181, 1873836001, 414664567,
+ -2012718362, -15766928, 1711684554, 285281116, -1889165569, -127750551, 1634467795, 376229701,
+ -1609899400, -686959890, 1308918612, 956543938, -1486412191, -799009033, 1231636301, 1047427035,
+ -1362007478, -640263460, 1088359270, 936918000, -1447252397, -558129467, 1202900863, 817233897,
+ -1111625188, -893730166, 1404277552, 615818150, -1160759803, -841546093, 1423857449, 601450431,
+ -1285129682, -1000256840, 1567103746, 711928724, -1274298825, -1022587231, 1510334235, 755167117 }
+
+function LibCompress:fcs32init()
+ return FCSINIT32
+end
+
+function LibCompress:fcs32update(uFcs32, pBuffer)
+ local i
+ local length = string_len(pBuffer)
+ for i = 1, length do
+ uFcs32 = bit_bxor(bit_rshift(uFcs32, 8), fcs32tab[bit_band(bit_bxor(uFcs32, string_byte(pBuffer, i)), 255)])
+ end
+ return uFcs32
+end
+
+function LibCompress:fcs32final(uFcs32)
+ return bit_bnot(uFcs32)
+end
\ No newline at end of file
diff --git a/Libs/LibCompress/LibCompress.toc b/Libs/LibCompress/LibCompress.toc
new file mode 100644
index 00000000..42f16e96
--- /dev/null
+++ b/Libs/LibCompress/LibCompress.toc
@@ -0,0 +1,14 @@
+## Interface: 70200
+
+## Title: Lib: Compress
+## Notes: Compression and Decompression library
+## Author: Galmok at Stormrage-EU (Horde) and JJSheets
+## Version: @project-version@
+## X-Website: http://www.wowace.com/addons/libcompress/
+## X-Category: Library
+## X-eMail: galmok AT gmail DOT com, sheets DOT jeff AT gmail DOT com
+## X-License: LGPL v2.1
+## LoadOnDemand: 1
+
+LibStub\LibStub.lua
+lib.xml
\ No newline at end of file
diff --git a/Libs/LibCompress/lib.xml b/Libs/LibCompress/lib.xml
new file mode 100644
index 00000000..c85b2134
--- /dev/null
+++ b/Libs/LibCompress/lib.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua b/Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua
new file mode 100644
index 00000000..341dbe8b
--- /dev/null
+++ b/Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua
@@ -0,0 +1,333 @@
+
+-----------------------------------------------------------------------
+-- LibDBIcon-1.0
+--
+-- Allows addons to easily create a lightweight minimap icon as an alternative to heavier LDB displays.
+--
+
+local DBICON10 = "LibDBIcon-1.0"
+local DBICON10_MINOR = 34 -- Bump on changes
+if not LibStub then error(DBICON10 .. " requires LibStub.") end
+local ldb = LibStub("LibDataBroker-1.1", true)
+if not ldb then error(DBICON10 .. " requires LibDataBroker-1.1.") end
+local lib = LibStub:NewLibrary(DBICON10, DBICON10_MINOR)
+if not lib then return end
+
+lib.disabled = lib.disabled or nil
+lib.objects = lib.objects or {}
+lib.callbackRegistered = lib.callbackRegistered or nil
+lib.callbacks = lib.callbacks or LibStub("CallbackHandler-1.0"):New(lib)
+lib.notCreated = lib.notCreated or {}
+
+function lib:IconCallback(event, name, key, value)
+ if lib.objects[name] then
+ if key == "icon" then
+ lib.objects[name].icon:SetTexture(value)
+ elseif key == "iconCoords" then
+ lib.objects[name].icon:UpdateCoord()
+ elseif key == "iconR" then
+ local _, g, b = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(value, g, b)
+ elseif key == "iconG" then
+ local r, _, b = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(r, value, b)
+ elseif key == "iconB" then
+ local r, g = lib.objects[name].icon:GetVertexColor()
+ lib.objects[name].icon:SetVertexColor(r, g, value)
+ end
+ end
+end
+if not lib.callbackRegistered then
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__icon", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconCoords", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconR", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconG", "IconCallback")
+ ldb.RegisterCallback(lib, "LibDataBroker_AttributeChanged__iconB", "IconCallback")
+ lib.callbackRegistered = true
+end
+
+local function getAnchors(frame)
+ local x, y = frame:GetCenter()
+ if not x or not y then return "CENTER" end
+ local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
+ local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
+ return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
+end
+
+local function onEnter(self)
+ if self.isMoving then return end
+ local obj = self.dataObject
+ if obj.OnTooltipShow then
+ GameTooltip:SetOwner(self, "ANCHOR_NONE")
+ GameTooltip:SetPoint(getAnchors(self))
+ obj.OnTooltipShow(GameTooltip)
+ GameTooltip:Show()
+ elseif obj.OnEnter then
+ obj.OnEnter(self)
+ end
+end
+
+local function onLeave(self)
+ local obj = self.dataObject
+ GameTooltip:Hide()
+ if obj.OnLeave then obj.OnLeave(self) end
+end
+
+--------------------------------------------------------------------------------
+
+local onClick, onMouseUp, onMouseDown, onDragStart, onDragStop, updatePosition
+
+do
+ local minimapShapes = {
+ ["ROUND"] = {true, true, true, true},
+ ["SQUARE"] = {false, false, false, false},
+ ["CORNER-TOPLEFT"] = {false, false, false, true},
+ ["CORNER-TOPRIGHT"] = {false, false, true, false},
+ ["CORNER-BOTTOMLEFT"] = {false, true, false, false},
+ ["CORNER-BOTTOMRIGHT"] = {true, false, false, false},
+ ["SIDE-LEFT"] = {false, true, false, true},
+ ["SIDE-RIGHT"] = {true, false, true, false},
+ ["SIDE-TOP"] = {false, false, true, true},
+ ["SIDE-BOTTOM"] = {true, true, false, false},
+ ["TRICORNER-TOPLEFT"] = {false, true, true, true},
+ ["TRICORNER-TOPRIGHT"] = {true, false, true, true},
+ ["TRICORNER-BOTTOMLEFT"] = {true, true, false, true},
+ ["TRICORNER-BOTTOMRIGHT"] = {true, true, true, false},
+ }
+
+ function updatePosition(button)
+ local angle = math.rad(button.db and button.db.minimapPos or button.minimapPos or 225)
+ local x, y, q = math.cos(angle), math.sin(angle), 1
+ if x < 0 then q = q + 1 end
+ if y > 0 then q = q + 2 end
+ local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND"
+ local quadTable = minimapShapes[minimapShape]
+ if quadTable[q] then
+ x, y = x*80, y*80
+ else
+ local diagRadius = 103.13708498985 --math.sqrt(2*(80)^2)-10
+ x = math.max(-80, math.min(x*diagRadius, 80))
+ y = math.max(-80, math.min(y*diagRadius, 80))
+ end
+ button:SetPoint("CENTER", Minimap, "CENTER", x, y)
+ end
+end
+
+function onClick(self, b) if self.dataObject.OnClick then self.dataObject.OnClick(self, b) end end
+function onMouseDown(self) self.isMouseDown = true; self.icon:UpdateCoord() end
+function onMouseUp(self) self.isMouseDown = false; self.icon:UpdateCoord() end
+
+do
+ local function onUpdate(self)
+ local mx, my = Minimap:GetCenter()
+ local px, py = GetCursorPosition()
+ local scale = Minimap:GetEffectiveScale()
+ px, py = px / scale, py / scale
+ if self.db then
+ self.db.minimapPos = math.deg(math.atan2(py - my, px - mx)) % 360
+ else
+ self.minimapPos = math.deg(math.atan2(py - my, px - mx)) % 360
+ end
+ updatePosition(self)
+ end
+
+ function onDragStart(self)
+ self:LockHighlight()
+ self.isMouseDown = true
+ self.icon:UpdateCoord()
+ self:SetScript("OnUpdate", onUpdate)
+ self.isMoving = true
+ GameTooltip:Hide()
+ end
+end
+
+function onDragStop(self)
+ self:SetScript("OnUpdate", nil)
+ self.isMouseDown = false
+ self.icon:UpdateCoord()
+ self:UnlockHighlight()
+ self.isMoving = nil
+end
+
+local defaultCoords = {0, 1, 0, 1}
+local function updateCoord(self)
+ local coords = self:GetParent().dataObject.iconCoords or defaultCoords
+ local deltaX, deltaY = 0, 0
+ if not self:GetParent().isMouseDown then
+ deltaX = (coords[2] - coords[1]) * 0.05
+ deltaY = (coords[4] - coords[3]) * 0.05
+ end
+ self:SetTexCoord(coords[1] + deltaX, coords[2] - deltaX, coords[3] + deltaY, coords[4] - deltaY)
+end
+
+local function createButton(name, object, db)
+ local button = CreateFrame("Button", "LibDBIcon10_"..name, Minimap)
+ button.dataObject = object
+ button.db = db
+ button:SetFrameStrata("MEDIUM")
+ button:SetSize(31, 31)
+ button:SetFrameLevel(8)
+ button:RegisterForClicks("anyUp")
+ button:RegisterForDrag("LeftButton")
+ button:SetHighlightTexture(136477) --"Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight"
+ local overlay = button:CreateTexture(nil, "OVERLAY")
+ overlay:SetSize(53, 53)
+ overlay:SetTexture(136430) --"Interface\\Minimap\\MiniMap-TrackingBorder"
+ overlay:SetPoint("TOPLEFT")
+ local background = button:CreateTexture(nil, "BACKGROUND")
+ background:SetSize(20, 20)
+ background:SetTexture(136467) --"Interface\\Minimap\\UI-Minimap-Background"
+ background:SetPoint("TOPLEFT", 7, -5)
+ local icon = button:CreateTexture(nil, "ARTWORK")
+ icon:SetSize(17, 17)
+ icon:SetTexture(object.icon)
+ icon:SetPoint("TOPLEFT", 7, -6)
+ button.icon = icon
+ button.isMouseDown = false
+
+ local r, g, b = icon:GetVertexColor()
+ icon:SetVertexColor(object.iconR or r, object.iconG or g, object.iconB or b)
+
+ icon.UpdateCoord = updateCoord
+ icon:UpdateCoord()
+
+ button:SetScript("OnEnter", onEnter)
+ button:SetScript("OnLeave", onLeave)
+ button:SetScript("OnClick", onClick)
+ if not db or not db.lock then
+ button:SetScript("OnDragStart", onDragStart)
+ button:SetScript("OnDragStop", onDragStop)
+ end
+ button:SetScript("OnMouseDown", onMouseDown)
+ button:SetScript("OnMouseUp", onMouseUp)
+
+ lib.objects[name] = button
+
+ if lib.loggedIn then
+ updatePosition(button)
+ if not db or not db.hide then button:Show()
+ else button:Hide() end
+ end
+ lib.callbacks:Fire("LibDBIcon_IconCreated", button, name) -- Fire 'Icon Created' callback
+end
+
+-- We could use a metatable.__index on lib.objects, but then we'd create
+-- the icons when checking things like :IsRegistered, which is not necessary.
+local function check(name)
+ if lib.notCreated[name] then
+ createButton(name, lib.notCreated[name][1], lib.notCreated[name][2])
+ lib.notCreated[name] = nil
+ end
+end
+
+lib.loggedIn = lib.loggedIn or false
+-- Wait a bit with the initial positioning to let any GetMinimapShape addons
+-- load up.
+if not lib.loggedIn then
+ local f = CreateFrame("Frame")
+ f:SetScript("OnEvent", function()
+ for _, object in pairs(lib.objects) do
+ updatePosition(object)
+ if not lib.disabled and (not object.db or not object.db.hide) then object:Show()
+ else object:Hide() end
+ end
+ lib.loggedIn = true
+ f:SetScript("OnEvent", nil)
+ f = nil
+ end)
+ f:RegisterEvent("PLAYER_LOGIN")
+end
+
+local function getDatabase(name)
+ return lib.notCreated[name] and lib.notCreated[name][2] or lib.objects[name].db
+end
+
+function lib:Register(name, object, db)
+ if not object.icon then error("Can't register LDB objects without icons set!") end
+ if lib.objects[name] or lib.notCreated[name] then error("Already registered, nubcake.") end
+ if not lib.disabled and (not db or not db.hide) then
+ createButton(name, object, db)
+ else
+ lib.notCreated[name] = {object, db}
+ end
+end
+
+function lib:Lock(name)
+ if not lib:IsRegistered(name) then return end
+ if lib.objects[name] then
+ lib.objects[name]:SetScript("OnDragStart", nil)
+ lib.objects[name]:SetScript("OnDragStop", nil)
+ end
+ local db = getDatabase(name)
+ if db then db.lock = true end
+end
+
+function lib:Unlock(name)
+ if not lib:IsRegistered(name) then return end
+ if lib.objects[name] then
+ lib.objects[name]:SetScript("OnDragStart", onDragStart)
+ lib.objects[name]:SetScript("OnDragStop", onDragStop)
+ end
+ local db = getDatabase(name)
+ if db then db.lock = nil end
+end
+
+function lib:Hide(name)
+ if not lib.objects[name] then return end
+ lib.objects[name]:Hide()
+end
+function lib:Show(name)
+ if lib.disabled then return end
+ check(name)
+ lib.objects[name]:Show()
+ updatePosition(lib.objects[name])
+end
+function lib:IsRegistered(name)
+ return (lib.objects[name] or lib.notCreated[name]) and true or false
+end
+function lib:Refresh(name, db)
+ if lib.disabled then return end
+ check(name)
+ local button = lib.objects[name]
+ if db then button.db = db end
+ updatePosition(button)
+ if not button.db or not button.db.hide then
+ button:Show()
+ else
+ button:Hide()
+ end
+ if not button.db or not button.db.lock then
+ button:SetScript("OnDragStart", onDragStart)
+ button:SetScript("OnDragStop", onDragStop)
+ else
+ button:SetScript("OnDragStart", nil)
+ button:SetScript("OnDragStop", nil)
+ end
+end
+function lib:GetMinimapButton(name)
+ return lib.objects[name]
+end
+
+function lib:EnableLibrary()
+ lib.disabled = nil
+ for name, object in pairs(lib.objects) do
+ if not object.db or not object.db.hide then
+ object:Show()
+ updatePosition(object)
+ end
+ end
+ for name, data in pairs(lib.notCreated) do
+ if not data.db or not data.db.hide then
+ createButton(name, data[1], data[2])
+ lib.notCreated[name] = nil
+ end
+ end
+end
+
+function lib:DisableLibrary()
+ lib.disabled = true
+ for name, object in pairs(lib.objects) do
+ object:Hide()
+ end
+end
+
diff --git a/Libs/LibDBIcon-1.0/lib.xml b/Libs/LibDBIcon-1.0/lib.xml
new file mode 100644
index 00000000..501278e4
--- /dev/null
+++ b/Libs/LibDBIcon-1.0/lib.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/Libs/LibGraph-2.0/1-1.tga b/Libs/LibGraph-2.0/1-1.tga
new file mode 100644
index 00000000..bbf8393b
Binary files /dev/null and b/Libs/LibGraph-2.0/1-1.tga differ
diff --git a/Libs/LibGraph-2.0/1-128.tga b/Libs/LibGraph-2.0/1-128.tga
new file mode 100644
index 00000000..f6f417a5
Binary files /dev/null and b/Libs/LibGraph-2.0/1-128.tga differ
diff --git a/Libs/LibGraph-2.0/1-16.tga b/Libs/LibGraph-2.0/1-16.tga
new file mode 100644
index 00000000..720ba193
Binary files /dev/null and b/Libs/LibGraph-2.0/1-16.tga differ
diff --git a/Libs/LibGraph-2.0/1-2.tga b/Libs/LibGraph-2.0/1-2.tga
new file mode 100644
index 00000000..723269d4
Binary files /dev/null and b/Libs/LibGraph-2.0/1-2.tga differ
diff --git a/Libs/LibGraph-2.0/1-32.tga b/Libs/LibGraph-2.0/1-32.tga
new file mode 100644
index 00000000..78f507d5
Binary files /dev/null and b/Libs/LibGraph-2.0/1-32.tga differ
diff --git a/Libs/LibGraph-2.0/1-4.tga b/Libs/LibGraph-2.0/1-4.tga
new file mode 100644
index 00000000..5976672f
Binary files /dev/null and b/Libs/LibGraph-2.0/1-4.tga differ
diff --git a/Libs/LibGraph-2.0/1-64.tga b/Libs/LibGraph-2.0/1-64.tga
new file mode 100644
index 00000000..24700474
Binary files /dev/null and b/Libs/LibGraph-2.0/1-64.tga differ
diff --git a/Libs/LibGraph-2.0/1-8.tga b/Libs/LibGraph-2.0/1-8.tga
new file mode 100644
index 00000000..f16926f4
Binary files /dev/null and b/Libs/LibGraph-2.0/1-8.tga differ
diff --git a/Libs/LibGraph-2.0/LibGraph-2.0.lua b/Libs/LibGraph-2.0/LibGraph-2.0.lua
new file mode 100644
index 00000000..5150a172
--- /dev/null
+++ b/Libs/LibGraph-2.0/LibGraph-2.0.lua
@@ -0,0 +1,2267 @@
+--[[
+Name: LibGraph-2.0
+Revision: $Rev: 54 $
+Author(s): Cryect (cryect@gmail.com), Xinhuan
+Website: http://www.wowace.com/
+Documentation: http://www.wowace.com/wiki/GraphLib
+SVN: http://svn.wowace.com/root/trunk/GraphLib/
+Description: Allows for easy creation of graphs
+]]
+
+--Thanks to Nelson Minar for catching several errors where width was being used instead of height (damn copy and paste >_>)
+
+local major = "LibGraph-2.0"
+local minor = 90000 + tonumber(("$Revision: 54 $"):match("(%d+)"))
+
+
+--Search for just Addon\\ at the front since the interface part often gets trimmed
+--Do this before anything else, so if it errors, any existing loaded copy of LibGraph-2.0
+--doesn't get modified with a newer revision (this one)
+local TextureDirectory
+do
+ local path = string.match(debugstack(1, 1, 0), "AddOns\\(.+)LibGraph%-2%.0%.lua")
+ if path then
+ TextureDirectory = "Interface\\AddOns\\"..path
+ else
+ error(major.." cannot determine the folder it is located in because the path is too long and got truncated in the debugstack(1, 1, 0) function call")
+ end
+end
+
+
+if not LibStub then error(major .. " requires LibStub") end
+
+local lib, oldLibMinor = LibStub:NewLibrary(major, minor)
+if not lib then return end
+
+local GraphFunctions = {}
+
+local gsub = gsub
+local ipairs = ipairs
+local pairs = pairs
+local sqrt = sqrt
+local table = table
+local tinsert = tinsert
+local tremove = tremove
+local type = type
+local math_max = math.max
+local math_min = math.min
+local math_ceil = math.ceil
+local math_pi = math.pi
+local math_floor = math.floor
+local math_pow = math.pow
+local math_random = math.random
+local math_cos = math.cos
+local math_sin = math.sin
+local math_deg = math.deg
+local math_atan = math.atan
+local math_abs = math.abs
+local math_fmod = math.fmod
+local math_huge = math.huge
+
+local CreateFrame = CreateFrame
+local GetCursorPosition = GetCursorPosition
+local GetTime = GetTime
+local MouseIsOver = MouseIsOver
+local UnitHealth = UnitHealth
+
+local UIParent = UIParent
+
+local DEFAULT_CHAT_FRAME = DEFAULT_CHAT_FRAME
+
+-- lib upgrade stuff
+lib.RegisteredGraphRealtime = lib.RegisteredGraphRealtime or {}
+lib.RegisteredGraphLine = lib.RegisteredGraphLine or {}
+lib.RegisteredGraphScatterPlot = lib.RegisteredGraphScatterPlot or {}
+lib.RegisteredGraphPieChart = lib.RegisteredGraphPieChart or {}
+
+
+--------------------------------------------------------------------------------
+--Graph Creation Functions
+--------------------------------------------------------------------------------
+
+--Realtime Graph
+local function SetupGraphRealtimeFunctions(graph, upgrade)
+ local self = lib
+
+ --Set the various functions
+ graph.SetXAxis = GraphFunctions.SetXAxis
+ graph.SetYMax = GraphFunctions.SetYMax
+ graph.AddTimeData = GraphFunctions.AddTimeData
+ graph.OnUpdate = GraphFunctions.OnUpdateGraphRealtime
+ graph.CreateGridlines = GraphFunctions.CreateGridlines
+ graph.RefreshGraph = GraphFunctions.RefreshRealtimeGraph
+ graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
+ graph.SetGridSpacing = GraphFunctions.SetGridSpacing
+ graph.SetAxisColor = GraphFunctions.SetAxisColor
+ graph.SetGridColor = GraphFunctions.SetGridColor
+ graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
+ graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
+ graph.SetFilterRadius = GraphFunctions.SetFilterRadius
+ --graph.SetAutoscaleYAxis = GraphFunctions.SetAutoscaleYAxis
+ graph.SetBarColors = GraphFunctions.SetBarColors
+ graph.SetMode = GraphFunctions.SetMode
+ graph.SetAutoScale = GraphFunctions.SetAutoScale
+ if not upgrade then
+ -- This is the original frame:SetWidth() and frame:SetHeight()
+ -- standard frame functions
+ graph.OldSetWidth = graph.SetWidth
+ graph.OldSetHeight = graph.SetHeight
+ end
+ graph.SetWidth = GraphFunctions.RealtimeSetWidth
+ graph.SetHeight = GraphFunctions.RealtimeSetHeight
+ graph.SetBarColors = GraphFunctions.RealtimeSetColors
+ graph.GetMaxValue = GraphFunctions.GetMaxValue
+ graph.GetValue = GraphFunctions.RealtimeGetValue
+ graph.SetUpdateLimit = GraphFunctions.SetUpdateLimit
+ graph.SetDecay = GraphFunctions.SetDecay
+ graph.SetMinMaxY = GraphFunctions.SetMinMaxY
+ graph.AddBar = GraphFunctions.AddBar
+ graph.SetYLabels = GraphFunctions.SetYLabels
+
+
+ graph.DrawLine = self.DrawLine
+ graph.DrawHLine = self.DrawHLine
+ graph.DrawVLine = self.DrawVLine
+ graph.HideLines = self.HideLines
+ graph.HideFontStrings = GraphFunctions.HideFontStrings
+ graph.FindFontString = GraphFunctions.FindFontString
+ graph.SetBars = GraphFunctions.SetBars
+
+
+ --Set the update function
+ graph:SetScript("OnUpdate", graph.OnUpdate)
+end
+
+function lib:CreateGraphRealtime(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
+ local graph
+ local i
+ graph = CreateFrame("Frame", name, parent)
+
+ Width = math_floor(Width)
+
+
+ graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
+ graph:SetWidth(Width)
+ graph:SetHeight(Height)
+ graph:Show()
+
+ --Create the bars
+ graph.Bars = {}
+ graph.BarsUsing = {}
+ graph.BarNum = Width
+ graph.Height = Height
+ for i = 1, Width do
+ local bar
+ bar = CreateFrame("StatusBar", name.."Bar"..i, graph)--graph:CreateTexture(nil, "ARTWORK")
+ bar:SetPoint("BOTTOMLEFT", graph, "BOTTOMLEFT", i - 1, 0)
+ bar:SetHeight(Height)
+ bar:SetWidth(1)
+ bar:SetOrientation("VERTICAL")
+ bar:SetMinMaxValues(0, 1)
+ bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
+ bar:GetStatusBarTexture():SetHorizTile(false)
+ bar:GetStatusBarTexture():SetVertTile(false)
+
+ local t = bar:GetStatusBarTexture()
+ t:SetGradientAlpha("VERTICAL", 0.2, 0.0, 0.0, 0.5, 1.0, 0.0, 0.0, 1.0)
+
+ bar:Show()
+ tinsert(graph.Bars, bar)
+ tinsert(graph.BarsUsing, bar)
+ end
+
+
+ SetupGraphRealtimeFunctions(graph)
+
+
+ --Initialize Data
+ graph.GraphType = "REALTIME"
+ graph.YMax = 60
+ graph.YMin = 0
+ graph.XMax = -0.75
+ graph.XMin = -10
+ graph.TimeRadius = 0.5
+ graph.Mode = "FAST"
+ graph.Filter = "RECT"
+ graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
+ graph.GridColor = {0.5, 0.5, 0.5, 0.5}
+ graph.BarColorTop = {1.0, 0.0, 0.0, 1.0}
+ graph.BarColorBot = {0.2, 0.0, 0.0, 0.5}
+ graph.AutoScale = false
+ graph.Data = {}
+ graph.MinMaxY = 0
+ graph.CurVal = 0
+ graph.LastDataTime = GetTime()
+
+ graph.Textures = {}
+ graph.TexturesUsed = {}
+
+ graph.LimitUpdates = 0
+ graph.NextUpdate = 0
+
+ graph.BarHeight = {}
+ graph.LastShift = GetTime()
+ graph.BarWidth = (graph.XMax - graph.XMin) / graph.BarNum
+ graph.DecaySet = 0.8
+ graph.Decay = math_pow(graph.DecaySet, graph.BarWidth)
+ graph.ExpNorm = 1 / (1 - graph.Decay)
+
+ graph.FilterOverlap = math_max(math_ceil((graph.TimeRadius + graph.XMax) / graph.BarWidth), 0)
+ for i = 1, graph.BarNum do
+ graph.BarHeight[i] = 0
+ end
+
+ graph.TextFrame = CreateFrame("Frame", nil, graph)
+ graph.TextFrame:SetAllPoints(graph)
+ graph.TextFrame:SetFrameLevel(graph:GetFrameLevel() + 2)
+
+ tinsert(self.RegisteredGraphRealtime, graph)
+ return graph
+end
+
+--Line Graph
+local function SetupGraphLineFunctions(graph)
+ local self = lib
+
+ --Set the various functions
+ graph.SetXAxis = GraphFunctions.SetXAxis
+ graph.SetYAxis = GraphFunctions.SetYAxis
+ graph.AddDataSeries = GraphFunctions.AddDataSeries
+ graph.AddFilledDataSeries = GraphFunctions.AddFilledDataSeries
+ graph.ResetData = GraphFunctions.ResetData
+ graph.RefreshGraph = GraphFunctions.RefreshLineGraph
+ graph.CreateGridlines = GraphFunctions.CreateGridlines
+ graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
+ graph.SetGridSpacing = GraphFunctions.SetGridSpacing
+ graph.SetAxisColor = GraphFunctions.SetAxisColor
+ graph.SetGridColor = GraphFunctions.SetGridColor
+ graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
+ graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
+ graph.SetAutoScale = GraphFunctions.SetAutoScale
+ graph.SetYLabels = GraphFunctions.SetYLabels
+ graph.OnUpdate = GraphFunctions.OnUpdateGraph
+
+ graph.SetLineTexture = GraphFunctions.SetLineTexture
+ graph.SetBorderSize = GraphFunctions.SetBorderSize
+
+ graph.LockXMin = GraphFunctions.LockXMin
+ graph.LockXMax = GraphFunctions.LockXMax
+ graph.LockYMin = GraphFunctions.LockYMin
+ graph.LockYMax = GraphFunctions.LockYMax
+
+
+ graph.DrawLine = self.DrawLine
+ graph.DrawHLine = self.DrawHLine
+ graph.DrawVLine = self.DrawVLine
+ graph.HideLines = self.HideLines
+ graph.DrawBar = self.DrawBar
+ graph.HideBars = self.HideBars
+ graph.HideFontStrings = GraphFunctions.HideFontStrings
+ graph.FindFontString = GraphFunctions.FindFontString
+
+ --Set the update function
+ graph:SetScript("OnUpdate", graph.OnUpdate)
+end
+
+--TODO: Clip lines with the bounds
+function lib:CreateGraphLine(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
+ local graph
+ local i
+ graph = CreateFrame("Frame", name, parent)
+
+
+ graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
+ graph:SetWidth(Width)
+ graph:SetHeight(Height)
+ graph:Show()
+
+
+ SetupGraphLineFunctions(graph)
+
+
+ graph.NeedsUpdate = false
+
+
+ --Initialize Data
+ graph.GraphType = "LINE"
+ graph.YMax = 1
+ graph.YMin = -1
+ graph.XMax = 1
+ graph.XMin = -1
+ graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
+ graph.GridColor = {0.5, 0.5, 0.5, 0.5}
+ graph.XGridInterval = 0.25
+ graph.YGridInterval = 0.25
+ graph.XAxisDrawn = true
+ graph.YAxisDrawn = true
+
+ graph.LockOnXMin = false
+ graph.LockOnXMax = false
+ graph.LockOnYMin = false
+ graph.LockOnYMax = false
+ graph.Data = {}
+ graph.FilledData = {}
+ graph.Textures = {}
+ graph.TexturesUsed = {}
+ graph.TextFrame = CreateFrame("Frame", nil, graph)
+ graph.TextFrame:SetAllPoints(graph)
+
+
+ tinsert(self.RegisteredGraphLine, graph)
+ return graph
+end
+
+
+--Scatter Plot
+local function SetupGraphScatterPlotFunctions(graph)
+ local self = lib
+
+ --Set the various functions
+ graph.SetXAxis = GraphFunctions.SetXAxis
+ graph.SetYAxis = GraphFunctions.SetYAxis
+ graph.AddDataSeries = GraphFunctions.AddDataSeries
+ graph.ResetData = GraphFunctions.ResetData
+ graph.RefreshGraph = GraphFunctions.RefreshScatterPlot
+ graph.CreateGridlines = GraphFunctions.CreateGridlines
+ graph.OnUpdate = GraphFunctions.OnUpdateGraph
+
+ graph.LinearRegression = GraphFunctions.LinearRegression
+ graph.SetAxisDrawing = GraphFunctions.SetAxisDrawing
+ graph.SetGridSpacing = GraphFunctions.SetGridSpacing
+ graph.SetAxisColor = GraphFunctions.SetAxisColor
+ graph.SetGridColor = GraphFunctions.SetGridColor
+ graph.SetGridColorSecondary = GraphFunctions.SetGridColorSecondary
+ graph.SetGridSecondaryMultiple = GraphFunctions.SetGridSecondaryMultiple
+ graph.SetLinearFit = GraphFunctions.SetLinearFit
+ graph.SetAutoScale = GraphFunctions.SetAutoScale
+ graph.SetYLabels = GraphFunctions.SetYLabels
+
+ graph.LockXMin = GraphFunctions.LockXMin
+ graph.LockXMax = GraphFunctions.LockXMax
+ graph.LockYMin = GraphFunctions.LockYMin
+ graph.LockYMax = GraphFunctions.LockYMax
+
+ graph.DrawLine = self.DrawLine
+ graph.DrawHLine = self.DrawHLine
+ graph.DrawVLine = self.DrawVLine
+ graph.HideLines = self.HideLines
+ graph.HideTextures = GraphFunctions.HideTextures
+ graph.FindTexture = GraphFunctions.FindTexture
+ graph.HideFontStrings = GraphFunctions.HideFontStrings
+ graph.FindFontString = GraphFunctions.FindFontString
+
+ --Set the update function
+ graph:SetScript("OnUpdate", graph.OnUpdate)
+end
+
+function lib:CreateGraphScatterPlot(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
+ local graph
+ local i
+ graph = CreateFrame("Frame",name, parent)
+
+
+ graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
+ graph:SetWidth(Width)
+ graph:SetHeight(Height)
+ graph:Show()
+
+
+ SetupGraphScatterPlotFunctions(graph)
+
+
+ graph.NeedsUpdate = false
+
+ --Initialize Data
+ graph.GraphType = "SCATTER"
+ graph.YMax = 1
+ graph.YMin = -1
+ graph.XMax = 1
+ graph.XMin = -1
+ graph.AxisColor = {1.0, 1.0, 1.0, 1.0}
+ graph.GridColor = {0.5, 0.5, 0.5, 0.5}
+ graph.XGridInterval = 0.25
+ graph.YGridInterval = 0.25
+ graph.XAxisDrawn = true
+ graph.YAxisDrawn = true
+ graph.AutoScale = false
+ graph.LinearFit = false
+ graph.LockOnXMin = false
+ graph.LockOnXMax = false
+ graph.LockOnYMin = false
+ graph.LockOnYMax = false
+ graph.Data = {}
+ graph.Textures = {}
+ graph.TexturesUsed = {}
+
+ graph.TextFrame = CreateFrame("Frame", nil, graph)
+ graph.TextFrame:SetAllPoints(graph)
+
+ tinsert(self.RegisteredGraphScatterPlot, graph)
+ return graph
+end
+
+--Pie Chart
+local function SetupGraphPieChartFunctions(graph)
+ local self = lib
+
+ --Set the various functions
+ graph.AddPie = GraphFunctions.AddPie
+ graph.CompletePie = GraphFunctions.CompletePie
+ graph.ResetPie = GraphFunctions.ResetPie
+
+ graph.DrawLine = self.DrawLine
+ graph.DrawHLine = self.DrawHLine
+ graph.DrawVLine = self.DrawVLine
+ graph.DrawLinePie = GraphFunctions.DrawLinePie
+ graph.HideLines = self.HideLines
+ graph.HideTextures = GraphFunctions.HideTextures
+ graph.FindTexture = GraphFunctions.FindTexture
+ graph.OnUpdate = GraphFunctions.PieChart_OnUpdate
+ graph.SetSelectionFunc = GraphFunctions.SetSelectionFunc
+
+ graph:SetScript("OnUpdate", graph.OnUpdate)
+end
+
+function lib:CreateGraphPieChart(name, parent, relative, relativeTo, offsetX, offsetY, Width, Height)
+ local graph
+ local i
+ graph = CreateFrame("Frame",name, parent)
+
+
+ graph:SetPoint(relative, parent, relativeTo, offsetX, offsetY)
+ graph:SetWidth(Width)
+ graph:SetHeight(Height)
+ graph:Show()
+
+
+ SetupGraphPieChartFunctions(graph)
+
+
+ --Initialize Data
+ graph.GraphType = "PIE"
+ graph.PieUsed = 0
+ graph.PercentOn = 0
+ graph.Remaining = 0
+ graph.Textures = {}
+ graph.Ratio = Width / Height
+ graph.Radius = 0.88 * (Width / 2)
+ graph.Radius = graph.Radius * graph.Radius
+ graph.Sections = {}
+ graph.Textures = {}
+ graph.TexturesUsed = {}
+ graph.LastSection = nil
+ graph.onColor = 1
+ graph.TotalSections = 0
+
+ tinsert(self.RegisteredGraphPieChart, graph)
+ return graph
+end
+
+
+-------------------------------------------------------------------------------
+--Functions for Realtime Graphs
+-------------------------------------------------------------------------------
+
+--AddTimeData - Adds a data value to the realtime graph at this moment in time
+function GraphFunctions:AddTimeData(value)
+ if type(value) ~= "number" then
+ return
+ end
+
+ local t = {}
+ t.Time = GetTime()
+ self.LastDataTime = t.Time
+ t.Value = value
+ tinsert(self.Data, t)
+end
+
+--RefreshRealtimeGraph - Refreshes the gridlines for the realtime graph
+function GraphFunctions:RefreshRealtimeGraph()
+ self:HideLines(self)
+ self:CreateGridlines()
+end
+
+--SetFilterRadius - controls the radius of the filter
+function GraphFunctions:SetFilterRadius(radius)
+ self.TimeRadius = radius
+end
+
+--SetAutoscaleYAxis - If enabled the maximum y axis is adjusted to be 25% more than the max value
+function GraphFunctions:SetAutoscaleYAxis(scale)
+ self.AutoScale = scale
+end
+
+--SetBarColors -
+function GraphFunctions:SetBarColors(BotColor, TopColor)
+ local Temp
+ if BotColor.r then
+ Temp = BotColor
+ BotColor = {Temp.r, Temp.g, Temp.b, Temp.a}
+ end
+ if TopColor.r then
+ Temp = TopColor
+ TopColor = {Temp.r, Temp.g, Temp.b, Temp.a}
+ end
+ for i = 1, self.BarNum do
+ local t = self.Bars[i]:GetStatusBarTexture()
+ t:SetGradientAlpha("VERTICAL", BotColor[1], BotColor[2], BotColor[3], BotColor[4], TopColor[1], TopColor[2], TopColor[3], TopColor[4])
+ end
+end
+
+function GraphFunctions:SetMode(mode)
+ self.Mode = mode
+
+ if mode ~= "SLOW" then
+ self.LastShift = GetTime() + self.XMin
+ end
+end
+
+function GraphFunctions:RealtimeSetColors(BotColor, TopColor)
+ local Temp
+ if BotColor.r then
+ Temp = BotColor
+ BotColor = {Temp.r, Temp.g, Temp.b, Temp.a}
+ end
+ if TopColor.r then
+ Temp = TopColor
+ TopColor = {Temp.r, Temp.g, Temp.b, Temp.a}
+ end
+ self.BarColorBot = BotColor
+ self.BarColorTop = TopColor
+ for _, v in pairs(self.Bars) do
+ v:GetStatusBarTexture():SetGradientAlpha("VERTICAL", self.BarColorBot[1], self.BarColorBot[2], self.BarColorBot[3], self.BarColorBot[4], self.BarColorTop[1], self.BarColorTop[2], self.BarColorTop[3], self.BarColorTop[4])
+ end
+end
+
+function GraphFunctions:RealtimeSetWidth(Width)
+ Width = math_floor(Width)
+
+ if Width == self.BarNum then
+ return
+ end
+
+ self.BarNum = Width
+ for i = 1, Width do
+ if type(self.Bars[i]) == "nil" then
+ local bar
+ bar = CreateFrame("StatusBar", self:GetName().."Bar"..i, self)
+ bar:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", i - 1, 0)
+ bar:SetHeight(self.Height)
+ bar:SetWidth(1)
+ bar:SetOrientation("VERTICAL")
+ bar:SetMinMaxValues(0, 1)
+ bar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8")
+ bar:GetStatusBarTexture():SetHorizTile(false)
+ bar:GetStatusBarTexture():SetVertTile(false)
+
+ local t = bar:GetStatusBarTexture()
+ t:SetGradientAlpha("VERTICAL", self.BarColorBot[1], self.BarColorBot[2], self.BarColorBot[3], self.BarColorBot[4], self.BarColorTop[1], self.BarColorTop[2], self.BarColorTop[3], self.BarColorTop[4])
+
+ tinsert(self.Bars, bar)
+ else
+ self.Bars[i]:SetPoint("BOTTOMLEFT", self, "BOTTOMLEFT", i - 1, 0)
+ end
+ self.BarHeight[i] = 0
+ end
+
+ local SizeOfBarsUsed = table.maxn(self.BarsUsing)
+
+ if Width > SizeOfBarsUsed then
+ for i = SizeOfBarsUsed + 1, Width do
+ tinsert(self.BarsUsing, self.Bars[i])
+ self.Bars[i]:Show()
+ end
+ elseif Width < SizeOfBarsUsed then
+ for i = Width + 1, SizeOfBarsUsed do
+ tremove(self.BarsUsing, Width + 1)
+ self.Bars[i]:Hide()
+ end
+ end
+
+ self.BarWidth = (self.XMax - self.XMin) / self.BarNum
+ self.Decay = math_pow(self.DecaySet, self.BarWidth)
+ self.ExpNorm = 1 / (1 - self.Decay) / 0.95 --Actually a finite geometric series
+
+
+ self:OldSetWidth(Width)
+ self:RefreshGraph()
+end
+
+function GraphFunctions:RealtimeSetHeight(Height)
+ self.Height = Height
+
+ for i = 1, self.BarNum do
+ --self.Bars[i]:Hide()
+ self.Bars[i]:SetValue(0)
+ self.Bars[i]:SetHeight(self.Height)
+ end
+
+ self:OldSetHeight(Height)
+ self:RefreshGraph()
+end
+
+function GraphFunctions:GetMaxValue()
+ --Is there any data that could possibly be not zero?
+ if self.LastDataTime < (self.LastShift + self.XMin - self.TimeRadius) then
+ return 0
+ end
+
+ local MaxY = 0
+
+ for i = 1, self.BarNum do
+ MaxY = math_max(MaxY, self.BarHeight[i])
+ end
+
+ return MaxY
+end
+
+
+
+function GraphFunctions:RealtimeGetValue(Time)
+ local Bar
+ if Time < self.XMin or Time > self.XMax then
+ return 0
+ end
+
+ Bar = math_min(math_max(math_floor(self.BarNum * (Time - self.XMin) / (self.XMax - self.XMin) + 0.5), 1), self.BarNum)
+
+ return self.BarHeight[Bar]
+end
+
+function GraphFunctions:SetUpdateLimit(Time)
+ self.LimitUpdates = Time
+end
+
+function GraphFunctions:SetDecay(decay)
+ self.DecaySet = decay
+ self.Decay = math_pow(self.DecaySet, self.BarWidth)
+ self.ExpNorm = 1 / (1 - self.Decay) / 0.95 --Actually a finite geometric series (divide 0.96 instead of 1 since seems doesn't quite work right)
+end
+
+function GraphFunctions:AddBar(value)
+ for i = 1, self.BarNum - 1 do
+ self.BarHeight[i] = self.BarHeight[i + 1]
+ end
+ self.BarHeight[self.BarNum] = value
+ self.AddedBar = true
+end
+
+function GraphFunctions:SetBars()
+ local YHeight = self.YMax - self.YMin
+
+ for i, bar in pairs(self.BarsUsing) do
+ local h
+ h = (self.BarHeight[i] - self.YMin) / YHeight
+
+ bar:SetValue(h)
+ end
+end
+
+
+-------------------------------------------------------------------------------
+--Functions for Line Graph Data
+-------------------------------------------------------------------------------
+
+function GraphFunctions:AddDataSeries(points, color, n2, linetexture)
+ local data
+ --Make sure there is data points
+ if not points then
+ return
+ end
+
+ data = points
+ if n2 == nil then
+ n2 = false
+ end
+ if n2 or (table.getn(points) == 2 and table.getn(points[1]) ~= 2) then
+ data = {}
+ for k, v in ipairs(points[1]) do
+ tinsert(data, {v, points[2][k]})
+ end
+ end
+
+ if linetexture then
+ if not linetexture:find ("\\") and not linetexture:find ("//") then
+ linetexture = TextureDirectory..linetexture
+ end
+ end
+
+ tinsert(self.Data,{Points = data; Color = color; LineTexture=linetexture})
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:AddFilledDataSeries(points, color, n2)
+ local data
+ --Make sure there is data points
+ if not points or #points == 0 then
+ return
+ end
+
+ data = points
+ if n2 == nil then
+ n2 = false
+ end
+
+ if n2 or (table.getn(points) == 2 and table.getn(points[1]) ~= 2) then
+ data = {}
+ for k, v in ipairs(points[1]) do
+ tinsert(data, {v, points[2][k]})
+ end
+ end
+
+ tinsert(self.FilledData, {Points = data; Color = color})
+
+ self.NeedsUpdate = true
+end
+
+
+function GraphFunctions:ResetData()
+ self.Data = {}
+
+ if self.FilledData then
+ self.FilledData = {}
+ end
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetLinearFit(fit)
+ self.LinearFit = fit
+
+ self.NeedsUpdate = true
+end
+
+
+
+function GraphFunctions:HideTextures()
+ local k = #self.TexturesUsed
+ while k > 0 do
+ self.Textures[#self.Textures + 1] = self.TexturesUsed[k]
+ self.TexturesUsed[k]:Hide()
+ self.TexturesUsed[k] = nil
+ k = k - 1
+ end
+end
+
+--Make sure to show a texture after you grab it or its free for anyone else to grab
+function GraphFunctions:FindTexture()
+ local t
+ if #self.Textures > 0 then
+ t = self.Textures[#self.Textures]
+ self.TexturesUsed[#self.TexturesUsed + 1] = t
+ self.Textures[#self.Textures] = nil
+ return t
+ end
+ local g = self:CreateTexture(nil, "BACKGROUND")
+ self.TexturesUsed[#self.TexturesUsed + 1] = g
+ return g
+end
+
+function GraphFunctions:HideFontStrings()
+ if not self.FontStrings then
+ self.FontStrings = {}
+ end
+ for k, t in pairs(self.FontStrings) do
+ t:Hide()
+ end
+end
+
+--Make sure to show a fontstring after you grab it or its free for anyone else to grab
+function GraphFunctions:FindFontString()
+ for k, t in pairs(self.FontStrings) do
+ if not t:IsShown() then
+ return t
+ end
+ end
+ local g
+
+ if self.TextFrame then
+ g = self.TextFrame:CreateFontString(nil, "OVERLAY")
+ else
+ g = self:CreateFontString(nil, "OVERLAY")
+ end
+ tinsert(self.FontStrings, g)
+ return g
+end
+
+--Linear Regression via Least Squares
+function GraphFunctions:LinearRegression(data)
+ local alpha, beta
+ local n, SX, SY, SXX, SXY = 0, 0, 0, 0, 0
+
+ for k, v in pairs(data) do
+ n = n + 1
+
+ SX = SX + v[1]
+ SXX = SXX + v[1] * v[1]
+ SY = SY + v[2]
+ SXY = SXY + v[1] * v[2]
+ end
+
+ beta = (n * SXY - SX * SY) / (n * SXX - SX * SX)
+ alpha = (SY - beta * SX) / n
+
+ return alpha, beta
+end
+
+
+-------------------------------------------------------------------------------
+--Functions for Pie Chart
+-------------------------------------------------------------------------------
+
+local PiePieces = {
+ "1-2",
+ "1-4",
+ "1-8",
+ "1-16",
+ "1-32",
+ "1-64",
+ "1-128"
+}
+
+--26 Colors
+local ColorTable = {
+ {0.9, 0.1, 0.1},
+ {0.1, 0.9, 0.1},
+ {0.1, 0.1, 0.9},
+ {0.9, 0.9, 0.1},
+ {0.9, 0.1, 0.9},
+ {0.1, 0.9, 0.9},
+ {0.9, 0.9, 0.9},
+ {0.5, 0.1, 0.1},
+ {0.1, 0.5, 0.1},
+ {0.1, 0.1, 0.5},
+ {0.5, 0.5, 0.1},
+ {0.5, 0.1, 0.5},
+ {0.1, 0.5, 0.5},
+ {0.5, 0.5, 0.5},
+ {0.75, 0.15, 0.15},
+ {0.15, 0.75, 0.15},
+ {0.15, 0.15, 0.75},
+ {0.75, 0.75, 0.15},
+ {0.75, 0.15, 0.75},
+ {0.15, 0.75, 0.75},
+ {0.9, 0.5, 0.1},
+ {0.1, 0.5, 0.9},
+ {0.9, 0.1, 0.5},
+ {0.5, 0.9, 0.1},
+ {0.5, 0.1, 0.9},
+ {0.1, 0.9, 0.5},
+}
+
+function GraphFunctions:AddPie(Percent, Color)
+ local PiePercent = self.PercentOn
+
+ local CurPiece = 50
+ local Angle = 180
+ local CurAngle = PiePercent * 360 / 100
+
+ self.TotalSections = self.TotalSections + 1
+ if type(self.Sections[self.TotalSections]) ~= "table" then
+ self.Sections[self.TotalSections] = {}
+ end
+
+ local Section = self.Sections[self.TotalSections]
+ Section.Textures = {}
+
+ if type(Color) ~= "table" then
+ if self.onColor <= table.maxn(ColorTable) then
+ Color = ColorTable[self.onColor]
+ else
+ Color = {math_random(), math_random(), math_random()}
+ end
+ self.onColor = self.onColor + 1
+ end
+
+ if PiePercent == 0 then
+ self:DrawLinePie(0)
+ end
+
+ Percent = Percent + self.Remaining
+ local LastPiece = 0
+ for k, v in pairs(PiePieces) do
+ if (Percent + 0.1) > CurPiece then
+ local t = self:FindTexture()
+ t:SetTexture(TextureDirectory..v)
+ t:ClearAllPoints()
+ t:SetPoint("CENTER", self, "CENTER", 0, 0)
+ t:SetHeight(self:GetHeight())
+ t:SetWidth(self:GetWidth())
+ GraphFunctions:RotateTexture(t, CurAngle)
+ t:Show()
+
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ Percent = Percent - CurPiece
+ PiePercent = PiePercent + CurPiece
+ CurAngle = CurAngle + Angle
+
+ tinsert(Section.Textures, t)
+
+ if k == 7 then
+ LastPiece = 0.09
+ end
+ end
+ CurPiece = CurPiece / 2
+ Angle = Angle / 2
+ end
+
+ --Finish adding section data
+ Section.Color = Color
+ Section.Angle = CurAngle
+
+ self:DrawLinePie((PiePercent + LastPiece) * 360 / 100)
+ self.PercentOn = PiePercent
+ self.Remaining = Percent
+
+ return Color
+end
+
+function GraphFunctions:CompletePie(Color)
+ local Percent = 100 - self.PercentOn
+ local PiePercent = self.PercentOn
+
+ local CurPiece = 50
+ local Angle = 180
+ local CurAngle = PiePercent * 360 / 100
+
+ self.TotalSections = self.TotalSections + 1
+ if not self.Sections[self.TotalSections] then
+ self.Sections[self.TotalSections] = {}
+ end
+
+ local Section = self.Sections[self.TotalSections]
+ Section.Textures = {}
+
+ if type(Color) ~= "table" then
+ if self.onColor <= table.maxn(ColorTable) then
+ Color = ColorTable[self.onColor]
+ else
+ Color = {math_random(), math_random(), math_random()}
+ end
+ self.onColor = self.onColor + 1
+ end
+
+ Percent = Percent + self.Remaining
+ if PiePercent ~= 0 then
+ for k, v in pairs(PiePieces) do
+ if (Percent + 0.1) > CurPiece then
+ local t = self:FindTexture()
+ t:SetTexture(TextureDirectory..v)
+ t:ClearAllPoints()
+ t:SetPoint("CENTER", self, "CENTER", 0, 0)
+ t:SetHeight(self:GetHeight())
+ t:SetWidth(self:GetWidth())
+ GraphFunctions:RotateTexture(t, CurAngle)
+ t:Show()
+
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ Percent = Percent - CurPiece
+ PiePercent = PiePercent + CurPiece
+ CurAngle = CurAngle + Angle
+
+ tinsert(Section.Textures, t)
+ end
+ CurPiece = CurPiece / 2
+ Angle = Angle / 2
+ end
+ else--Special case if its by itself
+ local t = self:FindTexture()
+ t:SetTexture(TextureDirectory.."1-1")
+ t:ClearAllPoints()
+ t:SetPoint("CENTER", self, "CENTER", 0, 0)
+ t:SetHeight(self:GetHeight())
+ t:SetWidth(self:GetWidth())
+ GraphFunctions:RotateTexture(t, CurAngle)
+ t:Show()
+
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ tinsert(Section.Textures, t)
+ end
+
+ --Finish adding section data
+ Section.Color = Color
+ Section.Angle = 360
+
+ self.PercentOn = PiePercent
+ self.Remaining = Percent
+
+ return Color
+end
+
+function GraphFunctions:ResetPie()
+ self:HideTextures()
+ self:HideLines(self)
+
+ self.PieUsed = 0
+ self.PercentOn = 0
+ self.Remaining = 0
+ self.onColor = 1
+ self.LastSection = nil
+ self.TotalSections = 0
+ --self.Sections = {}
+end
+
+function GraphFunctions:DrawLinePie(angle)
+ local sx, sy, ex, ey
+ local Radian = math_pi * (90 - angle) / 180
+ local w, h
+ w = self:GetWidth() / 2
+ h = self:GetHeight() / 2
+
+
+ sx = w
+ sy = h
+
+ ex = sx + 0.88 * w * math_cos(Radian)
+ ey = sx + 0.88 * h * math_sin(Radian)
+
+ self:DrawLine(self, sx, sy, ex, ey, 34, {0.0, 0.0, 0.0, 1.0}, "OVERLAY")
+end
+
+--Used to rotate the pie slices
+function GraphFunctions:RotateTexture(texture, angle)
+ local Radian = math_pi * (45 - angle) / 180
+ local Radian2 = math_pi * (45 + 90 - angle) / 180
+ local Radius = 0.70710678118654752440084436210485
+
+ local tx, ty, tx2, ty2
+ tx = Radius * math_cos(Radian)
+ ty = Radius * math_sin(Radian)
+ tx2 = -ty
+ ty2 = tx
+
+ texture:SetTexCoord(0.5 - tx, 0.5 - ty, 0.5 + tx2, 0.5 + ty2, 0.5 - tx2, 0.5 - ty2, 0.5 + tx, 0.5 + ty)
+end
+
+function GraphFunctions:SetSelectionFunc(f)
+ self.SelectionFunc = f
+end
+
+--TODO: Pie chart pieces need to be clickable
+function GraphFunctions:PieChart_OnUpdate()
+ if (MouseIsOver(self)) then
+ local sX, sY = self:GetCenter()
+ local Scale = self:GetEffectiveScale()
+ local mX, mY = GetCursorPosition()
+ local dX, dY
+
+ dX = mX / Scale - sX
+ dY = mY / Scale - sY
+
+ local Angle = 90-math_deg(math_atan(dY / dX))
+ dY = dY * self.Ratio
+ local Dist = dX * dX + dY * dY
+
+ if dX < 0 then
+ Angle = Angle + 180
+ end
+
+ --Are we on the Pie Chart?
+ if Dist < self.Radius then
+ --What section are we on?
+ for k = 1, self.TotalSections do
+ local v = self.Sections[k]
+ if Angle < v.Angle then
+ local Color
+ if k ~= self.LastSection then
+ if self.LastSection then
+ local Section = self.Sections[self.LastSection]
+ for _, t in pairs(Section.Textures) do
+ Color = Section.Color
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ end
+ end
+
+ if self.SelectionFunc then
+ self:SelectionFunc(k)
+ end
+ end
+
+ local ColorAdd = 0.15 * math_abs(math_fmod(GetTime(), 3) - 1.5) - 0.1125
+
+ Color = {}
+ Color[1] = v.Color[1]+ColorAdd
+ Color[2] = v.Color[2]+ColorAdd
+ Color[3] = v.Color[3]+ColorAdd
+
+ for _, t in pairs(v.Textures) do
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ end
+
+ self.LastSection = k
+
+ return
+ end
+ end
+ elseif self.LastSection then
+ local Section = self.Sections[self.LastSection]
+ for _, t in pairs(Section.Textures) do
+ local Color = Section.Color
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ end
+ self.LastSection = nil
+ if self.SelectionFunc then
+ self:SelectionFunc(nil)
+ end
+ end
+ else
+ if self.LastSection then
+ local Section = self.Sections[self.LastSection]
+ for _, t in pairs(Section.Textures) do
+ local Color = Section.Color
+ t:SetVertexColor(Color[1], Color[2], Color[3], 1.0)
+ end
+ self.LastSection = nil
+ if self.SelectionFunc then
+ self:SelectionFunc(nil)
+ end
+ end
+ end
+end
+
+
+-------------------------------------------------------------------------------
+--Axis Setting Functions
+-------------------------------------------------------------------------------
+
+function GraphFunctions:SetYMax(ymax)
+ if ymax == self.YMax then
+ return
+ end
+
+ self.YMax = ymax
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetYAxis(ymin, ymax)
+ if self.YMin == ymin and self.YMax == ymax then
+ return
+ end
+
+ self.YMin = ymin
+ self.YMax = ymax
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetMinMaxY(val)
+ if self.MinMaxY == val then
+ return
+ end
+
+ self.MinMaxY = val
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetXAxis(xmin, xmax)
+ if self.XMin == xmin and self.XMax == xmax then
+ return
+ end
+
+ self.XMin = xmin
+ self.XMax = xmax
+
+ self.NeedsUpdate = true
+
+ if self.GraphType == "REALTIME" then
+ self.BarWidth = (xmax - xmin) / self.BarNum
+ self.Decay = math_pow(self.DecaySet, self.BarWidth)
+ self.FilterOverlap = math_max(math_ceil((self.TimeRadius + xmax) / self.BarWidth), 0)
+ self.LastShift = GetTime() + xmin
+ end
+end
+
+function GraphFunctions:SetAutoScale(auto)
+ self.AutoScale = auto
+
+ self.NeedsUpdate = true
+end
+
+--The various Lock Functions let you use Autoscale but holds the locked points in place
+function GraphFunctions:LockXMin(state)
+ if state == nil then
+ self.LockOnXMin = not self.LockOnXMin
+ return
+ end
+ self.LockOnXMin = state
+end
+
+function GraphFunctions:LockXMax(state)
+ if state == nil then
+ self.LockOnXMax = not self.LockOnXMax
+ return
+ end
+ self.LockOnXMax = state
+end
+
+function GraphFunctions:LockYMin(state)
+ if state == nil then
+ self.LockOnYMin = not self.LockOnYMin
+ return
+ end
+ self.LockOnYMin = state
+end
+
+function GraphFunctions:LockYMax(state)
+ if state == nil then
+ self.LockOnYMax = not self.LockOnYMax
+ return
+ end
+ self.LockOnYMax = state
+end
+
+
+-------------------------------------------------------------------------------
+--Grid & Axis Drawing Functions
+-------------------------------------------------------------------------------
+
+function GraphFunctions:SetAxisDrawing(xaxis, yaxis)
+ if xaxis == self.XAxisDrawn and self.YAxisDrawn == yaxis then
+ return
+ end
+
+ self.XAxisDrawn = xaxis
+ self.YAxisDrawn = yaxis
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetGridSpacing(xspacing, yspacing)
+ if xspacing == self.XGridInterval and self.YGridInterval == yspacing then
+ return
+ end
+
+ self.XGridInterval = xspacing
+ self.YGridInterval = yspacing
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetAxisColor(color)
+ if self.AxisColor[1] == color[1] and self.AxisColor[2] == color[2] and self.AxisColor[3] == color[3] and self.AxisColor[4] == color[4] then
+ return
+ end
+
+ self.AxisColor = color
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetGridColor(color)
+ if self.GridColor[1] == color[1] and self.GridColor[2] == color[2] and self.GridColor[3] == color[3] and self.GridColor[4] == color[4] then
+ return
+ end
+
+ self.GridColor = color
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetGridColorSecondary(color)
+ if self.GridColorSecondary ~= nil and self.GridColorSecondary[1] == color[1] and self.GridColorSecondary[2] == color[2] and self.GridColorSecondary[3] == color[3] and self.GridColorSecondary[4] == color[4] then
+ return
+ end
+
+ self.GridColorSecondary = color
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetGridSecondaryMultiple(XAxis, YAxis)
+ if type(XAxis) ~= "number" then
+ XAxis = 1
+ end
+ if type(YAxis) ~= "number" then
+ YAxis = 1
+ end
+ self.GridSecondaryX = XAxis
+ self.GridSecondaryY = YAxis
+
+ self.NeedsUpdate = true
+end
+
+function GraphFunctions:SetYLabels(Left, Right)
+ self.YLabelsLeft = Left
+ self.YLabelsRight = Right
+end
+
+function GraphFunctions:SetLineTexture(texture)
+ if (type (texture) ~= "string") then
+ return assert (false, "Parameter 1 for SetLineTexture must be a string")
+ end
+
+ --> full path
+ if (texture:find ("\\") or texture:find ("//")) then
+ self.CustomLine = texture
+ --> using an image inside lib-graph folder
+ else
+ self.CustomLine = TextureDirectory..texture
+ end
+end
+
+function GraphFunctions:SetBorderSize(border, size)
+ border = string.lower (border)
+
+ if (type (size) ~= "number") then
+ return assert (false, "Parameter 2 for SetBorderSize must be a number")
+ end
+
+ if (border == "left") then
+ self.CustomLeftBorder = size
+ return true
+ elseif (border == "right") then
+ self.CustomRightBorder = size
+ return true
+ elseif (border == "top") then
+ self.CustomTopBorder = size
+ return true
+ elseif (border == "bottom") then
+ self.CustomBottomBorder = size
+ return true
+ end
+
+ return assert (false, "Usage: GraphObject:SetBorderSize (LEFT RIGHT TOP BOTTOM, SIZE)")
+end
+
+function GraphFunctions:CreateGridlines()
+ local Width = self:GetWidth()
+ local Height = self:GetHeight()
+ local NoSecondary = (self.GridSecondaryY == nil) or (self.GridSecondaryX == nil) or (type(self.GridColorSecondary) ~= "table")
+ local F
+ self:HideLines(self)
+ self:HideFontStrings()
+
+ if self.YGridInterval then
+ local LowerYGridLine, UpperYGridLine, TopSpace
+ LowerYGridLine = self.YMin / self.YGridInterval
+ LowerYGridLine = math_max(math_floor(LowerYGridLine), math_ceil(LowerYGridLine))
+ UpperYGridLine = self.YMax / self.YGridInterval
+ UpperYGridLine = math_min(math_floor(UpperYGridLine), math_ceil(UpperYGridLine))
+ --UpperYGridLine = math_min(UpperYGridLine, self.YGridMax or 16)
+ TopSpace = Height * (1 - (UpperYGridLine * self.YGridInterval - self.YMin) / (self.YMax - self.YMin))
+
+ for i = LowerYGridLine, UpperYGridLine do
+ if i ~= 0 or not self.YAxisDrawn then
+ local YPos, T
+ YPos = Height * (i * self.YGridInterval - self.YMin) / (self.YMax - self.YMin)
+ if NoSecondary or math_fmod(i, self.GridSecondaryY) == 0 then
+ T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.GridColor, "BACKGROUND")
+ else
+ T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.GridColorSecondary, "BACKGROUND")
+ end
+
+ if ((i ~= UpperYGridLine) or (TopSpace > 12)) and (NoSecondary or math_fmod(i, self.GridSecondaryY) == 0) then
+ if self.YLabelsLeft then
+ F = self:FindFontString()
+ F:SetFontObject("GameFontHighlightSmall")
+ F:SetTextColor(1, 1, 1)
+ F:ClearAllPoints()
+ F:SetPoint("BOTTOMLEFT", T, "LEFT", 2, 2)
+ F:SetText(i * self.YGridInterval)
+ F:Show()
+ end
+
+ if self.YLabelsRight then
+ F = self:FindFontString()
+ F:SetFontObject("GameFontHighlightSmall")
+ F:SetTextColor(1, 1, 1)
+ F:ClearAllPoints()
+ F:SetPoint("BOTTOMRIGHT", T, "RIGHT", -2, 2)
+ F:SetText(i * self.YGridInterval)
+ F:Show()
+ end
+ end
+ end
+ end
+ end
+
+ if self.XGridInterval then
+ local LowerXGridLine, UpperXGridLine
+ LowerXGridLine = self.XMin / self.XGridInterval
+ LowerXGridLine = math_max(math_floor(LowerXGridLine), math_ceil(LowerXGridLine))
+ UpperXGridLine = self.XMax / self.XGridInterval
+ UpperXGridLine = math_min(math_floor(UpperXGridLine), math_ceil(UpperXGridLine))
+ --UpperXGridLine = math_min(UpperXGridLine, self.XGridMax or 16)
+
+ for i = LowerXGridLine, UpperXGridLine do
+ if i ~= 0 or not self.XAxisDrawn then
+ local XPos
+ XPos = Width * (i * self.XGridInterval - self.XMin) / (self.XMax - self.XMin)
+ if NoSecondary or math_fmod(i, self.GridSecondaryX) == 0 then
+ self:DrawLine(self, XPos, 0, XPos, Height, 24, self.GridColor, "BACKGROUND")
+ else
+ self:DrawLine(self, XPos, 0, XPos, Height, 24, self.GridColorSecondary, "BACKGROUND")
+ end
+ end
+ end
+ end
+
+ if self.YAxisDrawn and self.YMax >= 0 and self.YMin <= 0 then
+ local YPos, T
+
+ YPos = Height * (-self.YMin) / (self.YMax - self.YMin)
+ T = self:DrawLine(self, 0, YPos, Width, YPos, 24, self.AxisColor, "BACKGROUND")
+
+ if self.YLabelsLeft then
+ F = self:FindFontString()
+ F:SetFontObject("GameFontHighlightSmall")
+ F:SetTextColor(1, 1, 1)
+ F:ClearAllPoints()
+ F:SetPoint("BOTTOMLEFT", T, "LEFT", 2, 2)
+ F:SetText(0)
+ F:Show()
+ end
+ if self.YLabelsRight then
+ F = self:FindFontString()
+ F:SetFontObject("GameFontHighlightSmall")
+ F:SetTextColor(1, 1, 1)
+ F:ClearAllPoints()
+ F:SetPoint("BOTTOMRIGHT", T, "RIGHT", -2, 2)
+ F:SetText(0)
+ F:Show()
+ end
+ end
+
+ if self.XAxisDrawn and self.XMax >= 0 and self.XMin <= 0 then
+ local XPos
+
+ XPos = Width * (-self.XMin) / (self.XMax - self.XMin)
+ self:DrawLine(self, XPos, 0, XPos, Height, 24, self.AxisColor, "BACKGROUND")
+ end
+end
+
+
+--------------------------------------------------------------------------------
+--Refresh functions
+--------------------------------------------------------------------------------
+
+function GraphFunctions:OnUpdateGraph()
+ if self.NeedsUpdate and self.RefreshGraph then
+ self:RefreshGraph()
+ self.NeedsUpdate = false
+ end
+end
+
+--Performs a convolution in realtime allowing to graph Framerate, DPS, or any other data you want graphed in realtime
+function GraphFunctions:OnUpdateGraphRealtime()
+ local CurTime = GetTime()
+ local BarsChanged
+
+ if self.NextUpdate > CurTime or (self.Mode == "RAW" and not (self.NeedsUpdate or self.AddedBar)) then
+ return
+ end
+
+ self.NextUpdate = CurTime + self.LimitUpdates
+
+ --Slow Mode performs an entire convolution every frame
+ if self.Mode == "SLOW" then
+ --Initialize Bar Data
+ self.BarHeight = {}
+ for i = 1, self.BarNum do
+ self.BarHeight[i] = 0
+ end
+ local k, v
+ local BarTimeRadius = (self.XMax - self.XMin) / self.BarNum
+ local DataValue = 1 / (2 * self.TimeRadius)
+
+ if self.Filter == "RECT" then
+ --Take the convolution of the dataset on to the bars wtih a rectangular filter
+ local DataValue = 1 / (2 * self.TimeRadius)
+ for k, v in pairs(self.Data) do
+ if v.Time < (CurTime + self.XMin - self.TimeRadius) then
+ tremove(self.Data, k)
+ else
+ local DataTime = v.Time - CurTime
+ local LowestBar = math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), 1)
+ local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
+ for i = LowestBar, HighestBar do
+ self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue
+ end
+ end
+ end
+ elseif self.Filter == "TRI" then
+ --Needs optimization badly
+ --Take the convolution of the dataset on to the bars wtih a triangular filter
+ local DataValue = 1 / (self.TimeRadius)
+ for k, v in pairs(self.Data) do
+ local Temp
+ if v.Time < (CurTime + self.XMin - self.TimeRadius) then
+ tremove(self.Data, k)
+ else
+ local DataTime = v.Time - CurTime
+ local LowestBar = math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), 1)
+ local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
+
+ for i = LowestBar, HighestBar do
+ self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue * math_abs(BarTimeRadius * i + self.XMin - DataTime)
+ end
+ end
+ end
+ end
+ BarsChanged = true
+ elseif self.Mode == "FAST" then
+ local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
+
+ if ShiftBars > 0 and not (self.LastDataTime < (self.LastShift + self.XMin - self.TimeRadius * 2)) then
+ local RecalcBars = self.BarNum - (ShiftBars + self.FilterOverlap) + 1
+
+ for i = 1, self.BarNum do
+ if i < RecalcBars then
+ self.BarHeight[i] = self.BarHeight[i + ShiftBars]
+ else
+ self.BarHeight[i] = 0
+ end
+ end
+
+ local BarTimeRadius = (self.XMax - self.XMin) / self.BarNum
+ local DataValue = 1 / (2 * self.TimeRadius)
+ local TimeDiff = CurTime-self.LastShift
+
+ CurTime = self.LastShift + ShiftBars * self.BarWidth
+ self.LastShift = CurTime
+
+ if self.Filter == "RECT" then
+ --Take the convolution of the dataset on to the bars wtih a rectangular filter
+ local DataValue = 1 / (2 * self.TimeRadius)
+ for k, v in pairs(self.Data) do
+ if v.Time < (CurTime + self.XMax - self.TimeRadius - TimeDiff) then
+ tremove(self.Data, k)
+ else
+ local DataTime = v.Time - CurTime
+ local LowestBar = math_max(math_max(math_floor((DataTime - self.XMin - self.TimeRadius) / BarTimeRadius), RecalcBars), 1)
+ local HighestBar = math_min(math_ceil((DataTime - self.XMin + self.TimeRadius) / BarTimeRadius), self.BarNum)
+ if LowestBar <= HighestBar then
+ for i = LowestBar, HighestBar do
+ self.BarHeight[i] = self.BarHeight[i] + v.Value * DataValue
+ end
+ end
+ end
+ end
+ end
+ BarsChanged = true
+ else
+ CurTime = self.LastShift + ShiftBars * self.BarWidth
+ self.LastShift = CurTime
+ end
+ elseif self.Mode == "EXP" then
+ local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
+
+ if ShiftBars > 0 then
+ local RecalcBars = self.BarNum - ShiftBars + 1
+
+ for i = 1, self.BarNum do
+ if i < RecalcBars then
+ self.BarHeight[i] = self.BarHeight[i + ShiftBars]
+ end
+ end
+
+ --Now to calculate the new bars
+ local Total
+ local Weight = 1 / self.TimeRadius / self.ExpNorm
+
+ for i = RecalcBars, self.BarNum do
+ Total = 0
+
+ --Implement an EXPFAST which does this only once instead of for each bar
+ for k, v in pairs(self.Data) do
+ Total = Total + v.Value * Weight
+ if v.Time < (self.LastShift - self.TimeRadius) then
+ tremove(self.Data, k)
+ end
+ end
+
+ self.CurVal = self.Decay * self.CurVal + Total
+
+ self.BarHeight[i] = self.CurVal
+
+ self.LastShift = self.LastShift + self.BarWidth
+ end
+
+ if self.CurVal < 0.1 then
+ self.CurVal = 0
+ end
+ BarsChanged = true
+ else
+ self.LastShift = self.LastShift + self.BarWidth * ShiftBars
+ end
+ elseif self.Mode == "EXPFAST" then
+ local ShiftBars = math_floor((CurTime - self.LastShift) / self.BarWidth)
+ local RecalcBars = self.BarNum - ShiftBars + 1
+
+ if ShiftBars > 0 and not (self.LastDataTime < (self.LastShift + self.XMin-self.TimeRadius)) then
+ for i = 1, self.BarNum do
+ if i < RecalcBars then
+ self.BarHeight[i] = self.BarHeight[i + ShiftBars]
+ end
+ end
+
+ --Now to calculate the new bars
+ local Total
+ local Weight = 1 / self.TimeRadius / self.ExpNorm
+
+ Total = 0
+
+ --Implement an EXPFAST which does this only once instead of for each bar
+ for k, v in pairs(self.Data) do
+ Total = Total + v.Value * Weight
+ if v.Time < (self.LastShift - self.TimeRadius) then
+ tremove(self.Data, k)
+ end
+ end
+
+ --self.LastShift = self.LastShift+self.BarWidth*ShiftBars
+
+ if self.CurVal ~= 0 or Total ~= 0 then
+ for i = RecalcBars, self.BarNum do
+ self.CurVal = self.Decay * self.CurVal + Total
+ self.BarHeight[i] = self.CurVal
+ end
+ self.LastDataTime = self.LastShift + self.BarWidth * ShiftBars
+ else
+ for i = RecalcBars, self.BarNum do
+ self.BarHeight[i] = 0
+ end
+ end
+
+ if self.CurVal < 0.1 then
+ self.CurVal = 0
+ end
+ BarsChanged = true
+ end
+ self.LastShift = self.LastShift + self.BarWidth * ShiftBars
+ elseif self.Mode == "RAW" then
+ --Do nothing really
+ --Using .AddedBar so we cut down on updating the grid
+ self.AddedBar = false
+ BarsChanged = true
+ end
+
+
+ if BarsChanged then
+ if self.AutoScale then
+ local MaxY = 0
+
+ for i = 1, self.BarNum do
+ MaxY = math_max(MaxY, self.BarHeight[i])
+ end
+ MaxY = 1.25 * MaxY
+
+ MaxY = math_max(MaxY, self.MinMaxY)
+
+ if MaxY ~= 0 and math_abs(self.YMax - MaxY) > 0.01 then
+ self.YMax = MaxY
+ self.NeedsUpdate = true
+
+ local Spacing
+ if self.YMax < 25 then
+ Spacing = -1
+ else
+ Spacing = math.log(self.YMax / 100) / math.log(2)
+ end
+
+ self.YGridInterval = 25 * math.pow(2, math.floor(Spacing))
+ end
+ end
+ self:SetBars()
+ end
+
+ if self.NeedsUpdate then
+ self.NeedsUpdate = false
+ self:RefreshGraph()
+ end
+end
+
+--Line Graph
+function GraphFunctions:RefreshLineGraph()
+ self:HideLines(self)
+ self:HideBars(self)
+
+ if self.AutoScale and self.Data then
+ local MinX, MaxX, MinY, MaxY = math_huge, -math_huge, math_huge, -math_huge
+ --Go through line data first
+ for k1, series in pairs(self.Data) do
+ for k2, point in pairs(series.Points) do
+ MinX = math_min(point[1], MinX)
+ MaxX = math_max(point[1], MaxX)
+ MinY = math_min(point[2], MinY)
+ MaxY = math_max(point[2], MaxY)
+ end
+ end
+
+ --Now through the Filled Lines
+ for k1, series in pairs(self.FilledData) do
+ for k2, point in pairs(series.Points) do
+ MinX = math_min(point[1], MinX)
+ MaxX = math_max(point[1], MaxX)
+ MinY = math_min(point[2], MinY)
+ MaxY = math_max(point[2], MaxY)
+ end
+ end
+
+ local XBorder, YBorder
+
+ XBorder = 0.1 * (MaxX - MinX)
+ YBorder = 0.1 * (MaxY - MinY)
+
+ if not self.LockOnXMin then
+ if (self.CustomLeftBorder) then
+ self.XMin = MinX + self.CustomLeftBorder --> custom size of left border
+ else
+ self.XMin = MinX - XBorder
+ end
+ end
+
+ if not self.LockOnXMax then
+ if (self.CustomRightBorder) then
+ self.XMax = MaxX + self.CustomRightBorder --> custom size of right border
+ else
+ self.XMax = MaxX + XBorder
+ end
+ end
+
+ if not self.LockOnYMin then
+ if (self.CustomBottomBorder) then
+ self.YMin = MinY + self.CustomBottomBorder --> custom size of bottom border
+ else
+ self.YMin = MinY - YBorder
+ end
+ end
+
+ if not self.LockOnYMax then
+ if (self.CustomTopBorder) then
+ self.YMax = MaxY + self.CustomTopBorder --> custom size of top border
+ else
+ self.YMax = MaxY + YBorder
+ end
+ end
+
+ end
+
+ self:CreateGridlines()
+
+ local Width = self:GetWidth()
+ local Height = self:GetHeight()
+
+ for k1, series in pairs(self.Data) do
+ local LastPoint
+ LastPoint = nil
+
+ for k2, point in pairs(series.Points) do
+ if LastPoint then
+ local TPoint = {x = point[1]; y = point[2]}
+
+ TPoint.x = Width * (TPoint.x - self.XMin) / (self.XMax - self.XMin)
+ TPoint.y = Height * (TPoint.y - self.YMin) / (self.YMax - self.YMin)
+
+ self:DrawLine(self, LastPoint.x, LastPoint.y, TPoint.x, TPoint.y, 32, series.Color, nil, series.LineTexture)
+
+ LastPoint = TPoint
+ else
+ LastPoint = {x = point[1]; y = point[2]}
+ LastPoint.x = Width * (LastPoint.x - self.XMin) / (self.XMax - self.XMin)
+ LastPoint.y = Height * (LastPoint.y - self.YMin) / (self.YMax - self.YMin)
+ end
+ end
+ end
+
+ --Filled Line Graphs
+ for k1, series in pairs(self.FilledData) do
+ local LastPoint
+ LastPoint = nil
+
+ for k2, point in pairs(series.Points) do
+ if LastPoint then
+ local TPoint = {x = point[1]; y = point[2]}
+
+ TPoint.x = Width * (TPoint.x - self.XMin) / (self.XMax - self.XMin)
+ TPoint.y = Height * (TPoint.y - self.YMin ) /(self.YMax - self.YMin)
+
+ self:DrawBar(self, LastPoint.x, LastPoint.y, TPoint.x, TPoint.y, series.Color, k1)
+
+ LastPoint = TPoint
+ else
+ LastPoint = {x = point[1]; y = point[2]}
+ LastPoint.x = Width * (LastPoint.x - self.XMin) / (self.XMax - self.XMin)
+ LastPoint.y = Height * (LastPoint.y - self.YMin) / (self.YMax - self.YMin)
+ end
+ end
+ end
+end
+
+--Scatter Plot Refresh
+function GraphFunctions:RefreshScatterPlot()
+ self:HideLines(self)
+
+ if self.AutoScale and self.Data then
+ local MinX, MaxX, MinY, MaxY = math_huge, -math_huge, math_huge, -math_huge
+ for k1, series in pairs(self.Data) do
+ for k2, point in pairs(series.Points) do
+ MinX = math_min(point[1], MinX)
+ MaxX = math_max(point[1], MaxX)
+ MinY = math_min(point[2], MinY)
+ MaxY = math_max(point[2], MaxY)
+ end
+ end
+
+ local XBorder, YBorder
+
+ XBorder = 0.1 * (MaxX - MinX)
+ YBorder = 0.1 * (MaxY - MinY)
+
+ if not self.LockOnXMin then
+ self.XMin = MinX - XBorder
+ end
+ if not self.LockOnXMax then
+ self.XMax = MaxX + XBorder
+ end
+ if not self.LockOnYMin then
+ self.YMin = MinY - YBorder
+ end
+ if not self.LockOnYMax then
+ self.YMax = MaxY + YBorder
+ end
+ end
+
+ self:CreateGridlines()
+
+ local Width = self:GetWidth()
+ local Height = self:GetHeight()
+
+ self:HideTextures()
+ for k1, series in pairs(self.Data) do
+ local MinX, MaxX = self.XMax, self.XMin
+ for k2, point in pairs(series.Points) do
+ local x, y
+ MinX = math_min(point[1],MinX)
+ MaxX = math_max(point[1],MaxX)
+ x = Width * (point[1] - self.XMin) / (self.XMax - self.XMin)
+ y = Height * (point[2] - self.YMin) / (self.YMax - self.YMin)
+
+ local g = self:FindTexture()
+ g:SetTexture("Spells\\GENERICGLOW2_64")
+ g:SetWidth(6)
+ g:SetHeight(6)
+ g:ClearAllPoints()
+ g:SetPoint("CENTER", self, "BOTTOMLEFT", x, y)
+ g:SetVertexColor(series.Color[1], series.Color[2], series.Color[3], series.Color[4])
+ g:Show()
+ end
+
+ if self.LinearFit then
+ local alpha, beta = self:LinearRegression(series.Points)
+ local sx, sy, ex, ey
+
+ sx = MinX
+ sy = beta * sx + alpha
+ ex = MaxX
+ ey = beta*ex+alpha
+
+ sx = Width * (sx - self.XMin) / (self.XMax - self.XMin)
+ sy = Height * (sy - self.YMin) / (self.YMax - self.YMin)
+ ex = Width * (ex - self.XMin) / (self.XMax - self.XMin)
+ ey = Height * (ey - self.YMin) / (self.YMax - self.YMin)
+
+ self:DrawLine(self, sx, sy, ex, ey, 32, series.Color)
+ end
+ end
+end
+
+--Copied from Blizzard's TaxiFrame code and modifed for IMBA then remodified for GraphLib
+
+-- The following function is used with permission from Daniel Stephens
+local TAXIROUTE_LINEFACTOR = 128 / 126 -- Multiplying factor for texture coordinates
+local TAXIROUTE_LINEFACTOR_2 = TAXIROUTE_LINEFACTOR / 2 -- Half of that
+
+-- T - Texture
+-- C - Canvas Frame (for anchoring)
+-- sx, sy - Coordinate of start of line
+-- ex, ey - Coordinate of end of line
+-- w - Width of line
+-- relPoint - Relative point on canvas to interpret coords (Default BOTTOMLEFT)
+function lib:DrawLine(C, sx, sy, ex, ey, w, color, layer, linetexture)
+ local relPoint = "BOTTOMLEFT"
+
+ if sx == ex then
+ if sy == ey then
+ return
+ else
+ return self:DrawVLine(C, sx, sy, ey, w, color, layer)
+ end
+ elseif sy == ey then
+ return self:DrawHLine(C, sx, ex, sy, w, color, layer)
+ end
+
+ if not C.GraphLib_Lines then
+ C.GraphLib_Lines = {}
+ C.GraphLib_Lines_Used = {}
+ end
+
+ local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
+
+ if linetexture then --> this data series texture
+ T:SetTexture(linetexture)
+ elseif C.CustomLine then --> overall chart texture
+ T:SetTexture(C.CustomLine)
+ else --> no texture assigned, use default
+ T:SetTexture(TextureDirectory.."line")
+ end
+
+ tinsert(C.GraphLib_Lines_Used, T)
+
+ T:SetDrawLayer(layer or "ARTWORK")
+
+ T:SetVertexColor(color[1], color[2], color[3], color[4])
+ -- Determine dimensions and center point of line
+ local dx, dy = ex - sx, ey - sy
+ local cx, cy = (sx + ex) / 2, (sy + ey) / 2
+
+ -- Normalize direction if necessary
+ if (dx < 0) then
+ dx, dy = -dx, -dy
+ end
+
+ -- Calculate actual length of line
+ local l = sqrt((dx * dx) + (dy * dy))
+
+ -- Sin and Cosine of rotation, and combination (for later)
+ local s, c = -dy / l, dx / l
+ local sc = s * c
+
+ -- Calculate bounding box size and texture coordinates
+ local Bwid, Bhgt, BLx, BLy, TLx, TLy, TRx, TRy, BRx, BRy
+ if (dy >= 0) then
+ Bwid = ((l * c) - (w * s)) * TAXIROUTE_LINEFACTOR_2
+ Bhgt = ((w * c) - (l * s)) * TAXIROUTE_LINEFACTOR_2
+ BLx, BLy, BRy = (w / l) * sc, s * s, (l / w) * sc
+ BRx, TLx, TLy, TRx = 1 - BLy, BLy, 1 - BRy, 1 - BLx
+ TRy = BRx
+ else
+ Bwid = ((l * c) + (w * s)) * TAXIROUTE_LINEFACTOR_2
+ Bhgt = ((w * c) + (l * s)) * TAXIROUTE_LINEFACTOR_2
+ BLx, BLy, BRx = s * s, -(l / w) * sc, 1 + (w / l) * sc
+ BRy, TLx, TLy, TRy = BLx, 1 - BRx, 1 - BLx, 1 - BLy
+ TRx = TLy
+ end
+
+ -- Thanks Blizzard for adding (-)10000 as a hard-cap and throwing errors!
+ -- The cap was added in 3.1.0 and I think it was upped in 3.1.1
+ -- (way less chance to get the error)
+ if TLx > 10000 then TLx = 10000 elseif TLx < -10000 then TLx = -10000 end
+ if TLy > 10000 then TLy = 10000 elseif TLy < -10000 then TLy = -10000 end
+ if BLx > 10000 then BLx = 10000 elseif BLx < -10000 then BLx = -10000 end
+ if BLy > 10000 then BLy = 10000 elseif BLy < -10000 then BLy = -10000 end
+ if TRx > 10000 then TRx = 10000 elseif TRx < -10000 then TRx = -10000 end
+ if TRy > 10000 then TRy = 10000 elseif TRy < -10000 then TRy = -10000 end
+ if BRx > 10000 then BRx = 10000 elseif BRx < -10000 then BRx = -10000 end
+ if BRy > 10000 then BRy = 10000 elseif BRy < -10000 then BRy = -10000 end
+
+ -- Set texture coordinates and anchors
+ T:ClearAllPoints()
+ T:SetTexCoord(TLx, TLy, BLx, BLy, TRx, TRy, BRx, BRy)
+ T:SetPoint("BOTTOMLEFT", C, relPoint, cx - Bwid, cy - Bhgt)
+ T:SetPoint("TOPRIGHT", C, relPoint, cx + Bwid, cy + Bhgt)
+ T:Show()
+ return T
+end
+
+--Thanks to Celandro
+function lib:DrawVLine(C, x, sy, ey, w, color, layer)
+ local relPoint = "BOTTOMLEFT"
+
+ if not C.GraphLib_Lines then
+ C.GraphLib_Lines = {}
+ C.GraphLib_Lines_Used = {}
+ end
+
+ local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
+ T:SetTexture(TextureDirectory.."sline")
+ tinsert(C.GraphLib_Lines_Used, T)
+
+ T:SetDrawLayer(layer or "ARTWORK")
+
+ T:SetVertexColor(color[1], color[2], color[3], color[4])
+
+ if sy > ey then
+ sy, ey = ey, sy
+ end
+
+ -- Set texture coordinates and anchors
+ T:ClearAllPoints()
+ T:SetTexCoord(1, 0, 0, 0, 1, 1, 0, 1)
+ T:SetPoint("BOTTOMLEFT", C, relPoint, x - w / 2, sy)
+ T:SetPoint("TOPRIGHT", C, relPoint, x + w / 2, ey)
+ T:Show()
+ return T
+end
+
+function lib:DrawHLine(C, sx, ex, y, w, color, layer)
+ local relPoint = "BOTTOMLEFT"
+
+ if not C.GraphLib_Lines then
+ C.GraphLib_Lines = {}
+ C.GraphLib_Lines_Used = {}
+ end
+
+ local T = tremove(C.GraphLib_Lines) or C:CreateTexture(nil, "ARTWORK")
+ T:SetTexture(TextureDirectory.."sline")
+ tinsert(C.GraphLib_Lines_Used, T)
+
+ T:SetDrawLayer(layer or "ARTWORK")
+
+ T:SetVertexColor(color[1], color[2], color[3], color[4])
+
+ if sx > ex then
+ sx, ex = ex, sx
+ end
+
+ -- Set texture coordinates and anchors
+ T:ClearAllPoints()
+ T:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
+ T:SetPoint("BOTTOMLEFT", C, relPoint, sx, y - w / 2)
+ T:SetPoint("TOPRIGHT", C, relPoint, ex, y + w / 2)
+ T:Show()
+ return T
+end
+
+function lib:HideLines(C)
+ if C.GraphLib_Lines then
+ for i = #C.GraphLib_Lines_Used, 1, -1 do
+ C.GraphLib_Lines_Used[i]:Hide()
+ tinsert(C.GraphLib_Lines, tremove(C.GraphLib_Lines_Used))
+ end
+ end
+end
+
+--Two parts to each bar
+function lib:DrawBar(C, sx, sy, ex, ey, color, level)
+ local Bar, Tri, barNum, MinY, MaxY
+
+ --Want sx <= ex if not then flip them
+ if sx > ex then
+ sx, ex = ex, sx
+ sy, ey = ey, sy
+ end
+
+ if not C.GraphLib_Bars then
+ C.GraphLib_Bars = {}
+ C.GraphLib_Tris = {}
+ C.GraphLib_Bars_Used = {}
+ C.GraphLib_Tris_Used = {}
+ C.GraphLib_Frames = {}
+ end
+
+ if (#C.GraphLib_Bars) > 0 then
+ Bar = C.GraphLib_Bars[#C.GraphLib_Bars]
+ tremove(C.GraphLib_Bars, #C.GraphLib_Bars)
+ Bar:Show()
+
+ Tri = C.GraphLib_Tris[#C.GraphLib_Tris]
+ tremove(C.GraphLib_Tris, #C.GraphLib_Tris)
+ Tri:Show()
+ end
+
+ if not Bar then
+ Bar = C:CreateTexture(nil, "ARTWORK")
+ Bar:SetColorTexture(1, 1, 1, 1)
+
+ Tri = C:CreateTexture(nil, "ARTWORK")
+ Tri:SetTexture(TextureDirectory.."triangle")
+ end
+
+ tinsert(C.GraphLib_Bars_Used, Bar)
+ tinsert(C.GraphLib_Tris_Used, Tri)
+
+ if level then
+ if type(C.GraphLib_Frames[level]) == "nil" then
+ local newLevel = C:GetFrameLevel() + level
+ C.GraphLib_Frames[level] = CreateFrame("Frame", nil, C)
+ C.GraphLib_Frames[level]:SetFrameLevel(newLevel)
+ C.GraphLib_Frames[level]:SetAllPoints(C)
+
+ if C.TextFrame and C.TextFrame:GetFrameLevel() <= newLevel then
+ C.TextFrame:SetFrameLevel(newLevel + 1)
+ self.NeedsUpdate = true
+ end
+ end
+
+ Bar:SetParent(C.GraphLib_Frames[level])
+ Tri:SetParent(C.GraphLib_Frames[level])
+ end
+
+ Bar:SetVertexColor(color[1], color[2], color[3], color[4])
+ Tri:SetVertexColor(color[1], color[2], color[3], color[4])
+
+
+ if sy < ey then
+ Tri:SetTexCoord(0, 0, 0, 1, 1, 0, 1, 1)
+ MinY = sy
+ MaxY = ey
+ else
+ Tri:SetTexCoord(1, 0, 1, 1, 0, 0, 0, 1)
+ MinY = ey
+ MaxY = sy
+ end
+
+ --Has to be at least 1 wide
+ if MinY <= 1 then
+ MinY = 1
+ end
+
+
+ Bar:ClearAllPoints()
+ Bar:SetPoint("BOTTOMLEFT", C, "BOTTOMLEFT", sx, 0)
+
+ local Width = ex - sx
+ if Width < 1 then
+ Width = 1
+ end
+ Bar:SetWidth(Width)
+ Bar:SetHeight(MinY)
+
+
+ if (MaxY-MinY) >= 1 then
+ Tri:ClearAllPoints()
+ Tri:SetPoint("BOTTOMLEFT", C, "BOTTOMLEFT", sx, MinY)
+ Tri:SetWidth(Width)
+ Tri:SetHeight(MaxY - MinY)
+ else
+ Tri:Hide()
+ end
+end
+
+
+function lib:HideBars(C)
+ if not C.GraphLib_Bars then
+ return
+ end
+
+ while (#C.GraphLib_Bars_Used) > 0 do
+ C.GraphLib_Bars[#C.GraphLib_Bars + 1] = C.GraphLib_Bars_Used[#C.GraphLib_Bars_Used]
+ C.GraphLib_Bars[#C.GraphLib_Bars]:Hide()
+ C.GraphLib_Bars_Used[#C.GraphLib_Bars_Used] = nil
+
+ C.GraphLib_Tris[#C.GraphLib_Tris + 1] = C.GraphLib_Tris_Used[#C.GraphLib_Tris_Used]
+ C.GraphLib_Tris[#C.GraphLib_Tris]:Hide()
+ C.GraphLib_Tris_Used[#C.GraphLib_Tris_Used] = nil
+ end
+end
+
+-- lib upgrade stuff, overwrite the old function references in
+-- existing graphs with the ones in this newer library
+for _, graph in ipairs(lib.RegisteredGraphRealtime) do
+ SetupGraphRealtimeFunctions(graph, true)
+end
+for _, graph in ipairs(lib.RegisteredGraphLine) do
+ SetupGraphLineFunctions(graph)
+end
+for _, graph in ipairs(lib.RegisteredGraphScatterPlot) do
+ SetupGraphScatterPlotFunctions(graph)
+end
+for _, graph in ipairs(lib.RegisteredGraphPieChart) do
+ SetupGraphPieChartFunctions(graph)
+end
+
+---------------------------------------------------
+--Test Functions, for reference for addon authors to test, use and copy
+--To test the library do /script LibStub("LibGraph-2.0"):TestGraph2Lib()
+local function TestRealtimeGraph()
+ local Graph = LibStub(major)
+ local g = Graph:CreateGraphRealtime("TestRealtimeGraph", UIParent, "CENTER", "CENTER", -90, 90, 150, 150)
+ g:SetAutoScale(true)
+ g:SetGridSpacing(1.0, 10.0)
+ g:SetYMax(120)
+ g:SetXAxis(-11, -1)
+ g:SetFilterRadius(1)
+ g:SetBarColors({0.2, 0.0, 0.0, 0.4}, {1.0, 0.0, 0.0, 1.0})
+
+ local f = CreateFrame("Frame")
+ f:SetScript("OnUpdate", function()
+ g:AddTimeData(1)
+ end)
+ f:Show()
+ DEFAULT_CHAT_FRAME:AddMessage("Testing Realtime Graph")
+end
+
+local function TestRealtimeGraphRaw()
+ local Graph = LibStub(major)
+ local g = Graph:CreateGraphRealtime("TestRealtimeGraph", UIParent, "TOP", "TOP", 0, 0, 150, 150)
+ g:SetAutoScale(true)
+ g:SetGridSpacing(1.0, 10.0)
+ g:SetYMax(120)
+ g:SetXAxis(-10, 0)
+ g:SetMode("RAW")
+ g:SetBarColors({0.2, 0.0, 0.0, 0.4}, {1.0, 0.0, 0.0, 1.0})
+
+ local f = CreateFrame("Frame")
+ f.frames = 0
+ f.NextUpdate = GetTime()
+ f:SetScript("OnUpdate",function()
+ if f.NextUpdate > GetTime() then
+ return
+ end
+
+ g:AddBar(UnitHealth("player"))
+ f.NextUpdate = f.NextUpdate + g.BarWidth
+ end)
+ f:Show()
+ DEFAULT_CHAT_FRAME:AddMessage("Testing 0")
+end
+
+local function TestLineGraph()
+ local Graph = LibStub(major)
+ local g = Graph:CreateGraphLine("TestLineGraph", UIParent, "CENTER", "CENTER", 90, 90, 150, 150)
+ g:SetXAxis(-1, 1)
+ g:SetYAxis(-1, 1)
+ g:SetGridSpacing(0.25, 0.25)
+ g:SetGridColor({0.5, 0.5, 0.5, 0.5})
+ g:SetAxisDrawing(true, true)
+ g:SetAxisColor({1.0, 1.0, 1.0, 1.0})
+ g:SetAutoScale(true)
+
+ local Data1 = {{0.05, 0.05}, {0.2, 0.3}, {0.4, 0.2}, {0.9, 0.6}}
+ local Data2 = {{0.05, 0.8}, {0.3, 0.1}, {0.5, 0.4}, {0.95, 0.05}}
+
+ g:AddDataSeries(Data1,{1.0, 0.0, 0.0, 0.8})
+ g:AddDataSeries(Data2,{0.0, 1.0, 0.0, 0.8})
+ DEFAULT_CHAT_FRAME:AddMessage("Testing Line Graph")
+end
+
+local function TestScatterPlot()
+ local Graph = LibStub(major)
+ local g = Graph:CreateGraphScatterPlot("TestScatterPlot", UIParent, "CENTER", "CENTER", 90, -90, 150, 150)
+ g:SetXAxis(-1, 1)
+ g:SetYAxis(-1, 1)
+ g:SetGridSpacing(0.25, 0.25)
+ g:SetGridColor({0.5, 0.5, 0.5, 0.5})
+ g:SetAxisDrawing(true, true)
+ g:SetAxisColor({1.0, 1.0, 1.0, 1.0})
+ g:SetLinearFit(true)
+ g:SetAutoScale(true)
+
+ local Data1 = {{0.05, 0.05}, {0.2, 0.3}, {0.4, 0.2}, {0.9, 0.6}}
+ local Data2 = {{0.05, 0.8}, {0.3, 0.1}, {0.5, 0.4}, {0.95, 0.05}}
+
+ g:AddDataSeries(Data1,{1.0, 0.0, 0.0, 0.8})
+ g:AddDataSeries(Data2,{0.0, 1.0, 0.0, 0.8})
+ DEFAULT_CHAT_FRAME:AddMessage("Testing Scatter Plot")
+end
+
+local function TestPieChart()
+ local Graph = LibStub(major)
+ local g = Graph:CreateGraphPieChart("TestPieChart", UIParent, "CENTER", "CENTER", -90, -90, 150, 150)
+ g:AddPie(35, {1.0, 0.0, 0.0})
+ g:AddPie(21, {0.0, 1.0, 0.0})
+ g:AddPie(10, {1.0, 1.0, 1.0})
+ g:CompletePie({0.2, 0.2, 1.0})
+ DEFAULT_CHAT_FRAME:AddMessage("Testing Pie Chart")
+end
+
+function lib:TestGraph2Lib()
+ DEFAULT_CHAT_FRAME:AddMessage("Testing "..major..", "..gsub(minor, "%$", ""))
+ TestRealtimeGraph()
+ TestLineGraph()
+ TestScatterPlot()
+ TestPieChart()
+end
diff --git a/Libs/LibGraph-2.0/line.tga b/Libs/LibGraph-2.0/line.tga
new file mode 100644
index 00000000..68fc74da
Binary files /dev/null and b/Libs/LibGraph-2.0/line.tga differ
diff --git a/Libs/LibGraph-2.0/sline.tga b/Libs/LibGraph-2.0/sline.tga
new file mode 100644
index 00000000..cf018cec
Binary files /dev/null and b/Libs/LibGraph-2.0/sline.tga differ
diff --git a/Libs/LibGraph-2.0/triangle.tga b/Libs/LibGraph-2.0/triangle.tga
new file mode 100644
index 00000000..0ccce337
Binary files /dev/null and b/Libs/LibGraph-2.0/triangle.tga differ
diff --git a/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.lua b/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.lua
new file mode 100644
index 00000000..20534f44
--- /dev/null
+++ b/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.lua
@@ -0,0 +1,910 @@
+-- vim: ts=2 sw=2 ai et fenc=utf8
+
+--[[
+-- These events can be registered for using the regular CallbackHandler ways.
+--
+-- "GroupInSpecT_Update", guid, unit, info
+-- "GroupInSpecT_Remove, guid
+-- "GroupInSpecT_InspectReady", guid, unit
+--
+-- Where is a table containing some or all of the following:
+-- .guid
+-- .name
+-- .realm
+-- .race
+-- .race_localized
+-- .class
+-- .class_localized
+-- .class_id
+-- .gender -- 2 = male, 3 = female
+-- .global_spec_id
+-- .spec_index
+-- .spec_name_localized
+-- .spec_description
+-- .spec_icon
+-- .spec_background
+-- .spec_role
+-- .spec_role_detailed
+-- .spec_group -- active spec group (1/2/nil)
+-- .talents = {
+-- [] = {
+-- .tier
+-- .column
+-- .name_localized
+-- .icon
+-- .talent_id
+-- .spell_id
+-- }
+-- ...
+-- }
+-- .lku -- last known unit id
+-- .not_visible
+--
+-- Functions for external use:
+--
+-- lib:Rescan (guid or nil)
+-- Force a rescan of the given group member GUID, or of all current group members if nil.
+--
+-- lib:QueuedInspections ()
+-- Returns an array of GUIDs of outstanding inspects.
+--
+-- lib:StaleInspections ()
+-- Returns an array of GUIDs for which the data has become stale and is
+-- awaiting an update (no action required, the refresh happens internally).
+-- Due to Blizzard exposing no events on (re/un)talent, there will be
+-- frequent marking of inspect data as being stale.
+--
+-- lib:GetCachedInfo (guid)
+-- Returns the cached info for the given GUID, if available, nil otherwise.
+-- Information is cached for current group members only.
+--
+-- lib:GroupUnits ()
+-- Returns an array with the set of unit ids for the current group.
+--]]
+
+local MAJOR, MINOR = "LibGroupInSpecT-1.1", tonumber (("$Revision: 89 $"):match ("(%d+)") or 0)
+
+if not LibStub then error(MAJOR.." requires LibStub") end
+local lib = LibStub:NewLibrary (MAJOR, MINOR)
+if not lib then return end
+
+lib.events = lib.events or LibStub ("CallbackHandler-1.0"):New (lib)
+if not lib.events then error(MAJOR.." requires CallbackHandler") end
+
+
+local UPDATE_EVENT = "GroupInSpecT_Update"
+local REMOVE_EVENT = "GroupInSpecT_Remove"
+local INSPECT_READY_EVENT = "GroupInSpecT_InspectReady"
+local QUEUE_EVENT = "GroupInSpecT_QueueChanged"
+
+local COMMS_PREFIX = "LGIST11"
+local COMMS_FMT = "1"
+local COMMS_DELIM = "\a"
+
+local INSPECT_DELAY = 1.5
+local INSPECT_TIMEOUT = 10 -- If we get no notification within 10s, give up on unit
+
+local MAX_ATTEMPTS = 2
+
+--@debug@
+lib.debug = false
+local function debug (...)
+ if lib.debug then -- allow programmatic override of debug output by client addons
+ print (...)
+ end
+end
+--@end-debug@
+
+function lib.events:OnUsed(target, eventname)
+ if eventname == INSPECT_READY_EVENT then
+ target.inspect_ready_used = true
+ end
+end
+
+function lib.events:OnUnused(target, eventname)
+ if eventname == INSPECT_READY_EVENT then
+ target.inspect_ready_used = nil
+ end
+end
+
+-- Frame for events
+local frame = _G[MAJOR .. "_Frame"] or CreateFrame ("Frame", MAJOR .. "_Frame")
+lib.frame = frame
+frame:Hide()
+frame:UnregisterAllEvents ()
+frame:RegisterEvent ("PLAYER_LOGIN")
+frame:RegisterEvent ("PLAYER_LOGOUT")
+if not frame.OnEvent then
+ frame.OnEvent = function(this, event, ...)
+ local eventhandler = lib[event]
+ return eventhandler and eventhandler (lib, ...)
+ end
+ frame:SetScript ("OnEvent", frame.OnEvent)
+end
+
+
+-- Hide our run-state in an easy-to-dump object
+lib.state = {
+ mainq = {}, staleq = {}, -- inspect queues
+ t = 0,
+ last_inspect = 0,
+ current_guid = nil,
+ throttle = 0,
+ tt = 0,
+ debounce_send_update = 0,
+}
+lib.cache = {}
+lib.static_cache = {}
+
+
+-- Note: if we cache NotifyInspect, we have to hook before we cache it!
+if not lib.hooked then
+ hooksecurefunc("NotifyInspect", function (...) return lib:NotifyInspect (...) end)
+ lib.hooked = true
+end
+function lib:NotifyInspect(unit)
+ self.state.last_inspect = GetTime()
+end
+
+
+-- Get local handles on the key API functions
+local CanInspect = _G.CanInspect
+local ClearInspectPlayer = _G.ClearInspectPlayer
+local GetClassInfo = _G.GetClassInfo
+local GetNumSubgroupMembers = _G.GetNumSubgroupMembers
+local GetNumSpecializationsForClassID = _G.GetNumSpecializationsForClassID
+local GetPlayerInfoByGUID = _G.GetPlayerInfoByGUID
+local GetInspectSpecialization = _G.GetInspectSpecialization
+local GetSpecialization = _G.GetSpecialization
+local GetSpecializationInfo = _G.GetSpecializationInfo
+local GetSpecializationInfoForClassID = _G.GetSpecializationInfoForClassID
+local GetSpecializationRoleByID = _G.GetSpecializationRoleByID
+local GetSpellInfo = _G.GetSpellInfo
+local GetTalentInfo = _G.GetTalentInfo
+local IsInRaid = _G.IsInRaid
+--local NotifyInspect = _G.NotifyInspect -- Don't cache, as to avoid missing future hooks
+local GetNumClasses = _G.GetNumClasses
+local UnitExists = _G.UnitExists
+local UnitGUID = _G.UnitGUID
+local UnitInParty = _G.UnitInParty
+local UnitInRaid = _G.UnitInRaid
+local UnitIsConnected = _G.UnitIsConnected
+local UnitIsPlayer = _G.UnitIsPlayer
+local UnitIsUnit = _G.UnitIsUnit
+local UnitName = _G.UnitName
+
+
+local global_spec_id_roles_detailed = {
+ -- Death Knight
+ [250] = "tank", -- Blood
+ [251] = "melee", -- Frost
+ [252] = "melee", -- Unholy
+ -- Demon Hunter
+ [577] = "melee", -- Havoc
+ [581] = "tank", -- Vengeance
+ -- Druid
+ [102] = "ranged", -- Balance
+ [103] = "melee", -- Feral
+ [104] = "tank", -- Guardian
+ [105] = "healer", -- Restoration
+ -- Hunter
+ [253] = "ranged", -- Beast Mastery
+ [254] = "ranged", -- Marksmanship
+ [255] = "melee", -- Survival
+ -- Mage
+ [62] = "ranged", -- Arcane
+ [63] = "ranged", -- Fire
+ [64] = "ranged", -- Frost
+ -- Monk
+ [268] = "tank", -- Brewmaster
+ [269] = "melee", -- Windwalker
+ [270] = "healer", -- Mistweaver
+ -- Paladin
+ [65] = "healer", -- Holy
+ [66] = "tank", -- Protection
+ [70] = "melee", -- Retribution
+ -- Priest
+ [256] = "healer", -- Discipline
+ [257] = "healer", -- Holy
+ [258] = "ranged", -- Shadow
+ -- Rogue
+ [259] = "melee", -- Assassination
+ [260] = "melee", -- Combat
+ [261] = "melee", -- Subtlety
+ -- Shaman
+ [262] = "ranged", -- Elemental
+ [263] = "melee", -- Enhancement
+ [264] = "healer", -- Restoration
+ -- Warlock
+ [265] = "ranged", -- Affliction
+ [266] = "ranged", -- Demonology
+ [267] = "ranged", -- Destruction
+ -- Warrior
+ [71] = "melee", -- Arms
+ [72] = "melee", -- Fury
+ [73] = "tank", -- Protection
+}
+
+local class_fixed_roles = {
+ HUNTER = "DAMAGER",
+ MAGE = "DAMAGER",
+ ROGUE = "DAMAGER",
+ WARLOCK = "DAMAGER",
+}
+
+local class_fixed_roles_detailed = {
+ MAGE = "ranged",
+ ROGUE = "melee",
+ WARLOCK = "ranged",
+}
+
+-- Inspects only work after being fully logged in, so track that
+function lib:PLAYER_LOGIN ()
+ self.state.logged_in = true
+
+ self:CacheGameData ()
+
+ frame:RegisterEvent ("INSPECT_READY")
+ frame:RegisterEvent ("GROUP_ROSTER_UPDATE")
+ frame:RegisterEvent ("PLAYER_ENTERING_WORLD")
+ frame:RegisterEvent ("UNIT_LEVEL")
+ frame:RegisterEvent ("PLAYER_TALENT_UPDATE")
+ frame:RegisterEvent ("PLAYER_SPECIALIZATION_CHANGED")
+ frame:RegisterEvent ("UNIT_SPELLCAST_SUCCEEDED")
+ frame:RegisterEvent ("UNIT_NAME_UPDATE")
+ frame:RegisterEvent ("UNIT_AURA")
+ frame:RegisterEvent ("CHAT_MSG_ADDON")
+ RegisterAddonMessagePrefix (COMMS_PREFIX)
+
+ local guid = UnitGUID ("player")
+ local info = self:BuildInfo ("player")
+ self.events:Fire (UPDATE_EVENT, guid, "player", info)
+end
+
+function lib:PLAYER_LOGOUT ()
+ self.state.logged_in = false
+end
+
+
+-- Simple timer
+do
+ lib.state.t = 0
+ if not frame.OnUpdate then -- ticket #4 if the OnUpdate code every changes we should stop borrowing the existing handler
+ frame.OnUpdate = function(this, elapsed)
+ lib.state.t = lib.state.t + elapsed
+ lib.state.tt = lib.state.tt + elapsed
+ if lib.state.t > INSPECT_DELAY then
+ lib:ProcessQueues ()
+ lib.state.t = 0
+ end
+ -- Unthrottle, essentially allowing 1 msg every 3 seconds, but with substantial burst capacity
+ if lib.state.tt > 3 and lib.state.throttle > 0 then
+ lib.state.throttle = lib.state.throttle - 1
+ lib.state.tt = 0
+ end
+ if lib.state.debounce_send_update > 0 then
+ local debounce = lib.state.debounce_send_update - elapsed
+ lib.state.debounce_send_update = debounce
+ if debounce <= 0 then lib:SendLatestSpecData () end
+ end
+ end
+ frame:SetScript("OnUpdate", frame.OnUpdate) -- this is good regardless of the handler check above because otherwise a new anonymous function is created every time the OnUpdate code runs
+ end
+end
+
+
+-- Internal library functions
+
+-- Caches to deal with API shortcomings as well as performance
+lib.static_cache.global_specs = {} -- [gspec] -> { .idx, .name_localized, .description, .icon, .background, .role }
+lib.static_cache.class_to_class_id = {} -- [CLASS] -> class_id
+
+-- The talents cache can no longer be pre-fetched on login, but is now constructed class-by-class as we inspect people.
+-- This probably means we want to only ever access it through the GetCachedTalentInfo() helper function below.
+lib.static_cache.talents = {} -- [talent_id] -> { .spell_id, .talent_id, .name_localized, .icon, .tier, .column }
+
+-- Dridzt: I'd love another way but none of the GetTalent* functions return spellID, GetTalentLink() and parsing the link gives talentID that's not related to spellID as well
+-- A quick tooltip scan is cheap though so elegance aside this is a good workaround considering this only runs once
+local tip = CreateFrame ("GameTooltip", MAJOR.."ScanTip", nil, "GameTooltipTemplate")
+tip:SetOwner (UIParent, "ANCHOR_NONE")
+
+function lib:GetCachedTalentInfo (class_id, tier, col, group, is_inspect, unit)
+ local talents = self.static_cache.talents
+ local talent_id, name, icon, sel, avail = GetTalentInfo (tier, col, group, is_inspect, unit)
+ if not talent_id or not class_id then
+ --@debug@
+ debug ("GetCachedTalentInfo("..tostring(class_id)..","..tier..","..col..","..group..","..tostring(is_inspect)..","..tostring(unit)..") returned nil") --@end-debug@
+ return {}
+ end
+ talents[class_id] = talents[class_id] or {}
+ local class_talents = talents[class_id]
+ if not class_talents[talent_id] then
+ tip:ClearLines ()
+ tip:SetTalent (talent_id, is_inspect, group)
+ local _, _,spell_id = tip:GetSpell ()
+ class_talents[talent_id] = {
+ spell_id = spell_id,
+ talent_id = talent_id,
+ name_localized = name,
+ icon = icon,
+ tier = tier,
+ column = col,
+ }
+ end
+ return class_talents[talent_id], sel
+end
+
+
+function lib:CacheGameData ()
+ local gspecs = self.static_cache.global_specs
+ gspecs[0] = {} -- Handle no-specialization case
+ for class_id = 1, GetNumClasses () do
+ for idx = 1, GetNumSpecializationsForClassID (class_id) do
+ local gspec_id, name, description, icon, background = GetSpecializationInfoForClassID (class_id, idx)
+ gspecs[gspec_id] = {}
+ local gspec = gspecs[gspec_id]
+ gspec.idx = idx
+ gspec.name_localized = name
+ gspec.description = description
+ gspec.icon = icon
+ gspec.background = background
+ gspec.role = GetSpecializationRoleByID (gspec_id)
+ end
+
+ local _, class = GetClassInfo (class_id)
+ self.static_cache.class_to_class_id[class] = class_id
+ end
+end
+
+
+function lib:GuidToUnit (guid)
+ local info = self.cache[guid]
+ if info and info.lku and UnitGUID (info.lku) == guid then return info.lku end
+
+ for i,unit in ipairs (self:GroupUnits ()) do
+ if UnitExists (unit) and UnitGUID (unit) == guid then
+ if info then info.lku = unit end
+ return unit
+ end
+ end
+end
+
+
+function lib:Query (unit)
+ if not UnitIsPlayer (unit) then return end -- NPC
+
+ if UnitIsUnit (unit, "player") then
+ self.events:Fire (UPDATE_EVENT, UnitGUID("player"), "player", self:BuildInfo ("player"))
+ return
+ end
+
+ local mainq, staleq = self.state.mainq, self.state.staleq
+
+ local guid = UnitGUID (unit)
+ if not mainq[guid] then
+ mainq[guid] = 1
+ staleq[guid] = nil
+ self.frame:Show () -- Start timer if not already running
+ self.events:Fire (QUEUE_EVENT)
+ end
+end
+
+
+function lib:Refresh (unit)
+ local guid = UnitGUID (unit)
+ if not guid then return end
+ --@debug@
+ debug ("Refreshing "..unit) --@end-debug@
+ if not self.state.mainq[guid] then
+ self.state.staleq[guid] = 1
+ self.frame:Show ()
+ self.events:Fire (QUEUE_EVENT)
+ end
+end
+
+
+function lib:ProcessQueues ()
+ if not self.state.logged_in then return end
+ if InCombatLockdown () then return end -- Never inspect while in combat
+ if UnitIsDead ("player") then return end -- You can't inspect while dead, so don't even try
+ if InspectFrame and InspectFrame:IsShown () then return end -- Don't mess with the UI's inspections
+
+ local mainq = self.state.mainq
+ local staleq = self.state.staleq
+
+ if not next (mainq) and next(staleq) then
+ --@debug@
+ debug ("Main queue empty, swapping main and stale queues") --@end-debug@
+ self.state.mainq, self.state.staleq = self.state.staleq, self.state.mainq
+ mainq, staleq = staleq, mainq
+ end
+
+ if (self.state.last_inspect + INSPECT_TIMEOUT) < GetTime () then
+ -- If there was an inspect going, it's timed out, so either retry or move it to stale queue
+ local guid = self.state.current_guid
+ if guid then
+ --@debug@
+ debug ("Inspect timed out for "..guid) --@end-debug@
+
+ local count = mainq and mainq[guid] or (MAX_ATTEMPTS + 1)
+ if not self:GuidToUnit (guid) then
+ --@debug@
+ debug ("No longer applicable, removing from queues") --@end-debug@
+ mainq[guid], staleq[guid] = nil, nil
+ elseif count > MAX_ATTEMPTS then
+ --@debug@
+ debug ("Excessive retries, moving to stale queue") --@end-debug@
+ mainq[guid], staleq[guid] = nil, 1
+ else
+ mainq[guid] = count + 1
+ end
+ self.state.current_guid = nil
+ end
+ end
+
+ if self.state.current_guid then return end -- Still waiting on our inspect data
+
+ for guid,count in pairs (mainq) do
+ local unit = self:GuidToUnit (guid)
+ if not unit then
+ --@debug@
+ debug ("No longer applicable, removing from queues") --@end-debug@
+ mainq[guid], staleq[guid] = nil, nil
+ elseif not CanInspect (unit) or not UnitIsConnected (unit) then
+ --@debug@
+ debug ("Cannot inspect "..unit..", aka "..(UnitName(unit) or "nil")..", moving to stale queue") --@end-debug@
+ mainq[guid], staleq[guid] = nil, 1
+ else
+ --@debug@
+ debug ("Inspecting "..unit..", aka "..(UnitName(unit) or "nil")) --@end-debug@
+ mainq[guid] = count + 1
+ self.state.current_guid = guid
+ NotifyInspect (unit)
+ break
+ end
+ end
+
+ if not next (mainq) and not next (staleq) and self.state.throttle == 0 and self.state.debounce_send_update <= 0 then
+ frame:Hide() -- Cancel timer, nothing queued and no unthrottling to be done
+ end
+ self.events:Fire (QUEUE_EVENT)
+end
+
+
+function lib:UpdatePlayerInfo (guid, unit, info)
+ info.class_localized, info.class, info.race_localized, info.race, info.gender, info.name, info.realm = GetPlayerInfoByGUID (guid)
+ local class = info.class
+ if info.realm and info.realm == "" then info.realm = nil end
+ info.class_id = class and self.static_cache.class_to_class_id[class]
+ if not info.spec_role then info.spec_role = class and class_fixed_roles[class] end
+ if not info.spec_role_detailed then info.spec_role_detailed = class and class_fixed_roles_detailed[class] end
+ info.lku = unit
+end
+
+
+function lib:BuildInfo (unit)
+ local guid = UnitGUID (unit)
+ if not guid then return end
+
+ local cache = self.cache
+ local info = cache[guid] or {}
+ cache[guid] = info
+ info.guid = guid
+
+ self:UpdatePlayerInfo (guid, unit, info)
+ -- On a cold login, GetPlayerInfoByGUID() doesn't seem to be usable, so mark as stale
+ local class = info.class
+ if not class and not self.state.mainq[guid] then
+ self.state.staleq[guid] = 1
+ self.frame:Show ()
+ self.events:Fire (QUEUE_EVENT)
+ end
+
+ local is_inspect = not UnitIsUnit (unit, "player")
+ local spec = GetSpecialization ()
+ info.global_spec_id = is_inspect and GetInspectSpecialization (unit) or spec and GetSpecializationInfo (spec)
+
+ local gspecs = self.static_cache.global_specs
+ if not info.global_spec_id or not gspecs[info.global_spec_id] then -- not a valid spec_id
+ info.global_spec_id = nil
+ else
+ local gspec_id = info.global_spec_id
+ local spec_info = gspecs[gspec_id]
+ info.spec_index = spec_info.idx
+ info.spec_name_localized = spec_info.name_localized
+ info.spec_description = spec_info.description
+ info.spec_icon = spec_info.icon
+ info.spec_background = spec_info.background
+ info.spec_role = spec_info.role
+ info.spec_role_detailed = global_spec_id_roles_detailed[gspec_id]
+ end
+
+ if not info.spec_role then info.spec_role = class and class_fixed_roles[class] end
+ if not info.spec_role_detailed then info.spec_role_detailed = class and class_fixed_roles_detailed[class] end
+
+ info.talents = info.talents or {}
+
+ -- If GetPlayerInfoByGUID didn't return the class, we can't do talents yet
+ if info.class_id then
+ info.spec_group = GetActiveSpecGroup (is_inspect)
+ wipe (info.talents) -- Due to spec-specific talents we might leave things in on a spec-change otherwise
+ for tier = 1, MAX_TALENT_TIERS do
+ for col = 1, NUM_TALENT_COLUMNS do
+ local talent, sel = self:GetCachedTalentInfo (info.class_id, tier, col, info.spec_group, is_inspect, unit)
+ if talent and talent.talent_id and sel then
+ info.talents[talent.talent_id] = talent
+ end
+ end
+ end
+ end
+
+ info.glyphs = wipe (info.glyphs or {}) -- kept for addons that still refer to this
+
+ if is_inspect and not UnitIsVisible (unit) and UnitIsConnected (unit) then info.not_visible = true end
+
+ return info
+end
+
+
+function lib:INSPECT_READY (guid)
+ local unit = self:GuidToUnit (guid)
+ local finalize = false
+ if unit then
+ if guid == self.state.current_guid then
+ self.state.current_guid = nil -- Got what we asked for
+ finalize = true
+ --@debug@
+ debug ("Got inspection data for requested guid "..guid) --@end-debug@
+ end
+
+ local mainq, staleq = self.state.mainq, self.state.staleq
+ mainq[guid], staleq[guid] = nil, nil
+
+ local gspec_id = GetInspectSpecialization (unit)
+ if not self.static_cache.global_specs[gspec_id] then -- Bah, got garbage, flag as stale and try again
+ staleq[guid] = 1
+ return
+ end
+
+ self.events:Fire (UPDATE_EVENT, guid, unit, self:BuildInfo (unit))
+ self.events:Fire (INSPECT_READY_EVENT, guid, unit)
+ end
+ if finalize then
+ ClearInspectPlayer ()
+ end
+ self.events:Fire (QUEUE_EVENT)
+end
+
+
+function lib:PLAYER_ENTERING_WORLD ()
+ if self.commScope == "INSTANCE_CHAT" then
+ -- Handle moving directly from one LFG to another
+ self.commScope = nil
+ self:UpdateCommScope ()
+ end
+end
+
+
+-- Group handling parts
+
+local members = {}
+function lib:GROUP_ROSTER_UPDATE ()
+ local group = self.cache
+ local units = self:GroupUnits ()
+ -- Find new members
+ for i,unit in ipairs (self:GroupUnits ()) do
+ local guid = UnitGUID (unit)
+ if guid then
+ members[guid] = true
+ if not group[guid] then
+ self:Query (unit)
+ -- Update with what we have so far (guid, unit, name/class/race?)
+ self.events:Fire (UPDATE_EVENT, guid, unit, self:BuildInfo (unit))
+ end
+ end
+ end
+ -- Find removed members
+ for guid in pairs (group) do
+ if not members[guid] then
+ group[guid] = nil
+ self.events:Fire (REMOVE_EVENT, guid, nil)
+ end
+ end
+ wipe (members)
+ self:UpdateCommScope ()
+end
+
+
+function lib:DoPlayerUpdate ()
+ self:Query ("player")
+ self.state.debounce_send_update = 2.5 -- Hold off 2.5sec before sending update
+ self.frame:Show ()
+end
+
+
+function lib:SendLatestSpecData ()
+ local scope = self.commScope
+ if not scope then return end
+
+ local guid = UnitGUID ("player")
+ local info = self.cache[guid]
+ if not info then return end
+
+ -- fmt, guid, global_spec_id, talent1 -> MAX_TALENT_TIERS
+ -- sequentially, allow no gaps for missing talents we decode by index on the receiving end.
+ local datastr = COMMS_FMT..COMMS_DELIM..guid..COMMS_DELIM..(info.global_spec_id or 0)
+ local talentCount = 1
+ for k in pairs(info.talents) do
+ datastr = datastr..COMMS_DELIM..k
+ talentCount = talentCount + 1
+ end
+ for i=talentCount,MAX_TALENT_TIERS do
+ datastr = datastr..COMMS_DELIM..0
+ end
+
+ --@debug@
+ debug ("Sending LGIST update to "..scope) --@end-debug@
+ SendAddonMessage(COMMS_PREFIX, datastr, scope)
+end
+
+
+function lib:UpdateCommScope ()
+ local scope = (IsInGroup (LE_PARTY_CATEGORY_INSTANCE) and "INSTANCE_CHAT") or (IsInRaid () and "RAID") or (IsInGroup (LE_PARTY_CATEGORY_HOME) and "PARTY")
+ if self.commScope ~= scope then
+ self.commScope = scope
+ self:DoPlayerUpdate ()
+ end
+end
+
+
+-- Indicies for various parts of the split data msg
+local msg_idx = {}
+msg_idx.fmt = 1
+msg_idx.guid = msg_idx.fmt + 1
+msg_idx.global_spec_id = msg_idx.guid + 1
+msg_idx.talents = msg_idx.global_spec_id + 1
+msg_idx.end_talents = msg_idx.talents + MAX_TALENT_TIERS - 1
+
+function lib:CHAT_MSG_ADDON (prefix, datastr, scope, sender)
+ if prefix ~= COMMS_PREFIX or scope ~= self.commScope then return end
+ --@debug@
+ debug ("Incoming LGIST update from "..(scope or "nil").."/"..(sender or "nil")..": "..(datastr:gsub(COMMS_DELIM,";") or "nil")) --@end-debug@
+
+ local data = { strsplit (COMMS_DELIM,datastr) }
+ local fmt = data[msg_idx.fmt]
+ if fmt ~= COMMS_FMT then return end -- Unknown format, ignore
+
+ local guid = data[msg_idx.guid]
+
+ local senderguid = UnitGUID(sender)
+ if senderguid and senderguid ~= guid then return end
+
+ local info = guid and self.cache[guid]
+ if not info then return end -- Never allow random message to create new group member entries!
+
+ local unit = self:GuidToUnit (guid)
+ if not unit then return end
+ if UnitIsUnit (unit, "player") then return end -- we're already up-to-date, comment out for solo debugging
+
+ self.state.throttle = self.state.throttle + 1
+ self.frame:Show () -- Ensure we're unthrottling
+ if self.state.throttle > 40 then return end -- If we ever hit this, someone's being "funny"
+
+ info.class_localized, info.class, info.race_localized, info.race, info.gender, info.name, info.realm = GetPlayerInfoByGUID (guid)
+ if info.realm and info.realm == "" then info.realm = nil end
+ info.class_id = self.static_cache.class_to_class_id[info.class]
+
+ local gspecs = self.static_cache.global_specs
+
+ local gspec_id = data[msg_idx.global_spec_id] and tonumber (data[msg_idx.global_spec_id])
+ if not gspec_id or not gspecs[gspec_id] then return end -- Malformed message, avoid throwing errors by using this nil
+
+ info.global_spec_id = gspec_id
+ info.spec_index = gspecs[gspec_id].idx
+ info.spec_name_localized = gspecs[gspec_id].name_localized
+ info.spec_description = gspecs[gspec_id].description
+ info.spec_icon = gspecs[gspec_id].icon
+ info.spec_background = gspecs[gspec_id].background
+ info.spec_role = gspecs[gspec_id].role
+ info.spec_role_detailed = global_spec_id_roles_detailed[gspec_id]
+
+ local need_inspect = nil
+ info.talents = wipe (info.talents or {})
+ local talents = self.static_cache.talents[info.class_id]
+ if talents then -- The group entry is created before we have inspect-data, so may not have cached talents yet
+ for i = msg_idx.talents, msg_idx.end_talents do
+ local talent_id = tonumber (data[i])
+ if talent_id and talent_id > 0 then
+ if talents[talent_id] then
+ info.talents[talent_id] = talents[talent_id]
+ else
+ -- While we had some talents for this class, we apparently didn't have all for this particular spec, so mark for inspect
+ need_inspect = 1
+ end
+ end
+ end
+ else
+ -- Talents weren't pre-cached, so mark for inspect
+ need_inspect = 1
+ end
+
+ info.glyphs = wipe (info.glyphs or {}) -- kept for addons that still refer to this
+
+ local mainq, staleq = self.state.mainq, self.state.staleq
+ local want_inspect = not need_inspect and self.inspect_ready_used and (mainq[guid] or staleq[guid]) and 1 or nil
+ mainq[guid], staleq[guid] = need_inspect, want_inspect
+ if need_inspect or want_inspect then self.frame:Show () end
+
+ --@debug@
+ debug ("Firing LGIST update event for unit "..unit..", GUID "..guid) --@end-debug@
+ self.events:Fire (UPDATE_EVENT, guid, unit, info)
+ self.events:Fire (QUEUE_EVENT)
+end
+
+
+function lib:UNIT_LEVEL (unit)
+ if UnitInRaid (unit) or UnitInParty (unit) then
+ self:Refresh (unit)
+ end
+ if UnitIsUnit (unit, "player") then
+ self:DoPlayerUpdate ()
+ end
+end
+
+
+function lib:PLAYER_TALENT_UPDATE ()
+ self:DoPlayerUpdate ()
+end
+
+
+function lib:PLAYER_SPECIALIZATION_CHANGED (unit)
+-- This event seems to fire a lot, and for no particular reason *sigh*
+-- if UnitInRaid (unit) or UnitInParty (unit) then
+-- self:Refresh (unit)
+-- end
+ if unit and UnitIsUnit (unit, "player") then
+ self:DoPlayerUpdate ()
+ end
+end
+
+
+function lib:UNIT_NAME_UPDATE (unit)
+ local group = self.cache
+ local guid = UnitGUID (unit)
+ local info = guid and group[guid]
+ if info then
+ self:UpdatePlayerInfo (guid, unit, info)
+ if info.name ~= UNKNOWN then
+ self.events:Fire (UPDATE_EVENT, guid, unit, info)
+ end
+ end
+end
+
+
+-- Always get a UNIT_AURA when a unit's UnitIsVisible() changes
+function lib:UNIT_AURA (unit)
+ local group = self.cache
+ local guid = UnitGUID (unit)
+ local info = guid and group[guid]
+ if info then
+ if not UnitIsUnit (unit, "player") then
+ if UnitIsVisible (unit) then
+ if info.not_visible then
+ info.not_visible = nil
+ --@debug@
+ debug (unit..", aka "..(UnitName(unit) or "nil")..", is now visible") --@end-debug@
+ if not self.state.mainq[guid] then
+ self.state.staleq[guid] = 1
+ self.frame:Show ()
+ self.events:Fire (QUEUE_EVENT)
+ end
+ end
+ elseif UnitIsConnected (unit) then
+ --@debug@
+ if not info.not_visible then
+ debug (unit..", aka "..(UnitName(unit) or "nil")..", is no longer visible")
+ end
+ --@end-debug@
+ info.not_visible = true
+ end
+ end
+ end
+end
+
+
+function lib:UNIT_SPELLCAST_SUCCEEDED (unit, spellname, rank, lineid, spellid)
+ if spellid == 200749 then
+ self:Query (unit) -- Definitely changed, so high prio refresh
+ end
+end
+
+
+-- External library functions
+
+function lib:QueuedInspections ()
+ local q = {}
+ for guid in pairs (self.state.mainq) do
+ table.insert (q, guid)
+ end
+ return q
+end
+
+
+function lib:StaleInspections ()
+ local q = {}
+ for guid in pairs (self.state.staleq) do
+ table.insert (q, guid)
+ end
+ return q
+end
+
+
+function lib:IsInspectQueued (guid)
+ return guid and ((self.state.mainq[guid] or self.state.staleq[guid]) and true)
+end
+
+
+function lib:GetCachedInfo (guid)
+ local group = self.cache
+ return guid and group[guid]
+end
+
+
+function lib:Rescan (guid)
+ local mainq, staleq = self.state.mainq, self.state.staleq
+ if guid then
+ local unit = self:GuidToUnit (guid)
+ if unit then
+ if UnitIsUnit (unit, "player") then
+ self.events:Fire (UPDATE_EVENT, guid, "player", self:BuildInfo ("player"))
+ elseif not mainq[guid] then
+ staleq[guid] = 1
+ end
+ end
+ else
+ for i,unit in ipairs (self:GroupUnits ()) do
+ if UnitExists (unit) then
+ if UnitIsUnit (unit, "player") then
+ self.events:Fire (UPDATE_EVENT, UnitGUID("player"), "player", self:BuildInfo ("player"))
+ else
+ local guid = UnitGUID (unit)
+ if guid and not mainq[guid] then
+ staleq[guid] = 1
+ end
+ end
+ end
+ end
+ end
+ self.frame:Show () -- Start timer if not already running
+
+ -- Evict any stale entries
+ self:GROUP_ROSTER_UPDATE ()
+ self.events:Fire (QUEUE_EVENT)
+end
+
+
+local unitstrings = {
+ raid = { "player" }, -- This seems to be needed under certain circumstances. Odd.
+ party = { "player" }, -- Player not part of partyN
+ player = { "player" }
+}
+for i = 1,40 do table.insert (unitstrings.raid, "raid"..i) end
+for i = 1,4 do table.insert (unitstrings.party, "party"..i) end
+
+
+-- Returns an array with the set of unit ids for the current group
+function lib:GroupUnits ()
+ local units
+ if IsInRaid () then
+ units = unitstrings.raid
+ elseif GetNumSubgroupMembers () > 0 then
+ units = unitstrings.party
+ else
+ units = unitstrings.player
+ end
+ return units
+end
+
+
+-- If demand-loaded, we need to synthesize a login event
+if IsLoggedIn () then lib:PLAYER_LOGIN () end
diff --git a/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.toc b/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.toc
new file mode 100644
index 00000000..63aa683c
--- /dev/null
+++ b/Libs/LibGroupInSpecT-1.1/LibGroupInSpecT-1.1.toc
@@ -0,0 +1,11 @@
+## Interface: 70200
+## Title: Lib: GroupInSpecT-1.1
+## Notes: Keeps track of group members and keeps an up-to-date cache of their specialization and talents.
+## Version: @project-version@
+## Author: Anyia of HordeYakka (Jubei'Thos)
+## X-Category: Library
+
+Libs\LibStub\LibStub.lua
+Libs\CallbackHandler-1.0\CallbackHandler-1.0.lua
+
+lib.xml
diff --git a/Libs/LibGroupInSpecT-1.1/lib.xml b/Libs/LibGroupInSpecT-1.1/lib.xml
new file mode 100644
index 00000000..898d4cc5
--- /dev/null
+++ b/Libs/LibGroupInSpecT-1.1/lib.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Libs/LibItemUpgradeInfo-1.0/CHANGES.txt b/Libs/LibItemUpgradeInfo-1.0/CHANGES.txt
new file mode 100644
index 00000000..094ccd29
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/CHANGES.txt
@@ -0,0 +1,18 @@
+tag 5876ed6c59ef3d2568d88b9bba9ec77682e6074e Release-70200-28
+Author: Alar of Runetotem
+Date: Tue Mar 28 12:21:53 2017 +0200
+
+Toc updated to 70200
+
+commit 7c5485ab35700b2e38a008feb84e4af34b7396f0
+Author: Alar of Runetotem
+Date: Tue Mar 28 12:07:53 2017 +0200
+
+ Updated TOC to 70200
+
+commit c4ed50190aad123c1297705da7f38c86f7ab1d10
+Author: Alar of Runetotem
+Date: Tue Mar 28 11:30:56 2017 +0200
+
+ toc update
+
diff --git a/Libs/LibItemUpgradeInfo-1.0/Core.lua b/Libs/LibItemUpgradeInfo-1.0/Core.lua
new file mode 100644
index 00000000..29aa7209
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/Core.lua
@@ -0,0 +1,654 @@
+local MAJOR, MINOR = "LibItemUpgradeInfo-1.0", 28
+local type,tonumber,select,strsplit,GetItemInfoFromHyperlink=type,tonumber,select,strsplit,GetItemInfoFromHyperlink
+local library,previous = _G.LibStub:NewLibrary(MAJOR, MINOR)
+local lib=library --#lib Needed to keep Eclipse LDT happy
+if not lib then return end
+local pp=print
+--[===[@debug@
+LoadAddOn("Blizzard_DebugTools")
+LoadAddOn("LibDebug")
+if LibDebug then LibDebug() end
+--@end-debug@]===]
+--@non-debug@
+local print=function() end
+--@end-non-debug@
+--[[
+Caching system
+1 itemName String The name of the item.
+2 itemLink String The item link of the item.
+3 itemRarity Number The quality of the item. The value is 0 to 7, which represents Poor to Heirloom. This appears to include gains from upgrades/bonuses.
+4 itemLevel Number The item level of this item, not including item levels gained from upgrades. There is currently no API to get the item level including upgrades/bonuses.
+5 itemMinLevel Number The minimum level required to use the item, 0 meaning no level requirement.
+6 itemType String The type of the item: Armor, Weapon, Quest, Key, etc. (localized)
+7 itemSubType String The sub-type of the item: Enchanting, Cloth, Sword, etc. See itemType. (localized)
+8 itemStackCount Number How many of the item per stack: 20 for Runecloth, 1 for weapon, 100 for Alterac Ram Hide, etc.
+9 itemEquipLoc String The type of inventory equipment location in which the item may be equipped, or "" if it can't be equippable. The string returned is also the name of a global string variable e.g. if "INVTYPE_WEAPONMAINHAND" is returned, _G["INVTYPE_WEAPONMAINHAND"] will be the localized, displayable name of the location.
+10 iconFileDataID Number The FileDataID for the icon texture for the item.
+11 itemSellPrice Number The price, in copper, a vendor is willing to pay for this item, 0 for items that cannot be sold.
+12 itemClassID Number This is the numerical value that determines the string to display for 'itemType'.
+13 itemSubClassID Number This is the numerical value that determines the string to display for 'itemSubType'
+14 ? number
+15 expansionId
+16 ? ?
+17 ? boolean
+--]]
+-- ItemLink Constants
+local i_Name=1
+local i_Link=2
+local i_Rarity=3
+local i_Quality=3
+local i_Level=4
+local i_MinLevel =5
+local i_ClassName=6
+local i_SubClassName=7
+local i_StackCount=8
+local i_EquipLoc=9
+local i_TextureId=10
+local i_SellPrice=11
+local i_ClassID=12
+local i_SubClass_ID=13
+local i_unk1=14
+local i_unk2=15
+local i_unk3=16
+local i_unk4=17
+
+
+do
+local oGetItemInfo=GetItemInfo
+lib.itemcache=lib.itemcache or
+ setmetatable({miss=0,tot=0},{
+ __index=function(table,key)
+ if (not key) then return "" end
+ if (key=="miss") then return 0 end
+ if (key=="tot") then return 0 end
+ local cached={oGetItemInfo(key)}
+ if #cached==0 then return nil end
+ local itemLink=cached[2]
+ if not itemLink then return nil end
+ local itemID=lib:GetItemID(itemLink)
+ local quality=cached[3]
+ local cacheIt=true
+ if quality==LE_ITEM_QUALITY_ARTIFACT then
+ local relic1, relic2, relic3 = select(4,strsplit(':', itemLink))
+ if relic1 and relic1 ~= '' and not oGetItemInfo(relic1) then cacheIt = false end
+ if relic2 and relic2 ~= '' and not oGetItemInfo(relic2) then cacheIt = false end
+ if relic3 and relic3 ~= '' and not oGetItemInfo(relic3) then cacheIt = false end
+ end
+ cached.englishClass=GetItemClassInfo(cached[12])
+ cached.englishSubClass=GetItemSubClassInfo(cached[12],cached[13])
+ if cacheIt then
+ rawset(table,key,cached)
+ end
+ table.miss=table.miss+1
+ return cached
+ end
+
+ })
+end
+local cache,select,unpack=lib.itemcache,select,unpack
+local function CachedGetItemInfo(key,index)
+ if not key then return nil end
+ index=index or 1
+ cache.tot=cache.tot+1
+ local cached=cache[key]
+ if cached and type(cached)=='table' then
+ return select(index,unpack(cached))
+ else
+ rawset(cache,key,nil) -- voiding broken cache entry
+ end
+end
+
+local upgradeTable = {
+ [ 1] = { upgrade = 1, max = 1, ilevel = 8 },
+ [373] = { upgrade = 1, max = 3, ilevel = 4 },
+ [374] = { upgrade = 2, max = 3, ilevel = 8 },
+ [375] = { upgrade = 1, max = 3, ilevel = 4 },
+ [376] = { upgrade = 2, max = 3, ilevel = 4 },
+ [377] = { upgrade = 3, max = 3, ilevel = 4 },
+ [378] = { ilevel = 7 },
+ [379] = { upgrade = 1, max = 2, ilevel = 4 },
+ [380] = { upgrade = 2, max = 2, ilevel = 4 },
+ [445] = { upgrade = 0, max = 2, ilevel = 0 },
+ [446] = { upgrade = 1, max = 2, ilevel = 4 },
+ [447] = { upgrade = 2, max = 2, ilevel = 8 },
+ [451] = { upgrade = 0, max = 1, ilevel = 0 },
+ [452] = { upgrade = 1, max = 1, ilevel = 8 },
+ [453] = { upgrade = 0, max = 2, ilevel = 0 },
+ [454] = { upgrade = 1, max = 2, ilevel = 4 },
+ [455] = { upgrade = 2, max = 2, ilevel = 8 },
+ [456] = { upgrade = 0, max = 1, ilevel = 0 },
+ [457] = { upgrade = 1, max = 1, ilevel = 8 },
+ [458] = { upgrade = 0, max = 4, ilevel = 0 },
+ [459] = { upgrade = 1, max = 4, ilevel = 4 },
+ [460] = { upgrade = 2, max = 4, ilevel = 8 },
+ [461] = { upgrade = 3, max = 4, ilevel = 12 },
+ [462] = { upgrade = 4, max = 4, ilevel = 16 },
+ [465] = { upgrade = 0, max = 2, ilevel = 0 },
+ [466] = { upgrade = 1, max = 2, ilevel = 4 },
+ [467] = { upgrade = 2, max = 2, ilevel = 8 },
+ [468] = { upgrade = 0, max = 4, ilevel = 0 },
+ [469] = { upgrade = 1, max = 4, ilevel = 4 },
+ [470] = { upgrade = 2, max = 4, ilevel = 8 },
+ [471] = { upgrade = 3, max = 4, ilevel = 12 },
+ [472] = { upgrade = 4, max = 4, ilevel = 16 },
+ [491] = { upgrade = 0, max = 4, ilevel = 0 },
+ [492] = { upgrade = 1, max = 4, ilevel = 4 },
+ [493] = { upgrade = 2, max = 4, ilevel = 8 },
+ [494] = { upgrade = 0, max = 6, ilevel = 0 },
+ [495] = { upgrade = 1, max = 6, ilevel = 4 },
+ [496] = { upgrade = 2, max = 6, ilevel = 8 },
+ [497] = { upgrade = 3, max = 6, ilevel = 12 },
+ [498] = { upgrade = 4, max = 6, ilevel = 16 },
+ [503] = { upgrade = 3, max = 3, ilevel = 1 },
+ [504] = { upgrade = 3, max = 4, ilevel = 12 },
+ [505] = { upgrade = 4, max = 4, ilevel = 16 },
+ [506] = { upgrade = 5, max = 6, ilevel = 20 },
+ [507] = { upgrade = 6, max = 6, ilevel = 24 },
+ [529] = { upgrade = 0, max = 2, ilevel = 0 },
+ [530] = { upgrade = 1, max = 2, ilevel = 5 },
+ [531] = { upgrade = 2, max = 2, ilevel = 10 },
+ [535] = { upgrade = 1, max = 3, ilevel = 15 },
+ [536] = { upgrade = 2, max = 3, ilevel = 30 },
+ [537] = { upgrade = 3, max = 3, ilevel = 45 },
+ [538] = { upgrade = 0, max = 3, ilevel = 0 },
+
+}
+do
+ local stub = { ilevel = 0 }
+ setmetatable(upgradeTable, { __index = function(t, key)
+ return stub
+ end})
+end
+-- Tooltip Scanning stuff
+local itemLevelPattern = _G.ITEM_LEVEL:gsub("%%d", "(%%d+)")
+local soulboundPattern = _G.ITEM_SOULBOUND
+local boePattern=_G.ITEM_BIND_ON_EQUIP
+local bopPattern=_G.ITEM_BIND_ON_PICKUP
+local boaPattern1=_G.ITEM_BIND_TO_BNETACCOUNT
+local boaPattern2=_G.ITEM_BNETACCOUNTBOUND
+
+local scanningTooltip
+local anchor
+local tipCache = lib.tipCache or setmetatable({},{__index=function(table,key) return {} end})
+local emptytable={}
+
+local function ScanTip(itemLink,itemLevel,show)
+ if type(itemLink)=="number" then
+ itemLink=CachedGetItemInfo(itemLink,2)
+ if not itemLink then return emptytable end
+ end
+ if type(tipCache[itemLink].ilevel)=="nil"then -- or not tipCache[itemLink].cached then
+ local cacheIt=true
+ if not scanningTooltip then
+ anchor=CreateFrame("Frame")
+ anchor:Hide()
+ scanningTooltip = _G.CreateFrame("GameTooltip", "LibItemUpgradeInfoTooltip", nil, "GameTooltipTemplate")
+ end
+ --scanningTooltip:ClearLines()
+ GameTooltip_SetDefaultAnchor(scanningTooltip,anchor)
+ local itemString=itemLink:match("|H(.-)|h")
+ local rc,message=pcall(scanningTooltip.SetHyperlink,scanningTooltip,itemString)
+ if (not rc) then
+ return emptytable
+ end
+ scanningTooltip:Show()
+ local quality,_,_,class,subclass,_,_,_,_,classIndex,subclassIndex=CachedGetItemInfo(itemLink,3)
+
+ -- line 1 is the item name
+ -- line 2 may be the item level, or it may be a modifier like "Heroic"
+ -- check up to line 6 just in case
+ local ilevel,soulbound,bop,boe,boa,heirloom
+ if quality==LE_ITEM_QUALITY_ARTIFACT and itemLevel then
+ local relic1, relic2, relic3 = select(4,strsplit(':', itemLink))
+ if relic1 and relic1 ~= '' and not CachedGetItemInfo(relic1) then cacheIt = false end
+ if relic2 and relic2 ~= '' and not CachedGetItemInfo(relic2) then cacheIt = false end
+ if relic3 and relic3 ~= '' and not CachedGetItemInfo(relic3) then cacheIt = false end
+ ilevel=itemLevel
+ end
+ if show then
+ for i=1,12 do
+ local l, ltext = _G["LibItemUpgradeInfoTooltipTextLeft"..i], nil
+ local r, rtext = _G["LibItemUpgradeInfoTooltipTextRight"..i], nil
+ ltext=l:GetText()
+ rtext=r:GetText()
+ pp(i,ltext,' - ',rtext)
+ end
+ end
+ for i = 2, 6 do
+ local label, text = _G["LibItemUpgradeInfoTooltipTextLeft"..i], nil
+ if label then text=label:GetText() end
+ if text then
+ if ilevel==nil then ilevel = tonumber(text:match(itemLevelPattern)) end
+ if soulbound==nil then soulbound = text:find(soulboundPattern) end
+ if bop==nil then bop = text:find(bopPattern) end
+ if boe==nil then boe = text:find(boePattern) end
+ if boa==nil then boa = text:find(boaPattern1) end
+ if boa==nil then boa = text:find(boaPattern2) end
+ end
+ end
+ tipCache[itemLink]={
+ ilevel=ilevel or itemLevel,
+ soulbound=soulbound,
+ bop=bop,
+ boe=boe,
+ cached=cacheIt
+ }
+ scanningTooltip:Hide()
+ end
+ return tipCache[itemLink]
+end
+
+
+-- GetUpgradeID(itemString)
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Number - The upgrade ID (possibly 0), or nil if the input is invalid or
+-- does not contain upgrade info
+function lib:GetUpgradeID(itemString)
+ if type(itemString)~="string" then return end
+ local itemString = itemString:match("item[%-?%d:]+") or ""-- Standardize itemlink to itemstring
+ local instaid, _, numBonuses, affixes = select(12, strsplit(":", itemString, 15))
+ instaid=tonumber(instaid) or 7
+ numBonuses=tonumber(numBonuses) or 0
+ if instaid >0 and (instaid-4)%8==0 then
+ return tonumber((select(numBonuses + 1, strsplit(":", affixes))))
+ end
+end
+
+-- GetCurrentUpgrade(id)
+--
+-- Returns the current upgrade level of the item, e.g. 1 for a 1/2 item.
+--
+-- Arguments:
+-- id - Number - The upgrade ID of the item (obtained via GetUpgradeID())
+--
+-- Returns:
+-- Number - The current upgrade level of the item. Returns nil if the item
+-- cannot be upgraded
+function lib:GetCurrentUpgrade(id)
+ return upgradeTable[id].upgrade
+end
+
+-- GetMaximumUpgrade(id)
+--
+-- Returns the maximum upgrade level of the item, e.g. 2 for a 1/2 item.
+--
+-- Arguments:
+-- id - Number - The upgrade ID of the item (obtained via GetUpgradeID())
+--
+-- Returns:
+-- Number - The maximum upgrade level of the item. Returns nil if the item
+-- cannot be upgraded
+function lib:GetMaximumUpgrade(id)
+ return upgradeTable[id].max
+end
+
+-- GetItemLevelUpgrade(id)
+--
+-- Returns the item level increase that this upgrade is worth, e.g. 4 for a
+-- 1/2 item or 8 for a 2/2 item.
+--
+-- Arguments:
+-- id - Number - The upgrade ID of the item (obtained via GetUpgradeID())
+--
+-- Returns:
+-- Number - The item level increase of the item. Returns 0 if the item
+-- cannot be or has not been upgraded
+function lib:GetItemLevelUpgrade(id)
+ return upgradeTable[id].ilevel
+end
+
+-- GetItemUpgradeInfo(itemString)
+--
+-- Returns the current upgrade level, maximum upgrade level, and item level
+-- increase for an item.
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns if the item can be upgraded:
+-- Number - The current upgrade level of the item
+-- Number - The maximum upgrade level of the item
+-- Number - The item level increase of the item
+-- or if the item cannot be upgraded:
+-- nil
+-- nil
+-- 0
+-- or if the item is invalid or does not contain upgrade info:
+-- nil
+function lib:GetItemUpgradeInfo(itemString)
+ local id = self:GetUpgradeID(itemString)
+ if id then
+ local cur = self:GetCurrentUpgrade(id)
+ local max = self:GetMaximumUpgrade(id)
+ local delta = self:GetItemLevelUpgrade(id)
+ return cur, max, delta
+ end
+ return nil
+end
+
+-- GetHeirloomTrueLevel(itemString)
+--
+-- Returns the true item level for an heirloom (actually, returns the true level for any adapting item)
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Number, Boolean - The true item level of the item. If the item is not
+-- an heirloom, or an error occurs when trying to scan the
+-- item tooltip, the second return value is false. Otherwise
+-- the second return value is true. If the input is invalid,
+-- (nil, false) is returned.
+-- Convert the ITEM_LEVEL constant into a pattern for our use
+function lib:GetHeirloomTrueLevel(itemString)
+ if type(itemString) ~= "string" then return nil,false end
+ local _, itemLink, rarity, itemLevel = CachedGetItemInfo(itemString)
+ if (not itemLink) then
+ return nil,false
+ end
+ local rc=ScanTip(itemLink,itemLevel)
+ if rc.ilevel then
+ return rc.ilevel,true
+ end
+ return itemLevel, false
+end
+
+-- GetUpgradedItemLevel(itemString)
+--
+-- Returns the true item level of the item, including upgrades and heirlooms.
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Number - The true item level of the item, or nil if the input is invalid
+function lib:GetUpgradedItemLevel(itemString)
+ -- check for heirlooms first
+ local ilvl, isTrue = self:GetHeirloomTrueLevel(itemString)
+ if isTrue then
+ return ilvl
+ end
+ -- not an heirloom? fall back to the regular item logic
+ local id = self:GetUpgradeID(itemString)
+ if ilvl and id then
+ ilvl = ilvl + self:GetItemLevelUpgrade(id)
+ end
+ return ilvl
+end
+
+-- IsBop(itemString)
+--
+-- Check an item for Bind On Pickup.
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Boolean - True if Bind On Pickup
+
+function lib:IsBop(itemString)
+ local rc=ScanTip(itemString)
+ return rc.bop
+end
+-- IsBoe(itemString)
+--
+-- Check an item for Bind On Equip.
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Boolean - True if Bind On Equip
+
+function lib:IsBoe(itemString)
+ local rc=ScanTip(itemString)
+ return rc.boe
+end
+-- IsBoa(itemString)
+--
+-- Check an item for Bind On Aaccount
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Boolean - True if Bind On Equip
+
+function lib:IsBoa(itemString)
+ local rc=ScanTip(itemString)
+ return rc.boa
+end
+
+-- IsArtifact(itemString)
+--
+-- Check an item for Heirloom
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Boolean - True if Artifact
+
+function lib:IsArtifact(itemString)
+ return CachedGetItemInfo(itemString,i_Quality)==LE_ITEM_QUALITY_ARTIFACT
+end
+
+-- GetClassInfoIsHeirloom(itemString)
+--
+-- Retrieve class and subclass
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- class,subclass
+
+
+function lib:GetClassInfo(itemString)
+ local rc=ScantTip(itemString)
+ return rc.class,rc.subclass
+end
+
+
+-- IsHeirloom(itemString)
+--
+-- Check an item for Heirloom
+--
+-- Arguments:
+-- itemString - String - An itemLink or itemString denoting the item
+--
+-- Returns:
+-- Boolean - True if Heirloom
+
+function lib:IsHeirloom(itemString)
+ return CachedGetItemInfo(itemString,i_Quality) ==LE_ITEM_QUALITY_HEIRLOOM
+end
+---
+-- Parses an itemlink and returns itemId without calling API again
+-- @param #lib self
+-- @param #string itemlink
+-- @return #number itemId or 0
+function lib:GetItemID(itemlink)
+ if (type(itemlink)=="string") then
+ local itemid,context=GetItemInfoFromHyperlink(itemlink)
+ return tonumber(itemid) or 0
+ --return tonumber(itemlink:match("Hitem:(%d+):")) or 0
+ else
+ return 0
+ end
+end
+
+---
+--
+-- Returns a caching version of GetItemInfo. Can be used to override the original one.
+-- Adds a second parameter to directly retrieving a specific value
+-- (Note: internally uses select so it's actually like calling select(n,GetItemInfo(itemID))
+--
+-- Arguments:
+-- self #lib self
+--
+-- Returns:
+-- #function The new function
+
+--@do-not-package--
+function lib:ScanTip(itemLink)
+ local GameTooltip=LibItemUpgradeInfoTooltip
+ if GameTooltip then
+ GameTooltip_SetDefaultAnchor(GameTooltip, UIParent)
+ GameTooltip:SetHyperlink(itemLink)
+ GameTooltip:Show()
+ end
+ return ScanTip(itemLink,100,true)
+end
+function lib:GetCachingGetItemInfo()
+ return CachedGetItemInfo
+end
+function lib:GetCacheStats()
+ local c=lib.itemcache
+ local h=c.tot-c.miss
+ local perc=( h>0) and h/c.tot*100 or 0
+ return c.miss,h,perc
+end
+function lib:GetCache()
+ return lib.itemcache
+end
+function lib:CleanCache()
+ return wipe(lib.itemcache)
+end
+
+--[===========[ ]===========]
+--[===[ Debug utilities ]===]
+--[===========[ ]===========]
+
+local function compareTables(t1, t2)
+ local seen = {}
+ for k, v1 in pairs(t1) do
+ seen[k] = true
+ local v2 = rawget(t2, k)
+ if not v2 then return false end
+ if type(v1) ~= type(v2) then return false end
+ if type(v1) == "table" then
+ if not compareTables(v1, v2) then return false end
+ elseif v1 ~= v2 then return false end
+ end
+ for k in pairs(t2) do
+ if not seen[k] then return false end
+ end
+ return true
+end
+
+-- prints the table rows in red and green
+-- omits the lead { and the trailing }
+local function printDiffTable(t1, t2)
+ local keys, seen = {}, {}
+ for k in pairs(t1) do
+ keys[#keys+1] = k
+ seen[k] = true
+ end
+ for k in pairs(t2) do
+ if not seen[k] then
+ keys[#keys+1] = k
+ end
+ end
+ table.sort(keys)
+ local function formatTable(t)
+ local comps = {}
+ for k, v in pairs(t) do
+ comps[#comps+1] = ("%s = %d"):format(k, v)
+ end
+ return "{ " .. table.concat(comps, ", ") .. " }"
+ end
+ for _, k in ipairs(keys) do
+ local v1, v2 = rawget(t1, k), rawget(t2, k)
+ local equal
+ if type(v1) == "table" and type(v2) == "table" then equal = compareTables(v1, v2)
+ else equal = v1 == v2 end
+ if not equal then
+ if v1 then
+ pp(("|cffff0000 [%d] = %s,|r"):format(k, formatTable(v1)))
+ end
+ if v2 then
+ pp(("|cff00ff00 [%d] = %s,|r"):format(k, formatTable(v2)))
+ end
+ end
+ end
+end
+
+-- Scans the first 10000 upgrade IDs
+-- Run this with /run LibStub:GetLibrary("LibItemUpgradeInfo-1.0"):_CheckUpgradeTable()
+-- If you don't have Aspirant's Staff of Harmony cached it may error out, just try again.
+do
+ local debugFrame
+ local worker
+ local newTable
+ local debugTooltip
+ function lib:_CheckUpgradeTable(itemLink)
+ if worker then
+ pp("|cffff0000LibItemUpgradeInfo-1.0: upgrade check already in progress")
+ return
+ end
+ if not debugFrame then
+ debugFrame = _G.CreateFrame("frame")
+ debugFrame:Hide()
+ debugFrame:SetScript("OnUpdate", function()
+ local ok, result, count, max = pcall(worker)
+ if not ok or result then
+ debugFrame:Hide()
+ worker = nil
+ end
+ if not ok then
+ pp("|cffff0000LibItemUpgradeInfo-1.0 error: " .. result .. "|r")
+ elseif result then
+ pp("LibItemUpgradeInfo-1.0: scan complete")
+ if compareTables(upgradeTable, newTable) then
+ pp("LibItemUpgradeInfo-1.0: |cff00ff00No changes|r")
+ else
+ pp("LibItemUpgradeInfo-1.0: |cffff0000New table:|r {")
+ printDiffTable(upgradeTable, newTable)
+ pp("}")
+ end
+ else
+ pp("LibItemUpgradeInfo-1.0: scanning " .. count .. "/" .. max)
+ end
+ end)
+ end
+ if not debugTooltip then
+ debugTooltip = _G.CreateFrame("GameTooltip", "LibItemUpgradeInfoDebugTooltip", nil, "GameTooltipTemplate")
+ debugTooltip:SetOwner(_G.WorldFrame, "ANCHOR_NONE")
+ end
+ newTable = {}
+ --local itemLink = "|cff0070dd|Hitem:89551:0:0:0:0:0:0:0:90:253:0:0:1:0|h[Aspirant's Staff of Harmony]|h|r"
+ local itemLink = itemLink or "|cff0070dd|Hitem:89551:0:0:0:0:0:0:0:100:253:4:0:0:0|h[Aspirant's Staff of Harmony]|h|r"
+-- Livello è il 9,upgradeid il 14. Al decimo posto, un valore che deve essere 4 o 4+n *8) per far scattare l'uso dell'upgradeid
+ local itemLevel = select(4, _G.GetItemInfo(itemLink))
+ assert(itemLevel, "Can't find item level for itemLink")
+ local count, max, batchsize = 0, 10000, 200
+ worker = function()
+ for i = count, math.min(max, count+batchsize) do
+ local link = itemLink:gsub("%d+|h", i.."|h")
+ debugTooltip:ClearLines()
+ debugTooltip:SetHyperlink(link)
+ local upgrade, max
+ local curLevel, maxLevel = _G.LibItemUpgradeInfoDebugTooltipTextLeft3:GetText():match("^Upgrade Level: (%d+)/(%d+)")
+ local ilvl = tonumber(_G.LibItemUpgradeInfoDebugTooltipTextLeft2:GetText():match("Item Level (%d+)"))
+ if not ilvl then
+ ilvl = tonumber(_G.LibItemUpgradeInfoDebugTooltipTextLeft3:GetText():match("Item Level (%d+)"))
+ end
+ assert(ilvl ~= nil, "Can't find ItemLevel in tooltip: " .. _G.LibItemUpgradeInfoDebugTooltipTextLeft2:GetText())
+ if curLevel or maxLevel or ilvl ~= itemLevel then
+ newTable[i] = { upgrade = tonumber(curLevel), max = tonumber(maxLevel), ilevel = ilvl - itemLevel }
+ end
+ end
+ count = count + batchsize
+ return (count > max), count, max
+ end
+ debugFrame:Show()
+ end
+end
+--@end-do-not-package--
+
+-- vim: set noet sw=4 ts=4:
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.toc b/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.toc
new file mode 100644
index 00000000..ca4e3315
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.toc
@@ -0,0 +1,9 @@
+## Interface: 70200
+## Title: Lib: ItemUpgradeInfo-1.0
+## Notes: Database of item upgrade IDs
+## Author: eridius
+## Version: Release-70200-28 70200
+## X-Revision: 7c5485a
+## X-Category: Library
+
+LibItemUpgradeInfo-1.0.xml
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.xml b/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.xml
new file mode 100644
index 00000000..8bca6c45
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibItemUpgradeInfo-1.0.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.lua b/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.lua
new file mode 100644
index 00000000..f5fc9192
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.lua
@@ -0,0 +1,51 @@
+-- $Id: LibStub.lua 76 2007-09-03 01:50:17Z mikk $
+-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain
+-- Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+-- Check to see is this version of the stub is obsolete
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+ LibStub = LibStub or {libs = {}, minors = {} }
+ _G[LIBSTUB_MAJOR] = LibStub
+ LibStub.minor = LIBSTUB_MINOR
+
+ -- LibStub:NewLibrary(major, minor)
+ -- major (string) - the major version of the library
+ -- minor (string or number ) - the minor version of the library
+ --
+ -- returns nil if a newer or same version of the lib is already present
+ -- returns empty library object or old library object if upgrade is needed
+ function LibStub:NewLibrary(major, minor)
+ assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+ minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+
+ local oldminor = self.minors[major]
+ if oldminor and oldminor >= minor then return nil end
+ self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+ return self.libs[major], oldminor
+ end
+
+ -- LibStub:GetLibrary(major, [silent])
+ -- major (string) - the major version of the library
+ -- silent (boolean) - if true, library is optional, silently return nil if its not found
+ --
+ -- throws an error if the library can not be found (except silent is set)
+ -- returns the library object if found
+ function LibStub:GetLibrary(major, silent)
+ if not self.libs[major] and not silent then
+ error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+ end
+ return self.libs[major], self.minors[major]
+ end
+
+ -- LibStub:IterateLibraries()
+ --
+ -- Returns an iterator for the currently registered libraries
+ function LibStub:IterateLibraries()
+ return pairs(self.libs)
+ end
+
+ setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.toc b/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.toc
new file mode 100644
index 00000000..846aeae7
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/LibStub.toc
@@ -0,0 +1,9 @@
+## Interface: 70200
+## Title: Lib: LibStub
+## Notes: Universal Library Stub
+## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel
+## X-Website: http://www.wowace.com/addons/libstub/
+## X-Category: Library
+## X-License: Public Domain
+
+LibStub.lua
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test.lua b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test.lua
new file mode 100644
index 00000000..fb3bcbd6
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test.lua
@@ -0,0 +1,41 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+local lib, oldMinor = LibStub:NewLibrary("Pants", 1) -- make a new thingy
+assert(lib) -- should return the library table
+assert(not oldMinor) -- should not return the old minor, since it didn't exist
+
+-- the following is to create data and then be able to check if the same data exists after the fact
+function lib:MyMethod()
+end
+local MyMethod = lib.MyMethod
+lib.MyTable = {}
+local MyTable = lib.MyTable
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 1) -- try to register a library with the same version, should silently fail
+assert(not newLib) -- should not return since out of date
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 0) -- try to register a library with a previous, should silently fail
+assert(not newLib) -- should not return since out of date
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 2) -- register a new version
+assert(newLib) -- library table
+assert(rawequal(newLib, lib)) -- should be the same reference as the previous
+assert(newOldMinor == 1) -- should return the minor version of the previous version
+
+assert(rawequal(lib.MyMethod, MyMethod)) -- verify that values were saved
+assert(rawequal(lib.MyTable, MyTable)) -- verify that values were saved
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 3 Blah") -- register a new version with a string minor version (instead of a number)
+assert(newLib) -- library table
+assert(newOldMinor == 2) -- previous version was 2
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 4 and please ignore 15 Blah") -- register a new version with a string minor version (instead of a number)
+assert(newLib)
+assert(newOldMinor == 3) -- previous version was 3 (even though it gave a string)
+
+local newLib, newOldMinor = LibStub:NewLibrary("Pants", 5) -- register a new library, using a normal number instead of a string
+assert(newLib)
+assert(newOldMinor == 4) -- previous version was 4 (even though it gave a string)
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test2.lua b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test2.lua
new file mode 100644
index 00000000..af431dd3
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test2.lua
@@ -0,0 +1,27 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+for major, library in LibStub:IterateLibraries() do
+ -- check that MyLib doesn't exist yet, by iterating through all the libraries
+ assert(major ~= "MyLib")
+end
+
+assert(not LibStub:GetLibrary("MyLib", true)) -- check that MyLib doesn't exist yet by direct checking
+assert(not pcall(LibStub.GetLibrary, LibStub, "MyLib")) -- don't silently fail, thus it should raise an error.
+local lib = LibStub:NewLibrary("MyLib", 1) -- create the lib
+assert(lib) -- check it exists
+assert(rawequal(LibStub:GetLibrary("MyLib"), lib)) -- verify that :GetLibrary("MyLib") properly equals the lib reference
+
+assert(LibStub:NewLibrary("MyLib", 2)) -- create a new version
+
+local count=0
+for major, library in LibStub:IterateLibraries() do
+ -- check that MyLib exists somewhere in the libraries, by iterating through all the libraries
+ if major == "MyLib" then -- we found it!
+ count = count +1
+ assert(rawequal(library, lib)) -- verify that the references are equal
+ end
+end
+assert(count == 1) -- verify that we actually found it, and only once
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test3.lua b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test3.lua
new file mode 100644
index 00000000..3c06002c
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test3.lua
@@ -0,0 +1,14 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+local proxy = newproxy() -- non-string
+
+assert(not pcall(LibStub.NewLibrary, LibStub, proxy, 1)) -- should error, proxy is not a string, it's userdata
+local success, ret = pcall(LibStub.GetLibrary, proxy, true)
+assert(not success or not ret) -- either error because proxy is not a string or because it's not actually registered.
+
+assert(not pcall(LibStub.NewLibrary, LibStub, "Something", "No number in here")) -- should error, minor has no string in it.
+
+assert(not LibStub:GetLibrary("Something", true)) -- shouldn't've created it from the above statement
diff --git a/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test4.lua b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test4.lua
new file mode 100644
index 00000000..294623b9
--- /dev/null
+++ b/Libs/LibItemUpgradeInfo-1.0/LibStub/tests/test4.lua
@@ -0,0 +1,41 @@
+debugstack = debug.traceback
+strmatch = string.match
+
+loadfile("../LibStub.lua")()
+
+
+-- Pretend like loaded libstub is old and doesn't have :IterateLibraries
+assert(LibStub.minor)
+LibStub.minor = LibStub.minor - 0.0001
+LibStub.IterateLibraries = nil
+
+loadfile("../LibStub.lua")()
+
+assert(type(LibStub.IterateLibraries)=="function")
+
+
+-- Now pretend that we're the same version -- :IterateLibraries should NOT be re-created
+LibStub.IterateLibraries = 123
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+-- Now pretend that a newer version is loaded -- :IterateLibraries should NOT be re-created
+LibStub.minor = LibStub.minor + 0.0001
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+-- Again with a huge number
+LibStub.minor = LibStub.minor + 1234567890
+
+loadfile("../LibStub.lua")()
+
+assert(LibStub.IterateLibraries == 123)
+
+
+print("OK")
diff --git a/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua b/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua
new file mode 100644
index 00000000..4a31cfeb
--- /dev/null
+++ b/Libs/LibSharedMedia-3.0/LibSharedMedia-3.0.lua
@@ -0,0 +1,292 @@
+--[[
+Name: LibSharedMedia-3.0
+Revision: $Revision: 91 $
+Author: Elkano (elkano@gmx.de)
+Inspired By: SurfaceLib by Haste/Otravi (troeks@gmail.com)
+Website: http://www.wowace.com/projects/libsharedmedia-3-0/
+Description: Shared handling of media data (fonts, sounds, textures, ...) between addons.
+Dependencies: LibStub, CallbackHandler-1.0
+License: LGPL v2.1
+]]
+
+local MAJOR, MINOR = "LibSharedMedia-3.0", 6010002 -- 6.1.0 v2 / increase manually on changes
+local lib = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not lib then return end
+
+local _G = getfenv(0)
+
+local pairs = _G.pairs
+local type = _G.type
+
+local band = _G.bit.band
+
+local table_insert = _G.table.insert
+local table_sort = _G.table.sort
+
+local locale = GetLocale()
+local locale_is_western
+local LOCALE_MASK = 0
+lib.LOCALE_BIT_koKR = 1
+lib.LOCALE_BIT_ruRU = 2
+lib.LOCALE_BIT_zhCN = 4
+lib.LOCALE_BIT_zhTW = 8
+lib.LOCALE_BIT_western = 128
+
+local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
+
+lib.callbacks = lib.callbacks or CallbackHandler:New(lib)
+
+lib.DefaultMedia = lib.DefaultMedia or {}
+lib.MediaList = lib.MediaList or {}
+lib.MediaTable = lib.MediaTable or {}
+lib.MediaType = lib.MediaType or {}
+lib.OverrideMedia = lib.OverrideMedia or {}
+
+local defaultMedia = lib.DefaultMedia
+local mediaList = lib.MediaList
+local mediaTable = lib.MediaTable
+local overrideMedia = lib.OverrideMedia
+
+
+-- create mediatype constants
+lib.MediaType.BACKGROUND = "background" -- background textures
+lib.MediaType.BORDER = "border" -- border textures
+lib.MediaType.FONT = "font" -- fonts
+lib.MediaType.STATUSBAR = "statusbar" -- statusbar textures
+lib.MediaType.SOUND = "sound" -- sound files
+
+-- populate lib with default Blizzard data
+-- BACKGROUND
+if not lib.MediaTable.background then lib.MediaTable.background = {} end
+lib.MediaTable.background["None"] = [[]]
+lib.MediaTable.background["Blizzard Collections Background"] = [[Interface\Collections\CollectionsBackgroundTile]]
+lib.MediaTable.background["Blizzard Dialog Background"] = [[Interface\DialogFrame\UI-DialogBox-Background]]
+lib.MediaTable.background["Blizzard Dialog Background Dark"] = [[Interface\DialogFrame\UI-DialogBox-Background-Dark]]
+lib.MediaTable.background["Blizzard Dialog Background Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Background]]
+lib.MediaTable.background["Blizzard Garrison Background"] = [[Interface\Garrison\GarrisonUIBackground]]
+lib.MediaTable.background["Blizzard Garrison Background 2"] = [[Interface\Garrison\GarrisonUIBackground2]]
+lib.MediaTable.background["Blizzard Garrison Background 3"] = [[Interface\Garrison\GarrisonMissionUIInfoBoxBackgroundTile]]
+lib.MediaTable.background["Blizzard Low Health"] = [[Interface\FullScreenTextures\LowHealth]]
+lib.MediaTable.background["Blizzard Marble"] = [[Interface\FrameGeneral\UI-Background-Marble]]
+lib.MediaTable.background["Blizzard Out of Control"] = [[Interface\FullScreenTextures\OutOfControl]]
+lib.MediaTable.background["Blizzard Parchment"] = [[Interface\AchievementFrame\UI-Achievement-Parchment-Horizontal]]
+lib.MediaTable.background["Blizzard Parchment 2"] = [[Interface\AchievementFrame\UI-GuildAchievement-Parchment-Horizontal]]
+lib.MediaTable.background["Blizzard Rock"] = [[Interface\FrameGeneral\UI-Background-Rock]]
+lib.MediaTable.background["Blizzard Tabard Background"] = [[Interface\TabardFrame\TabardFrameBackground]]
+lib.MediaTable.background["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Background]]
+lib.MediaTable.background["Solid"] = [[Interface\Buttons\WHITE8X8]]
+lib.DefaultMedia.background = "None"
+
+-- BORDER
+if not lib.MediaTable.border then lib.MediaTable.border = {} end
+lib.MediaTable.border["None"] = [[]]
+lib.MediaTable.border["Blizzard Achievement Wood"] = [[Interface\AchievementFrame\UI-Achievement-WoodBorder]]
+lib.MediaTable.border["Blizzard Chat Bubble"] = [[Interface\Tooltips\ChatBubble-Backdrop]]
+lib.MediaTable.border["Blizzard Dialog"] = [[Interface\DialogFrame\UI-DialogBox-Border]]
+lib.MediaTable.border["Blizzard Dialog Gold"] = [[Interface\DialogFrame\UI-DialogBox-Gold-Border]]
+lib.MediaTable.border["Blizzard Party"] = [[Interface\CHARACTERFRAME\UI-Party-Border]]
+lib.MediaTable.border["Blizzard Tooltip"] = [[Interface\Tooltips\UI-Tooltip-Border]]
+lib.DefaultMedia.border = "None"
+
+-- FONT
+if not lib.MediaTable.font then lib.MediaTable.font = {} end
+local SML_MT_font = lib.MediaTable.font
+--[[
+All font files are currently in all clients, the following table depicts which font supports which charset as of 5.0.4
+Fonts were checked using langcover.pl from DejaVu fonts (http://sourceforge.net/projects/dejavu/) and FontForge (http://fontforge.org/)
+latin means check for: de, en, es, fr, it, pt
+
+file name latin koKR ruRU zhCN zhTW
+2002.ttf 2002 X X X - -
+2002B.ttf 2002 Bold X X X - -
+ARHei.ttf AR CrystalzcuheiGBK Demibold X - X X X
+ARIALN.TTF Arial Narrow X - X - -
+ARKai_C.ttf AR ZhongkaiGBK Medium (Combat) X - X X X
+ARKai_T.ttf AR ZhongkaiGBK Medium X - X X X
+bHEI00M.ttf AR Heiti2 Medium B5 - - - - X
+bHEI01B.ttf AR Heiti2 Bold B5 - - - - X
+bKAI00M.ttf AR Kaiti Medium B5 - - - - X
+bLEI00D.ttf AR Leisu Demi B5 - - - - X
+FRIZQT__.TTF Friz Quadrata TT X - - - -
+FRIZQT___CYR.TTF FrizQuadrataCTT x - X - -
+K_Damage.TTF YDIWingsM - X X - -
+K_Pagetext.TTF MoK X X X - -
+MORPHEUS.TTF Morpheus X - - - -
+MORPHEUS_CYR.TTF Morpheus X - X - -
+NIM_____.ttf Nimrod MT X - X - -
+SKURRI.TTF Skurri X - - - -
+SKURRI_CYR.TTF Skurri X - X - -
+
+WARNING: Although FRIZQT___CYR is available on western clients, it doesn't support special European characters e.g. é, ï, ö
+Due to this, we cannot use it as a replacement for FRIZQT__.TTF
+]]
+
+if locale == "koKR" then
+ LOCALE_MASK = lib.LOCALE_BIT_koKR
+--
+ SML_MT_font["굵은 글꼴"] = [[Fonts\2002B.TTF]]
+ SML_MT_font["기본 글꼴"] = [[Fonts\2002.TTF]]
+ SML_MT_font["데미지 글꼴"] = [[Fonts\K_Damage.TTF]]
+ SML_MT_font["퀘스트 글꼴"] = [[Fonts\K_Pagetext.TTF]]
+--
+ lib.DefaultMedia["font"] = "기본 글꼴" -- someone from koKR please adjust if needed
+--
+elseif locale == "zhCN" then
+ LOCALE_MASK = lib.LOCALE_BIT_zhCN
+--
+ SML_MT_font["伤害数字"] = [[Fonts\ARKai_C.ttf]]
+ SML_MT_font["默认"] = [[Fonts\ARKai_T.ttf]]
+ SML_MT_font["聊天"] = [[Fonts\ARHei.ttf]]
+--
+ lib.DefaultMedia["font"] = "默认" -- someone from zhCN please adjust if needed
+--
+elseif locale == "zhTW" then
+ LOCALE_MASK = lib.LOCALE_BIT_zhTW
+--
+ SML_MT_font["提示訊息"] = [[Fonts\bHEI00M.ttf]]
+ SML_MT_font["聊天"] = [[Fonts\bHEI01B.ttf]]
+ SML_MT_font["傷害數字"] = [[Fonts\bKAI00M.ttf]]
+ SML_MT_font["預設"] = [[Fonts\bLEI00D.ttf]]
+--
+ lib.DefaultMedia["font"] = "預設" -- someone from zhTW please adjust if needed
+
+elseif locale == "ruRU" then
+ LOCALE_MASK = lib.LOCALE_BIT_ruRU
+--
+ SML_MT_font["2002"] = [[Fonts\2002.TTF]]
+ SML_MT_font["2002 Bold"] = [[Fonts\2002B.TTF]]
+ SML_MT_font["AR CrystalzcuheiGBK Demibold"] = [[Fonts\ARHei.TTF]]
+ SML_MT_font["AR ZhongkaiGBK Medium (Combat)"] = [[Fonts\ARKai_C.TTF]]
+ SML_MT_font["AR ZhongkaiGBK Medium"] = [[Fonts\ARKai_T.TTF]]
+ SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]]
+ SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT___CYR.TTF]]
+ SML_MT_font["MoK"] = [[Fonts\K_Pagetext.TTF]]
+ SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS_CYR.TTF]]
+ SML_MT_font["Nimrod MT"] = [[Fonts\NIM_____.ttf]]
+ SML_MT_font["Skurri"] = [[Fonts\SKURRI_CYR.TTF]]
+--
+ lib.DefaultMedia.font = "Friz Quadrata TT"
+--
+else
+ LOCALE_MASK = lib.LOCALE_BIT_western
+ locale_is_western = true
+--
+ SML_MT_font["2002"] = [[Fonts\2002.TTF]]
+ SML_MT_font["2002 Bold"] = [[Fonts\2002B.TTF]]
+ SML_MT_font["AR CrystalzcuheiGBK Demibold"] = [[Fonts\ARHei.TTF]]
+ SML_MT_font["AR ZhongkaiGBK Medium (Combat)"] = [[Fonts\ARKai_C.TTF]]
+ SML_MT_font["AR ZhongkaiGBK Medium"] = [[Fonts\ARKai_T.TTF]]
+ SML_MT_font["Arial Narrow"] = [[Fonts\ARIALN.TTF]]
+ SML_MT_font["Friz Quadrata TT"] = [[Fonts\FRIZQT__.TTF]]
+ SML_MT_font["MoK"] = [[Fonts\K_Pagetext.TTF]]
+ SML_MT_font["Morpheus"] = [[Fonts\MORPHEUS_CYR.TTF]]
+ SML_MT_font["Nimrod MT"] = [[Fonts\NIM_____.ttf]]
+ SML_MT_font["Skurri"] = [[Fonts\SKURRI_CYR.TTF]]
+--
+ lib.DefaultMedia.font = "Friz Quadrata TT"
+--
+end
+
+-- STATUSBAR
+if not lib.MediaTable.statusbar then lib.MediaTable.statusbar = {} end
+lib.MediaTable.statusbar["Blizzard"] = [[Interface\TargetingFrame\UI-StatusBar]]
+lib.MediaTable.statusbar["Blizzard Character Skills Bar"] = [[Interface\PaperDollInfoFrame\UI-Character-Skills-Bar]]
+lib.MediaTable.statusbar["Blizzard Raid Bar"] = [[Interface\RaidFrame\Raid-Bar-Hp-Fill]]
+lib.DefaultMedia.statusbar = "Blizzard"
+
+-- SOUND
+if not lib.MediaTable.sound then lib.MediaTable.sound = {} end
+lib.MediaTable.sound["None"] = [[Interface\Quiet.ogg]] -- Relies on the fact that PlaySound[File] doesn't error on non-existing input.
+lib.DefaultMedia.sound = "None"
+
+local function rebuildMediaList(mediatype)
+ local mtable = mediaTable[mediatype]
+ if not mtable then return end
+ if not mediaList[mediatype] then mediaList[mediatype] = {} end
+ local mlist = mediaList[mediatype]
+ -- list can only get larger, so simply overwrite it
+ local i = 0
+ for k in pairs(mtable) do
+ i = i + 1
+ mlist[i] = k
+ end
+ table_sort(mlist)
+end
+
+function lib:Register(mediatype, key, data, langmask)
+ if type(mediatype) ~= "string" then
+ error(MAJOR..":Register(mediatype, key, data, langmask) - mediatype must be string, got "..type(mediatype))
+ end
+ if type(key) ~= "string" then
+ error(MAJOR..":Register(mediatype, key, data, langmask) - key must be string, got "..type(key))
+ end
+ mediatype = mediatype:lower()
+ if mediatype == lib.MediaType.FONT and ((langmask and band(langmask, LOCALE_MASK) == 0) or not (langmask or locale_is_western)) then return false end
+ if mediatype == lib.MediaType.SOUND and type(data) == "string" then
+ local path = data:lower()
+ -- Only ogg and mp3 are valid sounds.
+ if not path:find(".ogg", nil, true) and not path:find(".mp3", nil, true) then
+ return false
+ end
+ end
+ if not mediaTable[mediatype] then mediaTable[mediatype] = {} end
+ local mtable = mediaTable[mediatype]
+ if mtable[key] then return false end
+
+ mtable[key] = data
+ rebuildMediaList(mediatype)
+ self.callbacks:Fire("LibSharedMedia_Registered", mediatype, key)
+ return true
+end
+
+function lib:Fetch(mediatype, key, noDefault)
+ local mtt = mediaTable[mediatype]
+ local overridekey = overrideMedia[mediatype]
+ local result = mtt and ((overridekey and mtt[overridekey] or mtt[key]) or (not noDefault and defaultMedia[mediatype] and mtt[defaultMedia[mediatype]])) or nil
+ return result ~= "" and result or nil
+end
+
+function lib:IsValid(mediatype, key)
+ return mediaTable[mediatype] and (not key or mediaTable[mediatype][key]) and true or false
+end
+
+function lib:HashTable(mediatype)
+ return mediaTable[mediatype]
+end
+
+function lib:List(mediatype)
+ if not mediaTable[mediatype] then
+ return nil
+ end
+ if not mediaList[mediatype] then
+ rebuildMediaList(mediatype)
+ end
+ return mediaList[mediatype]
+end
+
+function lib:GetGlobal(mediatype)
+ return overrideMedia[mediatype]
+end
+
+function lib:SetGlobal(mediatype, key)
+ if not mediaTable[mediatype] then
+ return false
+ end
+ overrideMedia[mediatype] = (key and mediaTable[mediatype][key]) and key or nil
+ self.callbacks:Fire("LibSharedMedia_SetGlobal", mediatype, overrideMedia[mediatype])
+ return true
+end
+
+function lib:GetDefault(mediatype)
+ return defaultMedia[mediatype]
+end
+
+function lib:SetDefault(mediatype, key)
+ if mediaTable[mediatype] and mediaTable[mediatype][key] and not defaultMedia[mediatype] then
+ defaultMedia[mediatype] = key
+ return true
+ else
+ return false
+ end
+end
diff --git a/Libs/LibSharedMedia-3.0/lib.xml b/Libs/LibSharedMedia-3.0/lib.xml
new file mode 100644
index 00000000..5b4a5c87
--- /dev/null
+++ b/Libs/LibSharedMedia-3.0/lib.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/Libs/LibStub/LibStub.lua b/Libs/LibStub/LibStub.lua
new file mode 100644
index 00000000..cfc97de7
--- /dev/null
+++ b/Libs/LibStub/LibStub.lua
@@ -0,0 +1,30 @@
+-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info
+-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke
+local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS!
+local LibStub = _G[LIBSTUB_MAJOR]
+
+if not LibStub or LibStub.minor < LIBSTUB_MINOR then
+ LibStub = LibStub or {libs = {}, minors = {} }
+ _G[LIBSTUB_MAJOR] = LibStub
+ LibStub.minor = LIBSTUB_MINOR
+
+ function LibStub:NewLibrary(major, minor)
+ assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)")
+ minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.")
+
+ local oldminor = self.minors[major]
+ if oldminor and oldminor >= minor then return nil end
+ self.minors[major], self.libs[major] = minor, self.libs[major] or {}
+ return self.libs[major], oldminor
+ end
+
+ function LibStub:GetLibrary(major, silent)
+ if not self.libs[major] and not silent then
+ error(("Cannot find a library instance of %q."):format(tostring(major)), 2)
+ end
+ return self.libs[major], self.minors[major]
+ end
+
+ function LibStub:IterateLibraries() return pairs(self.libs) end
+ setmetatable(LibStub, { __call = LibStub.GetLibrary })
+end
diff --git a/Libs/LibWindow-1.1/LibWindow-1.1.lua b/Libs/LibWindow-1.1/LibWindow-1.1.lua
new file mode 100644
index 00000000..dd9d2b04
--- /dev/null
+++ b/Libs/LibWindow-1.1/LibWindow-1.1.lua
@@ -0,0 +1,317 @@
+--[[
+Name: LibWindow-1.1
+Revision: $Rev: 8 $
+Author(s): Mikk (dpsgnome@mail.com)
+Website: http://old.wowace.com/wiki/LibWindow-1.1
+Documentation: http://old.wowace.com/wiki/LibWindow-1.1
+SVN: http://svn.wowace.com/root/trunk/WindowLib/Window-1.0
+Description: A library that handles the basics of "window" style frames: scaling, smart position saving, dragging..
+Dependencies: none
+License: Public Domain
+]]
+
+local MAJOR = "LibWindow-1.1"
+local MINOR = tonumber(("$Revision: 8 $"):match("(%d+)"))
+
+local lib = LibStub:NewLibrary(MAJOR,MINOR)
+if not lib then return end
+
+local min,max,abs = min,max,abs
+local pairs = pairs
+local tostring = tostring
+local UIParent,GetScreenWidth,GetScreenHeight,IsAltKeyDown = UIParent,GetScreenWidth,GetScreenHeight,IsAltKeyDown
+-- GLOBALS: error, ChatFrame1, assert
+
+local function print(msg) ChatFrame1:AddMessage(MAJOR..": "..tostring(msg)) end
+
+lib.utilFrame = lib.utilFrame or CreateFrame("Frame")
+lib.delayedSavePosition = lib.delayedSavePosition or {}
+lib.windowData = lib.windowData or {}
+ --[frameref]={
+ -- names={optional names data from .RegisterConfig()}
+ -- storage= -- tableref where config data is read/written
+ -- altEnable=true/false
+ --}
+
+
+lib.embeds = lib.embeds or {}
+
+local mixins = {} -- "FuncName"=true
+
+
+
+---------------------------------------------------------
+-- UTILITIES
+---------------------------------------------------------
+
+
+local function getStorageName(frame, name)
+ local names = lib.windowData[frame].names
+ if names then
+ if names[name] then
+ return names[name]
+ end
+ if names.prefix then
+ return names.prefix .. name;
+ end
+ end
+ return name;
+end
+
+local function setStorage(frame, name, value)
+ lib.windowData[frame].storage[getStorageName(frame, name)] = value
+end
+
+local function getStorage(frame, name)
+ return lib.windowData[frame].storage[getStorageName(frame, name)]
+end
+
+
+lib.utilFrame:SetScript("OnUpdate", function(this)
+ this:Hide()
+ for frame,_ in pairs(lib.delayedSavePosition) do
+ lib.delayedSavePosition[frame] = nil
+ lib.SavePosition(frame)
+ end
+end)
+
+local function queueSavePosition(frame)
+ lib.delayedSavePosition[frame] = true
+ lib.utilFrame:Show()
+end
+
+
+---------------------------------------------------------
+-- IMPORTANT APIS
+---------------------------------------------------------
+
+mixins["RegisterConfig"]=true
+function lib.RegisterConfig(frame, storage, names)
+ if not lib.windowData[frame] then
+ lib.windowData[frame] = {}
+ end
+ lib.windowData[frame].names = names
+ lib.windowData[frame].storage = storage
+
+ --[[ debug
+ frame.tx = frame:CreateTexture()
+ frame.tx:SetTexture(0,0,0, 0.4)
+ frame.tx:SetAllPoints(frame)
+ frame.tx:Show()
+ ]]
+end
+
+
+
+
+---------------------------------------------------------
+-- POSITIONING AND SCALING
+---------------------------------------------------------
+
+local nilParent = {
+ GetWidth = function()
+ return GetScreenWidth() * UIParent:GetScale()
+ end,
+ GetHeight = function()
+ return GetScreenHeight() * UIParent:GetScale()
+ end,
+ GetScale = function()
+ return 1
+ end,
+}
+
+mixins["SavePosition"]=true
+function lib.SavePosition(frame)
+ local parent = frame:GetParent() or nilParent
+ -- No, this won't work very well with frames that aren't parented to nil or UIParent
+ local s = frame:GetScale()
+ local left,top = frame:GetLeft()*s, frame:GetTop()*s
+ local right,bottom = frame:GetRight()*s, frame:GetBottom()*s
+ local pwidth, pheight = parent:GetWidth(), parent:GetHeight()
+
+ local x,y,point;
+ if left < (pwidth-right) and left < abs((left+right)/2 - pwidth/2) then
+ x = left;
+ point="LEFT";
+ elseif (pwidth-right) < abs((left+right)/2 - pwidth/2) then
+ x = right-pwidth;
+ point="RIGHT";
+ else
+ x = (left+right)/2 - pwidth/2;
+ point="";
+ end
+
+ if bottom < (pheight-top) and bottom < abs((bottom+top)/2 - pheight/2) then
+ y = bottom;
+ point="BOTTOM"..point;
+ elseif (pheight-top) < abs((bottom+top)/2 - pheight/2) then
+ y = top-pheight;
+ point="TOP"..point;
+ else
+ y = (bottom+top)/2 - pheight/2;
+ -- point=""..point;
+ end
+
+ if point=="" then
+ point = "CENTER"
+ end
+
+ setStorage(frame, "x", x)
+ setStorage(frame, "y", y)
+ setStorage(frame, "point", point)
+ setStorage(frame, "scale", s)
+
+ frame:ClearAllPoints()
+ frame:SetPoint(point, frame:GetParent(), point, x/s, y/s);
+end
+
+
+mixins["RestorePosition"]=true
+function lib.RestorePosition(frame)
+ local x = getStorage(frame, "x")
+ local y = getStorage(frame, "y")
+ local point = getStorage(frame, "point")
+
+ local s = getStorage(frame, "scale")
+ if s then
+ (frame.lw11origSetScale or frame.SetScale)(frame,s)
+ else
+ s = frame:GetScale()
+ end
+
+ if not x or not y then -- nothing stored in config yet, smack it in the center
+ x=0; y=0; point="CENTER"
+ end
+
+ x = x/s
+ y = y/s
+
+ frame:ClearAllPoints()
+ if not point and y==0 then -- errr why did i do this check again? must have been a reason, but i can't remember it =/
+ point="CENTER"
+ end
+
+ if not point then -- we have position, but no point, which probably means we're going from data stored by the addon itself before LibWindow was added to it. It was PROBABLY topleft->bottomleft anchored. Most do it that way.
+ frame:SetPoint("TOPLEFT", frame:GetParent(), "BOTTOMLEFT", x, y)
+ -- make it compute a better attachpoint (on next update)
+ queueSavePosition(frame)
+ return
+ end
+
+ frame:SetPoint(point, frame:GetParent(), point, x, y)
+end
+
+
+mixins["SetScale"]=true
+function lib.SetScale(frame, scale)
+ setStorage(frame, "scale", scale);
+ (frame.lw11origSetScale or frame.SetScale)(frame,scale)
+ lib.RestorePosition(frame)
+end
+
+
+
+---------------------------------------------------------
+-- DRAG SUPPORT
+---------------------------------------------------------
+
+
+function lib.OnDragStart(frame)
+ lib.windowData[frame].isDragging = true
+ frame:StartMoving()
+end
+
+
+function lib.OnDragStop(frame)
+ frame:StopMovingOrSizing()
+ lib.SavePosition(frame)
+ lib.windowData[frame].isDragging = false
+ if lib.windowData[frame].altEnable and not IsAltKeyDown() then
+ frame:EnableMouse(false)
+ end
+end
+
+local function onDragStart(...) return lib.OnDragStart(...) end -- upgradable
+local function onDragStop(...) return lib.OnDragStop(...) end -- upgradable
+
+mixins["MakeDraggable"]=true
+function lib.MakeDraggable(frame)
+ assert(lib.windowData[frame])
+ frame:SetMovable(true)
+ frame:SetScript("OnDragStart", onDragStart)
+ frame:SetScript("OnDragStop", onDragStop)
+ frame:RegisterForDrag("LeftButton")
+end
+
+
+---------------------------------------------------------
+-- MOUSEWHEEL
+---------------------------------------------------------
+
+function lib.OnMouseWheel(frame, dir)
+ local scale = getStorage(frame, "scale")
+ if dir<0 then
+ scale=max(scale*0.9, 0.1)
+ else
+ scale=min(scale/0.9, 3)
+ end
+ lib.SetScale(frame, scale)
+end
+
+local function onMouseWheel(...) return lib.OnMouseWheel(...) end -- upgradable
+
+mixins["EnableMouseWheelScaling"]=true
+function lib.EnableMouseWheelScaling(frame)
+ frame:SetScript("OnMouseWheel", onMouseWheel)
+end
+
+
+---------------------------------------------------------
+-- ENABLEMOUSE-ON-ALT
+---------------------------------------------------------
+
+lib.utilFrame:SetScript("OnEvent", function(this, event, key, state)
+ if event=="MODIFIER_STATE_CHANGED" then
+ if key == "LALT" or key == "RALT" then
+ for frame,_ in pairs(lib.altEnabledFrames) do
+ if not lib.windowData[frame].isDragging then -- if it's already dragging, it'll disable mouse on DragStop instead
+ frame:EnableMouse(state == 1)
+ end
+ end
+ end
+ end
+end)
+
+mixins["EnableMouseOnAlt"]=true
+function lib.EnableMouseOnAlt(frame)
+ assert(lib.windowData[frame])
+ lib.windowData[frame].altEnable = true
+ frame:EnableMouse(not not IsAltKeyDown())
+ if not lib.altEnabledFrames then
+ lib.altEnabledFrames = {}
+ lib.utilFrame:RegisterEvent("MODIFIER_STATE_CHANGED")
+ end
+ lib.altEnabledFrames[frame] = true
+end
+
+
+
+---------------------------------------------------------
+-- Embed support (into FRAMES, not addons!)
+---------------------------------------------------------
+
+function lib:Embed(target)
+ if not target or not target[0] or not target.GetObjectType then
+ error("Usage: LibWindow:Embed(frame)", 1)
+ end
+ target.lw11origSetScale = target.SetScale
+ for name, _ in pairs(mixins) do
+ target[name] = self[name]
+ end
+ lib.embeds[target] = true
+ return target
+end
+
+for target, _ in pairs(lib.embeds) do
+ lib:Embed(target)
+end
diff --git a/Libs/LibsLibDataBroker-1.1/Changelog-libdatabroker-1-1-v1.1.4.txt b/Libs/LibsLibDataBroker-1.1/Changelog-libdatabroker-1-1-v1.1.4.txt
new file mode 100644
index 00000000..d5b31ede
--- /dev/null
+++ b/Libs/LibsLibDataBroker-1.1/Changelog-libdatabroker-1-1-v1.1.4.txt
@@ -0,0 +1,33 @@
+tag v1.1.4
+ddb0519a000c69ddf3a28c3f9fe2e62bb3fd00c5
+Tekkub
+2008-11-06 22:03:04 -0700
+
+Build 1.1.4
+
+
+--------------------
+
+Tekkub:
+ Add pairs and ipairs iters, since we can't use the normal iters on our dataobjs
+ Simplify readme, all docs have been moved into GitHub wiki pages
+ Documentation on how to use LDB data (for display addons)
+ Add StatBlockCore forum link
+ Add link to Fortress thread
+ And rearrange the addon list a bit too
+ Make field lists into nice pretty tables
+ Add list of who is using LDB
+ Always with the typos, I hate my fingers
+ Add tooltiptext and OnTooltipShow to data addon spec
+ Readme rejiggering
+ Add in some documentation on how to push data into LDB
+ Meh, fuck you textile
+ Adding readme
+ Pass current dataobj with attr change callbacks to avoid excessive calls to :GetDataObjectByName
+Tekkub Stoutwrithe:
+ Make passed dataobj actually work
+ I always forget the 'then'
+ Minor memory optimization
+ - Only hold upvalues to locals in the functions called frequently
+ - Retain the metatable across future lib upgrades (the one in v1 will be lost)
+ Allow caller to pass a pre-populated table to NewDataObject
diff --git a/Libs/LibsLibDataBroker-1.1/LibDataBroker-1.1.lua b/Libs/LibsLibDataBroker-1.1/LibDataBroker-1.1.lua
new file mode 100644
index 00000000..4182f2e1
--- /dev/null
+++ b/Libs/LibsLibDataBroker-1.1/LibDataBroker-1.1.lua
@@ -0,0 +1,90 @@
+
+assert(LibStub, "LibDataBroker-1.1 requires LibStub")
+assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
+
+local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
+if not lib then return end
+oldminor = oldminor or 0
+
+
+lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
+lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
+local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
+
+if oldminor < 2 then
+ lib.domt = {
+ __metatable = "access denied",
+ __index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
+ }
+end
+
+if oldminor < 3 then
+ lib.domt.__newindex = function(self, key, value)
+ if not attributestorage[self] then attributestorage[self] = {} end
+ if attributestorage[self][key] == value then return end
+ attributestorage[self][key] = value
+ local name = namestorage[self]
+ if not name then return end
+ callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
+ callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
+ end
+end
+
+if oldminor < 2 then
+ function lib:NewDataObject(name, dataobj)
+ if self.proxystorage[name] then return end
+
+ if dataobj then
+ assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
+ self.attributestorage[dataobj] = {}
+ for i,v in pairs(dataobj) do
+ self.attributestorage[dataobj][i] = v
+ dataobj[i] = nil
+ end
+ end
+ dataobj = setmetatable(dataobj or {}, self.domt)
+ self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
+ self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
+ return dataobj
+ end
+end
+
+if oldminor < 1 then
+ function lib:DataObjectIterator()
+ return pairs(self.proxystorage)
+ end
+
+ function lib:GetDataObjectByName(dataobjectname)
+ return self.proxystorage[dataobjectname]
+ end
+
+ function lib:GetNameByDataObject(dataobject)
+ return self.namestorage[dataobject]
+ end
+end
+
+if oldminor < 4 then
+ local next = pairs(attributestorage)
+ function lib:pairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return next, attributestorage[dataobj], nil
+ end
+
+ local ipairs_iter = ipairs(attributestorage)
+ function lib:ipairs(dataobject_or_name)
+ local t = type(dataobject_or_name)
+ assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)")
+
+ local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+ assert(attributestorage[dataobj], "Data object not found")
+
+ return ipairs_iter, attributestorage[dataobj], 0
+ end
+end
diff --git a/Libs/LibsLibDataBroker-1.1/README.textile b/Libs/LibsLibDataBroker-1.1/README.textile
new file mode 100644
index 00000000..ef16fede
--- /dev/null
+++ b/Libs/LibsLibDataBroker-1.1/README.textile
@@ -0,0 +1,13 @@
+LibDataBroker is a small WoW addon library designed to provide a "MVC":http://en.wikipedia.org/wiki/Model-view-controller interface for use in various addons.
+LDB's primary goal is to "detach" plugins for TitanPanel and FuBar from the display addon.
+Plugins can provide data into a simple table, and display addons can receive callbacks to refresh their display of this data.
+LDB also provides a place for addons to register "quicklaunch" functions, removing the need for authors to embed many large libraries to create minimap buttons.
+Users who do not wish to be "plagued" by these buttons simply do not install an addon to render them.
+
+Due to it's simple generic design, LDB can be used for any design where you wish to have an addon notified of changes to a table.
+
+h2. Links
+
+* "API documentation":http://github.com/tekkub/libdatabroker-1-1/wikis/api
+* "Data specifications":http://github.com/tekkub/libdatabroker-1-1/wikis/data-specifications
+* "Addons using LDB":http://github.com/tekkub/libdatabroker-1-1/wikis/addons-using-ldb
diff --git a/Libs/NickTag-1.0/NickTag-1.0.lua b/Libs/NickTag-1.0/NickTag-1.0.lua
new file mode 100644
index 00000000..9ff9ff79
--- /dev/null
+++ b/Libs/NickTag-1.0/NickTag-1.0.lua
@@ -0,0 +1,1216 @@
+--> Library NickTag is a small library for share individual nicknames and avatars.
+
+--> Basic Functions:
+-- NickTag:SetNickname (name) -> set the player nick name, after set nicktag will broadcast the nick over addon guild channel.
+--
+
+local major, minor = "NickTag-1.0", 9
+local NickTag, oldminor = LibStub:NewLibrary (major, minor)
+
+if (not NickTag) then
+ return
+end
+
+--> fix for old nicktag version
+if (_G.NickTag) then
+ if (_G.NickTag.OnEvent) then
+ _G.NickTag:UnregisterComm ("NickTag")
+ _G.NickTag.OnEvent = nil
+ end
+end
+
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> constants
+
+ local CONST_INDEX_NICKNAME = 1
+ local CONST_INDEX_AVATAR_PATH = 2
+ local CONST_INDEX_AVATAR_TEXCOORD = 3
+ local CONST_INDEX_BACKGROUND_PATH = 4
+ local CONST_INDEX_BACKGROUND_TEXCOORD = 5
+ local CONST_INDEX_BACKGROUND_COLOR = 6
+ local CONST_INDEX_REVISION = 7
+
+ local CONST_COMM_FULLPERSONA = 1
+ local CONST_COMM_LOGONREVISION = 2
+ local CONST_COMM_REQUESTPERSONA = 3
+
+ --[[global]] NICKTAG_DEFAULT_AVATAR = [[Interface\EncounterJournal\UI-EJ-BOSS-Default]]
+ --[[global]] NICKTAG_DEFAULT_BACKGROUND = [[Interface\PetBattles\Weather-ArcaneStorm]]
+ --[[global]] NICKTAG_DEFAULT_BACKGROUND_CORDS = {0.129609375, 1, 1, 0}
+
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> library stuff
+
+ _G.NickTag = NickTag --> nicktag object over global container
+
+ local pool = {default = true} --> pointer to the cache pool and the default pool if no cache
+ local queue_request = {}
+ local queue_send = {}
+ local last_queue = 0
+ local is_updating = false
+ NickTag.debug = false
+
+ local GetGuildRosterInfo = GetGuildRosterInfo
+
+ LibStub:GetLibrary ("AceComm-3.0"):Embed (NickTag)
+ LibStub:GetLibrary ("AceSerializer-3.0"):Embed (NickTag)
+ LibStub:GetLibrary ("AceTimer-3.0"):Embed (NickTag)
+ local CallbackHandler = LibStub:GetLibrary ("CallbackHandler-1.0")
+ NickTag.callbacks = NickTag.callbacks or CallbackHandler:New (NickTag)
+
+ NickTag.embeds = NickTag.embeds or {}
+ local embed_functions = {
+ "SetNickname",
+ "SetNicknameAvatar",
+ "SetNicknameBackground",
+ "GetNickname",
+ "GetNicknameAvatar",
+ "GetNicknameBackground",
+ "GetNicknameTable",
+ "NickTagSetCache"
+ }
+ function NickTag:Embed (target)
+ for k, v in pairs (embed_functions) do
+ target[v] = self[v]
+ end
+ self.embeds [target] = true
+ return target
+ end
+
+ function NickTag:Msg (text)
+ print ("|cFFFFFF00NickTag:|r",text)
+ end
+
+ local enUS = LibStub("AceLocale-3.0"):NewLocale ("NickTag-1.0", "enUS", true)
+ if (enUS) then
+ enUS ["STRING_ERROR_1"] = "Your nickname is too long, max of 12 characters is allowed."
+ enUS ["STRING_ERROR_2"] = "Only letters and two spaces are allowed."
+ enUS ["STRING_ERROR_3"] = "You can't use the same letter three times consecutively, two spaces consecutively or more then two spaces."
+ enUS ["STRING_INVALID_NAME"] = "Invalid Name"
+ end
+
+ local ptBR = LibStub("AceLocale-3.0"):NewLocale ("NickTag-1.0", "ptBR")
+ if (ptBR) then
+ ptBR ["STRING_ERROR_1"] = "Seu apelido esta muito longo, o maximo permitido sao 12 caracteres."
+ ptBR ["STRING_ERROR_2"] = "Apenas letras, numeros e espacos sao permitidos no apelido."
+ ptBR ["STRING_ERROR_3"] = "Voce nao pode usar a mesma letra mais de 2 vezes consecutivas, dois espacos consecutivos ou mais de 2 espacos."
+ ptBR ["STRING_INVALID_NAME"] = "Nome Invalido"
+ end
+
+ NickTag.background_pool = {
+ {[[Interface\PetBattles\Weather-ArcaneStorm]], "Arcane Storm", {0.129609375, 1, 1, 0}},
+ {[[Interface\PetBattles\Weather-Blizzard]], "Blizzard", {0.068704154, 1, 1, 0}},
+ {[[Interface\PetBattles\Weather-BurntEarth]], "Burnt Earth", {0.087890625, 0.916015625, 1, 0}},
+ {[[Interface\PetBattles\Weather-Darkness]], "Darkness", {0.080078125, 0.931640625, 1, 0}},
+ {[[Interface\PetBattles\Weather-Moonlight]], "Moonlight", {0.02765625, 0.94359375, 1, 0}},
+ {[[Interface\PetBattles\Weather-Moonlight]], "Moonlight (reverse)", {0.94359375, 0.02765625, 1, 0}},
+ {[[Interface\PetBattles\Weather-Mud]], "Mud", {0.068359375, 0.94359375, 1, 0}},
+ {[[Interface\PetBattles\Weather-Rain]], "Rain", {0.078125, 0.970703125, 1, 0}},
+ {[[Interface\PetBattles\Weather-Sandstorm]], "Sand Storm", {0.048828125, 0.947265625, 1, 0}},
+ {[[Interface\PetBattles\Weather-StaticField]], "Static Field", {0.1171875, 0.953125, 1, 0}},
+ {[[Interface\PetBattles\Weather-Sunlight]], "Sun Light", {0.1772721, 0.953125, 1, 0}},
+ {[[Interface\PetBattles\Weather-Windy]], "Windy", {0.9453125, 0.07421875, 0.8203125, 0}}
+ }
+
+ NickTag.avatar_pool = {
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Arcanist Doan]], "Arcanist Doan"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Archbishop Benedictus]], "Archbishop Benedictus"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Argent Confessor Paletress]], "Argent Confessor Paletress"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Armsmaster Harlan]], "Armsmaster Harlan"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Asira Dawnslayer]], "Asira Dawnslayer"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Baelog]], "Baelog"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Baron Ashbury]], "Baron Ashbury"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Baron Silverlaine]], "Baron Silverlaine"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Blood Guard Porung]], "Blood Guard Porung"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Bronjahm]], "Bronjahm"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Brother Korloff]], "Brother Korloff"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Captain Skarloc]], "Captain Skarloc"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Chief Ukorz Sandscalp]], "Chief Ukorz Sandscalp"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Commander Kolurg]], "Commander Kolurg"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Commander Malor]], "Commander Malor"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Commander Sarannis]], "Commander Sarannis"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Commander Springvale]], "Commander Springvale"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Commander Stoutbeard]], "Commander Stoutbeard"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Corla, Herald of Twilight]], "Corla, Herald of Twilight"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Cyanigosa]], "Cyanigosa"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Darkmaster Gandling]], "Darkmaster Gandling"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Doctor Theolen Krastinov]], "Doctor Theolen Krastinov"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-DoomRel]], "DoomRel"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Eadric the Pure]], "Eadric the Pure"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Emperor Thaurissan]], "Emperor Thaurissan"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Empyreal Queens]], "Lu'lin"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Exarch Maladaar]], "Exarch Maladaar"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Fineous Darkvire]], "Fineous Darkvire"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Galdarah]], "Galdarah"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Garajal the Spiritbinder]], "Garajal the Spiritbinder"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Garrosh Hellscream]], "Garrosh Hellscream"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-General Nazgrim]], "General Nazgrim"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Grand Champions-Alliance]], "Grand Champions-Alliance"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Grand Champions-Horde]], "Grand Champions-Horde"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Grand Magus Telestra]], "Grand Magus Telestra"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-HateRel]], "HateRel"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Hazzarah]], "Hazzarah"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Hearthsinger Forresten]], "Hearthsinger Forresten"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Helix Gearbreaker]], "Helix Gearbreaker"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-High Botanist Freywinn]], "High Botanist Freywinn"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-High Inquisitor Whitemane]], "High Inquisitor Whitemane"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-High Interrogator Gerstahn]], "High Interrogator Gerstahn"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-High Justice Grimstone]], "High Justice Grimstone"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Houndmaster Braun]], "Houndmaster Braun"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Houndmaster Loksey]], "Houndmaster Loksey"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Hydromancer Velratha]], "Hydromancer Velratha"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Illyanna Ravenoak]], "Illyanna Ravenoak"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Ingvar the Plunderer]], "Ingvar the Plunderer"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Instructor Galford]], "Instructor Galford"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Instructor Malicia]], "Instructor Malicia"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Interrogator Vishas]], "Interrogator Vishas"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Isiset]], "Isiset"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-JainaProudmoore]], "Jaina Proudmoore"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Jandice Barov]], "Jandice Barov"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Kaelthas Sunstrider]], "Kaelthas Sunstrider"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Kelidan the Breaker]], "Kelidan the Breaker"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Krick]], "Krick"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lady Anacondra]], "Lady Anacondra"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lady Illucia Barov]], "Lady Illucia Barov"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lethtendris]], "Lethtendris"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Loken]], "Loken"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lord Alexei Barov]], "Lord Alexei Barov"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lord Aurius Rivendare]], "Lord Aurius Rivendare"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lord Cobrahn]], "Lord Cobrahn"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lord Pythas]], "Lord Pythas"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lord Serpentis]], "Lord Serpentis"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Lorgus Jett]], "Lorgus Jett"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Mage Lord Urom]], "Mage Lord Urom"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Magister Kalendris]], "Magister Kalendris"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Magistrate Barthilas]], "Magistrate Barthilas"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Maiden of Grief]], "Maiden of Grief"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Maleki the Pallid]], "Maleki the Pallid"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Nethermancer Sepethrea]], "Nethermancer Sepethrea"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Olaf]], "Olaf"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Pathaleon the Calculator]], "Pathaleon the Calculator"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Prince Tortheldrin]], "Prince Tortheldrin"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Princess Moira Bronzebeard]], "Princess Moira Bronzebeard"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-QueenAzshara]], "Queen Azshara"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Randolph Moloch]], "Randolph Moloch"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Renataki]], "Renataki"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Ribbly Screwspigot]], "Ribbly Screwspigot"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Scarlet Commander Mograine]], "Scarlet Commander Mograine"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Selin Fireheart]], "Selin Fireheart"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Siegecrafter Blackfuse]], "Siegecrafter Blackfuse"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Skarvald the Constructor]], "Skarvald the Constructor"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Tribunal of the Ages]], "Tribunal of the Ages"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-TyrandeWhisperwind]], "Tyrande Whisperwind"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Twilight Lord Kelris]], "Twilight Lord Kelris"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Vanessa VanCleef]], "Vanessa VanCleef"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Vazruden]], "Vazruden"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Warchief Rend Blackhand]], "Warchief Rend Blackhand"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Willey Hopebreaker]], "Willey Hopebreaker"},
+ {[[Interface\EncounterJournal\UI-EJ-BOSS-Witch Doctor Zumrah]], "Witch Doctor Zumrah"},
+ }
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> send and receive functions
+
+ function NickTag:OnReceiveComm (_, data, _, source)
+
+ local _type, serial, arg3, name, realm, version = select (2, NickTag:Deserialize (data))
+
+ --> 0x1: received a full persona
+ if (_type == CONST_COMM_FULLPERSONA) then
+ local receivedPersona = arg3
+ version = name
+
+ if (serial ~= NickTag:GetSerial() and (version and version == minor)) then
+
+ local storedPersona = NickTag:GetNicknameTable (serial)
+ if (not storedPersona) then
+ storedPersona = NickTag:Create (serial)
+ end
+
+ if (storedPersona [CONST_INDEX_REVISION] < receivedPersona [CONST_INDEX_REVISION]) then
+ storedPersona [CONST_INDEX_REVISION] = receivedPersona [CONST_INDEX_REVISION]
+
+ --> we need to check if the received nickname fit in our rules.
+ --local allowNickName = NickTag:CheckName (receivedPersona [CONST_INDEX_NICKNAME])
+ --if (allowNickName) then
+ -- storedPersona [CONST_INDEX_NICKNAME] = receivedPersona [CONST_INDEX_NICKNAME]
+ --else
+ --storedPersona [CONST_INDEX_NICKNAME] = LibStub ("AceLocale-3.0"):GetLocale ("NickTag-1.0")["STRING_INVALID_NAME"]
+ --end
+
+ storedPersona [CONST_INDEX_NICKNAME] = receivedPersona [CONST_INDEX_NICKNAME]
+
+ --> update the rest
+ storedPersona [CONST_INDEX_AVATAR_PATH] = receivedPersona [CONST_INDEX_AVATAR_PATH]
+ storedPersona [CONST_INDEX_AVATAR_TEXCOORD] = receivedPersona [CONST_INDEX_AVATAR_TEXCOORD]
+ storedPersona [CONST_INDEX_BACKGROUND_TEXCOORD] = receivedPersona [CONST_INDEX_BACKGROUND_TEXCOORD]
+ storedPersona [CONST_INDEX_BACKGROUND_PATH] = receivedPersona [CONST_INDEX_BACKGROUND_PATH]
+ storedPersona [CONST_INDEX_BACKGROUND_COLOR] = receivedPersona [CONST_INDEX_BACKGROUND_COLOR]
+ end
+ end
+
+ --> 0x2: received a revision version from a guy which logon in the game
+ elseif (_type == CONST_COMM_LOGONREVISION) then
+
+ if (UnitName ("player") == name) then
+ return
+ end
+
+ local receivedRevision = arg3
+ local storedPersona = NickTag:GetNicknameTable (serial)
+
+ if (NickTag.debug) then
+ NickTag:Msg ("LOGONREVISION from: " .. name .. " rev: " .. receivedRevision)
+ end
+
+ if (type (version) ~= "number" or version ~= minor) then
+ return
+ end
+
+ if (not storedPersona or storedPersona [CONST_INDEX_REVISION] < receivedRevision) then
+ --> not sure how connected realms will work, but guess this will be fine
+ if (realm ~= GetRealmName()) then
+ name = name .. "-" .. realm
+ end
+
+ --> put in queue our request for receive a updated persona
+ NickTag:ScheduleTimer ("QueueRequest", math.random (10, 60), name)
+
+ if (NickTag.debug) then
+ NickTag:Msg ("LOGONREVISION from: " .. name .. " |cFFFF0000is out of date|r, queueing a request persona.")
+ end
+ else
+ if (NickTag.debug) then
+ NickTag:Msg ("LOGONREVISION from: " .. name .. " |cFF00FF00is up to date.")
+ end
+ end
+
+ --> 0x3: someone requested my persona, so i need to send to him
+ elseif (_type == CONST_COMM_REQUESTPERSONA) then
+
+ if (type (version) ~= "number" or version ~= minor) then
+ return
+ end
+
+ --> not sure how connected realms will work, but guess this will be fine
+ if (realm ~= GetRealmName()) then
+ name = name .. "-" .. realm
+ end
+
+ --> queue to send our persona for requested person
+ if (NickTag.debug) then
+ NickTag:Msg ("REQUESTPERSONA from: " .. name .. ", the request has been placed in queue.")
+ end
+
+ NickTag:QueueSend (name)
+ end
+
+ end
+
+ NickTag:RegisterComm ("NickTag", "OnReceiveComm")
+
+ function NickTag:UpdateRoster()
+ --> do not update roster if is in combat
+ if (not UnitAffectingCombat ("player")) then
+ GuildRoster()
+ end
+ end
+
+ function NickTag:IsOnline (name)
+
+ local isShownOffline = GetGuildRosterShowOffline()
+ if (isShownOffline) then
+ SetGuildRosterShowOffline (false)
+ end
+
+ local _, numOnlineMembers = GetNumGuildMembers()
+
+ if (NickTag.debug) then
+ NickTag:Msg ("IsOnline(): " .. numOnlineMembers .. " online members.")
+ end
+
+ for i = 1, numOnlineMembers do
+ local player_name = GetGuildRosterInfo (i)
+ if (player_name:find (name)) then
+ if (isShownOffline) then
+ SetGuildRosterShowOffline (true)
+ end
+ return true
+ end
+ end
+ if (isShownOffline) then
+ SetGuildRosterShowOffline (true)
+ end
+ return false
+ end
+
+ local event_frame = CreateFrame ("frame", nil, UIParent)
+ event_frame:Hide()
+ event_frame:SetScript ("OnEvent", function (_, _, local_update)
+ if (not local_update) then
+
+ --> roster was been updated
+ if (last_queue < time()) then
+ last_queue = time()+11
+ else
+ return
+ end
+
+ --> do not share if we are in combat
+ if (UnitAffectingCombat ("player")) then
+ return
+ end
+
+ --> start with send requested personas
+ if (#queue_send > 0) then
+
+ local name = queue_send [1]
+ table.remove (queue_send, 1)
+
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> ready to send persona to " .. name)
+ end
+
+ --> check if the player is online
+ if (NickTag:IsOnline (name)) then
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> " .. name .. " is online, running SendPersona().")
+ end
+ NickTag:SendPersona (name)
+ else
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> " .. name .. " is offline, cant request his persona.")
+ end
+ end
+
+ if (#queue_send == 0 and #queue_request == 0) then
+ NickTag:StopRosterUpdates()
+ end
+
+ elseif (#queue_request > 0) then
+
+ local name = queue_request [1]
+ table.remove (queue_request, 1)
+
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> ready to request the persona of " .. name)
+ end
+
+ --> check if the player is online
+ if (NickTag:IsOnline (name)) then
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> " .. name .. " is online, running RequestPersona().")
+ end
+ NickTag:RequestPersona (name)
+ else
+ if (NickTag.debug) then
+ NickTag:Msg ("QUEUE -> " .. name .. " is offline, cant request his persona.")
+ end
+ end
+
+ if (#queue_request == 0 and #queue_request == 0) then
+ NickTag:StopRosterUpdates()
+ end
+
+ else
+ NickTag:StopRosterUpdates()
+ end
+ end
+ end)
+
+ function NickTag:StopRosterUpdates()
+ if (NickTag.debug) then
+ NickTag:Msg ("ROSTER -> updates has been stopped")
+ end
+ if (NickTag.UpdateRosterTimer) then
+ NickTag:CancelTimer (NickTag.UpdateRosterTimer)
+ end
+ NickTag.UpdateRosterTimer = nil
+ event_frame:UnregisterEvent ("GUILD_ROSTER_UPDATE")
+ is_updating = false
+ end
+
+ function NickTag:StartRosterUpdates()
+ if (NickTag.debug) then
+ NickTag:Msg ("ROSTER -> updates has been actived")
+ end
+ event_frame:RegisterEvent ("GUILD_ROSTER_UPDATE")
+ if (not NickTag.UpdateRosterTimer) then
+ NickTag.UpdateRosterTimer = NickTag:ScheduleRepeatingTimer ("UpdateRoster", 12)
+ if (NickTag.debug) then
+ NickTag:Msg ("ROSTER -> new update thread created.")
+ end
+ else
+ if (NickTag.debug) then
+ NickTag:Msg ("ROSTER -> a update thread already exists.")
+ end
+ end
+ is_updating = true
+ end
+
+ --> we queue data for roster update and also check for combat
+ function NickTag:QueueRequest (name)
+ table.insert (queue_request, name)
+ if (not is_updating) then
+ NickTag:StartRosterUpdates()
+ end
+ end
+ function NickTag:QueueSend (name)
+ table.insert (queue_send, name)
+ if (not is_updating) then
+ NickTag:StartRosterUpdates()
+ end
+ end
+
+ --> after logon, we send our revision, who needs update my persona will send 0x3 (request persona) to me and i send back 0x1 (send persona)
+ function NickTag:SendRevision()
+
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ local myPersona = NickTag:GetNicknameTable (battlegroup_serial)
+ if (myPersona) then
+ if (NickTag.debug) then
+ NickTag:Msg ("SendRevision() -> SENT")
+ end
+ if (IsInGuild()) then
+ NickTag:SendCommMessage ("NickTag", NickTag:Serialize (CONST_COMM_LOGONREVISION, battlegroup_serial, myPersona [CONST_INDEX_REVISION], UnitName ("player"), GetRealmName(), minor), "GUILD")
+ end
+ end
+ end
+
+ --> i received 0x2 and his persona is out of date here, so i need to send 0x3 to him and him will send 0x1.
+ function NickTag:RequestPersona (target)
+ if (NickTag.debug) then
+ NickTag:Msg ("RequestPersona() -> requesting of " .. target)
+ end
+ if (IsInGuild()) then
+ NickTag:SendCommMessage ("NickTag", NickTag:Serialize (CONST_COMM_REQUESTPERSONA, 0, 0, UnitName ("player"), GetRealmName(), minor), "WHISPER", target)
+ end
+ end
+
+ --> this broadcast my persona to entire guild when i update my persona or send my persona to someone who doesn't have it or need to update.
+ function NickTag:SendPersona (target)
+ if (target) then
+ if (NickTag.debug) then
+ NickTag:Msg ("SendPersona() -> sent to " .. target)
+ end
+ end
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ --> auto change nickname if we have a invalid nickname
+ if (NickTag:GetNickname (UnitGUID ("player")) == LibStub ("AceLocale-3.0"):GetLocale ("NickTag-1.0")["STRING_INVALID_NAME"]) then
+ local nick_table = NickTag:GetNicknameTable (UnitGUID ("player"))
+ nick_table [CONST_INDEX_NICKNAME] = UnitName ("player")
+ end
+
+ if (target) then
+ --> was requested
+ if (IsInGuild()) then
+ NickTag:SendCommMessage ("NickTag", NickTag:Serialize (CONST_COMM_FULLPERSONA, battlegroup_serial, NickTag:GetNicknameTable (battlegroup_serial), minor), "WHISPER", target)
+ end
+ else
+ --> updating my own persona
+ NickTag.send_scheduled = false
+ --> need to increase 1 revision
+ NickTag:IncRevision()
+ --> broadcast over guild channel
+ if (IsInGuild()) then
+ NickTag:SendCommMessage ("NickTag", NickTag:Serialize (CONST_COMM_FULLPERSONA, battlegroup_serial, NickTag:GetNicknameTable (battlegroup_serial), minor), "GUILD")
+ end
+ end
+ end
+
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> on logon stuff
+
+ --> reset cache
+ function NickTag:ResetCache()
+
+ local guid = UnitGUID ("player")
+
+ if (guid) then
+ local player = NickTag:GetNicknameTable (guid)
+ if (player and pool.last_version == minor) then
+ local serial = NickTag:GetSerial (guid)
+ for this_serial, _ in pairs (pool) do
+ if (this_serial ~= serial) then
+ pool [this_serial] = nil
+ end
+ end
+ --vardump (pool)
+ else
+ table.wipe (pool)
+ end
+
+ pool.nextreset = time() + (60*60*24*15) --> 15 days or 1296000 seconds
+ pool.last_version = minor
+ else
+ --> sometimes player guid isn't available right after logon, so, just schedule until it become available.
+ NickTag:ScheduleTimer ("ResetCache", 0.3)
+ end
+ end
+
+ function NickTag:NickTagSetCache (_table)
+ if (not pool.default) then
+ return table.wipe (_table)
+ end
+
+ pool = _table
+
+ if (not pool.nextreset) then
+ pool.nextreset = time() + (60*60*24*15)
+ end
+ if (not pool.last_version) then
+ pool.last_version = minor
+ end
+ if (pool.last_version < minor) then
+ pool.nextreset = 1
+ end
+ if (time() > pool.nextreset) then
+ NickTag:ResetCache()
+ end
+
+ NickTag:ScheduleTimer ("SendRevision", 30)
+ end
+
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> basic functions
+
+ --> trim from from http://lua-users.org/wiki/StringTrim
+ function trim (s)
+ local from = s:match"^%s*()"
+ return from > #s and "" or s:match(".*%S", from)
+ end
+ --
+ local titlecase = function (first, rest)
+ return first:upper()..rest:lower()
+ end
+ --
+ local have_repeated = false
+ local count_spaces = 0
+ local check_repeated = function (char)
+ if (char == " ") then
+ have_repeated = true
+ elseif (string.len (char) > 2) then
+ have_repeated = true
+ elseif (char == " ") then
+ count_spaces = count_spaces + 1
+ end
+ end
+
+ --> we need to keep game smooth checking and formating nicknames.
+ --> SetNickname and names comming from other player need to be check.
+ function NickTag:CheckName (name)
+
+ --> as nicktag only work internally in the guild, we think that is not necessary a work filter to avoid people using bad language.
+
+ name = trim (name)
+
+ --> limit nickname to 12 characters, same as wow.
+ local len = string.len (name)
+ if (len > 12) then
+ return false, LibStub ("AceLocale-3.0"):GetLocale ("NickTag-1.0")["STRING_ERROR_1"] --> error 1 = nickname is too long, max of 12 characters.
+ end
+
+ --> check if contain any non allowed characters, by now only accpet letters, numbers and spaces.
+ --> by default wow do not accetp spaces, but here will allow.
+ --> tested over lua 5.2 and this capture was okey with accents, not sure why inside wow this doesn't work.
+ local notallow = string.find (name, "[^a-zA-Z%s]")
+ if (notallow) then
+ return false, LibStub ("AceLocale-3.0"):GetLocale ("NickTag-1.0")["STRING_ERROR_2"] --> error 2 = nickname only support letters, numbers and spaces.
+ end
+
+ --> check if there is sequencial repeated characters, like "Jasooon" were repeats 3 times the "o" character.
+ --> got this from http://stackoverflow.com/questions/15608299/lua-pattern-matching-repeating-character
+ have_repeated = false
+ count_spaces = 0
+ string.gsub (name, '.', '\0%0%0'):gsub ('(.)%z%1','%1'):gsub ('%z.([^%z]+)', check_repeated)
+ if (count_spaces > 2) then
+ have_repeated = true
+ end
+ if (have_repeated) then
+ return false, LibStub ("AceLocale-3.0"):GetLocale ("NickTag-1.0")["STRING_ERROR_3"] --> error 3 = cant use the same letter three times consecutively, 2 spaces consecutively or 3 or more spaces.
+ end
+
+ return true
+ end
+
+ --> set the "player" nickname and schedule for send updated persona
+ function NickTag:SetNickname (name)
+ --> check data before
+ assert (type (name) == "string", "NickTag 'SetNickname' expects a string on #1 argument.")
+
+ --> check if the nickname is okey to allowed to use.
+ local okey, errortype = NickTag:CheckName (name)
+ if (not okey) then
+ return false, errortype
+ end
+
+ --> here we format the text to match titles, e.g converts name like "JASON NICKSHOW" into "Jason Nickshow".
+ name = name:gsub ("(%a)([%w_']*)", titlecase)
+
+ --> get player serial, note that serials are unique between battlegroups and we are using serial instead of full GUID just for reduce memory usage,
+ --> e.g guids are strings with 18 characters, serials are 8 digits number (or 9).
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ --> get the full nick table.
+ local nick_table = NickTag:GetNicknameTable (battlegroup_serial)
+ if (not nick_table) then
+ nick_table = NickTag:Create (battlegroup_serial, true)
+ end
+
+ --> change the nickname for the player nick table.
+ if (nick_table [CONST_INDEX_NICKNAME] ~= name) then
+ nick_table [CONST_INDEX_NICKNAME] = name
+
+ --> send the update for script which need it.
+ NickTag.callbacks:Fire ("NickTag_Update", CONST_INDEX_NICKNAME)
+
+ --> schedule a update for revision and broadcast full persona.
+ --> this is a kind of protection for scripts which call SetNickname, SetColor and SetAvatar one after other, so scheduling here avoid three revisions upgrades and 3 broadcasts to the guild.
+ if (not NickTag.send_scheduled) then
+ NickTag.send_scheduled = true
+ NickTag:ScheduleTimer ("SendPersona", 1)
+ end
+ end
+
+ return true
+ end
+
+ function NickTag:SetNicknameAvatar (texture, l, r, t, b)
+
+ if (l == nil) then
+ l, r, t, b = 0, 1, 0, 1
+ elseif (type (l) == "table") then
+ l, r, t, b = unpack (l)
+ end
+
+ --> check data before
+ assert (texture and l and r and t and b, "NickTag 'SetAvatar' bad format. Usage NickTag:SetAvatar (texturepath [, L, R, T, B] or texturepath [, {L, R, T, B}])")
+
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ local nick_table = NickTag:GetNicknameTable (battlegroup_serial)
+ if (not nick_table) then
+ nick_table = NickTag:Create (battlegroup_serial, true)
+ end
+
+ if (nick_table [CONST_INDEX_AVATAR_PATH] ~= texture) then
+ nick_table [CONST_INDEX_AVATAR_PATH] = texture
+
+ --> by default, CONST_INDEX_AVATAR_TEXCOORD comes as boolean false
+ if (type (nick_table [CONST_INDEX_AVATAR_TEXCOORD]) == "boolean") then
+ nick_table [CONST_INDEX_AVATAR_TEXCOORD] = {}
+ end
+
+ nick_table [CONST_INDEX_AVATAR_TEXCOORD][1] = l
+ nick_table [CONST_INDEX_AVATAR_TEXCOORD][2] = r
+ nick_table [CONST_INDEX_AVATAR_TEXCOORD][3] = t
+ nick_table [CONST_INDEX_AVATAR_TEXCOORD][4] = b
+
+ NickTag.callbacks:Fire ("NickTag_Update", CONST_INDEX_AVATAR_PATH)
+
+ if (not NickTag.send_scheduled) then
+ NickTag.send_scheduled = true
+ NickTag:ScheduleTimer ("SendPersona", 1)
+ end
+ end
+
+ return true
+ end
+
+ --> set the background
+ function NickTag:SetNicknameBackground (path, texcoord, color, silent)
+
+ if (not silent) then
+ assert (type (path) == "string", "NickTag 'SetNicknameBackground' expects a string on #1 argument.")
+ else
+ if (type (path) ~= "string") then
+ return
+ end
+ end
+
+ if (not texcoord) then
+ texcoord = {0, 1, 0, 1}
+ end
+
+ if (not color) then
+ color = {1, 1, 1}
+ end
+
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ local nick_table = NickTag:GetNicknameTable (battlegroup_serial)
+ if (not nick_table) then
+ nick_table = NickTag:Create (battlegroup_serial, true)
+ end
+
+ local need_sync = false
+ if (nick_table [CONST_INDEX_BACKGROUND_PATH] ~= path) then
+ nick_table [CONST_INDEX_BACKGROUND_PATH] = path
+ need_sync = true
+ end
+
+ if (nick_table [CONST_INDEX_BACKGROUND_TEXCOORD] ~= texcoord) then
+ nick_table [CONST_INDEX_BACKGROUND_TEXCOORD] = texcoord
+ need_sync = true
+ end
+
+ if (nick_table [CONST_INDEX_BACKGROUND_COLOR] ~= color) then
+ nick_table [CONST_INDEX_BACKGROUND_COLOR] = color
+ need_sync = true
+ end
+
+ if (need_sync) then
+ NickTag.callbacks:Fire ("NickTag_Update", CONST_INDEX_BACKGROUND_PATH)
+
+ if (not NickTag.send_scheduled) then
+ NickTag.send_scheduled = true
+ NickTag:ScheduleTimer ("SendPersona", 1)
+ end
+ end
+
+ return true
+ end
+
+ function NickTag:GetNickname (serial, default, silent)
+ if (not silent) then
+ assert (serial, "NickTag 'GetNickname' expects a number or string on #1 argument.")
+ end
+
+ if (type (serial) == "string") then
+ serial = NickTag:GetSerial (serial, silent)
+ end
+
+ if (serial) then
+ local _table = pool [serial]
+ if (not _table) then
+ return default or nil
+ end
+ return _table [CONST_INDEX_NICKNAME] or default or nil
+ end
+ end
+
+ --> return the avatar and the texcoord.
+ function NickTag:GetNicknameAvatar (serial, default, silent)
+ if (not silent) then
+ assert (serial, "NickTag 'GetAvatar' expects a number or string on #1 argument.")
+ end
+
+ if (type (serial) == "string") then
+ serial = NickTag:GetSerial (serial, silent)
+ end
+
+ if (serial) then
+ local _table = pool [serial]
+ if (not _table and default) then
+ return default, {0, 1, 0, 1}
+ elseif (not _table) then
+ return "", {0, 1, 0, 1}
+ end
+ return _table [CONST_INDEX_AVATAR_PATH] or default or "", _table [CONST_INDEX_AVATAR_TEXCOORD] or {0, 1, 0, 1}
+ end
+ end
+
+ function NickTag:GetNicknameBackground (serial, default_path, default_texcoord, default_color, silent)
+ if (not silent) then
+ assert (serial, "NickTag 'GetNicknameBackground' expects a number or string on #1 argument.")
+ end
+
+ if (type (serial) == "string") then
+ serial = NickTag:GetSerial (serial, silent)
+ end
+
+ if (serial) then
+ local _table = pool [serial]
+ if (not _table) then
+ return default_path, default_texcoord, default_color
+ end
+ return _table [CONST_INDEX_BACKGROUND_PATH] or default_path, _table [CONST_INDEX_BACKGROUND_TEXCOORD] or default_texcoord, _table [CONST_INDEX_BACKGROUND_COLOR] or default_color
+ else
+ return default_path, default_texcoord, default_color
+ end
+ end
+
+ --> get the full nicktag table
+ function NickTag:GetNicknameTable (serial, silent)
+ --> check data before
+ if (not silent) then
+ assert (serial, "NickTag 'Get' expects a number on #1 argument.")
+ else
+ if (not serial) then
+ return
+ end
+ end
+
+ if (type (serial) == "string") then
+ serial = NickTag:GetSerial (serial, silent)
+ if (not serial) then
+ return
+ end
+ end
+
+ return pool [serial]
+ end
+
+------------------------------------------------------------------------------------------------------------------------------------------------------
+--> internal functions
+
+ --> create a empty nick table for the player
+ function NickTag:Create (serial, isSelf)
+ --> check data before
+ assert (type (serial) == "number", "NickTag 'Create' expects a number on #1 argument.")
+
+ --> check if alredy exists
+ local alredy_have = pool [serial]
+ if (alredy_have) then
+ return alredy_have
+ end
+
+ --> create the table:
+ local n = { UnitName ("player"), --[1] player nickname
+ false, --[2] avatar texture path
+ false, --[3] avatar texture coord
+ false, --[4] background texture path
+ false, --[5] background texcoord
+ false, --[6] background color
+ 1 --[7] revision
+ }
+
+ --> if not my persona, set revision to 0, this make always get update after creation
+ if (not isSelf) then
+ n [CONST_INDEX_REVISION] = 0
+ end
+
+ pool [serial] = n
+ return n
+ end
+
+ --> inc the revision of the player persona after update nick or avatar
+ function NickTag:IncRevision()
+ local battlegroup_serial = NickTag:GetSerial()
+ if (not battlegroup_serial) then
+ return
+ end
+
+ local nick_table = NickTag:GetNicknameTable (battlegroup_serial)
+ if (not nick_table) then
+ nick_table = NickTag:Create (battlegroup_serial, true)
+ end
+
+ nick_table [CONST_INDEX_REVISION] = nick_table [CONST_INDEX_REVISION] + 1
+
+ return true
+ end
+
+ --> convert GUID into serial number
+ function NickTag:GetSerial (serial, silent)
+ if (not serial) then
+ local guid = UnitGUID ("player")
+ if (not guid) then
+ return
+ end
+ serial = select ( 3, strsplit ( "-", guid ) )
+ else
+ if (not silent) then
+ assert (type (serial) == "string", "NickTag 'GetSerial' expects a GUID string on #1 parameter"..serial)
+ assert (string.len (serial) > 13, "NickTag 'GetSerial' expects a GUID string on #1 parameter")
+ else
+ if (type (serial) ~= "string") then
+ return
+ elseif (string.len (serial) < 14) then
+ return
+ end
+ end
+
+ serial = select ( 3, strsplit ( "-", serial ) )
+ end
+ if (not serial) then
+ return
+ end
+ return tonumber ("0x" .. serial)
+ end
+
+ --> choose avatar window
+do
+ local avatar_pick_frame = CreateFrame ("frame", "AvatarPickFrame", UIParent)
+ avatar_pick_frame:SetFrameStrata ("DIALOG")
+ avatar_pick_frame:SetBackdrop ({bgFile = [[Interface\FrameGeneral\UI-Background-Marble]], edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]], tile = true, tileSize = 256, edgeSize = 32, insets = {left = 11, right = 12, top = 12, bottom = 11}})
+ avatar_pick_frame:SetBackdropColor (.3, .3, .3, .9)
+ avatar_pick_frame:SetWidth (460)
+ avatar_pick_frame:SetHeight (240)
+
+ avatar_pick_frame.selected_avatar = 1
+ avatar_pick_frame.selected_background = 1
+ avatar_pick_frame.selected_color = {1, 1, 1}
+ avatar_pick_frame.selected_texcoord = {0, 1, 0, 1}
+
+ avatar_pick_frame:SetPoint ("center", UIParent, "center", 200, 0)
+ ---
+ local avatar_texture = avatar_pick_frame:CreateTexture ("AvatarPickFrameAvatarPreview", "overlay")
+ avatar_texture:SetPoint ("topleft", avatar_pick_frame, "topleft", 167, -10)
+ avatar_texture:SetTexture ([[Interface\EncounterJournal\UI-EJ-BOSS-Default]])
+ --
+ local background_texture = avatar_pick_frame:CreateTexture ("AvatarPickFrameBackgroundPreview", "artwork")
+ background_texture:SetPoint ("topleft", avatar_pick_frame, "topleft", 167, 2)
+ background_texture:SetWidth (290)
+ background_texture:SetHeight (75)
+ background_texture:SetTexture (NickTag.background_pool[1][1])
+ background_texture:SetTexCoord (unpack (NickTag.background_pool[1][3]))
+ --
+ local name = avatar_pick_frame:CreateFontString ("AvatarPickFrameName", "overlay", "GameFontHighlightHuge")
+ name:SetPoint ("left", avatar_texture, "right", -11, -17)
+ name:SetText (UnitName ("player"))
+ ---
+
+ local OnClickFunction = function (button)
+ if (button.isAvatar) then
+ local avatar = NickTag.avatar_pool [button.IconID]
+ _G.AvatarPickFrameAvatarPreview:SetTexture ( avatar [1] )
+ avatar_pick_frame.selected_avatar = avatar [1]
+ elseif (button.isBackground) then
+ local background = NickTag.background_pool [button.IconID]
+ _G.AvatarPickFrameBackgroundPreview:SetTexture ( background [1] )
+ _G.AvatarPickFrameBackgroundPreview:SetTexCoord (unpack (background [3]))
+ avatar_pick_frame.selected_background = background [1]
+ avatar_pick_frame.selected_texcoord = background [3]
+ end
+ end
+
+ local selectedColor = function()
+ local r, g, b = ColorPickerFrame:GetColorRGB()
+ background_texture:SetVertexColor (r, g, b)
+ avatar_pick_frame.selected_color[1] = r
+ avatar_pick_frame.selected_color[2] = g
+ avatar_pick_frame.selected_color[3] = b
+ end
+
+ local okey = CreateFrame ("button", "AvatarPickFrameAccept", avatar_pick_frame, "OptionsButtonTemplate")
+ okey:SetPoint ("bottomright", avatar_pick_frame, "bottomright", -37, 12)
+ okey:SetText ("Accept")
+ okey:SetFrameLevel (avatar_pick_frame:GetFrameLevel()+2)
+ okey:SetScript ("OnClick", function (self)
+ avatar_pick_frame:Hide()
+ if (avatar_pick_frame.callback) then
+ avatar_pick_frame.callback (avatar_pick_frame.selected_avatar, {0, 1, 0, 1}, avatar_pick_frame.selected_background, avatar_pick_frame.selected_texcoord, avatar_pick_frame.selected_color)
+ end
+ end)
+ local change_color = CreateFrame ("button", "AvatarPickFrameColor", avatar_pick_frame, "OptionsButtonTemplate")
+ change_color:SetPoint ("bottomright", avatar_pick_frame, "bottomright", -205, 12)
+ change_color:SetText ("Color")
+ change_color:SetFrameLevel (avatar_pick_frame:GetFrameLevel()+2)
+
+ change_color:SetScript ("OnClick", function (self)
+ ColorPickerFrame.func = selectedColor
+ ColorPickerFrame.hasOpacity = false
+ ColorPickerFrame:SetParent (avatar_pick_frame)
+ ColorPickerFrame:SetColorRGB (_G.AvatarPickFrameBackgroundPreview:GetVertexColor())
+ ColorPickerFrame:ClearAllPoints()
+ ColorPickerFrame:SetPoint ("left", avatar_pick_frame, "right", 0, -10)
+ ColorPickerFrame:Show()
+ end)
+
+ local buttons = {}
+ for i = 0, 2 do
+ local newbutton = CreateFrame ("button", "AvatarPickFrameAvatarScrollButton"..i+1, avatar_pick_frame)
+ newbutton:SetScript ("OnClick", OnClickFunction)
+ newbutton:SetWidth (128)
+ newbutton:SetHeight (64)
+ newbutton:SetPoint ("topleft", avatar_pick_frame, "topleft", 15, (i*70*-1) - 20)
+ newbutton:SetID (i+1)
+ newbutton.isAvatar = true
+ buttons [#buttons+1] = newbutton
+ end
+
+ local buttonsbg = {}
+ for i = 0, 2 do
+ local newbutton = CreateFrame ("button", "AvatarPickFrameBackgroundScrollButton"..i+1, avatar_pick_frame)
+ newbutton:SetScript ("OnClick", OnClickFunction)
+ newbutton:SetWidth (275)
+ newbutton:SetHeight (60)
+ newbutton:SetPoint ("topleft", avatar_pick_frame, "topleft", 157, (i*50*-1) - 80)
+ newbutton:SetID (i+1)
+ newbutton.isBackground = true
+ buttonsbg [#buttonsbg+1] = newbutton
+ end
+
+ local avatar_list = CreateFrame ("ScrollFrame", "AvatarPickFrameAvatarScroll", avatar_pick_frame, "ListScrollFrameTemplate")
+ avatar_list:SetPoint ("topleft", avatar_pick_frame, "topleft", 10, -10)
+ local background_list = CreateFrame ("ScrollFrame", "AvatarPickFrameBackgroundScroll", avatar_pick_frame, "ListScrollFrameTemplate")
+ background_list:SetPoint ("topleft", avatar_pick_frame, "topleft", 147, -85)
+
+ avatar_list:SetWidth (128)
+ avatar_list:SetHeight (220)
+ background_list:SetWidth (275)
+ background_list:SetHeight (140)
+
+ local avatar_scroll_update = function (self)
+ local numMacroIcons = #NickTag.avatar_pool
+ local macroPopupIcon, macroPopupButton, index, texture
+ local macroPopupOffset = FauxScrollFrame_GetOffset (avatar_list)
+
+ for i = 1, 3 do
+ macroPopupIcon = _G ["AvatarPickFrameAvatarScrollButton"..i]
+ macroPopupButton = _G ["AvatarPickFrameAvatarScrollButton"..i]
+ index = (macroPopupOffset * 1) + i
+
+ texture = NickTag.avatar_pool [index][1]
+ if ( index <= numMacroIcons and texture ) then
+ macroPopupButton:SetNormalTexture (texture)
+ macroPopupButton:SetPushedTexture (texture)
+ macroPopupButton:SetDisabledTexture (texture)
+ macroPopupButton:SetHighlightTexture (texture, "ADD")
+ macroPopupButton.IconID = index
+ macroPopupButton:Show()
+ else
+ macroPopupButton:Hide()
+ end
+ end
+ FauxScrollFrame_Update (avatar_list, numMacroIcons , 3, 64)
+ end
+ local background_scroll_update = function (self)
+ local numMacroIcons = #NickTag.background_pool
+ local macroPopupIcon, macroPopupButton, index, texture
+ local macroPopupOffset = FauxScrollFrame_GetOffset (background_list)
+
+ for i = 1, 3 do
+ macroPopupIcon = _G ["AvatarPickFrameBackgroundScrollButton"..i]
+ macroPopupButton = _G ["AvatarPickFrameBackgroundScrollButton"..i]
+ index = (macroPopupOffset * 1) + i
+
+ texture = NickTag.background_pool [index][1]
+ if ( index <= numMacroIcons and texture ) then
+ macroPopupButton:SetNormalTexture (texture)
+ macroPopupButton:SetPushedTexture (texture)
+ macroPopupButton:SetDisabledTexture (texture)
+ macroPopupButton:SetHighlightTexture (texture, "ADD")
+ macroPopupButton.IconID = index
+ macroPopupButton:Show()
+ else
+ macroPopupButton:Hide()
+ end
+ end
+ FauxScrollFrame_Update (background_list, numMacroIcons , 3, 40)
+ end
+
+ avatar_list:SetScript ("OnVerticalScroll", function (self, offset)
+ FauxScrollFrame_OnVerticalScroll (avatar_list, offset, 64, avatar_scroll_update)
+ end)
+ background_list:SetScript ("OnVerticalScroll", function (self, offset)
+ FauxScrollFrame_OnVerticalScroll (background_list, offset, 40, background_scroll_update)
+ end)
+
+ avatar_scroll_update (avatar_list)
+ background_scroll_update (background_list)
+
+ function avatar_pick_frame:SetAvatar (n)
+ if (type (n) ~= "number") then
+ n = 1
+ end
+ if (n > #NickTag.avatar_pool) then
+ n = 1
+ end
+ local avatar = NickTag.avatar_pool [n]
+ _G.AvatarPickFrameAvatarPreview:SetTexture ( avatar [1] )
+ avatar_pick_frame.selected_avatar = avatar [1]
+ end
+ function avatar_pick_frame:SetBackground (n)
+ if (type (n) ~= "number") then
+ n = 1
+ end
+ if (n > #NickTag.background_pool) then
+ n = 1
+ end
+ local background = NickTag.background_pool [n]
+ _G.AvatarPickFrameBackgroundPreview:SetTexture ( background [1] )
+ _G.AvatarPickFrameBackgroundPreview:SetTexCoord (unpack (background [3]))
+ _G.AvatarPickFrameBackgroundPreview:SetVertexColor (unpack (avatar_pick_frame.selected_color))
+ avatar_pick_frame.selected_background = background [1]
+ end
+ function avatar_pick_frame:SetColor (r, g, b)
+ if (type (r) ~= "number" or r > 1) then
+ r = 1
+ end
+ if (type (g) ~= "number" or g > 1) then
+ g = 1
+ end
+ if (type (b) ~= "number" or b > 1) then
+ b = 1
+ end
+ _G.AvatarPickFrameBackgroundPreview:SetVertexColor (r, g, b)
+ avatar_pick_frame.selected_color[1] = r
+ avatar_pick_frame.selected_color[2] = g
+ avatar_pick_frame.selected_color[3] = b
+ end
+
+ local CONST_INDEX_NICKNAME = 1
+ local CONST_INDEX_AVATAR_PATH = 2
+ local CONST_INDEX_AVATAR_TEXCOORD = 3
+ local CONST_INDEX_BACKGROUND_PATH = 4
+ local CONST_INDEX_BACKGROUND_TEXCOORD = 5
+ local CONST_INDEX_BACKGROUND_COLOR = 6
+
+ avatar_pick_frame:SetScript ("OnShow", function()
+ --get player avatar
+ local avatar = NickTag:GetNicknameTable (UnitGUID ("player"))
+ if (avatar) then
+ _G.AvatarPickFrameName:SetText ( avatar [1] or UnitName ("player"))
+
+ _G.AvatarPickFrameAvatarPreview:SetTexture ( avatar [CONST_INDEX_AVATAR_PATH] or [[Interface\EncounterJournal\UI-EJ-BOSS-Default]] )
+ avatar_pick_frame.selected_avatar = avatar [CONST_INDEX_AVATAR_PATH] or [[Interface\EncounterJournal\UI-EJ-BOSS-Default]]
+
+ _G.AvatarPickFrameAvatarPreview:SetTexCoord ( 0, 1, 0, 1 ) --> always
+
+ _G.AvatarPickFrameBackgroundPreview:SetTexture ( avatar [CONST_INDEX_BACKGROUND_PATH] or [[Interface\PetBattles\Weather-ArcaneStorm]] )
+ avatar_pick_frame.selected_background = avatar [CONST_INDEX_BACKGROUND_PATH] or [[Interface\PetBattles\Weather-ArcaneStorm]]
+
+ if (avatar [CONST_INDEX_BACKGROUND_TEXCOORD]) then
+ _G.AvatarPickFrameBackgroundPreview:SetTexCoord ( unpack (avatar [CONST_INDEX_BACKGROUND_TEXCOORD]) )
+ avatar_pick_frame.selected_texcoord = avatar [CONST_INDEX_BACKGROUND_TEXCOORD]
+ else
+ _G.AvatarPickFrameBackgroundPreview:SetTexCoord ( 0.129609375, 1, 1, 0 )
+ avatar_pick_frame.selected_texcoord = {0.129609375, 1, 1, 0}
+ end
+
+ if (avatar [CONST_INDEX_BACKGROUND_COLOR]) then
+ _G.AvatarPickFrameBackgroundPreview:SetVertexColor ( unpack (avatar [CONST_INDEX_BACKGROUND_COLOR]) )
+ avatar_pick_frame.selected_color = avatar [CONST_INDEX_BACKGROUND_COLOR]
+ else
+ _G.AvatarPickFrameBackgroundPreview:SetVertexColor ( 1, 1, 1 )
+ avatar_pick_frame.selected_color = {1, 1, 1}
+ end
+ else
+ --> if none
+ _G.AvatarPickFrameAvatarPreview:SetTexture ( [[Interface\EncounterJournal\UI-EJ-BOSS-Default]] )
+ avatar_pick_frame.selected_avatar = [[Interface\EncounterJournal\UI-EJ-BOSS-Default]]
+
+ local background = NickTag.background_pool [1]
+
+ if (background) then
+ _G.AvatarPickFrameBackgroundPreview:SetTexture ( background [1] )
+ avatar_pick_frame.selected_background = background [1]
+ _G.AvatarPickFrameBackgroundPreview:SetTexCoord (unpack (background [3]))
+ avatar_pick_frame.selected_texcoord = background [3]
+ _G.AvatarPickFrameBackgroundPreview:SetVertexColor (unpack (avatar_pick_frame.selected_color))
+ avatar_pick_frame.selected_color = avatar_pick_frame.selected_color
+ end
+
+ end
+ end)
+
+ avatar_pick_frame:Hide()
+end
\ No newline at end of file
diff --git a/Libs/NickTag-1.0/NickTag-1.0.toc b/Libs/NickTag-1.0/NickTag-1.0.toc
new file mode 100644
index 00000000..a099808c
--- /dev/null
+++ b/Libs/NickTag-1.0/NickTag-1.0.toc
@@ -0,0 +1,5 @@
+## Interface: 70200
+## Title: NickTag-1.0
+## Notes: Standalone version of the NickTag library.
+
+NickTag-1.0.xml
\ No newline at end of file
diff --git a/Libs/NickTag-1.0/NickTag-1.0.xml b/Libs/NickTag-1.0/NickTag-1.0.xml
new file mode 100644
index 00000000..fe622cb4
--- /dev/null
+++ b/Libs/NickTag-1.0/NickTag-1.0.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file