Archive

UI Mod Guide - Chapter 2

Updated Thu, Feb 21, 2008 by bpirkle

Krimm's Guide
to World of Warcraft Addon Design

Part 2
Slash Commands









Part 2 Index

Download Part 2 Example Files!


Welcome to Part Two of our guide to creating WoW AddOns!  In this installment we’ll build on the simple AddOn we created in Part One.  Even if you’re already familiar with creating AddOns, you might want to quickly skim Part One. 

In Part Two, I’ll assume that you’re familiar with the basics of Lua, XML, and toc files.  However, I won’t assume you have extensive programming experience.  Our focus will be on handling “slash” commands.  Along the way, we’ll also look a bit closer at Lua coding.  When we’re done, we’ll have expanded the rather lame “battle cry” AddOn from Part One to be customizable by end users without requiring them to modify the Lua code.  If you don’t already have the AddOn from Part One, you can get the files <here>.

WHAT ARE SLASH COMMANDS, AND WHY DO I CARE?

If you’ve played WoW for more than about five minutes, you’re familiar with slash commands.  They’re the things like “/say” and “/who” that you type while playing WoW.  Slash commands are a very convenient way to perform an action without using the mouse.  For AddOn writers, slash commands are much more straightforward to implement than a graphical user interface with windows and buttons.  If your needs are straightforward, slash commands might be all that your AddOn requires.

As you know, WoW provides about umpteen-gazillion such commands.  What you may not know is that AddOns can add their own slash commands.  So let’s take a look at what it takes to bump the slash command count to umpteen-gazillion-and-one.

HOW DO SLASH COMMANDS WORK?

Every slash command that an AddOn creates has a Lua function associated with it.  When the user types the slash command, WoW calls the Lua function.  Internally, WoW maintains a “SlashCmdList”, which is a table mapping all the different slash commands to the functions they should call.  You can think of this like a phone book.  (Remember back before chat rooms and IM, when people used the telephone? J)  The slash commands are like the names and the functions are like the phone numbers.  When the user types a slash command, WoW runs through the slash commands (names) until it finds the correct one.  Then it calls the function (phone number) for that entry.  Here’s the Lua to create a slash command:

SlashCmdList["TTH_AOG2_SET_MY_BATTLE_CRY"] = TthAog2SetMyBattleCry;

This line sets the function for the “TTH_AOG2_SET_MY_BATTLE_CRY” slash command to “TthAog2SetMyBattleCry”.  If the “TTH_AOG2_SET_MY_BATTLE_CRY” slash command doesn’t already exist in the “SlashCmdList” table, it is automatically created.  (Convenient, isn’t it?)  For this to actually work, we’d have to define the “TthAog2SetMyBattleCry” function somewhere in our Lua code.  The function might look like this:

function TthAog2SetMyBattleCry(msg)
  -- set the battle cry, for use later
  TthAog2MyBattleCry = msg;
end

(A word on convention:  notice that we used all-caps for the slash command name.  That is, we wrote TTH_AOG2_SET_MY_BATTLE_CRY” instead of “TthAog2SetMyBattleCry”.  In this guide, we’ll use all-caps for constants – values that should never change even if the world crumbles into interstellar dust or Blizzard nerfs Priests or something equally horrifying.  Any time you see me use an all-caps name, you’ll know I’m showing you a constant.)

Pop quiz:  what does a user type to run the new slash command?  Okay, I confess.  It is a trick question.  Although the above line of Lua creates a slash command and associates a Lua function with it, we still don’t have any way to execute it.  WoW uses a nifty aliasing system to associate what the user types with the actual slash commands.  Here’s the Lua code to do it:

SLASH_TTH_AOG2_SET_MY_BATTLE_CRY1 = "/setmybattlecry2";

Now if the user types “/setmybattlecry2 Not in the groin!”, the slash command “TTH_AOG2_SET_MY_BATTLE_CRY” will be executed, causing the function “TthAog2SetMyBattleCry” to be called with the parameter “Not in the groin!”.  (We put a “2” at the end of “/setmybattlecry2” because we’ll revise our AddOn in later parts of this guide and we don’t want the names to conflict.)

