Event stream abuse for fun and profit
I’m currently writing some World of Warcraft addons. In itself that’s a fun challenge, but not one that hasn’t been relatively exhaustively tackled over Warcraft’s 15-year lifetime to date. Even the subject area I was interested in - writing some simple analytics to keep track of in-game resource use has been done before. I wanted to make some notes, however, as it has me thinking about how we reconstruct context from a relatively thin, multi-grained 3rd party event sequence.
These addons extend the behaviour of the standard Warcraft UI, which provides an event stream and Lua API with the intent of allowing players to extend their UI, typically to enhance the display, provide warnings and alerts, and automatically trigger or chain actions. The events provided are quite extensive, but aren’t perfect.
The events published from the client engine vary in granularity, and focus (from “your cursor position updated” to “someone nearby has created something” to “a friend of yours has come online”, for example). Some, but not all, carry a payload, and depending on context that payload may be only partially filled. They can be multiply delivered, but delivery is not guaranteed. Finally, events are somewhat contextless, although the overall ordering of events seems consistent. For example - you might get an event telling you that the contents of a bag have been updated, but the event won’t tell you that it’s because you just traded with a merchant or another player, or whether it’s because you just decided to dump a lower value (“grey” in Warcraft terminology) item to make space. Reconstructing that player-action context is a task for the player-developer.
Unfortunately, I need that context in order to track where and why resources get used.
Following the event stream
At first the task appears impossible. The set of events is published, but when many are contextless it’s not clear how to determine the player’s intent. At that point there are two pragmatic options - get a handle on the API to cause additional context-rich events to be thrown as a result of player actions, or to study the event stream to determine whether context can be extracted from it. The latter mechanism has the advantage that you don’t need to modify the player’s UI experience to achieve the goal, but requires a bit of empirical investigation - or, more accurately, undertaking specific sets of actions and recording which events are published as a result.
Fortunately, Warcraft provides a debug tool (the event trace, /eventtrace command) that will monitor all published events and show them in a window. Unfortunately, the window is clunky to use and slow to update, doesn’t persist the data anywhere and doesn’t allow you to tag ranges of events. It’s not practical for a really structured survey.
Fortunately, Warcraft provides an API call that allows you to register for all, as well as specific events - which means I could write an EventTrace addon more suited to my needs. This addon can be turned on or off, with a label indicating the context you’re studying, e.g.
When toggled on all events are recorded - and all events recorded are persisted in a local database that can be studied afterwards. Here’s a comparison of the events recorded while buying something from a merchant - including one where the purchase failed because I had no bag space left.
Context and information
Finally, we want to fit those events and their payload into a mental model, along the lines of “buying from a merchant means: switching into a ‘merchant’ state; choosing an item; receiving the item; handing over payment; switching out of ‘merchant’ state”. We can identify specific events that consistently represent stages of that model, and determine whether we can extract any data required from the event, or need to go elsewhere for it.
Event | Payload | Comment |
---|---|---|
MERCHANT_SHOW | The player’s started talking to a merchant | |
ITEM_PUSH | 23,134186 | Almost useful - it gives us an item ID but doesn’t tell us how many. We could work this out by remembering what we had, but … |
CHAT_MSG_LOOT | You receive item: ... :::::|h[Delicious Cave Mold]|h|rx5 ... | The name of the item we’ve purchased is buried in this Warcraft URI - useful for the UI to render, but we need to dig a bit to pick up the right information. |
INVENTORY_UNIT_CHANGED | player | Just tells us the player’s inventory has changed – no details |
BAG_UPDATE | 4 | The bag in slot 4 has been updated .. but what with? Who would care? |
PLAYER_MONEY | There’s no payload – but we can get current cash with the API call GetMoney(). If we remember the money we had before the transaction then we can work out the cash outflow. | |
MERCHANT_CLOSE | The player’s stopped talking to a merchant. |
Even the events that are related to the exchange vary in utility - from my point of view. What I need is an event:
Event | Payload | Comment |
---|---|---|
PLAYER_EXCHANGED | Delicious Cave Mold,5,300,Merchant | 5 portions of lovely cave mold, for at price 300, with a merchant |
Instead I need to build a stateful event processor in order to
Determine the context in which an action takes place
Fine, but we can have nested actions (I can start talking to a merchant and realise that I need to make some space in my bags. Do I need a state stack?
Remember pre-action resource holdings
GetMoney is one thing, but I might also need a detailed snapshot of what I have in every bag slot for later comparison.
Build a single transaction over multiple events (e.g. CHAT_MSG_LOOT and PLAYER_MONEY)
Let’s hope that the client doesn’t helpfully bundle things up, like giving me an individual chat message for each item, but a single PLAYER_MONEY event.
Your events (partly) determine my app’s structure and complexity
The upshot is that we should think quite hard about the events we publish - and in particular about the uses they get put to. In the case I’m in it’s arguably not Warcraft’s fault, as the event stream seems to be designed to enable simple contextless UI updates, not rebuild a relatively sophisticated context-based transaction record. However, it’s easy to see how an event stream that’s designed without regard to the uses that it’ll be put might twist
Going back to my original goal, it looks like I need to determine which sets of player activities I’m interested in, and trace the events published while I’m undertaking then. After that I need to extract the common events and work out (from their payload) whether it’s possible to reconstruct the context and quantify the consequences.