diff --git a/Vect.lua b/Vect.lua
index f7f7faf..a1e744f 100644
--- a/Vect.lua
+++ b/Vect.lua
@@ -3,6 +3,8 @@
--Enable/disable on frames
--Player Entering World -> cleanup the db
--CD Sort Order
+--Chat Command
+--DR timers
--"Globals"
local aceDB = LibStub("AceDB-3.0")
diff --git a/data/options.lua b/data/options.lua
index 7ce545e..72b28d6 100644
--- a/data/options.lua
+++ b/data/options.lua
@@ -22,40 +22,40 @@ function Vect:GetVectOptions()
end
},
targetandfocus = {
- type = "group", name = "T. and F. CDs", desc = "Cooldown frame's settings.", childGroups = "tab", order = 3,
+ type = "group", name = "T. and F. CDs", desc = "Cooldown frame's settings.", childGroups = "tab", order = 2,
args = Vect:getTargetandFocusOptions();
},
droptions = {
- type = "group", name = "T. and F. DRs", desc = "DR frame's settings.", childGroups = "tab",order = 4,
+ type = "group", name = "T. and F. DRs", desc = "DR frame's settings.", childGroups = "tab",order = 3,
args = Vect:getDROptions();
},
selfdr = {
- type = "group", name = "Self DRs", desc = "Self DR frame's settings.", childGroups = "tab",order = 5,
+ type = "group", name = "Self DRs", desc = "Self DR frame's settings.", childGroups = "tab",order = 4,
args = Vect:getSelfDROptions()
},
debugoptions = {
- type = "group", name = "Debug", desc = "Debug settings.", childGroups = "tab",order = 6,
+ type = "group", name = "Debug", desc = "Debug settings.", childGroups = "tab",order = 5,
args = Vect:getDebugOptions();
}
}
}
return options;
end
-
+--order 10-20
function Vect:getTargetandFocusOptions()
local args = {
targetHeader = {
type = "header", name = "Target's settings", order = 10
},
targettoggle = {
- type = "toggle", name = "Target", desc = "Enable/Disable showing the target's cooldowns", order = 3,
+ type = "toggle", name = "Target", desc = "Enable/Disable showing the target's cooldowns", order = 11,
get = function() return Vect:isPartEnabled("target") end,
set = function(_, v)
Vect:SetPartEnabledOrDisabled("target", v);
end
},
targetrange = {
- type = "range", name = "Target's size", order = 11, min = 10, max = 150, step = 1,
+ type = "range", name = "Target's size", order = 12, min = 10, max = 150, step = 1,
get = function() return Vect:getFrameSize("target") end,
set = function(_, v)
Vect:setFrameSize("target", v);
@@ -64,7 +64,7 @@ function Vect:getTargetandFocusOptions()
},
targetGrowSelect = {
type = "select", style = "dropdown", name = "targetGrow",
- desc = "Change which way the target's cooldowns will grow", order = 12,
+ desc = "Change which way the target's cooldowns will grow", order = 13,
values = {
["1"] = "Up",
["2"] = "Right",
@@ -78,7 +78,7 @@ function Vect:getTargetandFocusOptions()
},
targetSortSelect = {
type = "select", style = "dropdown", name = "targetSortOrder",
- desc = "Change the target's cooldowns's sort order", order = 13,
+ desc = "Change the target's cooldowns's sort order", order = 14,
values = {
["1"] = "Ascending",
["2"] = "Descending",
@@ -91,18 +91,18 @@ function Vect:getTargetandFocusOptions()
Vect:setSortOrder("target", v);
end
},
- focusHeader = {
- type = "header", name = "Focus's settings", order = 14
+ focusHeader = {
+ type = "header", name = "Focus's settings", order = 15
},
focustoggle = {
- type = "toggle", name = "Focus", desc = "Enable/Disable showing the focus's cooldowns", order = 8,
+ type = "toggle", name = "Focus", desc = "Enable/Disable showing the focus's cooldowns", order = 16,
get = function() return Vect:isPartEnabled("focus") end,
set = function(_, v)
Vect:SetPartEnabledOrDisabled("focus", v);
end
},
focusRange = {
- type = "range", name = "Focus's size", order = 15, min = 10, max = 150, step = 1,
+ type = "range", name = "Focus's size", order = 17, min = 10, max = 150, step = 1,
get = function() return Vect:getFrameSize("focus") end,
set = function(_, v)
Vect:setFrameSize("focus", v);
@@ -110,7 +110,7 @@ function Vect:getTargetandFocusOptions()
},
focusGrowSelect = {
type = "select", style = "dropdown", name = "focusGrow",
- desc = "Change which way the focus's cooldowns will grow", order = 16,
+ desc = "Change which way the focus's cooldowns will grow", order = 18,
values = {
["1"] = "Up",
["2"] = "Right",
@@ -124,7 +124,7 @@ function Vect:getTargetandFocusOptions()
},
focusSortSelect = {
type = "select", style = "dropdown", name = "focusSortOrder",
- desc = "Change the focus's cooldowns's sort order", order = 17,
+ desc = "Change the focus's cooldowns's sort order", order = 19,
values = {
["1"] = "Ascending",
["2"] = "Descending",
diff --git a/data/spells.lua b/data/spells.lua
index 34d3c9a..b1567cf 100644
--- a/data/spells.lua
+++ b/data/spells.lua
@@ -170,6 +170,20 @@ Vect.spells = {
[14751] = {144, nil}, --Inner Focus
[33206] = {144, nil}, --Pain Supression
[10060] = {96, nil}, --Power infusion
+ [17] = {15, nil}, --Power Word: Shield r1
+ [592] = {15, nil}, --Power Word: Shield r2
+ [600] = {15, nil}, --Power Word: Shield r3
+ [3747] = {15, nil}, --Power Word: Shield r4
+ [6065] = {15, nil}, --Power Word: Shield r5
+ [6066] = {15, nil}, --Power Word: Shield r6
+ [10898] = {15, nil}, --Power Word: Shield r7
+ [10899] = {15, nil}, --Power Word: Shield r8
+ [10900] = {15, nil}, --Power Word: Shield r9
+ [10901] = {15, nil}, --Power Word: Shield r10
+ [25217] = {15, nil}, --Power Word: Shield r11
+ [25218] = {15, nil}, --Power Word: Shield r12
+ [48065] = {15, nil}, --Power Word: Shield r13
+ [48066] = {15, nil}, --Power Word: Shield r14
--Holy
[19203] = {120, nil}, --Desperate Prayer r1
[19238] = {120, nil}, --Desperate Prayer r2
diff --git a/embeds.xml b/embeds.xml
index 1840d46..80c290f 100644
--- a/embeds.xml
+++ b/embeds.xml
@@ -4,14 +4,21 @@
..\FrameXML\UI.xsd">
+
+
-
+
+
+
+
+
+
diff --git a/lib/AceBucket-3.0/AceBucket-3.0.lua b/lib/AceBucket-3.0/AceBucket-3.0.lua
new file mode 100644
index 0000000..d5e1065
--- /dev/null
+++ b/lib/AceBucket-3.0/AceBucket-3.0.lua
@@ -0,0 +1,293 @@
+--- A bucket to catch events in. **AceBucket-3.0** provides throttling of events that fire in bursts and
+-- your addon only needs to know about the full burst.
+--
+-- This Bucket implementation works as follows:\\
+-- Initially, no schedule is running, and its waiting for the first event to happen.\\
+-- The first event will start the bucket, and get the scheduler running, which will collect all
+-- events in the given interval. When that interval is reached, the bucket is pushed to the
+-- callback and a new schedule is started. When a bucket is empty after its interval, the scheduler is
+-- stopped, and the bucket is only listening for the next event to happen, basically back in its initial state.
+--
+-- In addition, the buckets collect information about the "arg1" argument of the events that fire, and pass those as a
+-- table to your callback. This functionality was mostly designed for the UNIT_* events.\\
+-- The table will have the different values of "arg1" as keys, and the number of occurances as their value, e.g.\\
+-- { ["player"] = 2, ["target"] = 1, ["party1"] = 1 }
+--
+-- **AceBucket-3.0** can be embeded into your addon, either explicitly by calling AceBucket: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 AceBucket itself.\\
+-- It is recommended to embed AceBucket, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceBucket.
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("BucketExample", "AceBucket-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Register a bucket that listens to all the HP related events,
+-- -- and fires once per second
+-- self:RegisterBucketEvent({"UNIT_HEALTH", "UNIT_MAXHEALTH"}, 1, "UpdateHealth")
+-- end
+--
+-- function MyAddon:UpdateHealth(units)
+-- if units.player then
+-- print("Your HP changed!")
+-- end
+-- end
+-- @class file
+-- @name AceBucket-3.0.lua
+-- @release $Id: AceBucket-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
+
+local MAJOR, MINOR = "AceBucket-3.0", 3
+local AceBucket, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceBucket then return end -- No Upgrade needed
+
+AceBucket.buckets = AceBucket.buckets or {}
+AceBucket.embeds = AceBucket.embeds or {}
+
+-- the libraries will be lazyly bound later, to avoid errors due to loading order issues
+local AceEvent, AceTimer
+
+-- Lua APIs
+local tconcat = table.concat
+local type, next, pairs, select = type, next, pairs, select
+local tonumber, tostring, rawset = tonumber, tostring, rawset
+local assert, loadstring, error = assert, loadstring, error
+
+-- 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, geterrorhandler
+
+local bucketCache = setmetatable({}, {__mode='k'})
+
+--[[
+ 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, ...)
+ return Dispatchers[select('#', ...)](func, ...)
+end
+
+-- FireBucket ( bucket )
+--
+-- send the bucket to the callback function and schedule the next FireBucket in interval seconds
+local function FireBucket(bucket)
+ local received = bucket.received
+
+ -- we dont want to fire empty buckets
+ if next(received) then
+ local callback = bucket.callback
+ if type(callback) == "string" then
+ safecall(bucket.object[callback], bucket.object, received)
+ else
+ safecall(callback, received)
+ end
+
+ for k in pairs(received) do
+ received[k] = nil
+ end
+
+ -- if the bucket was not empty, schedule another FireBucket in interval seconds
+ bucket.timer = AceTimer.ScheduleTimer(bucket, FireBucket, bucket.interval, bucket)
+ else -- if it was empty, clear the timer and wait for the next event
+ bucket.timer = nil
+ end
+end
+
+-- BucketHandler ( event, arg1 )
+--
+-- callback func for AceEvent
+-- stores arg1 in the received table, and schedules the bucket if necessary
+local function BucketHandler(self, event, arg1)
+ if arg1 == nil then
+ arg1 = "nil"
+ end
+
+ self.received[arg1] = (self.received[arg1] or 0) + 1
+
+ -- if we are not scheduled yet, start a timer on the interval for our bucket to be cleared
+ if not self.timer then
+ self.timer = AceTimer.ScheduleTimer(self, FireBucket, self.interval, self)
+ end
+end
+
+-- RegisterBucket( event, interval, callback, isMessage )
+--
+-- event(string or table) - the event, or a table with the events, that this bucket listens to
+-- interval(int) - time between bucket fireings
+-- callback(func or string) - function pointer, or method name of the object, that gets called when the bucket is cleared
+-- isMessage(boolean) - register AceEvent Messages instead of game events
+local function RegisterBucket(self, event, interval, callback, isMessage)
+ -- try to fetch the librarys
+ if not AceEvent or not AceTimer then
+ AceEvent = LibStub:GetLibrary("AceEvent-3.0", true)
+ AceTimer = LibStub:GetLibrary("AceTimer-3.0", true)
+ if not AceEvent or not AceTimer then
+ error(MAJOR .. " requires AceEvent-3.0 and AceTimer-3.0", 3)
+ end
+ end
+
+ if type(event) ~= "string" and type(event) ~= "table" then error("Usage: RegisterBucket(event, interval, callback): 'event' - string or table expected.", 3) end
+ if not callback then
+ if type(event) == "string" then
+ callback = event
+ else
+ error("Usage: RegisterBucket(event, interval, callback): cannot omit callback when event is not a string.", 3)
+ end
+ end
+ if not tonumber(interval) then error("Usage: RegisterBucket(event, interval, callback): 'interval' - number expected.", 3) end
+ if type(callback) ~= "string" and type(callback) ~= "function" then error("Usage: RegisterBucket(event, interval, callback): 'callback' - string or function or nil expected.", 3) end
+ if type(callback) == "string" and type(self[callback]) ~= "function" then error("Usage: RegisterBucket(event, interval, callback): 'callback' - method not found on target object.", 3) end
+
+ local bucket = next(bucketCache)
+ if bucket then
+ bucketCache[bucket] = nil
+ else
+ bucket = { handler = BucketHandler, received = {} }
+ end
+ bucket.object, bucket.callback, bucket.interval = self, callback, tonumber(interval)
+
+ local regFunc = isMessage and AceEvent.RegisterMessage or AceEvent.RegisterEvent
+
+ if type(event) == "table" then
+ for _,e in pairs(event) do
+ regFunc(bucket, e, "handler")
+ end
+ else
+ regFunc(bucket, event, "handler")
+ end
+
+ local handle = tostring(bucket)
+ AceBucket.buckets[handle] = bucket
+
+ return handle
+end
+
+--- Register a Bucket for an event (or a set of events)
+-- @param event The event to listen for, or a table of events.
+-- @param interval The Bucket interval (burst interval)
+-- @param callback The callback function, either as a function reference, or a string pointing to a method of the addon object.
+-- @return The handle of the bucket (for unregistering)
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceBucket-3.0")
+-- MyAddon:RegisterBucketEvent("BAG_UPDATE", 0.2, "UpdateBags")
+--
+-- function MyAddon:UpdateBags()
+-- -- do stuff
+-- end
+function AceBucket:RegisterBucketEvent(event, interval, callback)
+ return RegisterBucket(self, event, interval, callback, false)
+end
+
+--- Register a Bucket for an AceEvent-3.0 addon message (or a set of messages)
+-- @param message The message to listen for, or a table of messages.
+-- @param interval The Bucket interval (burst interval)
+-- @param callback The callback function, either as a function reference, or a string pointing to a method of the addon object.
+-- @return The handle of the bucket (for unregistering)
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceBucket-3.0")
+-- MyAddon:RegisterBucketEvent("SomeAddon_InformationMessage", 0.2, "ProcessData")
+--
+-- function MyAddon:ProcessData()
+-- -- do stuff
+-- end
+function AceBucket:RegisterBucketMessage(message, interval, callback)
+ return RegisterBucket(self, message, interval, callback, true)
+end
+
+--- Unregister any events and messages from the bucket and clear any remaining data.
+-- @param handle The handle of the bucket as returned by RegisterBucket*
+function AceBucket:UnregisterBucket(handle)
+ local bucket = AceBucket.buckets[handle]
+ if bucket then
+ AceEvent.UnregisterAllEvents(bucket)
+ AceEvent.UnregisterAllMessages(bucket)
+
+ -- clear any remaining data in the bucket
+ for k in pairs(bucket.received) do
+ bucket.received[k] = nil
+ end
+
+ if bucket.timer then
+ AceTimer.CancelTimer(bucket, bucket.timer)
+ bucket.timer = nil
+ end
+
+ AceBucket.buckets[handle] = nil
+ -- store our bucket in the cache
+ bucketCache[bucket] = true
+ end
+end
+
+--- Unregister all buckets of the current addon object (or custom "self").
+function AceBucket:UnregisterAllBuckets()
+ -- hmm can we do this more efficient? (it is not done often so shouldn't matter much)
+ for handle, bucket in pairs(AceBucket.buckets) do
+ if bucket.object == self then
+ AceBucket.UnregisterBucket(self, handle)
+ end
+ end
+end
+
+
+
+-- embedding and embed handling
+local mixins = {
+ "RegisterBucketEvent",
+ "RegisterBucketMessage",
+ "UnregisterBucket",
+ "UnregisterAllBuckets",
+}
+
+-- Embeds AceBucket into the target object making the functions from the mixins list available on target:..
+-- @param target target object to embed AceBucket in
+function AceBucket:Embed( target )
+ for _, v in pairs( mixins ) do
+ target[v] = self[v]
+ end
+ self.embeds[target] = true
+ return target
+end
+
+function AceBucket:OnEmbedDisable( target )
+ target:UnregisterAllBuckets()
+end
+
+for addon in pairs(AceBucket.embeds) do
+ AceBucket:Embed(addon)
+end
diff --git a/lib/AceBucket-3.0/AceBucket-3.0.xml b/lib/AceBucket-3.0/AceBucket-3.0.xml
new file mode 100644
index 0000000..43c6bee
--- /dev/null
+++ b/lib/AceBucket-3.0/AceBucket-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/lib/AceComm-3.0/AceComm-3.0.lua b/lib/AceComm-3.0/AceComm-3.0.lua
new file mode 100644
index 0000000..ad5268f
--- /dev/null
+++ b/lib/AceComm-3.0/AceComm-3.0.lua
@@ -0,0 +1,309 @@
+--- **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 895 2009-12-06 16:28:55Z 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", 6
+
+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 tinsert, tconcat = table.insert, table.concat
+local error, assert = error, assert
+
+-- 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
+
+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"
+
+AceComm.multipart_origprefixes = AceComm.multipart_origprefixes or {} -- e.g. "Prefix\001"="Prefix", "Prefix\002"="Prefix"
+AceComm.multipart_reassemblers = AceComm.multipart_reassemblers or {} -- e.g. "Prefix\001"="OnReceiveMultipartFirst"
+
+-- 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)
+-- @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
+
+ 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
+
+ if strfind(prefix, "[\001-\009]") then
+ if strfind(prefix, "[\001-\003]") then
+ error("SendCommMessage: Characters \\001--\\003 in prefix are reserved for AceComm metadata", 2)
+ elseif not warnedPrefix then
+ -- I have some ideas about future extensions that require more control characters /mikk, 20090808
+ geterrorhandler()("SendCommMessage: Heads-up developers: Characters \\004--\\009 in prefix are reserved for AceComm future extension")
+ warnedPrefix = true
+ end
+ end
+
+
+ local textlen = #text
+ local maxtextlen = 254 - #prefix -- 254 is the max length of prefix + text that can be sent in one message
+ local queueName = prefix..distribution..(target or "")
+
+ local ctlCallback = nil
+ if callbackFn then
+ ctlCallback = function(sent)
+ return callbackFn(callbackArg, sent, textlen)
+ end
+ end
+
+ if 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
+
+ -- 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
+ local prefix2 = prefix..MSG_MULTI_NEXT
+
+ while pos+maxtextlen <= textlen do
+ chunk = strsub(text, pos, pos+maxtextlen-1)
+ CTL:SendAddonMessage(prio, prefix2, 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
+ -- ensure that 'prefix to watch' table is consistent with registered
+ -- callbacks
+ AceComm.__prefixes = {}
+
+ AceComm.callbacks = CallbackHandler:New(AceComm,
+ "_RegisterComm",
+ "UnregisterComm",
+ "UnregisterAllComm")
+end
+
+function AceComm.callbacks:OnUsed(target, prefix)
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = prefix
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = "OnReceiveMultipartFirst"
+
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = prefix
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = "OnReceiveMultipartNext"
+
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = prefix
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = "OnReceiveMultipartLast"
+end
+
+function AceComm.callbacks:OnUnused(target, prefix)
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_FIRST] = nil
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_FIRST] = nil
+
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_NEXT] = nil
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_NEXT] = nil
+
+ AceComm.multipart_origprefixes[prefix..MSG_MULTI_LAST] = nil
+ AceComm.multipart_reassemblers[prefix..MSG_MULTI_LAST] = nil
+end
+
+local function OnEvent(this, event, ...)
+ if event == "CHAT_MSG_ADDON" then
+ local prefix,message,distribution,sender = ...
+ local reassemblername = AceComm.multipart_reassemblers[prefix]
+ if reassemblername then
+ -- multipart: reassemble
+ local aceCommReassemblerFunc = AceComm[reassemblername]
+ local origprefix = AceComm.multipart_origprefixes[prefix]
+ aceCommReassemblerFunc(AceComm, origprefix, message, distribution, sender)
+ 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/lib/AceComm-3.0/AceComm-3.0.xml b/lib/AceComm-3.0/AceComm-3.0.xml
new file mode 100644
index 0000000..09e8d87
--- /dev/null
+++ b/lib/AceComm-3.0/AceComm-3.0.xml
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/lib/AceComm-3.0/ChatThrottleLib.lua b/lib/AceComm-3.0/ChatThrottleLib.lua
new file mode 100644
index 0000000..b0afc92
--- /dev/null
+++ b/lib/AceComm-3.0/ChatThrottleLib.lua
@@ -0,0 +1,503 @@
+--
+-- 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! :-)
+--
+
+local CTL_VERSION = 21
+
+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
+
+
+
+-----------------------------------------------------------------------
+-- 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, which also happens to be a ring member
+
+ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different
+local PipeBin = setmetatable({}, {__mode="k"})
+
+local function DelPipe(pipe)
+ for i = #pipe, 1, -1 do
+ pipe[i] = nil
+ end
+ pipe.prev = nil
+ pipe.next = nil
+
+ PipeBin[pipe] = true
+end
+
+local function NewPipe()
+ local pipe = next(PipeBin)
+ if pipe then
+ 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
+
+function ChatThrottleLib:Despool(Prio)
+ local ring = Prio.Ring
+ while ring.pos and Prio.avail > ring.pos[1].nSize do
+ local msg = table_remove(Prio.Ring.pos, 1)
+ if not Prio.Ring.pos[1] then
+ 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
+ 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)
+ if msg.callbackFn then
+ msg.callbackFn (msg.callbackArg)
+ end
+ 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)
+ end
+ 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 = prefix:len() + 1 + text:len();
+
+ if nSize>255 then
+ error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 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.SendAddonMessage(prefix, text, chattype, target)
+ bMyTraffic = false
+ self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize
+ if callbackFn then
+ callbackFn (callbackArg)
+ end
+ 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/lib/AceHook-3.0/AceHook-3.0.lua b/lib/AceHook-3.0/AceHook-3.0.lua
new file mode 100644
index 0000000..096eb6f
--- /dev/null
+++ b/lib/AceHook-3.0/AceHook-3.0.lua
@@ -0,0 +1,514 @@
+--- **AceHook-3.0** offers safe Hooking/Unhooking of functions, methods and frame scripts.
+-- Using AceHook-3.0 is recommended when you need to unhook your hooks again, so the hook chain isn't broken
+-- when you manually restore the original function.
+--
+-- **AceHook-3.0** can be embeded into your addon, either explicitly by calling AceHook: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 AceHook itself.\\
+-- It is recommended to embed AceHook, otherwise you'll have to specify a custom `self` on all calls you
+-- make into AceHook.
+-- @class file
+-- @name AceHook-3.0
+-- @release $Id: AceHook-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $
+local ACEHOOK_MAJOR, ACEHOOK_MINOR = "AceHook-3.0", 5
+local AceHook, oldminor = LibStub:NewLibrary(ACEHOOK_MAJOR, ACEHOOK_MINOR)
+
+if not AceHook then return end -- No upgrade needed
+
+AceHook.embeded = AceHook.embeded or {}
+AceHook.registry = AceHook.registry or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end })
+AceHook.handlers = AceHook.handlers or {}
+AceHook.actives = AceHook.actives or {}
+AceHook.scripts = AceHook.scripts or {}
+AceHook.onceSecure = AceHook.onceSecure or {}
+AceHook.hooks = AceHook.hooks or {}
+
+-- local upvalues
+local registry = AceHook.registry
+local handlers = AceHook.handlers
+local actives = AceHook.actives
+local scripts = AceHook.scripts
+local onceSecure = AceHook.onceSecure
+
+-- Lua APIs
+local pairs, next, type = pairs, next, type
+local format = string.format
+local assert, error = assert, error
+
+-- WoW APIs
+local issecurevariable, hooksecurefunc = issecurevariable, hooksecurefunc
+local _G = _G
+
+-- functions for later definition
+local donothing, createHook, hook
+
+local protectedScripts = {
+ OnClick = true,
+}
+
+-- upgrading of embeded is done at the bottom of the file
+
+local mixins = {
+ "Hook", "SecureHook",
+ "HookScript", "SecureHookScript",
+ "Unhook", "UnhookAll",
+ "IsHooked",
+ "RawHook", "RawHookScript"
+}
+
+-- AceHook:Embed( target )
+-- target (object) - target object to embed AceHook in
+--
+-- Embeds AceEevent into the target object making the functions from the mixins list available on target:..
+function AceHook:Embed( target )
+ for k, v in pairs( mixins ) do
+ target[v] = self[v]
+ end
+ self.embeded[target] = true
+ -- inject the hooks table safely
+ target.hooks = target.hooks or {}
+ return target
+end
+
+-- AceHook:OnEmbedDisable( target )
+-- target (object) - target object that is being disabled
+--
+-- Unhooks all hooks when the target disables.
+-- this method should be called by the target manually or by an addon framework
+function AceHook:OnEmbedDisable( target )
+ target:UnhookAll()
+end
+
+function createHook(self, handler, orig, secure, failsafe)
+ local uid
+ local method = type(handler) == "string"
+ if failsafe and not secure then
+ -- failsafe hook creation
+ uid = function(...)
+ if actives[uid] then
+ if method then
+ self[handler](self, ...)
+ else
+ handler(...)
+ end
+ end
+ return orig(...)
+ end
+ -- /failsafe hook
+ else
+ -- all other hooks
+ uid = function(...)
+ if actives[uid] then
+ if method then
+ return self[handler](self, ...)
+ else
+ return handler(...)
+ end
+ elseif not secure then -- backup on non secure
+ return orig(...)
+ end
+ end
+ -- /hook
+ end
+ return uid
+end
+
+function donothing() end
+
+function hook(self, obj, method, handler, script, secure, raw, forceSecure, usage)
+ if not handler then handler = method end
+
+ -- These asserts make sure AceHooks's devs play by the rules.
+ assert(not script or type(script) == "boolean")
+ assert(not secure or type(secure) == "boolean")
+ assert(not raw or type(raw) == "boolean")
+ assert(not forceSecure or type(forceSecure) == "boolean")
+ assert(usage)
+
+ -- Error checking Battery!
+ if obj and type(obj) ~= "table" then
+ error(format("%s: 'object' - nil or table expected got %s", usage, type(obj)), 3)
+ end
+ if type(method) ~= "string" then
+ error(format("%s: 'method' - string expected got %s", usage, type(method)), 3)
+ end
+ if type(handler) ~= "string" and type(handler) ~= "function" then
+ error(format("%s: 'handler' - nil, string, or function expected got %s", usage, type(handler)), 3)
+ end
+ if type(handler) == "string" and type(self[handler]) ~= "function" then
+ error(format("%s: 'handler' - Handler specified does not exist at self[handler]", usage), 3)
+ end
+ if script then
+ if not secure and obj:IsProtected() and protectedScripts[method] then
+ error(format("Cannot hook secure script %q; Use SecureHookScript(obj, method, [handler]) instead.", method), 3)
+ end
+ if not obj or not obj.GetScript or not obj:HasScript(method) then
+ error(format("%s: You can only hook a script on a frame object", usage), 3)
+ end
+ else
+ local issecure
+ if obj then
+ issecure = onceSecure[obj] and onceSecure[obj][method] or issecurevariable(obj, method)
+ else
+ issecure = onceSecure[method] or issecurevariable(method)
+ end
+ if issecure then
+ if forceSecure then
+ if obj then
+ onceSecure[obj] = onceSecure[obj] or {}
+ onceSecure[obj][method] = true
+ else
+ onceSecure[method] = true
+ end
+ elseif not secure then
+ error(format("%s: Attempt to hook secure function %s. Use `SecureHook' or add `true' to the argument list to override.", usage, method), 3)
+ end
+ end
+ end
+
+ local uid
+ if obj then
+ uid = registry[self][obj] and registry[self][obj][method]
+ else
+ uid = registry[self][method]
+ end
+
+ if uid then
+ if actives[uid] then
+ -- Only two sane choices exist here. We either a) error 100% of the time or b) always unhook and then hook
+ -- choice b would likely lead to odd debuging conditions or other mysteries so we're going with a.
+ error(format("Attempting to rehook already active hook %s.", method))
+ end
+
+ if handlers[uid] == handler then -- turn on a decative hook, note enclosures break this ability, small memory leak
+ actives[uid] = true
+ return
+ elseif obj then -- is there any reason not to call unhook instead of doing the following several lines?
+ if self.hooks and self.hooks[obj] then
+ self.hooks[obj][method] = nil
+ end
+ registry[self][obj][method] = nil
+ else
+ if self.hooks then
+ self.hooks[method] = nil
+ end
+ registry[self][method] = nil
+ end
+ handlers[uid], actives[uid], scripts[uid] = nil, nil, nil
+ uid = nil
+ end
+
+ local orig
+ if script then
+ orig = obj:GetScript(method) or donothing
+ elseif obj then
+ orig = obj[method]
+ else
+ orig = _G[method]
+ end
+
+ if not orig then
+ error(format("%s: Attempting to hook a non existing target", usage), 3)
+ end
+
+ uid = createHook(self, handler, orig, secure, not (raw or secure))
+
+ if obj then
+ self.hooks[obj] = self.hooks[obj] or {}
+ registry[self][obj] = registry[self][obj] or {}
+ registry[self][obj][method] = uid
+
+ if not secure then
+ self.hooks[obj][method] = orig
+ end
+
+ if script then
+ -- If the script is empty before, HookScript will not work, so use SetScript instead
+ -- This will make the hook insecure, but shouldnt matter, since it was empty before.
+ -- It does not taint the full frame.
+ if not secure or orig == donothing then
+ obj:SetScript(method, uid)
+ elseif secure then
+ obj:HookScript(method, uid)
+ end
+ else
+ if not secure then
+ obj[method] = uid
+ else
+ hooksecurefunc(obj, method, uid)
+ end
+ end
+ else
+ registry[self][method] = uid
+
+ if not secure then
+ _G[method] = uid
+ self.hooks[method] = orig
+ else
+ hooksecurefunc(method, uid)
+ end
+ end
+
+ actives[uid], handlers[uid], scripts[uid] = true, handler, script and true or nil
+end
+
+--- Hook a function or a method on an object.
+-- The hook created will be a "safe hook", that means that your handler will be called
+-- before the hooked function ("Pre-Hook"), and you don't have to call the original function yourself,
+-- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
+-- This type of hook is typically used if you need to know if some function got called, and don't want to modify it.
+-- @paramsig [object], method, [handler], [hookSecure]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+-- @param hookSecure If true, AceHook will allow hooking of secure functions.
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
+-- self:Hook("ActionButton_UpdateHotkeys", true)
+-- end
+--
+-- function MyAddon:ActionButton_UpdateHotkeys(button, type)
+-- print(button:GetName() .. " is updating its HotKey")
+-- end
+function AceHook:Hook(object, method, handler, hookSecure)
+ if type(object) == "string" then
+ method, handler, hookSecure, object = object, method, handler, nil
+ end
+
+ if handler == true then
+ handler, hookSecure = nil, true
+ end
+
+ hook(self, object, method, handler, false, false, false, hookSecure or false, "Usage: Hook([object], method, [handler], [hookSecure])")
+end
+
+--- RawHook a function or a method on an object.
+-- The hook created will be a "raw hook", that means that your handler will completly replace
+-- the original function, and your handler has to call the original function (or not, depending on your intentions).\\
+-- The original function will be stored in `self.hooks[object][method]` or `self.hooks[functionName]` respectively.\\
+-- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
+-- or want to control execution of the original function.
+-- @paramsig [object], method, [handler], [hookSecure]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+-- @param hookSecure If true, AceHook will allow hooking of secure functions.
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook ActionButton_UpdateHotkeys, overwriting the secure status
+-- self:RawHook("ActionButton_UpdateHotkeys", true)
+-- end
+--
+-- function MyAddon:ActionButton_UpdateHotkeys(button, type)
+-- if button:GetName() == "MyButton" then
+-- -- do stuff here
+-- else
+-- self.hooks.ActionButton_UpdateHotkeys(button, type)
+-- end
+-- end
+function AceHook:RawHook(object, method, handler, hookSecure)
+ if type(object) == "string" then
+ method, handler, hookSecure, object = object, method, handler, nil
+ end
+
+ if handler == true then
+ handler, hookSecure = nil, true
+ end
+
+ hook(self, object, method, handler, false, false, true, hookSecure or false, "Usage: RawHook([object], method, [handler], [hookSecure])")
+end
+
+--- SecureHook a function or a method on an object.
+-- This function is a wrapper around the `hooksecurefunc` function in the WoW API. Using AceHook
+-- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
+-- required anymore, or the addon is being disabled.\\
+-- Secure Hooks should be used if the secure-status of the function is vital to its function,
+-- and taint would block execution. Secure Hooks are always called after the original function was called
+-- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
+-- @paramsig [object], method, [handler]
+-- @param object The object to hook a method from
+-- @param method If object was specified, the name of the method, or the name of the function to hook.
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked function)
+function AceHook:SecureHook(object, method, handler)
+ if type(object) == "string" then
+ method, handler, object = object, method, nil
+ end
+
+ hook(self, object, method, handler, false, true, false, false, "Usage: SecureHook([object], method, [handler])")
+end
+
+--- Hook a script handler on a frame.
+-- The hook created will be a "safe hook", that means that your handler will be called
+-- before the hooked script ("Pre-Hook"), and you don't have to call the original function yourself,
+-- however you cannot stop the execution of the function, or modify any of the arguments/return values.\\
+-- This is the frame script equivalent of the :Hook safe-hook. It would typically be used to be notified
+-- when a certain event happens to a frame.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook the OnShow of FriendsFrame
+-- self:HookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
+-- end
+--
+-- function MyAddon:FriendsFrameOnShow(frame)
+-- print("The FriendsFrame was shown!")
+-- end
+function AceHook:HookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, false, false, false, "Usage: HookScript(object, method, [handler])")
+end
+
+--- RawHook a script handler on a frame.
+-- The hook created will be a "raw hook", that means that your handler will completly replace
+-- the original script, and your handler has to call the original script (or not, depending on your intentions).\\
+-- The original script will be stored in `self.hooks[frame][script]`.\\
+-- This type of hook can be used for all purposes, and is usually the most common case when you need to modify arguments
+-- or want to control execution of the original script.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+-- @usage
+-- -- create an addon with AceHook embeded
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("HookDemo", "AceHook-3.0")
+--
+-- function MyAddon:OnEnable()
+-- -- Hook the OnShow of FriendsFrame
+-- self:RawHookScript(FriendsFrame, "OnShow", "FriendsFrameOnShow")
+-- end
+--
+-- function MyAddon:FriendsFrameOnShow(frame)
+-- -- Call the original function
+-- self.hooks[frame].OnShow(frame)
+-- -- Do our processing
+-- -- .. stuff
+-- end
+function AceHook:RawHookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, false, true, false, "Usage: RawHookScript(object, method, [handler])")
+end
+
+--- SecureHook a script handler on a frame.
+-- This function is a wrapper around the `frame:HookScript` function in the WoW API. Using AceHook
+-- extends the functionality of secure hooks, and adds the ability to unhook once the hook isn't
+-- required anymore, or the addon is being disabled.\\
+-- Secure Hooks should be used if the secure-status of the function is vital to its function,
+-- and taint would block execution. Secure Hooks are always called after the original function was called
+-- ("Post Hook"), and you cannot modify the arguments, return values or control the execution.
+-- @paramsig frame, script, [handler]
+-- @param frame The Frame to hook the script on
+-- @param script The script to hook
+-- @param handler The handler for the hook, a funcref or a method name. (Defaults to the name of the hooked script)
+function AceHook:SecureHookScript(frame, script, handler)
+ hook(self, frame, script, handler, true, true, false, false, "Usage: SecureHookScript(object, method, [handler])")
+end
+
+--- Unhook from the specified function, method or script.
+-- @paramsig [obj], method
+-- @param obj The object or frame to unhook from
+-- @param method The name of the method, function or script to unhook from.
+function AceHook:Unhook(obj, method)
+ local usage = "Usage: Unhook([obj], method)"
+ if type(obj) == "string" then
+ method, obj = obj, nil
+ end
+
+ if obj and type(obj) ~= "table" then
+ error(format("%s: 'obj' - expecting nil or table got %s", usage, type(obj)), 2)
+ end
+ if type(method) ~= "string" then
+ error(format("%s: 'method' - expeting string got %s", usage, type(method)), 2)
+ end
+
+ local uid
+ if obj then
+ uid = registry[self][obj] and registry[self][obj][method]
+ else
+ uid = registry[self][method]
+ end
+
+ if not uid or not actives[uid] then
+ -- Declining to error on an unneeded unhook since the end effect is the same and this would just be annoying.
+ return false
+ end
+
+ actives[uid], handlers[uid] = nil, nil
+
+ if obj then
+ registry[self][obj][method] = nil
+ registry[self][obj] = next(registry[self][obj]) and registry[self][obj] or nil
+
+ -- if the hook reference doesnt exist, then its a secure hook, just bail out and dont do any unhooking
+ if not self.hooks[obj] or not self.hooks[obj][method] then return true end
+
+ if scripts[uid] and obj:GetScript(method) == uid then -- unhooks scripts
+ obj:SetScript(method, self.hooks[obj][method] ~= donothing and self.hooks[obj][method] or nil)
+ scripts[uid] = nil
+ elseif obj and self.hooks[obj] and self.hooks[obj][method] and obj[method] == uid then -- unhooks methods
+ obj[method] = self.hooks[obj][method]
+ end
+
+ self.hooks[obj][method] = nil
+ self.hooks[obj] = next(self.hooks[obj]) and self.hooks[obj] or nil
+ else
+ registry[self][method] = nil
+
+ -- if self.hooks[method] doesn't exist, then this is a SecureHook, just bail out
+ if not self.hooks[method] then return true end
+
+ if self.hooks[method] and _G[method] == uid then -- unhooks functions
+ _G[method] = self.hooks[method]
+ end
+
+ self.hooks[method] = nil
+ end
+ return true
+end
+
+--- Unhook all existing hooks for this addon.
+function AceHook:UnhookAll()
+ for key, value in pairs(registry[self]) do
+ if type(key) == "table" then
+ for method in pairs(value) do
+ self:Unhook(key, method)
+ end
+ else
+ self:Unhook(key)
+ end
+ end
+end
+
+--- Check if the specific function, method or script is already hooked.
+-- @paramsig [obj], method
+-- @param obj The object or frame to unhook from
+-- @param method The name of the method, function or script to unhook from.
+function AceHook:IsHooked(obj, method)
+ -- we don't check if registry[self] exists, this is done by evil magicks in the metatable
+ if type(obj) == "string" then
+ if registry[self][obj] and actives[registry[self][obj]] then
+ return true, handlers[registry[self][obj]]
+ end
+ else
+ if registry[self][obj] and registry[self][obj][method] and actives[registry[self][obj][method]] then
+ return true, handlers[registry[self][obj][method]]
+ end
+ end
+
+ return false, nil
+end
+
+--- Upgrade our old embeded
+for target, v in pairs( AceHook.embeded ) do
+ AceHook:Embed( target )
+end
diff --git a/lib/AceHook-3.0/AceHook-3.0.xml b/lib/AceHook-3.0/AceHook-3.0.xml
new file mode 100644
index 0000000..add0f26
--- /dev/null
+++ b/lib/AceHook-3.0/AceHook-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/lib/AceLocale-3.0/AceLocale-3.0.lua b/lib/AceLocale-3.0/AceLocale-3.0.lua
new file mode 100644
index 0000000..0f7c8fd
--- /dev/null
+++ b/lib/AceLocale-3.0/AceLocale-3.0.lua
@@ -0,0 +1,136 @@
+--- **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 895 2009-12-06 16:28:55Z nevcairiel $
+local MAJOR,MINOR = "AceLocale-3.0", 2
+
+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 setmetatable, rawset, rawget = 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. Can only be set on the default locale.
+-- @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)
+
+ if silent and not isDefault then
+ error("Usage: NewLocale(application, locale[, isDefault[, silent]]): 'silent' can only be specified for the default locale", 2)
+ end
+
+ -- GAME_LOCALE allows translators to test translations of addons without having that wow client installed
+ -- Ammo: I still think this is a bad idea, for instance an addon that checks for some ingame string will fail, just because some other addon
+ -- gives the user the illusion that they can run in a different locale? Ditch this whole thing or allow a setting per 'application'. I'm of the
+ -- opinion to remove this.
+ local gameLocale = GAME_LOCALE or gameLocale
+
+ if locale ~= gameLocale and not isDefault then
+ return -- nop, we don't need these translations
+ end
+
+ local app = AceLocale.apps[application]
+
+ if not app then
+ app = setmetatable({}, silent and readmetasilent or readmeta)
+ AceLocale.apps[application] = app
+ AceLocale.appnames[app] = application
+ 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/lib/AceLocale-3.0/AceLocale-3.0.xml b/lib/AceLocale-3.0/AceLocale-3.0.xml
new file mode 100644
index 0000000..e017af0
--- /dev/null
+++ b/lib/AceLocale-3.0/AceLocale-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/lib/AceSerializer-3.0/AceSerializer-3.0.lua b/lib/AceSerializer-3.0/AceSerializer-3.0.lua
new file mode 100644
index 0000000..b072a2b
--- /dev/null
+++ b/lib/AceSerializer-3.0/AceSerializer-3.0.lua
@@ -0,0 +1,281 @@
+--- **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 910 2010-02-11 21:54:24Z mikk $
+local MAJOR,MINOR = "AceSerializer-3.0", 3
+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 serNaN = tostring(0/0)
+local serInf = tostring(1/0)
+local serNegInf = tostring(-1/0)
+
+
+-- 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 or str==serNaN or str==serInf or str==serNegInf then
+ -- translates just fine, transmit as-is
+ res[nres+1] = "^N"
+ res[nres+2] = str
+ 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)
+ if number == serNaN then
+ return 0/0
+ elseif number == serNegInf then
+ return -1/0
+ elseif number == serInf then
+ return 1/0
+ 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/lib/AceSerializer-3.0/AceSerializer-3.0.xml b/lib/AceSerializer-3.0/AceSerializer-3.0.xml
new file mode 100644
index 0000000..94924af
--- /dev/null
+++ b/lib/AceSerializer-3.0/AceSerializer-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/lib/AceTab-3.0/AceConfigTab-3.0.lua b/lib/AceTab-3.0/AceConfigTab-3.0.lua
new file mode 100644
index 0000000..cf3403c
--- /dev/null
+++ b/lib/AceTab-3.0/AceConfigTab-3.0.lua
@@ -0,0 +1,105 @@
+--- AceConfigTab-3.0 provides support for tab-completion to AceConfig tables.
+-- Note: This library is not yet finalized.
+-- @class file
+-- @name AceConfigTab-3.0
+-- @release $Id: AceConfigTab-3.0.lua 769 2009-04-04 11:05:08Z nevcairiel $
+
+local MAJOR, MINOR = "AceConfigTab-3.0", 1
+local lib = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not lib then return end
+
+local ac = LibStub("AceConsole-3.0")
+
+local function printf(...)
+ DEFAULT_CHAT_FRAME:AddMessage(string.format(...))
+end
+
+-- getChildren(opt, ...)
+--
+-- Retrieve the next valid group args in an AceConfig table.
+--
+-- opt - AceConfig options table
+-- ... - args following the slash command
+--
+-- opt will need to be determined by the slash-command
+-- The args will be obtained using AceConsole:GetArgs() or something similar on the remainder of the line.
+--
+-- Returns arg1, arg2, ...
+local function getLevel(opt, ...)
+ -- Walk down the options tree to the last arg in the commandline, or return if it does not follow the tree.
+ local path = ""
+ local lastChild
+ for i = 1, select('#', ...) do
+ local arg = select(i, ...)
+ if not arg or type(arg) == 'number' then break end
+ if opt.plugins then
+ for k in pairs(opt.plugins) do
+ if string.lower(k) == string.lower(arg) then
+ opt = opt.plugins[k]
+ path = path..arg.." "
+ lastChild = arg
+ break
+ end
+ end
+ elseif opt.args then
+ for k in pairs(opt.args) do
+ if string.lower(k) == string.lower(arg) then
+ opt = opt.args[k]
+ path = path..arg.." "
+ lastChild = arg
+ break
+ end
+ end
+ else
+ break
+ end
+ end
+ return opt, path
+end
+
+local function getChildren(opt, ...)
+ local lastChild, path
+ opt, path, lastChild = getLevel(opt, ...)
+ local args = {}
+ for _, field in ipairs({"args", "plugins"}) do
+ if type(opt[field]) == 'table' then
+ for k in pairs(opt[field]) do
+ if opt[field].type ~= 'header' then
+ table.insert(args, k)
+ end
+ end
+ end
+ end
+ return args, path
+end
+
+--LibStub("AceConfig-3.0"):RegisterOptionsTable("ag_UnitFrames", aUF.Options.table)
+local function createWordlist(t, cmdline, pos)
+ local cmd = string.match(cmdline, "(/[^ \t\n]+)")
+ local argslist = string.sub(cmdline, pos, this:GetCursorPosition())
+ local opt -- TODO: figure out options table using cmd
+ opt = LibStub("AceConfigRegistry-3.0"):GetOptionsTable("ag_UnitFrames", "cmd", "AceTab-3.0") -- hardcoded temporarily for testing
+ if not opt then return end
+ local args, path = getChildren(opt, ac:GetArgs(argslist, #argslist/2)) -- largest # of args representable by a string of length #argslist, since they must be separated by spaces
+ for _, v in ipairs(args) do
+ table.insert(t, path..v)
+ end
+end
+
+local function usage(t, matches, _, cmdline)
+ local cmd = string.match(cmdline, "(/[^ \t\n]+)")
+ local argslist = string.sub(cmdline, #cmd, this:GetCursorPosition())
+ local opt -- TODO: figure out options table using cmd
+ opt = LibStub("AceConfigRegistry-3.0"):GetOptionsTable("ag_UnitFrames")("cmd", "AceTab-3.0") -- hardcoded temporarily for testing
+ if not opt then return end
+ local level = getLevel(opt, ac:GetArgs(argslist, #argslist/2)) -- largest # of args representable by a string of length #argslist, since they must be separated by spaces
+ local option
+ for _, m in pairs(matches) do
+ local tail = string.match(m, "([^ \t\n]+)$")
+ option = level.plugins and level.plugins[tail] or level.args and level.args[tail]
+ printf("%s - %s", tail, option.desc)
+ end
+end
+
+LibStub("AceTab-3.0"):RegisterTabCompletion("aguftest", "%/%w+ ", createWordlist, usage)
diff --git a/lib/AceTab-3.0/AceTab-3.0.lua b/lib/AceTab-3.0/AceTab-3.0.lua
new file mode 100644
index 0000000..50891b5
--- /dev/null
+++ b/lib/AceTab-3.0/AceTab-3.0.lua
@@ -0,0 +1,443 @@
+--- AceTab-3.0 provides support for tab-completion.
+-- Note: This library is not yet finalized.
+-- @class file
+-- @name AceTab-3.0
+-- @release $Id: AceTab-3.0.lua 947 2010-06-29 16:44:48Z nevcairiel $
+
+local ACETAB_MAJOR, ACETAB_MINOR = 'AceTab-3.0', 8
+local AceTab, oldminor = LibStub:NewLibrary(ACETAB_MAJOR, ACETAB_MINOR)
+
+if not AceTab then return end -- No upgrade needed
+
+local is335 = GetBuildInfo() >= "3.3.5"
+
+AceTab.registry = AceTab.registry or {}
+
+-- local upvalues
+local _G = _G
+local pairs = pairs
+local ipairs = ipairs
+local type = type
+local registry = AceTab.registry
+
+local strfind = string.find
+local strsub = string.sub
+local strlower = string.lower
+local strformat = string.format
+local strmatch = string.match
+
+local function printf(...)
+ DEFAULT_CHAT_FRAME:AddMessage(strformat(...))
+end
+
+local function getTextBeforeCursor(this, start)
+ return strsub(this:GetText(), start or 1, this:GetCursorPosition())
+end
+
+-- Hook OnTabPressed and OnTextChanged for the frame, give it an empty matches table, and set its curMatch to 0, if we haven't done so already.
+local function hookFrame(f)
+ if f.hookedByAceTab3 then return end
+ f.hookedByAceTab3 = true
+ if f == (is335 and ChatEdit_GetActiveWindow() or ChatFrameEditBox) then
+ local origCTP = ChatEdit_CustomTabPressed
+ function ChatEdit_CustomTabPressed(...)
+ if AceTab:OnTabPressed(f) then
+ return origCTP(...)
+ else
+ return true
+ end
+ end
+ else
+ local origOTP = f:GetScript('OnTabPressed')
+ if type(origOTP) ~= 'function' then
+ origOTP = function() end
+ end
+ f:SetScript('OnTabPressed', function(...)
+ if AceTab:OnTabPressed(f) then
+ return origOTP(...)
+ end
+ end)
+ end
+ f.at3curMatch = 0
+ f.at3matches = {}
+end
+
+local firstPMLength
+
+local fallbacks, notfallbacks = {}, {} -- classifies completions into those which have preconditions and those which do not. Those without preconditions are only considered if no other completions have matches.
+local pmolengths = {} -- holds the number of characters to overwrite according to pmoverwrite and the current prematch
+-- ------------------------------------------------------------------------------
+-- RegisterTabCompletion( descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite )
+-- See http://www.wowace.com/wiki/AceTab-2.0 for detailed API documentation
+--
+-- descriptor string Unique identifier for this tab completion set
+--
+-- prematches string|table|nil String match(es) AFTER which this tab completion will apply.
+-- AceTab will ignore tabs NOT preceded by the string(s).
+-- If no value is passed, will check all tabs pressed in the specified editframe(s) UNLESS a more-specific tab complete applies.
+--
+-- wordlist function|table Function that will be passed a table into which it will insert strings corresponding to all possible completions, or an equivalent table.
+-- The text in the editbox, the position of the start of the word to be completed, and the uncompleted partial word
+-- are passed as second, third, and fourth arguments, to facilitate pre-filtering or conditional formatting, if desired.
+--
+-- usagefunc function|boolean|nil Usage statement function. Defaults to the wordlist, one per line. A boolean true squelches usage output.
+--
+-- listenframes string|table|nil EditFrames to monitor. Defaults to ChatFrameEditBox.
+--
+-- postfunc function|nil Post-processing function. If supplied, matches will be passed through this function after they've been identified as a match.
+--
+-- pmoverwrite boolean|number|nil Offset the beginning of the completion string in the editbox when making a completion. Passing a boolean true indicates that we want to overwrite
+-- the entire prematch string, and passing a number will overwrite that many characters prior to the cursor.
+-- This is useful when you want to use the prematch as an indicator character, but ultimately do not want it as part of the text, itself.
+--
+-- no return
+-- ------------------------------------------------------------------------------
+function AceTab:RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite)
+ -- Arg checks
+ if type(descriptor) ~= 'string' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'descriptor' - string expected.", 3) end
+ if prematches and type(prematches) ~= 'string' and type(prematches) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'prematches' - string, table, or nil expected.", 3) end
+ if type(wordlist) ~= 'function' and type(wordlist) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'wordlist' - function or table expected.", 3) end
+ if usagefunc and type(usagefunc) ~= 'function' and type(usagefunc) ~= 'boolean' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'usagefunc' - function or boolean expected.", 3) end
+ if listenframes and type(listenframes) ~= 'string' and type(listenframes) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'listenframes' - string or table expected.", 3) end
+ if postfunc and type(postfunc) ~= 'function' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'postfunc' - function expected.", 3) end
+ if pmoverwrite and type(pmoverwrite) ~= 'boolean' and type(pmoverwrite) ~= 'number' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'pmoverwrite' - boolean or number expected.", 3) end
+
+ local pmtable = type(prematches) == 'table' and prematches or {}
+ -- Mark this group as a fallback group if no value was passed.
+ if not prematches then
+ pmtable[1] = ""
+ fallbacks[descriptor] = true
+ -- Make prematches into a one-element table if it was passed as a string.
+ elseif type(prematches) == 'string' then
+ pmtable[1] = prematches
+ if prematches == "" then
+ fallbacks[descriptor] = true
+ else
+ notfallbacks[descriptor] = true
+ end
+ end
+
+ -- Make listenframes into a one-element table if it was not passed a table of frames.
+ if not listenframes then -- default
+ if is335 then
+ listenframes = {}
+ for i = 1, NUM_CHAT_WINDOWS do
+ listenframes[i] = _G["ChatFrame"..i.."EditBox"]
+ end
+ else
+ listenframes = { ChatFrameEditBox }
+ end
+ elseif type(listenframes) ~= 'table' or type(listenframes[0]) == 'userdata' and type(listenframes.IsObjectType) == 'function' then -- single frame or framename
+ listenframes = { listenframes }
+ end
+
+ -- Hook each registered listenframe and give it a matches table.
+ for _, f in pairs(listenframes) do
+ if type(f) == 'string' then
+ f = _G[f]
+ end
+ if type(f) ~= 'table' or type(f[0]) ~= 'userdata' or type(f.IsObjectType) ~= 'function' then
+ error(format(ACETAB_MAJOR..": Cannot register frame %q; it does not exist", f:GetName()))
+ end
+ if f then
+ if f:GetObjectType() ~= 'EditBox' then
+ error(format(ACETAB_MAJOR..": Cannot register frame %q; it is not an EditBox", f:GetName()))
+ else
+ hookFrame(f)
+ end
+ end
+ end
+
+ -- Everything checks out; register this completion.
+ if not registry[descriptor] then
+ registry[descriptor] = { prematches = pmtable, wordlist = wordlist, usagefunc = usagefunc, listenframes = listenframes, postfunc = postfunc, pmoverwrite = pmoverwrite }
+ end
+end
+
+function AceTab:IsTabCompletionRegistered(descriptor)
+ return registry and registry[descriptor]
+end
+
+function AceTab:UnregisterTabCompletion(descriptor)
+ registry[descriptor] = nil
+ pmolengths[descriptor] = nil
+ fallbacks[descriptor] = nil
+ notfallbacks[descriptor] = nil
+end
+
+-- ------------------------------------------------------------------------------
+-- gcbs( s1, s2 )
+--
+-- s1 string First string to be compared
+--
+-- s2 string Second string to be compared
+--
+-- returns the greatest common substring beginning s1 and s2
+-- ------------------------------------------------------------------------------
+local function gcbs(s1, s2)
+ if not s1 and not s2 then return end
+ if not s1 then s1 = s2 end
+ if not s2 then s2 = s1 end
+ if #s2 < #s1 then
+ s1, s2 = s2, s1
+ end
+ if strfind(strlower(s2), "^"..strlower(s1)) then
+ return s1
+ else
+ return gcbs(strsub(s1, 1, -2), s2)
+ end
+end
+
+local cursor -- Holds cursor position. Set in :OnTabPressed().
+-- ------------------------------------------------------------------------------
+-- cycleTab()
+-- For when a tab press has multiple possible completions, we need to allow the user to press tab repeatedly to cycle through them.
+-- If we have multiple possible completions, all tab presses after the first will call this function to cycle through and insert the different possible matches.
+-- This function will stop being called after OnTextChanged() is triggered by something other than AceTab (i.e. the user inputs a character).
+-- ------------------------------------------------------------------------------
+local previousLength, cMatch, matched, postmatch
+local function cycleTab(this)
+ cMatch = 0 -- Counter across all sets. The pseudo-index relevant to this value and corresponding to the current match is held in this.at3curMatch
+ matched = false
+
+ -- Check each completion group registered to this frame.
+ for desc, compgrp in pairs(this.at3matches) do
+
+ -- Loop through the valid completions for this set.
+ for m, pm in pairs(compgrp) do
+ cMatch = cMatch + 1
+ if cMatch == this.at3curMatch then -- we're back to where we left off last time through the combined list
+ this.at3lastMatch = m
+ this.at3lastWord = pm
+ this.at3curMatch = cMatch + 1 -- save the new cMatch index
+ matched = true
+ break
+ end
+ end
+ if matched then break end
+ end
+
+ -- If our index is beyond the end of the list, reset the original uncompleted substring and let the cycle start over next time tab is pressed.
+ if not matched then
+ this.at3lastMatch = this.at3origMatch
+ this.at3lastWord = this.at3origWord
+ this.at3curMatch = 1
+ end
+
+ -- Insert the completion.
+ this:HighlightText(this.at3matchStart-1, cursor)
+ this:Insert(this.at3lastWord or '')
+ this.at3_last_precursor = getTextBeforeCursor(this) or ''
+end
+
+local IsSecureCmd = IsSecureCmd
+
+local cands, candUsage = {}, {}
+local numMatches = 0
+local firstMatch, hasNonFallback, allGCBS, setGCBS, usage
+local text_precursor, text_all, text_pmendToCursor
+local matches, usagefunc -- convenience locals
+
+-- Fill the this.at3matches[descriptor] tables with matching completion pairs for each entry, based on
+-- the partial string preceding the cursor position and using the corresponding registered wordlist.
+--
+-- The entries of the matches tables are of the format raw_match = formatted_match, where raw_match is the plaintext completion and
+-- formatted_match is the match after being formatted/altered/processed by the registered postfunc.
+-- If no postfunc exists, then the formatted and raw matches are the same.
+local pms, pme, pmt, prematchStart, prematchEnd, text_prematch, entry
+local function fillMatches(this, desc, fallback)
+ entry = registry[desc]
+ -- See what frames are registered for this completion group. If the frame in which we pressed tab is one of them, then we start building matches.
+ for _, f in ipairs(entry.listenframes) do
+ if f == this then
+
+ -- Try each precondition string registered for this completion group.
+ for _, prematch in ipairs(entry.prematches) do
+
+ -- Test if our prematch string is satisfied.
+ -- If it is, then we find its last occurence prior to the cursor, calculate and store its pmoverwrite value (if applicable), and start considering completions.
+ if fallback then prematch = "%s" end
+
+ -- Find the last occurence of the prematch before the cursor.
+ pms, pme, pmt = nil, 1, ''
+ text_prematch, prematchEnd, prematchStart = nil, nil, nil
+ while true do
+ pms, pme, pmt = strfind(text_precursor, "("..prematch..")", pme)
+ if pms then
+ prematchStart, prematchEnd, text_prematch = pms, pme, pmt
+ pme = pme + 1
+ else
+ break
+ end
+ end
+
+ if not prematchStart and fallback then
+ prematchStart, prematchEnd, text_prematch = 0, 0, ''
+ end
+ if prematchStart then
+ -- text_pmendToCursor should be the sub-word/phrase to be completed.
+ text_pmendToCursor = strsub(text_precursor, prematchEnd + 1)
+
+ -- How many characters should we eliminate before the completion before writing it in.
+ pmolengths[desc] = entry.pmoverwrite == true and #text_prematch or entry.pmoverwrite or 0
+
+ -- This is where we will insert completions, taking the prematch overwrite into account.
+ this.at3matchStart = prematchEnd + 1 - (pmolengths[desc] or 0)
+
+ -- We're either a non-fallback set or all completions thus far have been fallback sets, and the precondition matches.
+ -- Create cands from the registered wordlist, filling it with all potential (unfiltered) completion strings.
+ local wordlist = entry.wordlist
+ local cands = type(wordlist) == 'table' and wordlist or {}
+ if type(wordlist) == 'function' then
+ wordlist(cands, text_all, prematchEnd + 1, text_pmendToCursor)
+ end
+ if cands ~= false then
+ matches = this.at3matches[desc] or {}
+ for i in pairs(matches) do matches[i] = nil end
+
+ -- Check each of the entries in cands to see if it completes the word before the cursor.
+ -- Finally, increment our match count and set firstMatch, if appropriate.
+ for _, m in ipairs(cands) do
+ if strfind(strlower(m), strlower(text_pmendToCursor), 1, 1) == 1 then -- we have a matching completion!
+ hasNonFallback = not fallback
+ matches[m] = entry.postfunc and entry.postfunc(m, prematchEnd + 1, text_all) or m
+ numMatches = numMatches + 1
+ if numMatches == 1 then
+ firstMatch = matches[m]
+ firstPMLength = pmolengths[desc] or 0
+ end
+ end
+ end
+ this.at3matches[desc] = numMatches > 0 and matches or nil
+ end
+ end
+ end
+ end
+ end
+end
+
+function AceTab:OnTabPressed(this)
+ if this:GetText() == '' then return true end
+
+ -- allow Blizzard to handle slash commands, themselves
+ if this == (is335 and ChatEdit_GetActiveWindow() or ChatFrameEditBox) then
+ local command = this:GetText()
+ if strfind(command, "^/[%a%d_]+$") then
+ return true
+ end
+ local cmd = strmatch(command, "^/[%a%d_]+")
+ if cmd and IsSecureCmd(cmd) then
+ return true
+ end
+ end
+
+ cursor = this:GetCursorPosition()
+
+ text_all = this:GetText()
+ text_precursor = getTextBeforeCursor(this) or ''
+
+ -- If we've already found some matches and haven't done anything since the last tab press, then (continue) cycling matches.
+ -- Otherwise, reset this frame's matches and proceed to creating our list of possible completions.
+ this.at3lastMatch = this.at3curMatch > 0 and (this.at3lastMatch or this.at3origWord)
+ -- Detects if we've made any edits since the last tab press. If not, continue cycling completions.
+ if text_precursor == this.at3_last_precursor then
+ return cycleTab(this)
+ else
+ for i in pairs(this.at3matches) do this.at3matches[i] = nil end
+ this.at3curMatch = 0
+ this.at3origWord = nil
+ this.at3origMatch = nil
+ this.at3lastWord = nil
+ this.at3lastMatch = nil
+ this.at3_last_precursor = text_precursor
+ end
+
+ numMatches = 0
+ firstMatch = nil
+ firstPMLength = 0
+ hasNonFallback = false
+ for i in pairs(pmolengths) do pmolengths[i] = nil end
+
+ for desc in pairs(notfallbacks) do
+ fillMatches(this, desc)
+ end
+ if not hasNonFallback then
+ for desc in pairs(fallbacks) do
+ fillMatches(this, desc, true)
+ end
+ end
+
+ if not firstMatch then
+ this.at3_last_precursor = "\0"
+ return true
+ end
+
+ -- We want to replace the entire word with our completion, so highlight it up to the cursor.
+ -- If only one match exists, then stick it in there and append a space.
+ if numMatches == 1 then
+ -- HighlightText takes the value AFTER which the highlighting starts, so we have to subtract 1 to have it start before the first character.
+ this:HighlightText(this.at3matchStart-1, cursor)
+
+ this:Insert(firstMatch)
+ this:Insert(" ")
+ else
+ -- Otherwise, we want to begin cycling through the valid completions.
+ -- Beginning a cycle also causes the usage statement to be printed, if one exists.
+
+ -- Print usage statements for each possible completion (and gather up the GCBS of all matches while we're walking the tables).
+ allGCBS = nil
+ for desc, matches in pairs(this.at3matches) do
+ -- Don't print usage statements for fallback completion groups if we have 'real' completion groups with matches.
+ if hasNonFallback and fallbacks[desc] then break end
+
+ -- Use the group's description as a heading for its usage statements.
+ DEFAULT_CHAT_FRAME:AddMessage(desc..":")
+
+ usagefunc = registry[desc].usagefunc
+ if not usagefunc then
+ -- No special usage processing; just print a list of the (formatted) matches.
+ for m, fm in pairs(matches) do
+ DEFAULT_CHAT_FRAME:AddMessage(fm)
+ allGCBS = gcbs(allGCBS, m)
+ end
+ else
+ -- Print a usage statement based on the corresponding registered usagefunc.
+ -- candUsage is the table passed to usagefunc to be filled with candidate = usage_statement pairs.
+ if type(usagefunc) == 'function' then
+ for i in pairs(candUsage) do candUsage[i] = nil end
+
+ -- usagefunc takes the greatest common substring of valid matches as one of its args, so let's find that now.
+ -- TODO: Make the GCBS function accept a vararg or table, after which we can just pass in the list of matches.
+ setGCBS = nil
+ for m in pairs(matches) do
+ setGCBS = gcbs(setGCBS, m)
+ end
+ allGCBS = gcbs(allGCBS, setGCBS)
+ usage = usagefunc(candUsage, matches, setGCBS, strsub(text_precursor, 1, prematchEnd))
+
+ -- If the usagefunc returns a string, then the entire usage statement has been taken care of by usagefunc, and we need only to print it...
+ if type(usage) == 'string' then
+ DEFAULT_CHAT_FRAME:AddMessage(usage)
+
+ -- ...otherwise, it should have filled candUsage with candidate-usage statement pairs, and we need to print the matching ones.
+ elseif next(candUsage) and numMatches > 0 then
+ for m, fm in pairs(matches) do
+ if candUsage[m] then DEFAULT_CHAT_FRAME:AddMessage(strformat("%s - %s", fm, candUsage[m])) end
+ end
+ end
+ end
+ end
+
+ -- Replace the original string with the greatest common substring of all valid completions.
+ this.at3curMatch = 1
+ this.at3origWord = strsub(text_precursor, this.at3matchStart, this.at3matchStart + pmolengths[desc] - 1) .. allGCBS or ""
+ this.at3origMatch = allGCBS or ""
+ this.at3lastWord = this.at3origWord
+ this.at3lastMatch = this.at3origMatch
+
+ this:HighlightText(this.at3matchStart-1, cursor)
+ this:Insert(this.at3origWord)
+ this.at3_last_precursor = getTextBeforeCursor(this) or ''
+ end
+ end
+end
diff --git a/lib/AceTab-3.0/AceTab-3.0.xml b/lib/AceTab-3.0/AceTab-3.0.xml
new file mode 100644
index 0000000..86bc9ca
--- /dev/null
+++ b/lib/AceTab-3.0/AceTab-3.0.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/lib/AceTimer-3.0/AceTimer-3.0.lua b/lib/AceTimer-3.0/AceTimer-3.0.lua
new file mode 100644
index 0000000..fdb2cff
--- /dev/null
+++ b/lib/AceTimer-3.0/AceTimer-3.0.lua
@@ -0,0 +1,473 @@
+--- **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, rescheduled
+-- 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.1s. This constant may change
+-- in the future, but for now it seemed like a good compromise in efficiency and accuracy.
+--
+-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you
+-- need to cancel or reschedule 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 895 2009-12-06 16:28:55Z nevcairiel $
+
+--[[
+ Basic assumptions:
+ * In a typical system, we do more re-scheduling per second than there are timer pulses per second
+ * Regardless of timer implementation, we cannot guarantee timely delivery due to FPS restriction (may be as low as 10)
+
+ This implementation:
+ CON: The smallest timer interval is constrained by HZ (currently 1/10s).
+ PRO: It will still correctly fire any timer slower than HZ over a length of time, e.g. 0.11s interval -> 90 times over 10 seconds
+ PRO: In lag bursts, the system simly skips missed timer intervals to decrease load
+ CON: Algorithms depending on a timer firing "N times per minute" will fail
+ PRO: (Re-)scheduling is O(1) with a VERY small constant. It's a simple linked list insertion in a hash bucket.
+ CAUTION: The BUCKETS constant constrains how many timers can be efficiently handled. With too many hash collisions, performance will decrease.
+
+ Major assumptions upheld:
+ - ALLOWS scheduling multiple timers with the same funcref/method
+ - ALLOWS scheduling more timers during OnUpdate processing
+ - ALLOWS unscheduling ANY timer (including the current running one) at any time, including during OnUpdate processing
+]]
+
+local MAJOR, MINOR = "AceTimer-3.0", 5
+local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
+
+if not AceTimer then return end -- No upgrade needed
+
+AceTimer.hash = AceTimer.hash or {} -- Array of [0..BUCKET-1] = linked list of timers (using .next member)
+ -- Linked list gets around ACE-88 and ACE-90.
+AceTimer.selfs = AceTimer.selfs or {} -- Array of [self]={[handle]=timerobj, [handle2]=timerobj2, ...}
+AceTimer.frame = AceTimer.frame or CreateFrame("Frame", "AceTimer30Frame")
+
+-- Lua APIs
+local assert, error, loadstring = assert, error, loadstring
+local setmetatable, rawset, rawget = setmetatable, rawset, rawget
+local select, pairs, type, next, tostring = select, pairs, type, next, tostring
+local floor, max, min = math.floor, math.max, math.min
+local tconcat = table.concat
+
+-- WoW APIs
+local GetTime = GetTime
+
+-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
+-- List them here for Mikk's FindGlobals script
+-- GLOBALS: DEFAULT_CHAT_FRAME, geterrorhandler
+
+-- Simple ONE-SHOT timer cache. Much more efficient than a full compost for our purposes.
+local timerCache = nil
+
+--[[
+ Timers will not be fired more often than HZ-1 times per second.
+ Keep at intended speed PLUS ONE or we get bitten by floating point rounding errors (n.5 + 0.1 can be n.599999)
+ If this is ever LOWERED, all existing timers need to be enforced to have a delay >= 1/HZ on lib upgrade.
+ If this number is ever changed, all entries need to be rehashed on lib upgrade.
+ ]]
+local HZ = 11
+
+--[[
+ Prime for good distribution
+ If this number is ever changed, all entries need to be rehashed on lib upgrade.
+]]
+local BUCKETS = 131
+
+local hash = AceTimer.hash
+for i=1,BUCKETS do
+ hash[i] = hash[i] or false -- make it an integer-indexed array; it's faster than hashes
+end
+
+--[[
+ xpcall safecall implementation
+]]
+local xpcall = xpcall
+
+local function errorhandler(err)
+ return geterrorhandler()(err)
+end
+
+local function CreateDispatcher(argCount)
+ local code = [[
+ local xpcall, eh = ... -- our arguments are received as unnamed values in "..." since we don't have a proper function declaration
+ 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, ...)
+ return Dispatchers[select('#', ...)](func, ...)
+end
+
+local lastint = floor(GetTime() * HZ)
+
+-- --------------------------------------------------------------------
+-- OnUpdate handler
+--
+-- traverse buckets, always chasing "now", and fire timers that have expired
+
+local function OnUpdate()
+ local now = GetTime()
+ local nowint = floor(now * HZ)
+
+ -- Have we passed into a new hash bucket?
+ if nowint == lastint then return end
+
+ local soon = now + 1 -- +1 is safe as long as 1 < HZ < BUCKETS/2
+
+ -- Pass through each bucket at most once
+ -- Happens on e.g. instance loads, but COULD happen on high local load situations also
+ for curint = (max(lastint, nowint - BUCKETS) + 1), nowint do -- loop until we catch up with "now", usually only 1 iteration
+ local curbucket = (curint % BUCKETS)+1
+ -- Yank the list of timers out of the bucket and empty it. This allows reinsertion in the currently-processed bucket from callbacks.
+ local nexttimer = hash[curbucket]
+ hash[curbucket] = false -- false rather than nil to prevent the array from becoming a hash
+
+ while nexttimer do
+ local timer = nexttimer
+ nexttimer = timer.next
+ local when = timer.when
+
+ if when < soon then
+ -- Call the timer func, either as a method on given object, or a straight function ref
+ local callback = timer.callback
+ if type(callback) == "string" then
+ safecall(timer.object[callback], timer.object, timer.arg)
+ elseif callback then
+ safecall(callback, timer.arg)
+ else
+ -- probably nilled out by CancelTimer
+ timer.delay = nil -- don't reschedule it
+ end
+
+ local delay = timer.delay -- NOW make a local copy, can't do it earlier in case the timer cancelled itself in the callback
+
+ if not delay then
+ -- single-shot timer (or cancelled)
+ AceTimer.selfs[timer.object][tostring(timer)] = nil
+ timerCache = timer
+ else
+ -- repeating timer
+ local newtime = when + delay
+ if newtime < now then -- Keep lag from making us firing a timer unnecessarily. (Note that this still won't catch too-short-delay timers though.)
+ newtime = now + delay
+ end
+ timer.when = newtime
+
+ -- add next timer execution to the correct bucket
+ local bucket = (floor(newtime * HZ) % BUCKETS) + 1
+ timer.next = hash[bucket]
+ hash[bucket] = timer
+ end
+ else -- if when>=soon
+ -- reinsert (yeah, somewhat expensive, but shouldn't be happening too often either due to hash distribution)
+ timer.next = hash[curbucket]
+ hash[curbucket] = timer
+ end -- if whenhandle->timer registry
+ local handle = tostring(timer)
+
+ local selftimers = AceTimer.selfs[self]
+ if not selftimers then
+ selftimers = {}
+ AceTimer.selfs[self] = selftimers
+ end
+ selftimers[handle] = timer
+ selftimers.__ops = (selftimers.__ops or 0) + 1
+
+ return handle
+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 arg An optional argument to be passed to the callback function.
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "AceTimer-3.0")
+--
+-- function MyAddon:OnEnable()
+-- self:ScheduleTimer("TimerFeedback", 5)
+-- end
+--
+-- function MyAddon:TimerFeedback()
+-- print("5 seconds passed")
+-- end
+function AceTimer:ScheduleTimer(callback, delay, arg)
+ return Reg(self, callback, delay, arg)
+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 arg An optional argument to be passed to the callback function.
+-- @usage
+-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("TimerTest", "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(callback, delay, arg)
+ return Reg(self, callback, delay, arg, true)
+end
+
+--- Cancels a timer with the given handle, 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 `handle` is valid
+-- and the timer has not fired yet or was canceled before.
+-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
+-- @param silent If true, no error is raised if the timer handle is invalid (expired or already canceled)
+-- @return True if the timer was successfully cancelled.
+function AceTimer:CancelTimer(handle, silent)
+ if not handle then return end -- nil handle -> bail out without erroring
+ if type(handle) ~= "string" then
+ error(MAJOR..": CancelTimer(handle): 'handle' - expected a string", 2) -- for now, anyway
+ end
+ local selftimers = AceTimer.selfs[self]
+ local timer = selftimers and selftimers[handle]
+ if silent then
+ if timer then
+ timer.callback = nil -- don't run it again
+ timer.delay = nil -- if this is the currently-executing one: don't even reschedule
+ -- The timer object is removed in the OnUpdate loop
+ end
+ return not not timer -- might return "true" even if we double-cancel. we'll live.
+ else
+ if not timer then
+ geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - no such timer registered")
+ return false
+ end
+ if not timer.callback then
+ geterrorhandler()(MAJOR..": CancelTimer(handle[, silent]): '"..tostring(handle).."' - timer already cancelled or expired")
+ return false
+ end
+ timer.callback = nil -- don't run it again
+ timer.delay = nil -- if this is the currently-executing one: don't even reschedule
+ return true
+ end
+end
+
+--- Cancels all timers registered to the current addon object ('self')
+function AceTimer:CancelAllTimers()
+ if not(type(self) == "string" or type(self) == "table") then
+ error(MAJOR..": CancelAllTimers(): 'self' - must be a string or a table",2)
+ end
+ if self == AceTimer then
+ error(MAJOR..": CancelAllTimers(): supply a meaningful 'self'", 2)
+ end
+
+ local selftimers = AceTimer.selfs[self]
+ if selftimers then
+ for handle,v in pairs(selftimers) do
+ if type(v) == "table" then -- avoid __ops, etc
+ AceTimer.CancelTimer(self, handle, true)
+ end
+ end
+ end
+end
+
+--- Returns the time left for a timer with the given handle, registered by the current addon object ('self').
+-- This function will raise a warning when the handle is invalid, but not stop execution.
+-- @param handle The handle of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer`
+-- @return The time left on the timer, or false if the handle is invalid.
+function AceTimer:TimeLeft(handle)
+ if not handle then return end
+ if type(handle) ~= "string" then
+ error(MAJOR..": TimeLeft(handle): 'handle' - expected a string", 2) -- for now, anyway
+ end
+ local selftimers = AceTimer.selfs[self]
+ local timer = selftimers and selftimers[handle]
+ if not timer then
+ geterrorhandler()(MAJOR..": TimeLeft(handle): '"..tostring(handle).."' - no such timer registered")
+ return false
+ end
+ return timer.when - GetTime()
+end
+
+
+-- ---------------------------------------------------------------------
+-- PLAYER_REGEN_ENABLED: Run through our .selfs[] array step by step
+-- and clean it out - otherwise the table indices can grow indefinitely
+-- if an addon starts and stops a lot of timers. AceBucket does this!
+--
+-- See ACE-94 and tests/AceTimer-3.0-ACE-94.lua
+
+local lastCleaned = nil
+
+local function OnEvent(this, event)
+ if event~="PLAYER_REGEN_ENABLED" then
+ return
+ end
+
+ -- Get the next 'self' to process
+ local selfs = AceTimer.selfs
+ local self = next(selfs, lastCleaned)
+ if not self then
+ self = next(selfs)
+ end
+ lastCleaned = self
+ if not self then -- should only happen if .selfs[] is empty
+ return
+ end
+
+ -- Time to clean it out?
+ local list = selfs[self]
+ if (list.__ops or 0) < 250 then -- 250 slosh indices = ~10KB wasted (max!). For one 'self'.
+ return
+ end
+
+ -- Create a new table and copy all members over
+ local newlist = {}
+ local n=0
+ for k,v in pairs(list) do
+ newlist[k] = v
+ n=n+1
+ end
+ newlist.__ops = 0 -- Reset operation count
+
+ -- And since we now have a count of the number of live timers, check that it's reasonable. Emit a warning if not.
+ if n>BUCKETS then
+ DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: The addon/module '"..tostring(self).."' has "..n.." live timers. Surely that's not intended?")
+ end
+
+ selfs[self] = newlist
+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
+
+-- ---------------------------------------------------------------------
+-- Debug tools (expose copies of internals to test suites)
+AceTimer.debug = AceTimer.debug or {}
+AceTimer.debug.HZ = HZ
+AceTimer.debug.BUCKETS = BUCKETS
+
+-- ---------------------------------------------------------------------
+-- Finishing touchups
+
+AceTimer.frame:SetScript("OnUpdate", OnUpdate)
+AceTimer.frame:SetScript("OnEvent", OnEvent)
+AceTimer.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
+
+-- In theory, we should hide&show the frame based on there being timers or not.
+-- However, this job is fairly expensive, and the chance that there will
+-- actually be zero timers running is diminuitive to say the lest.
diff --git a/lib/AceTimer-3.0/AceTimer-3.0.xml b/lib/AceTimer-3.0/AceTimer-3.0.xml
new file mode 100644
index 0000000..38e9021
--- /dev/null
+++ b/lib/AceTimer-3.0/AceTimer-3.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/lib/DRData-1.0/DRData-1.0.lua b/lib/DRData-1.0/DRData-1.0.lua
new file mode 100644
index 0000000..8dd1691
--- /dev/null
+++ b/lib/DRData-1.0/DRData-1.0.lua
@@ -0,0 +1,614 @@
+local major = "DRData-1.0"
+local minor = 1003
+assert(LibStub, string.format("%s requires LibStub.", major))
+
+local Data = LibStub:NewLibrary(major, minor)
+if( not Data ) then return end
+
+local L = {
+ ["Banish"] = "Banish",
+ ["Charge"] = "Charge",
+ ["Cheap Shot"] = "Cheap Shot",
+ ["Controlled stuns"] = "Controlled stuns",
+ ["Cyclone"] = "Cyclone",
+ ["Disarms"] = "Disarms",
+ ["Disorients"] = "Disorients",
+ ["Entrapment"] = "Entrapment",
+ ["Fears"] = "Fears",
+ ["Horrors"] = "Horrors",
+ ["Mind Control"] = "Mind Control",
+ ["Random roots"] = "Random roots",
+ ["Random stuns"] = "Random stuns",
+ ["Controlled roots"] = "Controlled roots",
+ ["Scatter Shot"] = "Scatter Shot",
+ ["Silences"] = "Silences",
+ ["Hibernate"] = "Hibernate",
+ ["Taunts"] = "Taunts",
+}
+
+if GetLocale() == "frFR" then
+ L["Banish"] = "Bannissement"
+ L["Charge"] = "Charge"
+ L["Cheap Shot"] = "Coup bas"
+ L["Controlled stuns"] = "Etourdissements contrôlés"
+ L["Cyclone"] = "Cyclone"
+ L["Disarms"] = "Désarmements"
+ L["Disorients"] = "Désorientations"
+ L["Entrapment"] = "Piège"
+ L["Fears"] = "Peurs"
+ L["Horrors"] = "Horreurs"
+ L["Mind Control"] = "Contrôle mental"
+ L["Random roots"] = "Immobilisations aléatoires"
+ L["Random stuns"] = "Etourdissemensts aléatoires"
+ L["Controlled roots"] = "Immobilisations contrôlées"
+ L["Scatter Shot"] = "Flèche de dispersion"
+ L["Silences"] = "Silences"
+ L["Hibernate"] = "Hibernation"
+ L["Taunts"] = "Provocations"
+end
+
+-- How long before DR resets
+-- While everyone will tell you it's 15 seconds, it's actually 16 - 20 seconds with 18 being a decent enough average
+Data.RESET_TIME = 18
+
+-- List of spellID -> DR category
+Data.spells = {
+ --[[ TAUNT ]]--
+ -- Taunt (Warrior)
+ [355] = "taunt",
+ -- Taunt (Pet)
+ [53477] = "taunt",
+ -- Mocking Blow
+ [694] = "taunt",
+ -- Growl (Druid)
+ [6795] = "taunt",
+ -- Dark Command
+ [56222] = "taunt",
+ -- Hand of Reckoning
+ [62124] = "taunt",
+ -- Righteous Defense
+ [31790] = "taunt",
+ -- Distracting Shot
+ [20736] = "taunt",
+ -- Challenging Shout
+ [1161] = "taunt",
+ -- Challenging Roar
+ [5209] = "taunt",
+ -- Death Grip
+ [49560] = "taunt",
+ -- Challenging Howl
+ [59671] = "taunt",
+ -- Angered Earth
+ [36213] = "taunt",
+
+ --[[ DISORIENTS ]]--
+ -- Dragon's Breath
+ [31661] = "disorient",
+ [33041] = "disorient",
+ [33042] = "disorient",
+ [33043] = "disorient",
+ [42949] = "disorient",
+ [42950] = "disorient",
+
+ -- Hungering Cold
+ [49203] = "disorient",
+
+ -- Sap
+ [6770] = "disorient",
+ [2070] = "disorient",
+ [11297] = "disorient",
+ [51724] = "disorient",
+
+ -- Gouge
+ [1776] = "disorient",
+
+ -- Hex (Guessing)
+ [51514] = "disorient",
+
+ -- Shackle
+ [9484] = "disorient",
+ [9485] = "disorient",
+ [10955] = "disorient",
+
+ -- Polymorph
+ [118] = "disorient",
+ [12824] = "disorient",
+ [12825] = "disorient",
+ [28272] = "disorient",
+ [28271] = "disorient",
+ [12826] = "disorient",
+ [61305] = "disorient",
+ [61025] = "disorient",
+ [61721] = "disorient",
+ [61780] = "disorient",
+
+ -- Freezing Trap
+ [3355] = "disorient",
+ [14308] = "disorient",
+ [14309] = "disorient",
+
+ -- Freezing Arrow
+ [60210] = "disorient",
+
+ -- Wyvern Sting
+ [19386] = "disorient",
+ [24132] = "disorient",
+ [24133] = "disorient",
+ [27068] = "disorient",
+ [49011] = "disorient",
+ [49012] = "disorient",
+
+ -- Repentance
+ [20066] = "disorient",
+
+ --[[ SILENCES ]]--
+ -- Nether Shock
+ [53588] = "silence",
+ [53589] = "silence",
+
+ -- Garrote
+ [1330] = "silence",
+
+ -- Arcane Torrent (Energy version)
+ [25046] = "silence",
+
+ -- Arcane Torrent (Mana version)
+ [28730] = "silence",
+
+ -- Arcane Torrent (Runic power version)
+ [50613] = "silence",
+
+ -- Silence
+ [15487] = "silence",
+
+ -- Silencing Shot
+ [34490] = "silence",
+
+ -- Improved Kick
+ [18425] = "silence",
+
+ -- Improved Counterspell
+ [18469] = "silence",
+
+ -- Spell Lock
+ [19244] = "silence",
+ [19647] = "silence",
+
+ -- Shield of the Templar
+ [63529] = "silence",
+
+ -- Strangulate
+ [47476] = "silence",
+ [49913] = "silence",
+ [49914] = "silence",
+ [49915] = "silence",
+ [49916] = "silence",
+
+ -- Gag Order (Warrior talent)
+ [18498] = "silence",
+
+ --[[ DISARMS ]]--
+ -- Snatch
+ [53542] = "disarm",
+ [53543] = "disarm",
+
+ -- Dismantle
+ [51722] = "disarm",
+
+ -- Disarm
+ [676] = "disarm",
+
+ -- Chimera Shot - Scorpid
+ [53359] = "disarm",
+
+ -- Psychic Horror (Disarm effect)
+ [64058] = "disarm",
+
+ --[[ FEARS ]]--
+ -- Blind
+ [2094] = "fear",
+
+ -- Fear (Warlock)
+ [5782] = "fear",
+ [6213] = "fear",
+ [6215] = "fear",
+
+ -- Seduction (Pet)
+ [6358] = "fear",
+
+ -- Howl of Terror
+ [5484] = "fear",
+ [17928] = "fear",
+
+ -- Psychic scream
+ [8122] = "fear",
+ [8124] = "fear",
+ [10888] = "fear",
+ [10890] = "fear",
+
+ -- Scare Beast
+ [1513] = "fear",
+ [14326] = "fear",
+ [14327] = "fear",
+
+ -- Turn Evil
+ [10326] = "fear",
+
+ -- Intimidating Shout
+ [5246] = "fear",
+
+
+ --[[ CONTROL STUNS ]]--
+ -- Intercept (Felguard)
+ [30153] = "ctrlstun",
+ [30195] = "ctrlstun",
+ [30197] = "ctrlstun",
+ [47995] = "ctrlstun",
+
+ -- Ravage
+ [50518] = "ctrlstun",
+ [53558] = "ctrlstun",
+ [53559] = "ctrlstun",
+ [53560] = "ctrlstun",
+ [53561] = "ctrlstun",
+ [53562] = "ctrlstun",
+
+ -- Sonic Blast
+ [50519] = "ctrlstun",
+ [53564] = "ctrlstun",
+ [53565] = "ctrlstun",
+ [53566] = "ctrlstun",
+ [53567] = "ctrlstun",
+ [53568] = "ctrlstun",
+
+ -- Concussion Blow
+ [12809] = "ctrlstun",
+
+ -- Shockwave
+ [46968] = "ctrlstun",
+
+ -- Hammer of Justice
+ [853] = "ctrlstun",
+ [5588] = "ctrlstun",
+ [5589] = "ctrlstun",
+ [10308] = "ctrlstun",
+
+ -- Bash
+ [5211] = "ctrlstun",
+ [6798] = "ctrlstun",
+ [8983] = "ctrlstun",
+
+ -- Intimidation
+ [19577] = "ctrlstun",
+
+ -- Maim
+ [22570] = "ctrlstun",
+ [49802] = "ctrlstun",
+
+ -- Kidney Shot
+ [408] = "ctrlstun",
+ [8643] = "ctrlstun",
+
+ -- War Stomp
+ [20549] = "ctrlstun",
+
+ -- Intercept
+ [20252] = "ctrlstun",
+
+ -- Deep Freeze
+ [44572] = "ctrlstun",
+
+ -- Shadowfury
+ [30283] = "ctrlstun",
+ [30413] = "ctrlstun",
+ [30414] = "ctrlstun",
+
+ -- Holy Wrath
+ [2812] = "ctrlstun",
+
+ -- Inferno Effect
+ [22703] = "ctrlstun",
+
+ -- Demon Charge
+ [60995] = "ctrlstun",
+
+ -- Gnaw (Ghoul)
+ [47481] = "ctrlstun",
+
+ --[[ RANDOM STUNS ]]--
+ -- Impact
+ [12355] = "rndstun",
+
+ -- Stoneclaw Stun
+ [39796] = "rndstun",
+
+ -- Seal of Justice
+ [20170] = "rndstun",
+
+ -- Revenge Stun
+ [12798] = "rndstun",
+
+ --[[ CYCLONE ]]--
+ -- Cyclone
+ [33786] = "cyclone",
+
+ --[[ ROOTS ]]--
+ -- Freeze (Water Elemental)
+ [33395] = "ctrlroot",
+
+ -- Pin (Crab)
+ [50245] = "ctrlroot",
+ [53544] = "ctrlroot",
+ [53545] = "ctrlroot",
+ [53546] = "ctrlroot",
+ [53547] = "ctrlroot",
+ [53548] = "ctrlroot",
+
+ -- Frost Nova
+ [122] = "ctrlroot",
+ [865] = "ctrlroot",
+ [6131] = "ctrlroot",
+ [10230] = "ctrlroot",
+ [27088] = "ctrlroot",
+ [42917] = "ctrlroot",
+
+ -- Entangling Roots
+ [339] = "ctrlroot",
+ [1062] = "ctrlroot",
+ [5195] = "ctrlroot",
+ [5196] = "ctrlroot",
+ [9852] = "ctrlroot",
+ [9853] = "ctrlroot",
+ [26989] = "ctrlroot",
+ [53308] = "ctrlroot",
+
+ -- Nature's Grasp (Uses different spellIDs than Entangling Roots for the same spell)
+ [19970] = "ctrlroot",
+ [19971] = "ctrlroot",
+ [19972] = "ctrlroot",
+ [19973] = "ctrlroot",
+ [19974] = "ctrlroot",
+ [19975] = "ctrlroot",
+ [27010] = "ctrlroot",
+ [53313] = "ctrlroot",
+
+ -- Earthgrab (Storm, Earth and Fire talent)
+ [8377] = "ctrlroot",
+ [31983] = "ctrlroot",
+
+ -- Web (Spider)
+ [4167] = "ctrlroot",
+
+ -- Venom Web Spray (Silithid)
+ [54706] = "ctrlroot",
+ [55505] = "ctrlroot",
+ [55506] = "ctrlroot",
+ [55507] = "ctrlroot",
+ [55508] = "ctrlroot",
+ [55509] = "ctrlroot",
+
+
+ --[[ RANDOM ROOTS ]]--
+ -- Improved Hamstring
+ [23694] = "rndroot",
+
+ -- Frostbite
+ [12494] = "rndroot",
+
+ -- Shattered Barrier
+ [55080] = "rndroot",
+
+ --[[ SLEEPS ]]--
+ -- Hibernate
+ [2637] = "sleep",
+ [18657] = "sleep",
+ [18658] = "sleep",
+
+ --[[ HORROR ]]--
+ -- Death Coil
+ [6789] = "horror",
+ [17925] = "horror",
+ [17926] = "horror",
+ [27223] = "horror",
+ [47859] = "horror",
+ [47860] = "horror",
+
+ -- Psychic Horror
+ [64044] = "horror",
+
+ --[[ MISC ]]--
+ -- Scatter Shot
+ [19503] = "scatters",
+
+ -- Cheap Shot
+ [1833] = "cheapshot",
+
+ -- Pounce
+ [9005] = "cheapshot",
+ [9823] = "cheapshot",
+ [9827] = "cheapshot",
+ [27006] = "cheapshot",
+ [49803] = "cheapshot",
+
+ -- Charge
+ [7922] = "charge",
+
+ -- Mind Control
+ [605] = "mc",
+
+ -- Banish
+ [710] = "banish",
+ [18647] = "banish",
+
+ -- Entrapment
+ [64804] = "entrapment",
+ [64804] = "entrapment",
+ [19185] = "entrapment",
+}
+
+-- DR Category names
+Data.categoryNames = {
+ ["banish"] = L["Banish"],
+ ["charge"] = L["Charge"],
+ ["cheapshot"] = L["Cheap Shot"],
+ ["ctrlstun"] = L["Controlled stuns"],
+ ["cyclone"] = L["Cyclone"],
+ ["disarm"] = L["Disarms"],
+ ["disorient"] = L["Disorients"],
+ ["entrapment"] = L["Entrapment"],
+ ["fear"] = L["Fears"],
+ ["horror"] = L["Horrors"],
+ ["mc"] = L["Mind Control"],
+ ["rndroot"] = L["Random roots"],
+ ["rndstun"] = L["Random stuns"],
+ ["ctrlroot"] = L["Controlled roots"],
+ ["scatters"] = L["Scatter Shot"],
+ ["silence"] = L["Silences"],
+ ["sleep"] = L["Hibernate"],
+ ["taunt"] = L["Taunts"],
+}
+
+-- Categories that have DR in PvE as well as PvP
+Data.pveDR = {
+ ["ctrlstun"] = true,
+ ["rndstun"] = true,
+ ["taunt"] = true,
+ ["cyclone"] = true,
+}
+
+-- Public APIs
+-- Category name in something usable
+function Data:GetCategoryName(cat)
+ return cat and Data.categoryNames[cat] or nil
+end
+
+-- Spell list
+function Data:GetSpells()
+ return Data.spells
+end
+
+-- Seconds before DR resets
+function Data:GetResetTime()
+ return Data.RESET_TIME
+end
+
+-- Get the category of the spellID
+function Data:GetSpellCategory(spellID)
+ return spellID and Data.spells[spellID] or nil
+end
+
+-- Does this category DR in PvE?
+function Data:IsPVE(cat)
+ return cat and Data.pveDR[cat] or nil
+end
+
+-- List of categories
+function Data:GetCategories()
+ return Data.categoryNames
+end
+
+-- Next DR, if it's 1.0, next is 0.50, if it's 0.[50] = "ctrlroot",next is 0.[25] = "ctrlroot",and such
+function Data:NextDR(diminished)
+ if( diminished == 1 ) then
+ return 0.50
+ elseif( diminished == 0.50 ) then
+ return 0.25
+ end
+
+ return 0
+end
+
+--[[ EXAMPLES ]]--
+-- This is how you would track DR easily, you're welcome to do whatever you want with the below functions
+
+--[[
+local trackedPlayers = {}
+local function debuffGained(spellID, destName, destGUID, isEnemy, isPlayer)
+ -- Not a player, and this category isn't diminished in PVE, as well as make sure we want to track NPCs
+ local drCat = DRData:GetSpellCategory(spellID)
+ if( not isPlayer and not DRData:IsPVE(drCat) ) then
+ return
+ end
+
+ if( not trackedPlayers[destGUID] ) then
+ trackedPlayers[destGUID] = {}
+ end
+
+ -- See if we should reset it back to undiminished
+ local tracked = trackedPlayers[destGUID][drCat]
+ if( tracked and tracked.reset <= GetTime() ) then
+ tracked.diminished = 1.0
+ end
+end
+
+local function debuffFaded(spellID, destName, destGUID, isEnemy, isPlayer)
+ local drCat = DRData:GetSpellCategory(spellID)
+ if( not isPlayer and not DRData:IsPVE(drCat) ) then
+ return
+ end
+
+ if( not trackedPlayers[destGUID] ) then
+ trackedPlayers[destGUID] = {}
+ end
+
+ if( not trackedPlayers[destGUID][drCat] ) then
+ trackedPlayers[destGUID][drCat] = { reset = 0, diminished = 1.0 }
+ end
+
+ local time = GetTime()
+ local tracked = trackedPlayers[destGUID][drCat]
+
+ tracked.reset = time + DRData:GetResetTime()
+ tracked.diminished = DRData:NextDR(tracked.diminished)
+
+ -- Diminishing returns changed, now you can do an update
+end
+
+local function resetDR(destGUID)
+ -- Reset the tracked DRs for this person
+ if( trackedPlayers[destGUID] ) then
+ for cat in pairs(trackedPlayers[destGUID]) do
+ trackedPlayers[destGUID][cat].reset = 0
+ trackedPlayers[destGUID][cat].diminished = 1.0
+ end
+ end
+end
+
+local COMBATLOG_OBJECT_TYPE_PLAYER = COMBATLOG_OBJECT_TYPE_PLAYER
+local COMBATLOG_OBJECT_REACTION_HOSTILE = COMBATLOG_OBJECT_REACTION_HOSTILE
+local COMBATLOG_OBJECT_CONTROL_PLAYER = COMBATLOG_OBJECT_CONTROL_PLAYER
+
+local eventRegistered = {["SPELL_AURA_APPLIED"] = true, ["SPELL_AURA_REFRESH"] = true, ["SPELL_AURA_REMOVED"] = true, ["PARTY_KILL"] = true, ["UNIT_DIED"] = true}
+local function COMBAT_LOG_EVENT_UNFILTERED(self, event, timestamp, eventType, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags, spellID, spellName, spellSchool, auraType)
+ if( not eventRegistered[eventType] ) then
+ return
+ end
+
+ -- Enemy gained a debuff
+ if( eventType == "SPELL_AURA_APPLIED" ) then
+ if( auraType == "DEBUFF" and DRData:GetSpellCategory(spellID) ) then
+ local isPlayer = ( bit.band(destFlags, COMBATLOG_OBJECT_TYPE_PLAYER) == COMBATLOG_OBJECT_TYPE_PLAYER or bit.band(destFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) == COMBATLOG_OBJECT_CONTROL_PLAYER )
+ debuffGained(spellID, destName, destGUID, (bit.band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE), isPlayer)
+ end
+
+ -- Enemy had a debuff refreshed before it faded, so fade + gain it quickly
+ elseif( eventType == "SPELL_AURA_REFRESH" ) then
+ if( auraType == "DEBUFF" and DRData:GetSpellCategory(spellID) ) then
+ local isPlayer = ( bit.band(destFlags, COMBATLOG_OBJECT_TYPE_PLAYER) == COMBATLOG_OBJECT_TYPE_PLAYER or bit.band(destFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) == COMBATLOG_OBJECT_CONTROL_PLAYER )
+ local isHostile = (bit.band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE)
+ debuffFaded(spellID, destName, destGUID, isHostile, isPlayer)
+ debuffGained(spellID, destName, destGUID, isHostile, isPlayer)
+ end
+
+ -- Buff or debuff faded from an enemy
+ elseif( eventType == "SPELL_AURA_REMOVED" ) then
+ if( auraType == "DEBUFF" and DRData:GetSpellCategory(spellID) ) then
+ local isPlayer = ( bit.band(destFlags, COMBATLOG_OBJECT_TYPE_PLAYER) == COMBATLOG_OBJECT_TYPE_PLAYER or bit.band(destFlags, COMBATLOG_OBJECT_CONTROL_PLAYER) == COMBATLOG_OBJECT_CONTROL_PLAYER )
+ debuffFaded(spellID, destName, destGUID, (bit.band(destFlags, COMBATLOG_OBJECT_REACTION_HOSTILE) == COMBATLOG_OBJECT_REACTION_HOSTILE), isPlayer)
+ end
+
+ -- Don't use UNIT_DIED inside arenas due to accuracy issues, outside of arenas we don't care too much
+ elseif( ( eventType == "UNIT_DIED" and select(2, IsInInstance()) ~= "arena" ) or eventType == "PARTY_KILL" ) then
+ resetDR(destGUID)
+ end
+end]]
diff --git a/lib/DRData-1.0/DRData-1.0.toc b/lib/DRData-1.0/DRData-1.0.toc
new file mode 100644
index 0000000..ab1e209
--- /dev/null
+++ b/lib/DRData-1.0/DRData-1.0.toc
@@ -0,0 +1,15 @@
+## Interface: 30300
+## Title: Lib: Diminishing Returns Data-1.0
+## Notes: Database of Diminishing return categories and general data
+## Author: Mayen
+## X-Category: Library
+## X-Curse-Packaged-Version: v2.2
+## X-Curse-Project-Name: DRData-1.0
+## X-Curse-Project-ID: drdata-1-0
+## X-Curse-Repository-ID: wow/drdata-1-0/mainline
+
+#@no-lib-strip@
+libs\LibStub\LibStub.lua
+#@end-no-lib-strip@
+
+DRData-1.0.xml
diff --git a/lib/DRData-1.0/DRData-1.0.xml b/lib/DRData-1.0/DRData-1.0.xml
new file mode 100644
index 0000000..e754f06
--- /dev/null
+++ b/lib/DRData-1.0/DRData-1.0.xml
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file