Why the extra step?  Why doesn’t creating the slash command “TTH_AOG2_SET_MY_BATTLE_CRY” automatically make it so the user can type “/tth_aog2_setmybattlecry” to run it?  There are two reasons.  You can already see the first:  although our internal names may make lots of sense in our code, they’re often terribly ugly to show to users.  The second reason is more important:  this extra step allows multiple keywords can execute the same slash command.  Typing “/setmybattlecry2” is kind of a pain.  Let’s give people a shorter way:

SLASH_TTH_AOG2_SET_MY_BATTLE_CRY2 = "/smbc2";

Now users of our AddOn can type either “/setmybattlecry2” or “/smbc2” to set the battle cry.  Notice that in the constant part (SLASH_TTH_AOG2_SET_MY_BATTLE_CRY2) we appended a “2” instead of a “1” this time.  We could add a third keyword, by appending a “3” instead, and so on.

Although slash commands aren’t as fancy or as visually appealing as windows and buttons, they’re often convenient for users.  And they’re a whole lot easier for you to create.  (If you still want a graphical interface to your AddOn, and you probably do, be patient.  We’ll cover that in Part Seven of this guide.)

LUA VARIABLES

You may have noticed something new in the function above.  Here it is again, in case you missed it:

function TthAog2SetMyBattleCry(msg)
  -- set the battle cry, for use later
  TthAog2MyBattleCry = msg;
end

The “TthAog2MyBattleCry = msg;” is the new part.  “TthAog2MyBattleCry” is a Lua variable.  Variables are just slots to hold any value of your choice.  Like many scripting languages, Lua is very flexible in dealing with variables.  If you’ve used a “strongly typed” programming language such as C++, you might wonder about the variable’s “type”.  Don’t.  You can think of Lua variables as big empty boxes that can hold pretty much anything you want to store there.  In this AddOn, we store the user’s preferred battle cry into the variable. 

One important aspect of variables is “scope”.  Scope essentially means “what code can see this variable”?  By default, variables have “global scope”, many any of your functions can see the variable.  The other type of scope is “local scope”.  What this means to us is that only code within the same “block” can see the variable.  A “block” basically means “until the next end”.  (The technical definition of scope in Lua is more complicated, but we don’t need to worry about those details here.)

In our above example, “TthAog2MyBattleCry” is a “global variable”, a variable with global scope.  The line “TthAog2MyBattleCry = msg;” within the “TthAog2SetMyBattleCry” function created it.  For those of you coming from languages like C++, this may seem counterintuititive.  But in Lua, if a variable is not explicitly identified as having local scope, it is automatically global.  Even if it declared within a function.  So the following would work:

function TthAog2SetMyBattleCry(msg)
  -- set the battle cry, for use later
  TthAgo2MyBattleCry = msg;
end

function TTHAog2Event()
  -- send a message to chat
  SendChatMessage(TthAog2MyBattleCry, "SAY");
end

Notice that TthAog2MyBattleCry is used in both functions.  In both places, it refers to the same variable.  Using the box analogy, if TthAog2SetMyBattleCry puts a value into the box, TthAog2Event can look into the box and see that value. 

However, declaring global variables in this way is bad practice.  It is much better to declare them at the top of your Lua file.  This has two advantages:

1)  the full list of global variables is right there, where you can easily see all of them in a glance

2)  you can be sure the variable is properly initialized.

So it is much preferable to do this:

--  this variable holds our battle cry
TthAog2MyBattleCry = "Not in the face!";

function TthAog2SetMyBattleCry(msg)
  -- set the battle cry, for use later
  TthAog2MyBattleCry = msg;
end

function TthAog2Event()
  -- send a message to chat
  SendChatMessage(TthAog2MyBattleCry, "SAY");
end

Now it is much easier to see what is going on.

Local variables are often useful when we only need a value briefly.  By using a local, it is very clear what code modifies the variable.  Because the local doesn’t exist very long, you can usually see all the code that might modify it at a glance.  It would be a good idea to echo the new battle cry back to the user when they set it.  Let’s modify TthAog2SetMyBattleCry to do this:

