View Single Post
02-16-12, 08:23 AM   #6
Savaena
Warcrafter
 
Savaena's Avatar
Premium Member
AddOn Author - Click to view addons
Join Date: Apr 2009
Posts: 4
Sure, okay, so I've been going over this script and added/changed a few things, but I still can't seem to get it to function at all. My game doesn't even register the welcome message when I enter world. When a player targets me and does a built in /emote, my character does not react at all.

What I want to have happen is my character replies with an emote after a 3 second delay. I also want my addon to show me a welcome message in the chat box in game when I enter world.

I have a full download of my most updated version addon available at: http://www.wowinterface.com/download...onse.html#info

But here is my current file structure and layout of my addon as well (in case this is easier for you) I just can't figure out why this won't run. >.<

Folder Layout:

Interface>Addons>BlushResponse
Interface>Addons>Ace3

Files Contained in BlushResponse:

AceTimer-3.0.lua
AceTimer-3.0.xml
BlushResponse.xml
BlushResponse.lua
BlushResponse.toc
LibStub.lua

BlushResponse.toc File

Code:
## Interface: 40300
## Title: Blush Response
## Notes: Build Version 1.2 - An addon that replies to emotes for you!  Dependencies: Ace Timer  and LibStub
## Author: Savaena and Poisoned Lizard

BlushResponse.lua
BlushResponse.xml
AceTimer-3.0.lua
AceTimer-3.0.xml
LibStub.lua
BlushResponse.xml File

Code:
<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">
 </Ui>
BlushResponse.lua File

Code:
-- BLUSH RESPONSE ADDON --

local DELAY = 3 -- Number of seconds before we reply with an emote.
local MAX_REPLIES = 1 -- The maximum number of reply emotes sent for every emote used on you.

local OPPOSITE_FACTION; -- These two get assigned values when PLAYER_LOGIN fires.
local PLAYER_GUID;

local ALLIANCE_RACES = {
	Dwarf = true,
	Draenei = true,
	Gnome = true,
	Human = true,
	NightElf = true,
	Worgen = true,
}

local HORDE_RACES = {
	BloodElf = true,
	Goblin = true,
	Orc = true,
	Tauren = true,
	Troll = true,
	Scourge = true,
}

local BR = CreateFrame("Frame") -- You can use BlushResponse instead of this frame if needed
local BRVersion = CreateFrame("Frame") -- This is my version/greeting frame
LibStub("AceTimer-3.0"):Embed(BR) -- Embed AceTimer, allowing use of its methods.

local triggers = {
-- This table contains the trigger phrases and reply type/messages.
-- The trigger phrase can use any patterns recognised by string.find, but for best results use ^%S at the start to match the name of the emoter.
-- The replyType can be "builtin" for an emote performed with DoEmote (e.g. /dance, /cower) or "custom" for an emote performed with SendChatMessage (e.g. /e is very scared!)
-- The replyMsg can be an EmoteToken for builtin emotes or a message for custom emotes (optionally with %N, which will be replaced with the name of the emoter)
-- You will need to figure out a way for the user to add/remove triggers from here.

	{ trigger = "^%S+ licks you",			replyType = "builtin", 	replyMsg = "COWER" },
	{ trigger = "^%S+ pokes you",			replyType = "builtin", 	replyMsg = "FART" },
	{ trigger = "^%S+ hugs you",			replyType = "builtin", 	replyMsg = "HELPME" },
	{ trigger = "^%S+ slaps you",			replyType = "builtin", 	replyMsg = "BURP" },
	{ trigger = "^%S+ winks slyly at you",		replyType = "builtin", 	replyMsg = "WINK" },
	{ trigger = "^%S+ cries on your shoulder",	replyType = "builtin", 	replyMsg = "VIOLIN" },
	{ trigger = "^%S+ loves you",			replyType = "builtin", 	replyMsg = "BLUSH" },
	{ trigger = "^%S+ thinks you are a sexy devil", 	replyType = "custom", 	replyMsg = "gasps in shock at %N!" },
	{ trigger = "^%S+ dances with you", 		replyType = "custom", 	replyMsg = "admires %N's moves." },
}

BR:SetScript("OnEvent", function(self, event, ...) -- This part only works if BR is a frame
	self[event](self, ...) -- Call the event name as a method of BR with all the arguments supplied.
end)
BRVersion:RegisterEvent("PLAYER_LOGIN")
BR:RegisterEvent("PLAYER_LOGIN")
BR:RegisterEvent("CHAT_MSG_TEXT_EMOTE") -- Fired for builtin emotes like /dance
BR:RegisterEvent("CHAT_MSG_EMOTE") -- Fired for custom emotes like /e is happy.

