Mercurial > wow > hansgar_and_franzok_assist
changeset 4:1c3534391efb
- Added timeout for stampers.
| author | Tercio |
|---|---|
| date | Mon, 02 Mar 2015 21:28:33 -0300 |
| parents | 4a3ffc2ee399 |
| children | 789bf9e40966 |
| files | Hansgar_And_Franzok_Assist.lua Hansgar_And_Franzok_Assist.toc Libs/AceComm-3.0/AceComm-3.0.lua Libs/AceComm-3.0/AceComm-3.0.xml Libs/AceComm-3.0/ChatThrottleLib.lua Libs/AceTimer-3.0/AceTimer-3.0.lua Libs/AceTimer-3.0/AceTimer-3.0.xml Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml Libs/LibStub/LibStub.lua Libs/LibStub/LibStub.toc Libs/LibStub/tests/test.lua Libs/LibStub/tests/test2.lua Libs/LibStub/tests/test3.lua Libs/LibStub/tests/test4.lua Libs/libs.xml |
| diffstat | 16 files changed, 1807 insertions(+), 10 deletions(-) [+] |
line wrap: on
line diff
--- a/Hansgar_And_Franzok_Assist.lua Tue Feb 24 14:53:29 2015 -0300 +++ b/Hansgar_And_Franzok_Assist.lua Mon Mar 02 21:28:33 2015 -0300 @@ -3,10 +3,14 @@ local UnitExists = UnitExists local GetPlayerMapPosition = GetPlayerMapPosition local UnitHealth = UnitHealth +local GetNumGroupMembers = GetNumGroupMembers +local abs = abs local f = CreateFrame ("frame", "Hansgar_And_Franzok_Assist", UIParent) f:SetFrameStrata ("DIALOG") +local tframe = CreateFrame ("frame", "Hansgar_And_Franzok_Assist_PTrack", UIParent) + f:SetSize (155, 156) f:SetBackdrop ({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, insets = {left = -1, right = -1, top = -1, bottom = -1}, edgeFile = "Interface\\AddOns\\Hansgar_And_Franzok_Assist\\border_2", edgeSize = 8}) @@ -44,8 +48,67 @@ -- +local player_pos_frame = CreateFrame ("frame", "Hansgar_And_Franzok_Assist_DanceBar", UIParent) +player_pos_frame:SetPoint ("topleft", player_bar, "bottomleft", 0, -3) +player_pos_frame:SetPoint ("topright", player_bar, "bottomright", 0, -3) +player_pos_frame:SetHeight (14) +player_pos_frame:SetBackdrop ({bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", tile = true, tileSize = 16, insets = {left = -1, right = -1, top = -1, bottom = -1}, +edgeFile = "Interface\\AddOns\\Hansgar_And_Franzok_Assist\\border_2", edgeSize = 8}) +player_pos_frame:SetBackdropColor (0, 0, 0, 1) +player_pos_frame:Hide() + +--red +local t1 = player_pos_frame:CreateTexture (nil, "artwork") +t1:SetPoint ("left", player_pos_frame, "left") +t1:SetSize (player_pos_frame:GetWidth()*0.30, 14) +t1:SetTexture (1, 1, 1) +--t1:SetTexCoord (260/512, 430/512, 29/256, 82/256) +t1:SetVertexColor (1, 0.2, 0.2, 0.4) + +--green +local t2 = player_pos_frame:CreateTexture (nil, "artwork") +t2:SetPoint ("left", t1, "right") +t2:SetSize (player_pos_frame:GetWidth()*0.15, 14) +t2:SetTexture (0.2, 1, 0.2, 0.4) + +--red +local middle = player_pos_frame:CreateTexture (nil, "artwork") +middle:SetPoint ("left", t2, "right") +middle:SetSize (player_pos_frame:GetWidth()*0.10, 14) +middle:SetTexture (1, 1, 1) +--middle:SetTexCoord (260/512, 430/512, 29/256, 82/256) +middle:SetVertexColor (1, 0.2, 0.2, 0.4) + +--green +local t3 = player_pos_frame:CreateTexture (nil, "artwork") +t3:SetPoint ("left", middle, "right") +t3:SetSize (player_pos_frame:GetWidth()*0.15, 14) +t3:SetTexture (0.2, 1, 0.2, 0.4) + +--red +local t4 = player_pos_frame:CreateTexture (nil, "artwork") +t4:SetPoint ("left", t3, "right") +t4:SetSize (player_pos_frame:GetWidth()*0.30, 14) +t4:SetTexture (1, 1, 1) +--t4:SetTexCoord (260/512, 430/512, 29/256, 82/256) +t4:SetVertexColor (1, 0.2, 0.2, 0.4) + +local div = player_pos_frame:CreateTexture (nil, "overlay") +div:SetPoint ("left", player_pos_frame, "left", 0, 0) +div:SetTexture (1, 1, 1, 1) +div:SetSize (1, 16) +div:Hide() +-- + +local AceTimer = LibStub:GetLibrary ("AceTimer-3.0") +AceTimer:Embed (f) +local AceComm = LibStub:GetLibrary ("AceComm-3.0") +AceComm:Embed (f) + local db +f.block_tracker = {} + frame_event:SetFrameStrata ("FULLSCREEN") frame_event:SetScript ("OnEvent", function (self, event, ...) @@ -57,8 +120,15 @@ db = {} Hansgar_And_Franzok_DB = db end + -- db.STAMPERS_DELAY = db.STAMPERS_DELAY or 5 - + if (db.CD_NUMBER == nil) then + db.CD_NUMBER = false + end + if (db.SHOW_DANCE == nil) then + db.SHOW_DANCE = true + end + -- SLASH_Hansgar_And_Franzok_Assist1, SLASH_Hansgar_And_Franzok_Assist2 = "/hansgar", "/franzok" function SlashCmdList.Hansgar_And_Franzok_Assist (msg, editbox) @@ -75,18 +145,40 @@ elseif (command == "test" or command == "show") then if (f.StampersPhase) then + f:EndTrackPlayerPosition() return f:StopTracking() end + f:StartTracking() + f:StartTrackPlayerPosition() elseif (command == "hide") then if (f.StampersPhase) then return f:StopTracking() end + f:EndTrackPlayerPosition() + elseif (command == "dance") then + db.SHOW_DANCE = not db.SHOW_DANCE + if (db.SHOW_DANCE) then + if (f.on_encounter) then + f:StartTrackPlayerPosition() + end + print ("|cFFFFAA00Hansgar and Franzok Assist|r dance bars enabled.") + else + f:EndTrackPlayerPosition() + print ("|cFFFFAA00Hansgar and Franzok Assist|r dance bars disabled.") + end + + elseif (command == "cooldown") then + db.CD_NUMBER = not db.CD_NUMBER + f:RefreshCooldownSettings() + else - print ("|cFFFFAA00Hansgar and Franzok Assist|r |cFF00FF00v0.4|r Commands:") + print ("|cFFFFAA00Hansgar and Franzok Assist|r |cFF00FF00v0.5|r Commands:") print ("|cFFFFFF00/hansgar delay <time>|r: time in seconds until the percentage goes from 0 to 100.") + print ("|cFFFFFF00/hansgar cooldown|r: show the countdown for each stamper go back up to the ceiling.") + --print ("|cFFFFFF00/hansgar dance|r: toggle dance bar (used to dodge regular stampers and searing plates).") print ("|cFFFFFF00/hansgar test|r: active the addon on test mode.") print ("|cFFFFFF00/hansgar show|r: show the window and start test mode.") print ("|cFFFFFF00/hansgar hide|r: hide the window.") @@ -96,6 +188,24 @@ elseif (event == "ENCOUNTER_START" or event == "ENCOUNTER_END") then local encounterID, encounterName, difficultyID, raidSize = select (1, ...) + + if (encounterID == 1693) then + if (event == "ENCOUNTER_START") then + f.on_encounter = true + elseif (event == "ENCOUNTER_END") then + f.on_encounter = false + end + end + + if (encounterID == 1693 and db.SHOW_DANCE) then + if (event == "ENCOUNTER_START") then + SetMapToCurrentZone() + f:StartTrackPlayerPosition() + elseif (event == "ENCOUNTER_END") then + f:EndTrackPlayerPosition() + end + end + if (encounterID == 1693 and difficultyID == 16) then if (event == "ENCOUNTER_START") then @@ -113,6 +223,8 @@ if (f.StampersPhase) then f:StopTracking() end + + f:EndTrackPlayerPosition() end end end @@ -137,7 +249,9 @@ local frame_tracker = CreateFrame ("frame", "Hansgar_And_Franzok_AssistTracker", UIParent) local on_update_tracker = function (self, elapsed) - for i = 1, GetNumGroupMembers() do + local raid_size = GetNumGroupMembers() + + for i = 1, raid_size do local x, y = GetPlayerMapPosition ("raid"..i) if (UnitExists ("raid"..i) and UnitHealth ("raid"..i) > 1 and x and y) then local block = f:WhichBlock (x, y) @@ -152,7 +266,7 @@ local px, py = GetPlayerMapPosition ("player") local player_block = f:WhichBlock (px, py) - if (player_block) then + if (player_block and raid_size > 0) then local time_limit_at = f.block_tracker [player_block] + db.STAMPERS_DELAY local time_now = GetTime() @@ -236,23 +350,53 @@ self:SetScript ("OnUpdate", nil) self.stamper_icon:Show() self.number:Hide() + self.cooldown:SetCooldown (GetTime(), 37 - db.STAMPERS_DELAY, 0, 0) end end end + +function f:UnPaint (block) + f:ResetBlock (block) +end function f:Paint (block) block.step = 0 block.total_time = 0 block:SetScript ("OnUpdate", painting) + local unpaint = f:ScheduleTimer ("UnPaint", 37, block) + block.unpaint_process = unpaint +end + +function f:ResetBlock (block) + block:SetScript ("OnUpdate", nil) + block:SetBackdropColor (.8, .8, .8, 0.5) + block.number:SetText (block.id) + block.number:SetTextColor (1, 1, 1, 0.5) + block.number:Show() + block.stamper_icon:Hide() + block.cooldown:SetCooldown (0, 0, 0, 0) + + f.block_tracker [block.id] = nil + if (block.unpaint_process) then + f:CancelTimer (block.unpaint_process) + block.unpaint_process = nil + end end function f:ResetBlocks() for _, block in ipairs (f.all_blocks) do - block:SetScript ("OnUpdate", nil) - block:SetBackdropColor (.8, .8, .8, 0.5) - block.number:SetText (block.id) - block.number:SetTextColor (1, 1, 1, 0.5) - block.number:Show() - block.stamper_icon:Hide() + f:ResetBlock (block) + end +end + +function f:RefreshCooldownSettings() + for _, block in ipairs (f.all_blocks) do + if (not db.CD_NUMBER) then + block.cooldown:SetHideCountdownNumbers (true) + block.cooldown:SetDrawEdge (false) + else + block.cooldown:SetHideCountdownNumbers (false) + block.cooldown:SetDrawEdge (true) + end end end @@ -280,6 +424,17 @@ block:SetScript ("OnMouseDown", on_mouse_down) block:SetScript ("OnMouseUp", on_mouse_up) + local cooldown = CreateFrame ("cooldown", "Hansgar_And_Franzok_Assist_BlockCooldown" .. i, block, "CooldownFrameTemplate") + cooldown:SetAllPoints() + cooldown:SetFrameLevel (block:GetFrameLevel()+2) + + if (not db.CD_NUMBER) then + cooldown:SetHideCountdownNumbers (true) + cooldown:SetDrawEdge (false) + end + + block.cooldown = cooldown + block.id = i local number = block:CreateFontString (nil, "artwork", "GameFontHighlight") @@ -310,6 +465,117 @@ end +local safe_track = { + --space 1 + { + block = {x1 = 0.50154542922974, x2 = 0.49563668874741}, + left = {x1 = 0.49963343143463, x2 = 0.49963343143463 - 0.000935000000000}, + right = {x1 = 0.49710285663605, x2 = 0.49710285663605 + 0.001012229919432}, + -- {x1 = 0.49963343143463, y1 = 0.73492467403412} -- {x1 = 0.49710285663605, y1 = 0.74445152282715} + }, + --space 2 + { + block = {x1 = 0.4858917593956, x2 = 0.48044270277023}, + left = {x1 = 0.48433673381805, x2 = 0.48433673381805 - 0.00091059207916}, + right = {x1 = 0.48206025362015, x2 = 0.48206025362015 + 0.00075059207916}, + -- {x1 = 0.48433673381805, y1 = 0.74292266368866} -- {x1 = 0.48206025362015, y1 = 0.78930181264877} + }, + --space 3 + { + block = {x1 = 0.47047740221024, x2 = 0.4648859500885}, + left = {x1 = 0.46893924474716, x2 = 0.46893924474716 - 0.001032948493956}, + right = {x1 = 0.46635687351227, x2 = 0.46635687351227 + 0.001032948493956}, + --{x1 = 0.46893924474716, y1 = 0.7981019616127} -- {x1 = 0.46635687351227, y1 = 0.73558133840561} + }, + --space 4 + { + block = {x1 = 0.45503282546997, x2 = 0.44976264238358}, + left = {x1 = 0.4533554315567, x2 = 0.4533554315567 - 0.000774573974608}, + right = {x1 = 0.45124399662018, x2 = 0.45124399662018 + 0.000770009999999}, + --{x1 = 0.4533554315567, y1 = 0.74078941345215} -- {x1 = 0.45124399662018, y1 = 0.74088287353516} + } +} +Hansgar_safe_track = safe_track + +-- /hansgar test +-- /run Hansgar_safe_track [1].block.x1 = 0.50154542922974 + +local red_alpha_disabled = 0.15 +local red_alpha_enabled = 0.5 + +local green_alpha_disabled = 0.05 +local green_alpha_enabled = 0.9 + +local track_function = function (self, elapsed) + + local x, _ = GetPlayerMapPosition ("player") + local block + + for i = 1, #safe_track do + local loc = safe_track [i] + if (x >= loc.block.x2 and x <= loc.block.x1) then + block = i + break + end + end + + if (block) then + + player_pos_frame:Show() + block = safe_track [block] + + if (x >= block.left.x2 and x <= block.left.x1) then + t2:SetTexture (0.1, 1, 0.1, green_alpha_enabled) + t3:SetTexture (0.2, 1, 0.2, green_alpha_disabled) + + t1:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + t4:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + middle:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + + elseif (x <= block.right.x2 and x >= block.right.x1) then + t3:SetTexture (0.1, 1, 0.1, green_alpha_enabled) + t2:SetTexture (0.2, 1, 0.2, green_alpha_disabled) + + t1:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + t4:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + middle:SetVertexColor (1, 0.2, 0.2, red_alpha_disabled) --red + + else + t1:SetVertexColor (1, 0.2, 0.2, red_alpha_enabled) --red + t4:SetVertexColor (1, 0.2, 0.2, red_alpha_enabled) --red + middle:SetVertexColor (1, 0.2, 0.2, red_alpha_enabled) --red + + t2:SetTexture (0.2, 1, 0.2, green_alpha_disabled) + t3:SetTexture (0.2, 1, 0.2, green_alpha_disabled) + + end + + --x = x - block.block.x2 + --local at = abs ((x / (block.block.x1 - block.block.x2) * 100) - 100) + --div:SetPoint ("left", player_pos_frame, "left", self.width_pixel * at, 0) + + else + player_pos_frame:Hide() + end +end + +function f:StartTrackPlayerPosition() + + --> under development + if (true) then + return + end + + player_pos_frame:Show() + tframe.width = player_pos_frame:GetWidth() + tframe.width_pixel = tframe.width / 100 + tframe:SetScript ("OnUpdate", track_function) +end +function f:EndTrackPlayerPosition() + player_pos_frame:Hide() + tframe:SetScript ("OnUpdate", nil) +end + local locs = { --block 1: {x1 = 0.51103663444519, y1 = 0.79726493358612, x2 = 0.50061076879501, y2 = 0.8241291642189},
--- a/Hansgar_And_Franzok_Assist.toc Tue Feb 24 14:53:29 2015 -0300 +++ b/Hansgar_And_Franzok_Assist.toc Mon Mar 02 21:28:33 2015 -0300 @@ -3,4 +3,8 @@ ## Notes: Helps with Smart Stampers on Mythic during this encounter. ## SavedVariables: Hansgar_And_Franzok_DB +#@no-lib-strip@ +Libs\libs.xml +#@end-no-lib-strip@ + Hansgar_And_Franzok_Assist.lua \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceComm-3.0/AceComm-3.0.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,302 @@ +--- **AceComm-3.0** allows you to send messages of unlimited length over the addon comm channels. +-- It'll automatically split the messages into multiple parts and rebuild them on the receiving end.\\ +-- **ChatThrottleLib** is of course being used to avoid being disconnected by the server. +-- +-- **AceComm-3.0** can be embeded into your addon, either explicitly by calling AceComm:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceComm itself.\\ +-- It is recommended to embed AceComm, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceComm. +-- @class file +-- @name AceComm-3.0 +-- @release $Id: AceComm-3.0.lua 1107 2014-02-19 16:40:32Z nevcairiel $ + +--[[ AceComm-3.0 + +TODO: Time out old data rotting around from dead senders? Not a HUGE deal since the number of possible sender names is somewhat limited. + +]] + +local MAJOR, MINOR = "AceComm-3.0", 9 + +local AceComm,oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceComm then return end + +local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0") +local CTL = assert(ChatThrottleLib, "AceComm-3.0 requires ChatThrottleLib") + +-- Lua APIs +local type, next, pairs, tostring = type, next, pairs, tostring +local strsub, strfind = string.sub, string.find +local match = string.match +local tinsert, tconcat = table.insert, table.concat +local error, assert = error, assert + +-- WoW APIs +local Ambiguate = Ambiguate + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, DEFAULT_CHAT_FRAME, geterrorhandler, RegisterAddonMessagePrefix + +AceComm.embeds = AceComm.embeds or {} + +-- for my sanity and yours, let's give the message type bytes some names +local MSG_MULTI_FIRST = "\001" +local MSG_MULTI_NEXT = "\002" +local MSG_MULTI_LAST = "\003" +local MSG_ESCAPE = "\004" + +-- remove old structures (pre WoW 4.0) +AceComm.multipart_origprefixes = nil +AceComm.multipart_reassemblers = nil + +-- the multipart message spool: indexed by a combination of sender+distribution+ +AceComm.multipart_spool = AceComm.multipart_spool or {} + +--- Register for Addon Traffic on a specified prefix +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent), max 16 characters +-- @param method Callback to call on message reception: Function reference, or method name (string) to call on self. Defaults to "OnCommReceived" +function AceComm:RegisterComm(prefix, method) + if method == nil then + method = "OnCommReceived" + end + + if #prefix > 16 then -- TODO: 15? + error("AceComm:RegisterComm(prefix,method): prefix length is limited to 16 characters") + end + RegisterAddonMessagePrefix(prefix) + + return AceComm._RegisterComm(self, prefix, method) -- created by CallbackHandler +end + +local warnedPrefix=false + +--- Send a message over the Addon Channel +-- @param prefix A printable character (\032-\255) classification of the message (typically AddonName or AddonNameEvent) +-- @param text Data to send, nils (\000) not allowed. Any length. +-- @param distribution Addon channel, e.g. "RAID", "GUILD", etc; see SendAddonMessage API +-- @param target Destination for some distributions; see SendAddonMessage API +-- @param prio OPTIONAL: ChatThrottleLib priority, "BULK", "NORMAL" or "ALERT". Defaults to "NORMAL". +-- @param callbackFn OPTIONAL: callback function to be called as each chunk is sent. receives 3 args: the user supplied arg (see next), the number of bytes sent so far, and the number of bytes total to send. +-- @param callbackArg: OPTIONAL: first arg to the callback function. nil will be passed if not specified. +function AceComm:SendCommMessage(prefix, text, distribution, target, prio, callbackFn, callbackArg) + prio = prio or "NORMAL" -- pasta's reference implementation had different prio for singlepart and multipart, but that's a very bad idea since that can easily lead to out-of-sequence delivery! + if not( type(prefix)=="string" and + type(text)=="string" and + type(distribution)=="string" and + (target==nil or type(target)=="string") and + (prio=="BULK" or prio=="NORMAL" or prio=="ALERT") + ) then + error('Usage: SendCommMessage(addon, "prefix", "text", "distribution"[, "target"[, "prio"[, callbackFn, callbackarg]]])', 2) + end + + local textlen = #text + local maxtextlen = 255 -- Yes, the max is 255 even if the dev post said 256. I tested. Char 256+ get silently truncated. /Mikk, 20110327 + local queueName = prefix..distribution..(target or "") + + local ctlCallback = nil + if callbackFn then + ctlCallback = function(sent) + return callbackFn(callbackArg, sent, textlen) + end + end + + local forceMultipart + if match(text, "^[\001-\009]") then -- 4.1+: see if the first character is a control character + -- we need to escape the first character with a \004 + if textlen+1 > maxtextlen then -- would we go over the size limit? + forceMultipart = true -- just make it multipart, no escape problems then + else + text = "\004" .. text + end + end + + if not forceMultipart and textlen <= maxtextlen then + -- fits all in one message + CTL:SendAddonMessage(prio, prefix, text, distribution, target, queueName, ctlCallback, textlen) + else + maxtextlen = maxtextlen - 1 -- 1 extra byte for part indicator in prefix(4.0)/start of message(4.1) + + -- first part + local chunk = strsub(text, 1, maxtextlen) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_FIRST..chunk, distribution, target, queueName, ctlCallback, maxtextlen) + + -- continuation + local pos = 1+maxtextlen + + while pos+maxtextlen <= textlen do + chunk = strsub(text, pos, pos+maxtextlen-1) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_NEXT..chunk, distribution, target, queueName, ctlCallback, pos+maxtextlen-1) + pos = pos + maxtextlen + end + + -- final part + chunk = strsub(text, pos) + CTL:SendAddonMessage(prio, prefix, MSG_MULTI_LAST..chunk, distribution, target, queueName, ctlCallback, textlen) + end +end + + +---------------------------------------- +-- Message receiving +---------------------------------------- + +do + local compost = setmetatable({}, {__mode = "k"}) + local function new() + local t = next(compost) + if t then + compost[t]=nil + for i=#t,3,-1 do -- faster than pairs loop. don't even nil out 1/2 since they'll be overwritten + t[i]=nil + end + return t + end + + return {} + end + + local function lostdatawarning(prefix,sender,where) + DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": Warning: lost network data regarding '"..tostring(prefix).."' from '"..tostring(sender).."' (in "..where..")") + end + + function AceComm:OnReceiveMultipartFirst(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + + --[[ + if spool[key] then + lostdatawarning(prefix,sender,"First") + -- continue and overwrite + end + --]] + + spool[key] = message -- plain string for now + end + + function AceComm:OnReceiveMultipartNext(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + local olddata = spool[key] + + if not olddata then + --lostdatawarning(prefix,sender,"Next") + return + end + + if type(olddata)~="table" then + -- ... but what we have is not a table. So make it one. (Pull a composted one if available) + local t = new() + t[1] = olddata -- add old data as first string + t[2] = message -- and new message as second string + spool[key] = t -- and put the table in the spool instead of the old string + else + tinsert(olddata, message) + end + end + + function AceComm:OnReceiveMultipartLast(prefix, message, distribution, sender) + local key = prefix.."\t"..distribution.."\t"..sender -- a unique stream is defined by the prefix + distribution + sender + local spool = AceComm.multipart_spool + local olddata = spool[key] + + if not olddata then + --lostdatawarning(prefix,sender,"End") + return + end + + spool[key] = nil + + if type(olddata) == "table" then + -- if we've received a "next", the spooled data will be a table for rapid & garbage-free tconcat + tinsert(olddata, message) + AceComm.callbacks:Fire(prefix, tconcat(olddata, ""), distribution, sender) + compost[olddata] = true + else + -- if we've only received a "first", the spooled data will still only be a string + AceComm.callbacks:Fire(prefix, olddata..message, distribution, sender) + end + end +end + + + + + + +---------------------------------------- +-- Embed CallbackHandler +---------------------------------------- + +if not AceComm.callbacks then + AceComm.callbacks = CallbackHandler:New(AceComm, + "_RegisterComm", + "UnregisterComm", + "UnregisterAllComm") +end + +AceComm.callbacks.OnUsed = nil +AceComm.callbacks.OnUnused = nil + +local function OnEvent(self, event, prefix, message, distribution, sender) + if event == "CHAT_MSG_ADDON" then + sender = Ambiguate(sender, "none") + local control, rest = match(message, "^([\001-\009])(.*)") + if control then + if control==MSG_MULTI_FIRST then + AceComm:OnReceiveMultipartFirst(prefix, rest, distribution, sender) + elseif control==MSG_MULTI_NEXT then + AceComm:OnReceiveMultipartNext(prefix, rest, distribution, sender) + elseif control==MSG_MULTI_LAST then + AceComm:OnReceiveMultipartLast(prefix, rest, distribution, sender) + elseif control==MSG_ESCAPE then + AceComm.callbacks:Fire(prefix, rest, distribution, sender) + else + -- unknown control character, ignore SILENTLY (dont warn unnecessarily about future extensions!) + end + else + -- single part: fire it off immediately and let CallbackHandler decide if it's registered or not + AceComm.callbacks:Fire(prefix, message, distribution, sender) + end + else + assert(false, "Received "..tostring(event).." event?!") + end +end + +AceComm.frame = AceComm.frame or CreateFrame("Frame", "AceComm30Frame") +AceComm.frame:SetScript("OnEvent", OnEvent) +AceComm.frame:UnregisterAllEvents() +AceComm.frame:RegisterEvent("CHAT_MSG_ADDON") + + +---------------------------------------- +-- Base library stuff +---------------------------------------- + +local mixins = { + "RegisterComm", + "UnregisterComm", + "UnregisterAllComm", + "SendCommMessage", +} + +-- Embeds AceComm-3.0 into the target object making the functions from the mixins list available on target:.. +-- @param target target object to embed AceComm-3.0 in +function AceComm:Embed(target) + for k, v in pairs(mixins) do + target[v] = self[v] + end + self.embeds[target] = true + return target +end + +function AceComm:OnEmbedDisable(target) + target:UnregisterAllComm() +end + +-- Update embeds +for target, v in pairs(AceComm.embeds) do + AceComm:Embed(target) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceComm-3.0/AceComm-3.0.xml Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,5 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="ChatThrottleLib.lua"/> + <Script file="AceComm-3.0.lua"/> +</Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceComm-3.0/ChatThrottleLib.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,524 @@ +-- +-- ChatThrottleLib by Mikk +-- +-- Manages AddOn chat output to keep player from getting kicked off. +-- +-- ChatThrottleLib:SendChatMessage/:SendAddonMessage functions that accept +-- a Priority ("BULK", "NORMAL", "ALERT") as well as prefix for SendChatMessage. +-- +-- Priorities get an equal share of available bandwidth when fully loaded. +-- Communication channels are separated on extension+chattype+destination and +-- get round-robinned. (Destination only matters for whispers and channels, +-- obviously) +-- +-- Will install hooks for SendChatMessage and SendAddonMessage to measure +-- bandwidth bypassing the library and use less bandwidth itself. +-- +-- +-- Fully embeddable library. Just copy this file into your addon directory, +-- add it to the .toc, and it's done. +-- +-- Can run as a standalone addon also, but, really, just embed it! :-) +-- +-- LICENSE: ChatThrottleLib is released into the Public Domain +-- + +local CTL_VERSION = 23 + +local _G = _G + +if _G.ChatThrottleLib then + if _G.ChatThrottleLib.version >= CTL_VERSION then + -- There's already a newer (or same) version loaded. Buh-bye. + return + elseif not _G.ChatThrottleLib.securelyHooked then + print("ChatThrottleLib: Warning: There's an ANCIENT ChatThrottleLib.lua (pre-wow 2.0, <v16) in an addon somewhere. Get the addon updated or copy in a newer ChatThrottleLib.lua (>=v16) in it!") + -- ATTEMPT to unhook; this'll behave badly if someone else has hooked... + -- ... and if someone has securehooked, they can kiss that goodbye too... >.< + _G.SendChatMessage = _G.ChatThrottleLib.ORIG_SendChatMessage + if _G.ChatThrottleLib.ORIG_SendAddonMessage then + _G.SendAddonMessage = _G.ChatThrottleLib.ORIG_SendAddonMessage + end + end + _G.ChatThrottleLib.ORIG_SendChatMessage = nil + _G.ChatThrottleLib.ORIG_SendAddonMessage = nil +end + +if not _G.ChatThrottleLib then + _G.ChatThrottleLib = {} +end + +ChatThrottleLib = _G.ChatThrottleLib -- in case some addon does "local ChatThrottleLib" above us and we're copypasted (AceComm-2, sigh) +local ChatThrottleLib = _G.ChatThrottleLib + +ChatThrottleLib.version = CTL_VERSION + + + +------------------ TWEAKABLES ----------------- + +ChatThrottleLib.MAX_CPS = 800 -- 2000 seems to be safe if NOTHING ELSE is happening. let's call it 800. +ChatThrottleLib.MSG_OVERHEAD = 40 -- Guesstimate overhead for sending a message; source+dest+chattype+protocolstuff + +ChatThrottleLib.BURST = 4000 -- WoW's server buffer seems to be about 32KB. 8KB should be safe, but seen disconnects on _some_ servers. Using 4KB now. + +ChatThrottleLib.MIN_FPS = 20 -- Reduce output CPS to half (and don't burst) if FPS drops below this value + + +local setmetatable = setmetatable +local table_remove = table.remove +local tostring = tostring +local GetTime = GetTime +local math_min = math.min +local math_max = math.max +local next = next +local strlen = string.len +local GetFramerate = GetFramerate +local strlower = string.lower +local unpack,type,pairs,wipe = unpack,type,pairs,wipe +local UnitInRaid,UnitInParty = UnitInRaid,UnitInParty + + +----------------------------------------------------------------------- +-- Double-linked ring implementation + +local Ring = {} +local RingMeta = { __index = Ring } + +function Ring:New() + local ret = {} + setmetatable(ret, RingMeta) + return ret +end + +function Ring:Add(obj) -- Append at the "far end" of the ring (aka just before the current position) + if self.pos then + obj.prev = self.pos.prev + obj.prev.next = obj + obj.next = self.pos + obj.next.prev = obj + else + obj.next = obj + obj.prev = obj + self.pos = obj + end +end + +function Ring:Remove(obj) + obj.next.prev = obj.prev + obj.prev.next = obj.next + if self.pos == obj then + self.pos = obj.next + if self.pos == obj then + self.pos = nil + end + end +end + + + +----------------------------------------------------------------------- +-- Recycling bin for pipes +-- A pipe is a plain integer-indexed queue of messages +-- Pipes normally live in Rings of pipes (3 rings total, one per priority) + +ChatThrottleLib.PipeBin = nil -- pre-v19, drastically different +local PipeBin = setmetatable({}, {__mode="k"}) + +local function DelPipe(pipe) + PipeBin[pipe] = true +end + +local function NewPipe() + local pipe = next(PipeBin) + if pipe then + wipe(pipe) + PipeBin[pipe] = nil + return pipe + end + return {} +end + + + + +----------------------------------------------------------------------- +-- Recycling bin for messages + +ChatThrottleLib.MsgBin = nil -- pre-v19, drastically different +local MsgBin = setmetatable({}, {__mode="k"}) + +local function DelMsg(msg) + msg[1] = nil + -- there's more parameters, but they're very repetetive so the string pool doesn't suffer really, and it's faster to just not delete them. + MsgBin[msg] = true +end + +local function NewMsg() + local msg = next(MsgBin) + if msg then + MsgBin[msg] = nil + return msg + end + return {} +end + + +----------------------------------------------------------------------- +-- ChatThrottleLib:Init +-- Initialize queues, set up frame for OnUpdate, etc + + +function ChatThrottleLib:Init() + + -- Set up queues + if not self.Prio then + self.Prio = {} + self.Prio["ALERT"] = { ByName = {}, Ring = Ring:New(), avail = 0 } + self.Prio["NORMAL"] = { ByName = {}, Ring = Ring:New(), avail = 0 } + self.Prio["BULK"] = { ByName = {}, Ring = Ring:New(), avail = 0 } + end + + -- v4: total send counters per priority + for _, Prio in pairs(self.Prio) do + Prio.nTotalSent = Prio.nTotalSent or 0 + end + + if not self.avail then + self.avail = 0 -- v5 + end + if not self.nTotalSent then + self.nTotalSent = 0 -- v5 + end + + + -- Set up a frame to get OnUpdate events + if not self.Frame then + self.Frame = CreateFrame("Frame") + self.Frame:Hide() + end + self.Frame:SetScript("OnUpdate", self.OnUpdate) + self.Frame:SetScript("OnEvent", self.OnEvent) -- v11: Monitor P_E_W so we can throttle hard for a few seconds + self.Frame:RegisterEvent("PLAYER_ENTERING_WORLD") + self.OnUpdateDelay = 0 + self.LastAvailUpdate = GetTime() + self.HardThrottlingBeginTime = GetTime() -- v11: Throttle hard for a few seconds after startup + + -- Hook SendChatMessage and SendAddonMessage so we can measure unpiped traffic and avoid overloads (v7) + if not self.securelyHooked then + -- Use secure hooks as of v16. Old regular hook support yanked out in v21. + self.securelyHooked = true + --SendChatMessage + hooksecurefunc("SendChatMessage", function(...) + return ChatThrottleLib.Hook_SendChatMessage(...) + end) + --SendAddonMessage + hooksecurefunc("SendAddonMessage", function(...) + return ChatThrottleLib.Hook_SendAddonMessage(...) + end) + end + self.nBypass = 0 +end + + +----------------------------------------------------------------------- +-- ChatThrottleLib.Hook_SendChatMessage / .Hook_SendAddonMessage + +local bMyTraffic = false + +function ChatThrottleLib.Hook_SendChatMessage(text, chattype, language, destination, ...) + if bMyTraffic then + return + end + local self = ChatThrottleLib + local size = strlen(tostring(text or "")) + strlen(tostring(destination or "")) + self.MSG_OVERHEAD + self.avail = self.avail - size + self.nBypass = self.nBypass + size -- just a statistic +end +function ChatThrottleLib.Hook_SendAddonMessage(prefix, text, chattype, destination, ...) + if bMyTraffic then + return + end + local self = ChatThrottleLib + local size = tostring(text or ""):len() + tostring(prefix or ""):len(); + size = size + tostring(destination or ""):len() + self.MSG_OVERHEAD + self.avail = self.avail - size + self.nBypass = self.nBypass + size -- just a statistic +end + + + +----------------------------------------------------------------------- +-- ChatThrottleLib:UpdateAvail +-- Update self.avail with how much bandwidth is currently available + +function ChatThrottleLib:UpdateAvail() + local now = GetTime() + local MAX_CPS = self.MAX_CPS; + local newavail = MAX_CPS * (now - self.LastAvailUpdate) + local avail = self.avail + + if now - self.HardThrottlingBeginTime < 5 then + -- First 5 seconds after startup/zoning: VERY hard clamping to avoid irritating the server rate limiter, it seems very cranky then + avail = math_min(avail + (newavail*0.1), MAX_CPS*0.5) + self.bChoking = true + elseif GetFramerate() < self.MIN_FPS then -- GetFrameRate call takes ~0.002 secs + avail = math_min(MAX_CPS, avail + newavail*0.5) + self.bChoking = true -- just a statistic + else + avail = math_min(self.BURST, avail + newavail) + self.bChoking = false + end + + avail = math_max(avail, 0-(MAX_CPS*2)) -- Can go negative when someone is eating bandwidth past the lib. but we refuse to stay silent for more than 2 seconds; if they can do it, we can. + + self.avail = avail + self.LastAvailUpdate = now + + return avail +end + + +----------------------------------------------------------------------- +-- Despooling logic +-- Reminder: +-- - We have 3 Priorities, each containing a "Ring" construct ... +-- - ... made up of N "Pipe"s (1 for each destination/pipename) +-- - and each pipe contains messages + +function ChatThrottleLib:Despool(Prio) + local ring = Prio.Ring + while ring.pos and Prio.avail > ring.pos[1].nSize do + local msg = table_remove(ring.pos, 1) + if not ring.pos[1] then -- did we remove last msg in this pipe? + local pipe = Prio.Ring.pos + Prio.Ring:Remove(pipe) + Prio.ByName[pipe.name] = nil + DelPipe(pipe) + else + Prio.Ring.pos = Prio.Ring.pos.next + end + local didSend=false + local lowerDest = strlower(msg[3] or "") + if lowerDest == "raid" and not UnitInRaid("player") then + -- do nothing + elseif lowerDest == "party" and not UnitInParty("player") then + -- do nothing + else + Prio.avail = Prio.avail - msg.nSize + bMyTraffic = true + msg.f(unpack(msg, 1, msg.n)) + bMyTraffic = false + Prio.nTotalSent = Prio.nTotalSent + msg.nSize + DelMsg(msg) + didSend = true + end + -- notify caller of delivery (even if we didn't send it) + if msg.callbackFn then + msg.callbackFn (msg.callbackArg, didSend) + end + -- USER CALLBACK MAY ERROR + end +end + + +function ChatThrottleLib.OnEvent(this,event) + -- v11: We know that the rate limiter is touchy after login. Assume that it's touchy after zoning, too. + local self = ChatThrottleLib + if event == "PLAYER_ENTERING_WORLD" then + self.HardThrottlingBeginTime = GetTime() -- Throttle hard for a few seconds after zoning + self.avail = 0 + end +end + + +function ChatThrottleLib.OnUpdate(this,delay) + local self = ChatThrottleLib + + self.OnUpdateDelay = self.OnUpdateDelay + delay + if self.OnUpdateDelay < 0.08 then + return + end + self.OnUpdateDelay = 0 + + self:UpdateAvail() + + if self.avail < 0 then + return -- argh. some bastard is spewing stuff past the lib. just bail early to save cpu. + end + + -- See how many of our priorities have queued messages (we only have 3, don't worry about the loop) + local n = 0 + for prioname,Prio in pairs(self.Prio) do + if Prio.Ring.pos or Prio.avail < 0 then + n = n + 1 + end + end + + -- Anything queued still? + if n<1 then + -- Nope. Move spillover bandwidth to global availability gauge and clear self.bQueueing + for prioname, Prio in pairs(self.Prio) do + self.avail = self.avail + Prio.avail + Prio.avail = 0 + end + self.bQueueing = false + self.Frame:Hide() + return + end + + -- There's stuff queued. Hand out available bandwidth to priorities as needed and despool their queues + local avail = self.avail/n + self.avail = 0 + + for prioname, Prio in pairs(self.Prio) do + if Prio.Ring.pos or Prio.avail < 0 then + Prio.avail = Prio.avail + avail + if Prio.Ring.pos and Prio.avail > Prio.Ring.pos[1].nSize then + self:Despool(Prio) + -- Note: We might not get here if the user-supplied callback function errors out! Take care! + end + end + end + +end + + + + +----------------------------------------------------------------------- +-- Spooling logic + +function ChatThrottleLib:Enqueue(prioname, pipename, msg) + local Prio = self.Prio[prioname] + local pipe = Prio.ByName[pipename] + if not pipe then + self.Frame:Show() + pipe = NewPipe() + pipe.name = pipename + Prio.ByName[pipename] = pipe + Prio.Ring:Add(pipe) + end + + pipe[#pipe + 1] = msg + + self.bQueueing = true +end + +function ChatThrottleLib:SendChatMessage(prio, prefix, text, chattype, language, destination, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendChatMessage("{BULK||NORMAL||ALERT}", "prefix", "text"[, "chattype"[, "language"[, "destination"]]]', 2) + end + if callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:ChatMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + end + + local nSize = text:len() + + if nSize>255 then + error("ChatThrottleLib:SendChatMessage(): message length cannot exceed 255 bytes", 2) + end + + nSize = nSize + self.MSG_OVERHEAD + + -- Check if there's room in the global available bandwidth gauge to send directly + if not self.bQueueing and nSize < self:UpdateAvail() then + self.avail = self.avail - nSize + bMyTraffic = true + _G.SendChatMessage(text, chattype, language, destination) + bMyTraffic = false + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + if callbackFn then + callbackFn (callbackArg, true) + end + -- USER CALLBACK MAY ERROR + return + end + + -- Message needs to be queued + local msg = NewMsg() + msg.f = _G.SendChatMessage + msg[1] = text + msg[2] = chattype or "SAY" + msg[3] = language + msg[4] = destination + msg.n = 4 + msg.nSize = nSize + msg.callbackFn = callbackFn + msg.callbackArg = callbackArg + + self:Enqueue(prio, queueName or (prefix..(chattype or "SAY")..(destination or "")), msg) +end + + +function ChatThrottleLib:SendAddonMessage(prio, prefix, text, chattype, target, queueName, callbackFn, callbackArg) + if not self or not prio or not prefix or not text or not chattype or not self.Prio[prio] then + error('Usage: ChatThrottleLib:SendAddonMessage("{BULK||NORMAL||ALERT}", "prefix", "text", "chattype"[, "target"])', 2) + end + if callbackFn and type(callbackFn)~="function" then + error('ChatThrottleLib:SendAddonMessage(): callbackFn: expected function, got '..type(callbackFn), 2) + end + + local nSize = text:len(); + + if RegisterAddonMessagePrefix then + if nSize>255 then + error("ChatThrottleLib:SendAddonMessage(): message length cannot exceed 255 bytes", 2) + end + else + nSize = nSize + prefix:len() + 1 + if nSize>255 then + error("ChatThrottleLib:SendAddonMessage(): prefix + message length cannot exceed 254 bytes", 2) + end + end + + nSize = nSize + self.MSG_OVERHEAD; + + -- Check if there's room in the global available bandwidth gauge to send directly + if not self.bQueueing and nSize < self:UpdateAvail() then + self.avail = self.avail - nSize + bMyTraffic = true + _G.SendAddonMessage(prefix, text, chattype, target) + bMyTraffic = false + self.Prio[prio].nTotalSent = self.Prio[prio].nTotalSent + nSize + if callbackFn then + callbackFn (callbackArg, true) + end + -- USER CALLBACK MAY ERROR + return + end + + -- Message needs to be queued + local msg = NewMsg() + msg.f = _G.SendAddonMessage + msg[1] = prefix + msg[2] = text + msg[3] = chattype + msg[4] = target + msg.n = (target~=nil) and 4 or 3; + msg.nSize = nSize + msg.callbackFn = callbackFn + msg.callbackArg = callbackArg + + self:Enqueue(prio, queueName or (prefix..chattype..(target or "")), msg) +end + + + + +----------------------------------------------------------------------- +-- Get the ball rolling! + +ChatThrottleLib:Init() + +--[[ WoWBench debugging snippet +if(WOWB_VER) then + local function SayTimer() + print("SAY: "..GetTime().." "..arg1) + end + ChatThrottleLib.Frame:SetScript("OnEvent", SayTimer) + ChatThrottleLib.Frame:RegisterEvent("CHAT_MSG_SAY") +end +]] + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceTimer-3.0/AceTimer-3.0.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,276 @@ +--- **AceTimer-3.0** provides a central facility for registering timers. +-- AceTimer supports one-shot timers and repeating timers. All timers are stored in an efficient +-- data structure that allows easy dispatching and fast rescheduling. Timers can be registered +-- or canceled at any time, even from within a running timer, without conflict or large overhead.\\ +-- AceTimer is currently limited to firing timers at a frequency of 0.01s as this is what the WoW timer API +-- restricts us to. +-- +-- All `:Schedule` functions will return a handle to the current timer, which you will need to store if you +-- need to cancel the timer you just registered. +-- +-- **AceTimer-3.0** can be embeded into your addon, either explicitly by calling AceTimer:Embed(MyAddon) or by +-- specifying it as an embeded library in your AceAddon. All functions will be available on your addon object +-- and can be accessed directly, without having to explicitly call AceTimer itself.\\ +-- It is recommended to embed AceTimer, otherwise you'll have to specify a custom `self` on all calls you +-- make into AceTimer. +-- @class file +-- @name AceTimer-3.0 +-- @release $Id: AceTimer-3.0.lua 1119 2014-10-14 17:23:29Z nevcairiel $ + +local MAJOR, MINOR = "AceTimer-3.0", 17 -- Bump minor on changes +local AceTimer, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceTimer then return end -- No upgrade needed +AceTimer.activeTimers = AceTimer.activeTimers or {} -- Active timer list +local activeTimers = AceTimer.activeTimers -- Upvalue our private data + +-- Lua APIs +local type, unpack, next, error, select = type, unpack, next, error, select +-- WoW APIs +local GetTime, C_TimerAfter = GetTime, C_Timer.After + +local function new(self, loop, func, delay, ...) + if delay < 0.01 then + delay = 0.01 -- Restrict to the lowest time that the C_Timer API allows us + end + + local timer = {...} + timer.object = self + timer.func = func + timer.looping = loop + timer.argsCount = select("#", ...) + timer.delay = delay + timer.ends = GetTime() + delay + + activeTimers[timer] = timer + + -- Create new timer closure to wrap the "timer" object + timer.callback = function() + if not timer.cancelled then + if type(timer.func) == "string" then + -- We manually set the unpack count to prevent issues with an arg set that contains nil and ends with nil + -- e.g. local t = {1, 2, nil, 3, nil} print(#t) will result in 2, instead of 5. This fixes said issue. + timer.object[timer.func](timer.object, unpack(timer, 1, timer.argsCount)) + else + timer.func(unpack(timer, 1, timer.argsCount)) + end + + if timer.looping and not timer.cancelled then + -- Compensate delay to get a perfect average delay, even if individual times don't match up perfectly + -- due to fps differences + local time = GetTime() + local delay = timer.delay - (time - timer.ends) + -- Ensure the delay doesn't go below the threshold + if delay < 0.01 then delay = 0.01 end + C_TimerAfter(delay, timer.callback) + timer.ends = time + delay + else + activeTimers[timer.handle or timer] = nil + end + end + end + + C_TimerAfter(delay, timer.callback) + return timer +end + +--- Schedule a new one-shot timer. +-- The timer will fire once in `delay` seconds, unless canceled before. +-- @param callback Callback function for the timer pulse (funcref or method name). +-- @param delay Delay for the timer, in seconds. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @usage +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- +-- function MyAddOn:OnEnable() +-- self:ScheduleTimer("TimerFeedback", 5) +-- end +-- +-- function MyAddOn:TimerFeedback() +-- print("5 seconds passed") +-- end +function AceTimer:ScheduleTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, nil, func, delay, ...) +end + +--- Schedule a repeating timer. +-- The timer will fire every `delay` seconds, until canceled. +-- @param callback Callback function for the timer pulse (funcref or method name). +-- @param delay Delay for the timer, in seconds. +-- @param ... An optional, unlimited amount of arguments to pass to the callback function. +-- @usage +-- MyAddOn = LibStub("AceAddon-3.0"):NewAddon("MyAddOn", "AceTimer-3.0") +-- +-- function MyAddOn:OnEnable() +-- self.timerCount = 0 +-- self.testTimer = self:ScheduleRepeatingTimer("TimerFeedback", 5) +-- end +-- +-- function MyAddOn:TimerFeedback() +-- self.timerCount = self.timerCount + 1 +-- print(("%d seconds passed"):format(5 * self.timerCount)) +-- -- run 30 seconds in total +-- if self.timerCount == 6 then +-- self:CancelTimer(self.testTimer) +-- end +-- end +function AceTimer:ScheduleRepeatingTimer(func, delay, ...) + if not func or not delay then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'callback' and 'delay' must have set values.", 2) + end + if type(func) == "string" then + if type(self) ~= "table" then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): 'self' - must be a table.", 2) + elseif not self[func] then + error(MAJOR..": ScheduleRepeatingTimer(callback, delay, args...): Tried to register '"..func.."' as the callback, but it doesn't exist in the module.", 2) + end + end + return new(self, true, func, delay, ...) +end + +--- Cancels a timer with the given id, registered by the same addon object as used for `:ScheduleTimer` +-- Both one-shot and repeating timers can be canceled with this function, as long as the `id` is valid +-- and the timer has not fired yet or was canceled before. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +function AceTimer:CancelTimer(id) + local timer = activeTimers[id] + + if not timer then + return false + else + timer.cancelled = true + activeTimers[id] = nil + return true + end +end + +--- Cancels all timers registered to the current addon object ('self') +function AceTimer:CancelAllTimers() + for k,v in pairs(activeTimers) do + if v.object == self then + AceTimer.CancelTimer(self, k) + end + end +end + +--- Returns the time left for a timer with the given id, registered by the current addon object ('self'). +-- This function will return 0 when the id is invalid. +-- @param id The id of the timer, as returned by `:ScheduleTimer` or `:ScheduleRepeatingTimer` +-- @return The time left on the timer. +function AceTimer:TimeLeft(id) + local timer = activeTimers[id] + if not timer then + return 0 + else + return timer.ends - GetTime() + end +end + + +-- --------------------------------------------------------------------- +-- Upgrading + +-- Upgrade from old hash-bucket based timers to C_Timer.After timers. +if oldminor and oldminor < 10 then + -- disable old timer logic + AceTimer.frame:SetScript("OnUpdate", nil) + AceTimer.frame:SetScript("OnEvent", nil) + AceTimer.frame:UnregisterAllEvents() + -- convert timers + for object,timers in pairs(AceTimer.selfs) do + for handle,timer in pairs(timers) do + if type(timer) == "table" and timer.callback then + local newTimer + if timer.delay then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.callback, timer.delay, timer.arg) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.callback, timer.when - GetTime(), timer.arg) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle + end + end + end + AceTimer.selfs = nil + AceTimer.hash = nil + AceTimer.debug = nil +elseif oldminor and oldminor < 17 then + -- Upgrade from old animation based timers to C_Timer.After timers. + AceTimer.inactiveTimers = nil + AceTimer.frame = nil + local oldTimers = AceTimer.activeTimers + -- Clear old timer table and update upvalue + AceTimer.activeTimers = {} + activeTimers = AceTimer.activeTimers + for handle, timer in pairs(oldTimers) do + local newTimer + -- Stop the old timer animation + local duration, elapsed = timer:GetDuration(), timer:GetElapsed() + timer:GetParent():Stop() + if timer.looping then + newTimer = AceTimer.ScheduleRepeatingTimer(timer.object, timer.func, duration, unpack(timer.args, 1, timer.argsCount)) + else + newTimer = AceTimer.ScheduleTimer(timer.object, timer.func, duration - elapsed, unpack(timer.args, 1, timer.argsCount)) + end + -- Use the old handle for old timers + activeTimers[newTimer] = nil + activeTimers[handle] = newTimer + newTimer.handle = handle + end + + -- Migrate transitional handles + if oldminor < 13 and AceTimer.hashCompatTable then + for handle, id in pairs(AceTimer.hashCompatTable) do + local t = activeTimers[id] + if t then + activeTimers[id] = nil + activeTimers[handle] = t + t.handle = handle + end + end + AceTimer.hashCompatTable = nil + end +end + +-- --------------------------------------------------------------------- +-- Embed handling + +AceTimer.embeds = AceTimer.embeds or {} + +local mixins = { + "ScheduleTimer", "ScheduleRepeatingTimer", + "CancelTimer", "CancelAllTimers", + "TimeLeft" +} + +function AceTimer:Embed(target) + AceTimer.embeds[target] = true + for _,v in pairs(mixins) do + target[v] = AceTimer[v] + end + return target +end + +-- AceTimer:OnEmbedDisable(target) +-- target (object) - target object that AceTimer is embedded in. +-- +-- cancel all timers registered for the object +function AceTimer:OnEmbedDisable(target) + target:CancelAllTimers() +end + +for addon in pairs(AceTimer.embeds) do + AceTimer:Embed(addon) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/AceTimer-3.0/AceTimer-3.0.xml Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="AceTimer-3.0.lua"/> +</Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,240 @@ +--[[ $Id: CallbackHandler-1.0.lua 965 2010-08-09 00:47:52Z mikk $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 6 +local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) + +if not CallbackHandler then return end -- No upgrade needed + +local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} + +-- Lua APIs +local tconcat = table.concat +local assert, error, loadstring = assert, error, loadstring +local setmetatable, rawset, rawget = setmetatable, rawset, rawget +local next, select, pairs, type, tostring = next, select, pairs, type, tostring + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: geterrorhandler + +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local next, xpcall, eh = ... + + local method, ARGS + local function call() method(ARGS) end + + local function dispatch(handlers, ...) + local index + index, method = next(handlers) + if not method then return end + local OLD_ARGS = ARGS + ARGS = ... + repeat + xpcall(call, eh) + index, method = next(handlers, index) + until not method + ARGS = OLD_ARGS + end + + return dispatch + ]] + + local ARGS, OLD_ARGS = {}, {} + for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end + code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) + +-------------------------------------------------------------------------- +-- CallbackHandler:New +-- +-- target - target object to embed public APIs in +-- RegisterName - name of the callback registration API, default "RegisterCallback" +-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" +-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. + +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName, OnUsed, OnUnused) + -- TODO: Remove this after beta has gone out + assert(not OnUsed and not OnUnused, "ACE-80: OnUsed/OnUnused are deprecated. Callbacks are now done to registry.OnUsed and registry.OnUnused") + + RegisterName = RegisterName or "RegisterCallback" + UnregisterName = UnregisterName or "UnregisterCallback" + if UnregisterAllName==nil then -- false is used to indicate "don't want this method" + UnregisterAllName = "UnregisterAllCallbacks" + end + + -- we declare all objects and exported APIs inside this closure to quickly gain access + -- to e.g. function names, the "target" parameter, etc + + + -- Create the registry object + local events = setmetatable({}, meta) + local registry = { recurse=0, events=events } + + -- registry:Fire() - fires the given event/message into the registry + function registry:Fire(eventname, ...) + if not rawget(events, eventname) or not next(events[eventname]) then return end + local oldrecurse = registry.recurse + registry.recurse = oldrecurse + 1 + + Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) + + registry.recurse = oldrecurse + + if registry.insertQueue and oldrecurse==0 then + -- Something in one of our callbacks wanted to register more callbacks; they got queued + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func + -- fire OnUsed callback? + if first and registry.OnUsed then + registry.OnUsed(registry, target, eventname) + first = nil + end + end + end + registry.insertQueue = nil + end + end + + -- Registration of a callback, handles: + -- self["method"], leads to self["method"](self, ...) + -- self with function ref, leads to functionref(...) + -- "addonId" (instead of self) with function ref, leads to functionref(...) + -- all with an optional arg, which, if present, gets passed as first argument (after self if present) + target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) + if type(eventname) ~= "string" then + error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) + end + + method = method or eventname + + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + + if type(method) ~= "string" and type(method) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) + end + + local regfunc + + if type(method) == "string" then + -- self["method"] calling style + if type(self) ~= "table" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) + elseif self==target then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) + elseif type(self[method]) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) self[method](self,arg,...) end + else + regfunc = function(...) self[method](self,...) end + end + else + -- function ref with self=object or self="addonId" or self=thread + if type(self)~="table" and type(self)~="string" and type(self)~="thread" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) method(arg,...) end + else + regfunc = method + end + end + + + if events[eventname][self] or registry.recurse<1 then + -- if registry.recurse<1 then + -- we're overwriting an existing entry, or not currently recursing. just set it. + events[eventname][self] = regfunc + -- fire OnUsed callback? + if registry.OnUsed and first then + registry.OnUsed(registry, target, eventname) + end + else + -- we're currently processing a callback in this registry, so delay the registration of this new entry! + -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency + registry.insertQueue = registry.insertQueue or setmetatable({},meta) + registry.insertQueue[eventname][self] = regfunc + end + end + + -- Unregister a callback + target[UnregisterName] = function(self, eventname) + if not self or self==target then + error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) + end + if type(eventname) ~= "string" then + error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) + end + if rawget(events, eventname) and events[eventname][self] then + events[eventname][self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(events[eventname]) then + registry.OnUnused(registry, target, eventname) + end + end + if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then + registry.insertQueue[eventname][self] = nil + end + end + + -- OPTIONAL: Unregister all callbacks for given selfs/addonIds + if UnregisterAllName then + target[UnregisterAllName] = function(...) + if select("#",...)<1 then + error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) + end + if select("#",...)==1 and ...==target then + error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) + end + + + for i=1,select("#",...) do + local self = select(i,...) + if registry.insertQueue then + for eventname, callbacks in pairs(registry.insertQueue) do + if callbacks[self] then + callbacks[self] = nil + end + end + end + for eventname, callbacks in pairs(events) do + if callbacks[self] then + callbacks[self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(callbacks) then + registry.OnUnused(registry, target, eventname) + end + end + end + end + end + end + + return registry +end + + +-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it +-- try to upgrade old implicit embeds since the system is selfcontained and +-- relies on closures to work. +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="CallbackHandler-1.0.lua"/> +</Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/LibStub.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,30 @@ +-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info +-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke +local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! +local LibStub = _G[LIBSTUB_MAJOR] + +if not LibStub or LibStub.minor < LIBSTUB_MINOR then + LibStub = LibStub or {libs = {}, minors = {} } + _G[LIBSTUB_MAJOR] = LibStub + LibStub.minor = LIBSTUB_MINOR + + function LibStub:NewLibrary(major, minor) + assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") + minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") + + local oldminor = self.minors[major] + if oldminor and oldminor >= minor then return nil end + self.minors[major], self.libs[major] = minor, self.libs[major] or {} + return self.libs[major], oldminor + end + + function LibStub:GetLibrary(major, silent) + if not self.libs[major] and not silent then + error(("Cannot find a library instance of %q."):format(tostring(major)), 2) + end + return self.libs[major], self.minors[major] + end + + function LibStub:IterateLibraries() return pairs(self.libs) end + setmetatable(LibStub, { __call = LibStub.GetLibrary }) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/LibStub.toc Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,13 @@ +## Interface: 40200 +## Title: Lib: LibStub +## Notes: Universal Library Stub +## Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel +## X-Website: http://www.wowace.com/addons/libstub/ +## X-Category: Library +## X-License: Public Domain +## X-Curse-Packaged-Version: r95 +## X-Curse-Project-Name: LibStub +## X-Curse-Project-ID: libstub +## X-Curse-Repository-ID: wow/libstub/mainline + +LibStub.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/tests/test.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,41 @@ +debugstack = debug.traceback +strmatch = string.match + +loadfile("../LibStub.lua")() + +local lib, oldMinor = LibStub:NewLibrary("Pants", 1) -- make a new thingy +assert(lib) -- should return the library table +assert(not oldMinor) -- should not return the old minor, since it didn't exist + +-- the following is to create data and then be able to check if the same data exists after the fact +function lib:MyMethod() +end +local MyMethod = lib.MyMethod +lib.MyTable = {} +local MyTable = lib.MyTable + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", 1) -- try to register a library with the same version, should silently fail +assert(not newLib) -- should not return since out of date + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", 0) -- try to register a library with a previous, should silently fail +assert(not newLib) -- should not return since out of date + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", 2) -- register a new version +assert(newLib) -- library table +assert(rawequal(newLib, lib)) -- should be the same reference as the previous +assert(newOldMinor == 1) -- should return the minor version of the previous version + +assert(rawequal(lib.MyMethod, MyMethod)) -- verify that values were saved +assert(rawequal(lib.MyTable, MyTable)) -- verify that values were saved + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 3 Blah") -- register a new version with a string minor version (instead of a number) +assert(newLib) -- library table +assert(newOldMinor == 2) -- previous version was 2 + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", "Blah 4 and please ignore 15 Blah") -- register a new version with a string minor version (instead of a number) +assert(newLib) +assert(newOldMinor == 3) -- previous version was 3 (even though it gave a string) + +local newLib, newOldMinor = LibStub:NewLibrary("Pants", 5) -- register a new library, using a normal number instead of a string +assert(newLib) +assert(newOldMinor == 4) -- previous version was 4 (even though it gave a string) \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/tests/test2.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,27 @@ +debugstack = debug.traceback +strmatch = string.match + +loadfile("../LibStub.lua")() + +for major, library in LibStub:IterateLibraries() do + -- check that MyLib doesn't exist yet, by iterating through all the libraries + assert(major ~= "MyLib") +end + +assert(not LibStub:GetLibrary("MyLib", true)) -- check that MyLib doesn't exist yet by direct checking +assert(not pcall(LibStub.GetLibrary, LibStub, "MyLib")) -- don't silently fail, thus it should raise an error. +local lib = LibStub:NewLibrary("MyLib", 1) -- create the lib +assert(lib) -- check it exists +assert(rawequal(LibStub:GetLibrary("MyLib"), lib)) -- verify that :GetLibrary("MyLib") properly equals the lib reference + +assert(LibStub:NewLibrary("MyLib", 2)) -- create a new version + +local count=0 +for major, library in LibStub:IterateLibraries() do + -- check that MyLib exists somewhere in the libraries, by iterating through all the libraries + if major == "MyLib" then -- we found it! + count = count +1 + assert(rawequal(library, lib)) -- verify that the references are equal + end +end +assert(count == 1) -- verify that we actually found it, and only once
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/tests/test3.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,14 @@ +debugstack = debug.traceback +strmatch = string.match + +loadfile("../LibStub.lua")() + +local proxy = newproxy() -- non-string + +assert(not pcall(LibStub.NewLibrary, LibStub, proxy, 1)) -- should error, proxy is not a string, it's userdata +local success, ret = pcall(LibStub.GetLibrary, proxy, true) +assert(not success or not ret) -- either error because proxy is not a string or because it's not actually registered. + +assert(not pcall(LibStub.NewLibrary, LibStub, "Something", "No number in here")) -- should error, minor has no string in it. + +assert(not LibStub:GetLibrary("Something", true)) -- shouldn't've created it from the above statement \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/LibStub/tests/test4.lua Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,41 @@ +debugstack = debug.traceback +strmatch = string.match + +loadfile("../LibStub.lua")() + + +-- Pretend like loaded libstub is old and doesn't have :IterateLibraries +assert(LibStub.minor) +LibStub.minor = LibStub.minor - 0.0001 +LibStub.IterateLibraries = nil + +loadfile("../LibStub.lua")() + +assert(type(LibStub.IterateLibraries)=="function") + + +-- Now pretend that we're the same version -- :IterateLibraries should NOT be re-created +LibStub.IterateLibraries = 123 + +loadfile("../LibStub.lua")() + +assert(LibStub.IterateLibraries == 123) + + +-- Now pretend that a newer version is loaded -- :IterateLibraries should NOT be re-created +LibStub.minor = LibStub.minor + 0.0001 + +loadfile("../LibStub.lua")() + +assert(LibStub.IterateLibraries == 123) + + +-- Again with a huge number +LibStub.minor = LibStub.minor + 1234567890 + +loadfile("../LibStub.lua")() + +assert(LibStub.IterateLibraries == 123) + + +print("OK") \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Libs/libs.xml Mon Mar 02 21:28:33 2015 -0300 @@ -0,0 +1,6 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> + <Script file="LibStub\LibStub.lua"/> + <Include file="CallbackHandler-1.0\CallbackHandler-1.0.xml"/> + <Include file="AceComm-3.0\AceComm-3.0.xml" /> + <Include file="AceTimer-3.0\AceTimer-3.0.xml" /> +</Ui> \ No newline at end of file
