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.

weak aura responding

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.

weak aura with heroic text

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:

weak auras code editor

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

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
Tagged with: #gaming #warcraft #lua #weak-auras

Thank you for reading! If you have any comments, additions or questions, please tweet or toot them at me!