Looking for Punters

Now that we’ve got a basic AddOn working, we can get down to the nitty-gritty and create the functionality that’s going to get some of that sweet, sweet Kargath coin.

On and Off

We’re going to need a way to turn our auto-inviter on and off. There’s nothing more annoying than asking for a portal in Orgrimmar and being auto-invited by an AFK mage so hopefully you’ll remember to turn this off when you’re taking a break from filling your pockets with gold.

Let’s create a method that will register a slash command with WoW so that we can type /portalwhere on and /portalwhere off and also allow us to use /pw as well since /portalwhere takes longer to type!

function PortalWhere:RegisterSlashCommand()
    SLASH_PORTALWHERE1 = "/pw"
    SLASH_PORTALWHERE2 = "/portalwhere"
    SlashCmdList["PORTALWHERE"] = function(msg)
        local _, _, command, args = string.find(msg, "%s?(%w+)%s?(.*)")
        if command then
            self:OnSlashCommand(command, args)
        end
    end
end

function PortalWhere:OnBoot()
    self:RegisterSlashCommand()
    self:Print("Loaded.")
end

Registering chat commands with WoW seems a bit hacky. You have to add your command to a predefined global table called SlashCmdList and then create some matching global variables. It’s a bit ugly but that’s how it works. Now whenever /pw or /portalwhere is entered in to the chat window, our lambda function will get called with the text that follows those commands. So if I typed /pw hello there then our function will receive the text “hello there”.

We’re then parsing that text. We take the first word we see and assume that’s a command and that all the following text are arguments.

function PortalWhere:OnSlashCommand(command, args)
    command = string.lower(command)
    if command == "on" then
        self:On()
    elseif command == "off" then
        self:Off()
    else
        self:Print("Unknown command.")
    end  
end

We use string.lower() to convert the command to lowercase so we can easily test it without worrying about case sensitivity. Now it you type /pw on (or /portalwhere on) then our On() method will get called. If you type /pw off (or /portalwhere off) then the Off() method will get called. We haven’t created On() and Off() yet so let’s get to it.

Monitoring Chat

When our AddOn is in the on state, we want to watch the say, yell and whisper channels and when it’s off then we want to stop watching those channels.

function PortalWhere:On()
    self:RegisterEvent("CHAT_MSG_SAY")
    self:RegisterEvent("CHAT_MSG_YELL")
    self:RegisterEvent("CHAT_MSG_WHISPER")
    self:Print("Looking for punters...")
end

function PortalWhere:Off()
    self:UnregisterEvent("CHAT_MSG_SAY")
    self:UnregisterEvent("CHAT_MSG_YELL")
    self:UnregisterEvent("CHAT_MSG_WHISPER")
    self:Print("All done. Time for breakfast.")
end

We’re then going to respond to say, yell and whisper in exactly the same way by routing it all to the same OnChat() method.

function PortalWhere:CHAT_MSG_SAY(...)
    self:OnChat(...)
end

function PortalWhere:CHAT_MSG_YELL(...)
    self:OnChat(...)
end

function PortalWhere:CHAT_MSG_WHISPER(...)
    self:OnChat(...)
end

function PortalWhere:OnChat(text, playerName, _, _, shortPlayerName, _, _, _, _, _, _, guid)
    
end

Before we flesh out OnChat(), let’s go to the top of the file and define the chat keywords that we’ll be looking for against the possible destinations. We’ll define them just under our CreateFrame() call.

PortalWhere = CreateFrame("Frame")

PortalWhere.matchWords = {
    ["Undercity"] = {
        "Undercity",
        "UC",
        "Undershitty",
    },
    ["Orgrimmar"] = {
        "Orgrimmar",
        "Orgrimar",
        "Org",
        "Orgri",
        "Ogri",
        "Ogr",
        "Og",
    },
    ["Thunderbluff"] = {
        "Thunderbluff",
        "TB",
    }
}

We’ll then convert these to lowercase in our OnBoot() method so we can do case insensitive matching.

function PortalWhere:MakeLowercaseMatchWords()
    for destination, wordList in pairs(self.matchWords) do
        for index, word in ipairs(wordList) do
            self.matchWords[destination][index] = string.lower(word)
        end
    end
end

function PortalWhere:OnBoot()
    self:MakeLowercaseMatchWords()
    self:RegisterSlashCommand()
    self:Print("Loaded.")
end

At this stage we know what people are saying and we have a list of words to look for. Time to join the dots! Let’s create a WantsPortal() method that will determine if the player talking is looking for a portal and where they want to go.

function PortalWhere:WantsPortal(playerName, guid, message)
    if playerName == UnitName("player") then
        return false
    end
    
    local _, playerClass = GetPlayerInfoByGUID(guid)
    if playerClass == "MAGE" then
        return false
    end

    for word in string.gmatch(message, "%a+") do
        local match, destination = self:MatchWordToDestination(word)
        if match then
            return match, destination
        end
    end

    return false
end

Notice that we’re first making sure that it isn’t us talking. Secondly, we’re also making sure that the player talking isn’t a mage since other mages aren’t likely to need a teleport but are very likely to trigger our keyword matching when they advertise their services. Really the first test isn’t needed since we’re also a mage but I like to be explicit. The guid parameter is something we receive with the chat message. It’s a unique identifier for the character and we use it to determine their class.

Another thing to note here is that we’re looking for whole words with %a+. I’ve been in Kargath before and found other bots do a terrible job at this. For example I could type “Let’s go to the truck-stop” and be automatically invited by a lesser quality AddOn since “truck-stop” contains the letters “uc”.

We’ll also need a couple of helper functions to make this work.

function PortalWhere:MatchWordToDestination(word)
    word = string.lower(word)
    for destination, wordList in pairs(self.matchWords) do
        if self:ArrayHas(word, wordList) then
            return true, destination
        end
    end
    return false
end

function PortalWhere:ArrayHas(item, array)
    for index, value in pairs(array) do
        if value == item then
            return true
        end
    end
    return false
end

MatchWordToDestination() receives a word and performs a case-insensitive search in our matchWords table. If it finds a match then it returns true along with the matched destination.

ArrayHas() is a helper method that we use to determine if an array contains a value. It’s a method that you may find useful when creating other AddOns too.

Last modified: June 22, 2020

Author

Comments

Write a Reply or Comment

Your email address will not be published.