function TthAog2SetMyBattleCry(msg)
  -- Set the battle cry, for use later.
  TthAog2MyBattleCry = msg;

  -- Echo the new battle cry for confirmation.
  local echo = TTH_AOG2_BATTLE_CRY_SET_TO .. msg;
  TthAog2OutputToChat(echo);
end

We’ve introduced several new things in addition to the local variable, so let’s briefly look at them one-by-one:

local echo = TTH_AOG2_BATTLE_CRY_SET_TO .. msg;

This line creates a local variable named “echo” to store the value that we’ll echo to the user.  The part after the equals sign makes a string of text that begins with the contents of the variable TTH_AOG2_BATTLE_CRY_SET_TO and ends with the contents of msg, then assigns that string of text to the local variable. 

Uh-oh, we haven’t defined the TTH_AOG2_BATTLE_CRY_SET_TO variable.  We’ll define it with this line, placed at the top of our file:

local TTH_AOG2_BATTLE_CRY_SET_TO = "Battle cry set to ";

As you can see, the TTH_AOG2_BATTLE_CRY_SET_TO variable simply contains a string.  Because this string never changes, it is called a “constant”.  Placing your constant at the top of your file is always a good idea.  That way, if you need to change the wording, the string is very easy to find.  (As we’ll see in Part Six, it also makes localization, or supporting multiple languages, much easier.)  Also, we’ve declared the TTH_AOG2_BATTLE_CRY_SET_TO variable as local, even though it is not in a function.  This means the variable exists only within this file, and not in any others.  If you’re only using a variable in one file, it is always good to make it local.  That way, you’re sure that no code outside your file might have modified it.  (You might be wondering why we didn’t make TthAog2MyBattleCry local.  That’s mostly because I wanted to show you what a global variable looked like.  But there’s another reason too, which you’ll see in Part Three.)

Now let’s move to the next line of our function:

TthAog2OutputToChat(echo);

This line calls a function that we must write, to send a message to the chat area.  The variable echo is passed to the function.  Here’s what this function looks like:

function TthAog2OutputToChat(msg)
  if (DEFAULT_CHAT_FRAME) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
  end
end

This function determines if we can display text to the default chat frame and, if so, displays the text in msg to the chat area.  This text is not seen by any other players – it is strictly informational text for the player running the AddOn.  If you’re not familiar with how passing variables to functions works, here’s how it goes:  when TthAog2SetMyBattleCry calls TthAog2OutputToChat by passing the echo variable, Lua some things behind the scenes.  It creates a local variable named msg in TthAog2OutputToChat, then copies the value in echo to msg.  This gives us a local variable named msg within the TthAog2OutputToChat function, which we can use as we please. 

We didn’t have to create a function to do this.  We could have put the code to output to the chat area directly in the TthAog2SetMyBattleCry function.  But using a function makes it really easy to output text to the chat area.  If we needed to do this somewhere else, we’d just need to call the function, not copy a whole block of code.  (We’ll take advantage of this in Part Three.)  Also, if we decided to show text to the user in a different way, we’d just need to modify the code within the function.  No other code would be affected, reducing the chance of a bug.  Finally, it simplified the TthAog2SetMyBattleCry function.  Easy-to-read code is easy-to-debug code.

We haven’t fully explored Lua string handling or the WoW frame hierarchy, but to do so at this point would be a distraction from our main focus of slash commands.  Hopefully the above is sufficient for you to understand what’s happening in this version of the AddOn. 

PUTTING IT ALL TOGETHER

We’re now ready to rewrite the battle cry AddOn to allow the user to specify a preferred battle cry via slash commands.  Just as in Part One, there are three files involved:
- toc file
- XML file
- Lua code file

The toc file is almost unchanged from Part One:

## Interface: 11000
## Title: TenTonHammer Guide Part 2
## Author:  TenTonHammer.com
## Notes:  Makes your character scream a customizable battle cry when combat begins
TthAog2.xml

We’ve just changed “1” to “2”, and changed the notes to call the battle cry “customizable” instead of “silly”.  Save this toc file as TthAog2.toc to a folder named TthAog2 under the folder AddOns.

The XML file is identical to Part One, except to change “1 to “2”:

