Apoc
Well-Known Member
- Joined
- Jan 16, 2010
- Messages
- 2,790
- Reaction score
- 94
You can ignore this entire post. It's going to be in the HB core soon. 
First and foremost, I'm not a Lua guy, so I most likely screwed up the Lua. Feel free to check it and correct any issues. (PM me if you need to)
I wrote this mostly so I could boilerplate some code to make things a little less... stupid. It is mostly untested! (The lua handlers are untested. I know the rest of the code works 100%. The C# side has 0 bugs. The Lua may!)
Now, on to the goods!
You can attach, and detach, from WoW's event system with this little set of classes.
The API is fairly simple to get a handle on. Below is an example handler setup:
And yes, I know, we can grab the 'last red error message'. However that will only grab the LAST message. There can quite possibly be 4+ messages we want to react to.
As you can see, the API is user friendly, and it can retrieve any number of arguments from the event occurrence.
Some notes about the implementation:
Please keep in mind; I haven't fully tested the Lua yet, and since I'm definitely not a Lua guy, I can't be 100% sure about it. Feel free to PM me and I'll send you the 'clean' version (with proper names and whatnot)
The implementation:
Please let me know if you find any bugs, I'll try and fix them ASAP.

First and foremost, I'm not a Lua guy, so I most likely screwed up the Lua. Feel free to check it and correct any issues. (PM me if you need to)
I wrote this mostly so I could boilerplate some code to make things a little less... stupid. It is mostly untested! (The lua handlers are untested. I know the rest of the code works 100%. The C# side has 0 bugs. The Lua may!)
Now, on to the goods!
You can attach, and detach, from WoW's event system with this little set of classes.
The API is fairly simple to get a handle on. Below is an example handler setup:
Code:
public class EventHandlers
{
// This class just handles some druid specific error strings.
internal static void Init()
{
WoWEvents.AttachEvent("UI_ERROR_MESSAGE", HandleUiErrorMessage);
}
public static void HandleUiErrorMessage(WoWEventArgs e)
{
//Logging.WriteDebug("UI_ERROR_MESSAGE: " + e.Args[0]);
switch (e.Args[0])
{
case "Can't use items while shapeshifted.":
case "You are in shapeshift form.":
case "You can't take a taxi while shapeshifted!":
Lua.DoString("CastShapeshiftForm(0)");
break;
case "You must be behind your target.":
Logging.WriteDebug("Jumping behind target.");
// WTB: Timed movement!
WoWMovement.Move(WoWMovement.MovementDirection.Forward);
Lua.DoString("JumpOrAscendStart()");
Thread.Sleep(100);
WoWMovement.MoveStop(WoWMovement.MovementDirection.Forward);
break;
case "Target not in line of sight.":
// TODO: Move into melee via the nav system
break;
}
}
}
And yes, I know, we can grab the 'last red error message'. However that will only grab the LAST message. There can quite possibly be 4+ messages we want to react to.
As you can see, the API is user friendly, and it can retrieve any number of arguments from the event occurrence.
Some notes about the implementation:
- Function, frame, and table names are randomized in the Lua. This is to prevent a few security issues. (Warden can still potentially find them, but they won't be able to use a static name, or length)
- The ProcessEvents() function is timed. If you take longer than 500ms to process the events, it simply dies off and waits until the next call to ProcessEvents() to pick up where it left off. This can potentially leave you with very stale data, however, not having this time could cause potential issues with events being flooded in constantly. -- I added a PurgeEventQueue() function to get around this issue. Feel free to use it if you want.
- Events are fired in the order they were received. (Queue, or FIFO)
- Requires that you call Initialize() somewhere (CC ctor), and ProcessEvents() (CC Pulse() preferably)
Please keep in mind; I haven't fully tested the Lua yet, and since I'm definitely not a Lua guy, I can't be 100% sure about it. Feel free to PM me and I'll send you the 'clean' version (with proper names and whatnot)
The implementation:
Code:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Styx;
namespace ApocDev.Events
{
public delegate void WoWEventHandler(WoWEventArgs e);
/// <summary>
/// A Lua event argument wrapper
/// </summary>
public sealed class WoWEventArgs
{
internal WoWEventArgs(string eventName, int timestamp, params string[] args)
{
Name = eventName;
Timestamp = timestamp;
Args = args;
}
/// <summary>
/// The name of the event
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The timestamp for when the event occured according to Lua's time() function
/// </summary>
public int Timestamp { get; private set; }
/// <summary>
/// Any arguments passed with the event
/// </summary>
public string[] Args { get; private set; }
}
/// <summary>
/// Allows developers to hook into WoW's Lua Event system.
/// </summary>
public static class WoWEvents
{
private static string _registerName;
private static string _unregisterName;
private static string _addInfoName;
private static string _handleEventName;
private static string _purgeName;
private static string _getEventName;
private static string _frameName;
private static string _tableName;
private static string _capturedCountName;
private static readonly Random Rand = new Random();
private static readonly Dictionary<string, Event> EventStrings = new Dictionary<string, Event>();
/// <summary>
/// The number of events waiting to be retrieved
/// </summary>
public static int EventCount { get { return Lua.GetReturnVal<int>(string.Format("return {0}()", _capturedCountName), 0); } }
private static string CreateRandomString()
{
char[] characters = "abcdefghijklmnopqrstuvwxyz".ToCharArray();
// Create a random length string
int length = Rand.Next(0, 15);
var sb = new StringBuilder();
for (int i = 0; i < length; i++)
{
sb.Append(Rand.NextDouble() > .5
? char.ToUpper(characters[Rand.Next(0, characters.Length)])
: characters[Rand.Next(0, characters.Length)]);
}
return sb.ToString();
}
private static void SetupLua()
{
// This is the main Lua code. It handles all the functions we need to be able to queue up events,
// and retrieve them later. (They are grabbed in real-time via WoW. We grab them later due to not being in an
// on-frame hook)
const string LUA =
@"{0} = CreateFrame('Frame','{0}');{0}:SetScript('OnEvent',{5});{1}={{}};
function {2}(e){0}:RegisterEvent(e);end
function {3}(e){0}:UnregisterEvent(e);end
function {4}(e,d)table.insert({1}, {{e,time(),d}});end
function {5}(s,e,...){4}(e,{{...}});end
function {6}()return {1}.count;end
function {7}()table.wipe({1});end
function {8}(i)local ret;ret={1}[i];{1}.remove(i);return ret;end";
string actual = string.Format(LUA,
_frameName,
_tableName,
_registerName,
_unregisterName,
_addInfoName,
_handleEventName,
_capturedCountName,
_purgeName,
_getEventName);
Lua.DoString(actual);
}
/// <summary>
/// Initializes the WoWEvent system by creating random function names, and registering the required functions and frames to capture Lua events.
/// </summary>
public static void Initialize()
{
// This is simply a minor security measure.
// Just make sure the end user can't accidentally use one
// of the event handler things
_registerName = CreateRandomString();
_unregisterName = CreateRandomString();
_addInfoName = CreateRandomString();
_handleEventName = CreateRandomString();
_purgeName = CreateRandomString();
_getEventName = CreateRandomString();
_frameName = CreateRandomString();
_tableName = CreateRandomString();
_capturedCountName = CreateRandomString();
SetupLua();
}
/// <summary>
/// Purges the current event queue stored in WoW.
/// </summary>
public static void PurgeEventQueue()
{
Lua.DoString(string.Format("{0}()", _purgeName));
}
/// <summary>
/// Attaches an event handler to a specific WoW event
/// </summary>
/// <param name="name">The name of the event. (E.g. CHAT_MSG_ADDON)</param>
/// <param name="handler">The handler for the event.</param>
/// <returns>Whether or not the event was successfully attached.</returns>
public static bool AttachEvent(string name, WoWEventHandler handler)
{
try
{
if (!EventStrings.ContainsKey(name))
{
Lua.DoString(string.Format("{0}('{1}')", _registerName, name));
EventStrings.Add(name, new Event());
}
EventStrings[name].AddTarget(handler);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Detaches an event handler from a specific WoW event.
/// </summary>
/// <param name="name">The name of the event (E.g. CHAT_MSG_ADDON)</param>
/// <param name="handler">The handler to detach.</param>
/// <returns>Whether or not the event was successfully detached.</returns>
public static bool DetachEvent(string name, WoWEventHandler handler)
{
try
{
if (!EventStrings.ContainsKey(name))
{
return false;
}
EventStrings[name].Targets -= handler;
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Detaches an event from all handlers.
/// </summary>
/// <param name="name">The name of the WoW event to detach. Or '*' to detach all.</param>
/// <returns>Whether the events were detached successfully.</returns>
public static bool DetachEvent(string name)
{
if (name == "*")
{
foreach (KeyValuePair<string, Event> e in EventStrings)
{
Lua.DoString(string.Format("{0}('{1}')", _unregisterName, e.Key));
e.Value.Detach();
}
EventStrings.Clear();
return true;
}
if (EventStrings.ContainsKey(name))
{
Lua.DoString(string.Format("{0}('{1}')", _unregisterName, name));
// Automagically performs cleanup
EventStrings[name].Detach();
EventStrings.Remove(name);
return true;
}
return false;
}
/// <summary>
/// Simply executes the event handlers
/// </summary>
/// <param name="name"></param>
/// <param name="timestamp"></param>
/// <param name="args"></param>
private static void ExecuteEvent(string name, int timestamp, string[] args)
{
Event e;
EventStrings.TryGetValue(name, out e);
if (e != null)
{
e.Invoke(new WoWEventArgs(name, timestamp, args));
}
}
/// <summary>
/// Processes the current event queue and executes the currently hooked event
/// </summary>
public static void ProcessEvents()
{
// Yes, this can hurt, bad.
var timer = new Stopwatch();
timer.Start();
// Make sure we only try to process events for 500ms
while (EventCount != 0 && timer.ElapsedMilliseconds < 500)
{
List<string> vals = Lua.LuaGetReturnValue(_getEventName + "(1)", "lua.lua");
string eventName = vals[0];
int timestamp = int.Parse(vals[1]);
var args = new List<string>();
for (int i = 2; i < vals.Count; i++)
{
args.Add(vals[i]);
}
ExecuteEvent(eventName, timestamp, args.ToArray());
}
}
#region Nested type: Event
private class Event
{
private readonly List<string> _targetNames;
private WoWEventHandler _target;
internal Event()
{
_targetNames = new List<string>();
}
public event WoWEventHandler Targets;
internal void AddTarget(WoWEventHandler targ)
{
if (_targetNames.Contains(targ.Method.Name))
{
return;
}
_targetNames.Add(targ.Method.Name);
Targets += targ;
}
internal void Invoke(WoWEventArgs e)
{
if (Targets != null)
{
Targets.Invoke(e);
}
}
internal void Detach()
{
if (_target != null)
{
Delegate[] tmp = Targets.GetInvocationList();
foreach (Delegate d in tmp)
{
Targets -= (WoWEventHandler) d;
}
_target = null;
}
}
}
#endregion
}
}
Please let me know if you find any bugs, I'll try and fix them ASAP.
Last edited: