Making a Weak Auras Chat Automation
World of Warcraft probably is one of the most extendable video games to write addons for that alter the interface and provide lots of customisation to the players. In this post we'll look at how to write a custom trigger for the weak auras framework to automate build a mini "chatbot" in order to provide explanations for my fellow raiders.
One of the popular low code options to display additional reminders as visual or auditive signals is called WeakAuras, which are often used for group activities like raids where groups of players between 10 and 40 players try to defeat bosses together. In these scenarios some roles or specific players get personal responsibilities like moving to a specific place in the arena or using specific abilities.
If these personal responsiblities apart from their usual role (like hunters shooting arrows or healers giving everyone a bit divine light to top of some health pools) are not taken care of, it will kill the entire group and you need to try the entire fight again. With fights being up to 10 minutes, that can be quite frustrating for the group, so as a raid leader, you often want to remind the players in your group of the mechanics in the fight that is about to happen.
If you don't raid in a guild with the same people 1-3 times a week, you have to invite more or less random players, that are a bit of a wild card when it comes to their knowledge of the encounters, the game in general and if you don't require that they join a voice that they're basically playing the game by themselves apart from the little chat window that only occupies a small portion of their interface.
What I used to do while leading groups through some of the raid dungeons that change every few months is to explain lots of mechanics in chat, by simply typing them out in varying degrees of specificity depending on how capable I believe a group to be.
This quickly got to a very ridiculous amount of typing the same things, which I thought would be a fun thing to automate away and pick up a bit of LUA on a train ride, the language used for weak auras and wow addons in general. All we want is a little bit of chat automation to make our multiple weekly runs through a boss filled raid dungeon a little smoother!
The Chatbot WeakAura
WeakAuras support a number of built-in reactions that can let you display something on screen, like when a player gains a positive effect, like when a mage can cast a spell instantly instead of a 5 second cast time after another spell. Reacting to chat messages luckily was a built in event listener, which was great, now we just needed to interpret the chat message and respond with the most important explanations for the upcoming boss fight.
The events we want to listen to are these:
CHAT_MSG_PARTY, CHAT_MSG_PARTY_LEADER, CHAT_MSG_RAID, CHAT_MSG_RAID_LEADER, CHAT_MSG_INSTANCE_CHAT
Which will let me test the code inside a regular party (for 5 people) or use it in the raid party chat.
As a little bonus, I wanted to only have the Heroic difficulty mechanics explained if I'm currently actually in the heroic difficulty raid.
As I had barely written a line of Lua in my life, I needed to start with a Hello World! example and work my way up from there. Our custom functionality will be implemented in a custom trigger, which lets us write lua code to process incoming events forwarded by the specified events.
Disclaimer: you can edit these while the game is running in a sort of "in addon code editor", but it gets stored as a stringified piece of code that gets evaluated every time your specified event happens. This can make debugging a bit cumbersome and you basically end up copy & pasting it in and out of the game editor into your a dedicated code editor. After changes you probably need to reload your interface by typing /reload
in the wow chat.
This is what the built in code editor looks like:
I learned from another weak aura that the input for the custom trigger function would look something like this:
function('CHAT_MSG_RAID', '!explain gnarlroot')
Developing the script outside the game client had some pitfalls, for example that World of Warcraft actually adds a couple of globally available functions (which don't exactly have an official documentation) via its API for the convenience of developing addons. One of these functions is strsplit
, which we kind of stole from stackoverflow:
-- usually provided by wow API
function strsplit(sep, inputstr)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
Secondly we needed the functions that we were going to depend on that are exposed by WoW, which we just return multiple values from to mimic that we're in a raid group and if it's on the Heroic difficulty or not:
-- fake functions
function GetInstanceInfo()
return '', 'raid', 1, '', '', '', '', '', ''
end
function GetDifficultyInfo(fakeArg1)
return '', '', true, '', true, '', ''
end
GetInstanceInfo
documentation: wowpedia.fandom.com/wiki/API_GetInstanceInfoGetDifficultyInfo
documentation: wowpedia.fandom.com/wiki/API_GetDifficultyInfo
Lastly we need a utility function that checks if a specific string is contained in the chat message to respond with the right boss explanation:
function FindSubStr(pattern, message)
if string.find(string.lower(message), string.lower(pattern)) then
return true
else
return false
end
end
Now, putting these together, we can develop our custom trigger lua code locally and run it:
lua explain.lua
function FindBoss(msgContent)
if FindSubStr("Gnarlroot", msgContent) then
return {"Touch all roots with the fire debuff!", "Heroic: drop puddles on the adds!"}
end
if FindSubStr("Igira", msgContent) then
return {"SOAK zones from left to right", "TANKS must stack", "help the HEALERS with the heal absorb"}
end
-- and so on
end
function Explain(event, message)
-- START not required in final script
local function strsplit(sep, inputstr)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
-- END not required in final script
local _, _, channel = strsplit("_", event)
local _,type,difficultyID,_,_,_,_,_,_ = GetInstanceInfo()
local _,_,isHeroic,_,displayHeroic,_,_ = GetDifficultyInfo(difficultyID)
local showHeroicText = type == "raid" and displayHeroic;
local explanationLines = findBoss(message)
for _, v in ipairs(explanationLines) do
if (FindSubStr("heroic:", v)) then
if (showHeroicText) then
-- print heroic guide lines if actually in heroic raid
print(v)
else
end
else
-- print normal guide lines
print(v)
end
end
end
Explain('CHAT_MSG_RAID', '!explain gnarlroot')
which will output:
Touch all roots with the fire debuff!
Heroic: drop puddles on the adds!
local _, _, channel = strsplit("_", event)
This means we're getting the channel an event happened on and then the string of the message, which is great for us. Now we need to filter messages and not execute anything further if the message doesn't contain !explain
and then we need to filter for what boss we would like an explanation for
One of the first things I learned about Lua was that function casing matters and that variables by default are global, which just means some linter will yell at you if you don't prepend them with local
to give them an explicit scope, which will be used in the full example below.
Summary
Weak Auras are a great way to get started coding in your favourite video game to update the interface, remind you of boss tactics and shape your experience and the ones of the players around you.
The weak auras I usually use are more complex and maintained by players way more hard core than me, but this was a fun experiment and I got lots of positive feedback over the raids done with the help of the explainy chat messages!
References
One of the weak auras I stole code from especially for the custom trigger is the "wichtige WeakAura" that reacts to a number of words, usually synonyms for male genitalia, and then every player with the weakaura installed posts an ASCII represanation of a phallus in varying lenghts, in order to compare the randomly generated lengths.
Bob: !abfahrt
Bob: B===D
Alice: B=====D
Pete: B==D
The documentation for the WoW APIs I found was rather spotty, but I did find a weakaura that flashes a message with the difficulty when entering a raid, so thanks to that one for letting me implement the Heroic: messages: wago.io/TsamMXYKS
You can find the complete custom trigger below if you want to write the next weak aura for the next raid tier and you do a lot of random raiding or the weak aura at wago.io: wago.io/wJvNnrQLP
Full custom trigger code:
function(event, msg)
local function findSubStr(pattern, msg)
if string.find(string.lower(msg), string.lower(pattern)) then
return true
else
return false
end
end
local function findBoss(msgContent)
local groupsText = "Groups 1,3,5 LEFT, 2,4,6 RIGHT"
if findSubStr("Gnarlroot", msgContent) then
return {"Touch all roots with the fire debuff!", "Heroic: drop puddles on the adds!", "Heroic: you must SOAK flame circle to burn roots"}
end
if findSubStr("Igira", msgContent) then
return {"SOAK zones from left to right", "TANKS must stack", "help the HEALERS with the heal absorb", "Heroic: fire debuffed peeps emit balls, dodge them"}
end
if findSubStr("Volcoross", msgContent) thehttps://wago.io/wJvNnrQLPn
return {groupsText, "SOAK zones!", "TANK must take all stacks, taunt on JAWS", "Heroic: The 'tail' emits hot juicy 'lava'"}
end
if findSubStr("Larodar", msgContent) then
return {"FOCUS damage and healing on Treants and Roots",
"TANKS run away from charge",
"Heroic: Tank and Melee must CATCH flame balls at boss",
"Heroic: Brambles need to be DOUSED with seed before you can attack them"}
end
if findSubStr("Council", msgContent) then
return {"Help tanks soak the bear charge", "Duck: eat plants, then go to URSOC and press 2 if he rages", "KILL them at the same time", "Heroic: TANK swap is a frontal", "Heroic: Barreling Charge alternating through ranged/melee players, care for debuff"}
end
if findSubStr("Nymue", msgContent) then
return {groupsText, "SOAK all plants", "Do NOT let adds cross lines"}
end
if findSubStr("Smolderon", msgContent) then
return {"If you DO NOT have a red circle, SOAK with the tank!", "After knockback, collect 5 red balls!"}
end
if findSubStr("Tindral", msgContent) then
return {"Stay close to boss for roots AOE", "Tanks take turns on the mushroom soak", "Stomp seeds", "BL on third platform"}
end
if findSubStr("Fyrakk", msgContent) then
return {"go first left, then right on Dream rend", "Catch all balls in intermission", "focus small adds", "heal souls", "assign seed carrier order"}
end
end
local _, _, channel = strsplit("_", event)
local _,type,difficultyID,_,_,_,_,_,_ = GetInstanceInfo()
local _,_,isHeroic,_,displayHeroic,_,_ = GetDifficultyInfo(difficultyID)
local showHeroicText = type == "raid" and displayHeroic;
if msg then
-- print(type,isHeroic,displayHeroic)
if findSubStr("!explain", msg) then
local explanationLines = findBoss(msg)
for _, v in ipairs(explanationLines) do
if (findSubStr("heroic:", v)) then
if (showHeroicText) then
-- print heroic guide lines if actually in heroic raid
SendChatMessage(v, channel)
else
end
else
SendChatMessage(v, channel)
end
end
end
end
end