<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/">
       <Script file=" TthAog2.lua"/>
       <Frame name=" TthAog2Core">
              <Scripts>
                     <OnLoad>TthAog2Load();</OnLoad>
                     <OnEvent>TthAog2Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Save this XML file as TthAog2.xml to a folder named TthAog2 under the folder AddOns.

Finally, we have the Lua file containing the code itself.  You’ve seen all of these pieces already:

--  Constant strings.
local TTH_AOG2_BATTLE_CRY_SET_TO = "Battle cry set to ";
 
--  This variable holds our battle cry.
TthAog2MyBattleCry = "Not in the face!";

--  Utility function for printing to chat area
function TthAog2OutputToChat(msg)
  if (DEFAULT_CHAT_FRAME) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
  end
end

--  Called when the AddOn loads.
function TthAog2Load()
  -- Register for an event.
  this:RegisterEvent("PLAYER_REGEN_DISABLED");

  --  Create the slash command.
  SlashCmdList["TTH_AOG2_SET_MY_BATTLE_CRY"] = TthAog2SetMyBattleCry;
  SLASH_TTH_AOG2_SET_MY_BATTLE_CRY1 = "/setmybattlecry2";
  SLASH_TTH_AOG2_SET_MY_BATTLE_CRY2 = "/smbc2";
end

--  Called when the slash command executes.
function TthAog2SetMyBattleCry(msg)
  -- Set the battle cry, for use later.
  TthAog2MyBattleCry = msg;

  -- Echo the new battle cry for confirmation.
  local echo = TTH_AOG2_BATTLE_CRY_SET_TO .. msg;
  TthAog2OutputToChat(echo);
end

--  Called to handle the event for which we’ve registered.
function TthAog2Event()
  -- Send a message to chat.
  SendChatMessage(TthAog2MyBattleCry, "SAY");
end

Compared to Part One, we’ve added the TthAog2MyBattleCry global variable, added the code to create the slash command, added the TthAog2SetMyBattleCry function, and used the TthAog2MyBattleCry global variable in TthAog2Event.  We’ve also added more comments.

Save this lua file as TthAog2.lua to a folder named TthAog2 under the folder AddOns.

WHAT DOES IT DO?

Now start WoW and get in a fight.  You’ll see the silly battle cry “Not in the face!”.  This reflects the default value of the TthAog2MyBattleCry variable.  Now type the following:

/smbc2 To Battle!

You’ll see the new battle cry echoed to the chat area.  Now get in another fight, and you’ll see “To Battle!” instead of “Not in the face!”.  If you prefer to turn off the battle cry, you can type just:

/smbc2

Our echo isn’t very well worded for this case (it will just say “Battle cry set to”).  If this were a real AddOn for use by the general public, we’d test the value of msg and display a special echo if the user is clearing the battle cry.  Because this is just an example, we won’t complicate the code.

FINAL WORDS

If you try the new version of the AddOn and your character screams two battle cries, it is probably because you still have the AddOn from Part One of this guide installed and active.  Because we were careful with naming, this won’t cause any errors.  But it probably isn’t what you want, either.  Just get disable or delete the first version – the new one is better anyway.

There’s one problem, though:  your battle cry is lost between sessions.  Every time you close and restart WoW, you’ll be back to “Not in the face!”.  When you’re ready, move on to Part Three and we’ll see how to make your preferred battle cry “stick” between sessions.


Go to Part 3 - Saved Variables!

WarlockWallpaper.jpg
Five classes that would be excellent additions to World of Warcraft.
Features
Fri, Jun 20, 2014
Mem
HotSAlphaLoad.jpg
Five things players should expect during the Alpha test of Warlords of Draenor.
Features
Fri, Jun 13, 2014
Mem
GG_25_02

I really don't understand racism in the real world. People are what people are, regardless of skin pigmentation or where their ancestors came from. There's really only one real-world race - the Human Race - and I loathe everyone equally.

Opinions
Mon, Jun 09, 2014
gunky
GarrisonTownHall.jpg
A basic guide to Garrisons in Warlords of Draenor.
Basics, Features, Guides
Fri, Jun 06, 2014
Mem

News from around the 'Net