function BR:ADDON_LOADED(self, event, arg1)
	if (event == "ADDON_LOADED") and (arg1 == "BR") then
		DEFAULT_CHAT_FRAME:AddMessage("Addon: Blush Response v. 1.2 successfully loaded!");
		BR:LoadDefaults()
		self:UnregisterEvent("ADDON_LOADED")
	end
end

function BR:PLAYER_LOGIN()
	OPPOSITE_FACTION = UnitFactionGroup("player") == "Alliance" and HORDE_RACES or ALLIANCE_RACES
	PLAYER_GUID = UnitGUID("player")
end

local function PerformEmotes(args)
 -- AceTimer only allows us to call the function with a single argument, so we put the message and name in a table
	local msg, name = unpack(args)
	local numEmotes = 0
	
	for _, data in ipairs(triggers) do
		if msg:find(data.trigger) then
			numEmotes = numEmotes + 1
			if data.replyType == "builtin" then
				DoEmote(data.replyMsg)
			else
				SendChatMessage(data.replyMsg:gsub("%%N", name), "EMOTE")
			end
			if numEmotes == MAX_EMOTES then break end -- Break the loop if we've performed the maximum number of reply emotes
		end
	end
end

function BR:CHAT_MSG_TEXT_EMOTE(msg, name, _, _, _, _, _, _, _, _, lineId, GUID)
	if GUID == PLAYER_GUID then return end -- Don't respond to emotes the player sent.
	local className, classId, raceName, raceId, gender, name, realm = GetPlayerInfoByGUID(GUID)
	if OPPOSITE_FACTION[raceId] then
		DoEmote("PONDER")
	end
	self:ScheduleTimer(PerformEmotes, DELAY, {msg, name}) -- Call PerformEmotes in DELAY seconds with a table of the message and name as its only argument
end

function BR:CHAT_MSG_EMOTE(msg, name, _, _, _, _, _, _, _, _, lineId, GUID)
	if GUID == PLAYER_GUID then return end
	self:ScheduleTimer(PerformEmotes, DELAY, {msg, name})
end

function BR:LoadDefaults()
	BR = {}
	for i = 1, #Defaults do
		tinsert(EmoteReply.Emotes, Defaults[i])
	end
end

function BR:CHAT_MSG_EMOTE(msg, name, _, _, _, _, _, _, _, _, lineId, GUID)
	if ( event == "CHAT_MSG_TEXT_EMOTE" ) then
	print(arg1)
		local arg1 = string.lower(arg1) --turns all uppercase letters into lowercase ones.
		foreach(trigger, 
			function(k,v)
				if (string.find(arg1, v.Trigger)) then
					if (string.find(arg1)) then
						replyMsg;
					end
					return;
				end
			end
		);
	end
end
LibStub.lua File

Code:
-- 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
AceTimer-3.0.lua File

Code:
--- **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 1037 2011-09-02 16:24:08Z mikk $

--[[
	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", 6
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 when<soon ... else
		end -- while nexttimer do
	end -- for curint=lastint,nowint
	
	lastint = nowint
end

-- ---------------------------------------------------------------------
-- Reg( callback, delay, arg, repeating )
--
-- callback( function or string ) - direct function ref or method name in our object for the callback
-- delay(int) - delay for the timer
-- arg(variant) - any argument to be passed to the callback function
-- repeating(boolean) - repeating timer, or oneshot
--
-- returns the handle of the timer for later processing (canceling etc)
local function Reg(self, callback, delay, arg, repeating)
	if type(callback) ~= "string" and type(callback) ~= "function" then 
		local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
		error(MAJOR..": " .. error_origin .. "(callback, delay, arg): 'callback' - function or method name expected.", 3)
	end
	if type(callback) == "string" then
		if type(self)~="table" then
			local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
			error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'self' - must be a table.", 3)
		end
		if type(self[callback]) ~= "function" then 
			local error_origin = repeating and "ScheduleRepeatingTimer" or "ScheduleTimer"
			error(MAJOR..": " .. error_origin .. "(\"methodName\", delay, arg): 'methodName' - method not found on target object.", 3)
		end
	end
	
	if delay < (1 / (HZ - 1)) then
		delay = 1 / (HZ - 1)
	end
	
	-- Create and stuff timer in the correct hash bucket
	local now = GetTime()
	
	local timer = timerCache or {}	-- Get new timer object (from cache if available)
	timerCache = nil
	
	timer.object = self
	timer.callback = callback
	timer.delay = (repeating and delay)
	timer.arg = arg
	timer.when = now + delay

	local bucket = (floor((now+delay)*HZ) % BUCKETS) + 1
	timer.next = hash[bucket]
	hash[bucket] = timer
	
	-- Insert timer in our self->handle->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 (worst case!). 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
		if type(v)=="table" and v.callback then -- if the timer is actually live: count it
			n=n+1
		end
	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 could 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 least.
AceTimer-3.0.xml File

Code:
<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>
  Reply With Quote