Archive

UI Mod Guide - Chapter 3

Updated Thu, Feb 21, 2008 by bpirkle

Krimm's Guide
to World of Warcraft Addon Design

Part 3
Saved Variables

Part 3 Index

Download Part 3 Example Files!


You’ve made it to Part Three of our guide to creating WoW AddOns!  If you’ve been following through from Part One, you now have a working, customizable battle cry AddOn.  Now we’ll extend this AddOn to remember the user’s preferred battle cry between WoW sessions. 

To remember the battle cry, we’ll learn how to use “Saved Variables”.  Along the way, we’ll also look more closely at event handling.  I’ll assume that you’re familiar with everything discussed in Part One and Part Two.  However, I still won’t assume you have extensive programming experience. 

WHAT ARE SAVED VARIABLES?

Saved variables are a way (in fact, the only supported way) for an AddOn to remember information between WoW sessions.  This is an incredibly cool feature, and we should each buy lunch for the guys at Blizzard for including saved variables in WoW.  There are so many uses for saved variables that I can’t even begin to list them all.  In our case, we’ll use them to remember the battle cry between sessions.

HOW DO I ADD A SAVED VARIABLE TO MY AddOn?

So how does all this work?  First I’m going to simply tell you how to add a saved variable to your AddOn.  It is so easy that you’ll wonder why I dedicated a whole section of this guide to saved variables.  But after I show you how to do it, I’ll explain more about what is actually going on.  This will give us a chance to discuss some things we haven’t touched on yet, such as the AddOn startup sequence and the events that an AddOn receives early in its execution.

Recall that in Part Two, we used a global variable named TthAog2MyBattleCry to hold the user’s preferred battle cry.  The variable declaration looked like this:

TthAgo2MyBattleCry = "Not in the face!";

What we’d really like is for this variable’s value to be saved between sessions, and automatically restored for us next time the AddOn loads.  To do this in version three of our AddOn, we add this line to our toc file:

## SavedVariablesPerCharacter: TthAog3MyBattleCry

That’s it.  One line.  I told you it was easy.  J  Here’s what will happen next time you start the AddOn, as it relates to the TthAog3MyBattleCry variable:

1)  The “TthAog3MyBattleCry” variable is created and assigned our silly default value of “Not in the face!”.

2)  WoW will look for a saved value for the variable.  (WoW knows to do that because of the line we added to the toc file.)  If WoW finds a saved value, then that saved value replaces the default value.  If WoW does not find a saved value, the default value remains unchanged.

3)  The AddOn continues to execute, and the next time you get in a fight the battle cry stored in “TthAog3MyBattleCry” is used.

This seems almost magical the first time you try it.  Our AddOn doesn’t need to do anything at all to store the value of the variable, or to restore it next time it executes.  WoW takes care of all that for us.  Hooray for Blizzard!

Notice that the line we added to our toc file includes “PerCharacter”.  This means that the saved variable value is unique to every character.  If we had three different characters, they could each have their own unique battle cry.  There is a second kind of saved variable that is shared between all characters on an account.  To use this per-account kind of variable, you just omit “PerCharacter” in the toc file.  So it would look like this:

## SavedVariables: TthAog3MyBattleCry

Now all three characters would share the same battle cry.  For this AddOn, we prefer the variable to be saved per-character.  But the per-account type is available should you ever need it.

There used to be a third type of saved variables, known as “global” saved variables.  Blizzard removed these in the 1.10 patch, so you don’t have to worry about them anymore (the new types are better anyway).  Global saved variables used a routine called
RegisterForSave.  Just be aware that if you see RegisterForSave mentioned, you’re looking at old information.

WHERE ARE SAVED VARIABLES STORED?

To really understand what is going on, and also to debug your AddOns, it is helpful to know where WoW stores saved variables.  Per-character saved variables are stored under the “World of Warcraft” folder in:

WTF\Account\{AccountName}\{RealmName}\{CharacterName}\SavedVariables\{AddOnName}.lua

All the parts in curly braces depend on your account, character, realm, and AddOn names.  If you use the form of saved variables that are shared between all characters, it is stored under the “World of Warcraft” folder in:

WTF\Account\{AccountName}\SavedVariables\{AddOnName}.lua

Global saved variables, should you choose to use them, are stored in:

WTF\Account\{AccountName}\SavedVariables.lua

You can look into the .lua files in all of these location to see what the AddOns you use are storing there.

HOW AND WHEN DOES WoW PROCESS SAVED VARIABLES?

WoW processes saved variable files as Lua code.  This explains how WoW “magically” restores variable values.  Basically WoW first executes your AddOn’s Lua files, then executes the saved variable files, then begins sending events to your AddOn.  Imagine that your AddOn did this:

TthAog3MyBattleCry = "Not in the face!";
TthAog3MyBattleCry = "Not in the groin!";

That’s effectively what would happen if WoW loaded a battle cry AddOn which uses the “TthAog3MyBattleCry” saved variable.  If there was no saved value for the “TthAog3MyBattleCry” variable, the second line (which comes from the saved variable file) wouldn’t be present, so the default value would “stick”.

When does all this happen?  First let’s answer when the variables are saved, because that is simpler.  WoW saves these files when the WoW user interface shuts down.  This can be when you leave your realm (either by exiting the game or logging out to the character selection screen), or when you reload the user interface (via the “/console reloadui” command we’ve discussed before.)
 
Now, when does WoW load variables?  The simple answer is, when the WoW user interface loads.  This is just after clicking “Enter Realm” on the character selection screen, or just before you get control back after doing “/console reloadui”.  That’s not so bad.  But what if your AddOn wants to do something when its variables are loaded?  For example, we might want to echo the current battle cry to the chat area when the AddOn loads, just to remind the user what it is set to.  To do that, we need to examine more closely what happens when an AddOn is loaded.  Here’s the sequence:

1)  our AddOn loads, and all hard-coded variable initializations occur

2)  WoW looks for an “OnLoad” element in our AddOn’s XML file.  If it finds one, it executes the Lua snippet contained in that element. 

3)  Per-character and per-account saved variables are loaded.

4)  The “ADDON_LOADED” event fires for your AddOn.

5)  All other AddOns are loaded.  The “ADDON__LOADED” event fires for these AddOns too, so if you’ve registered for this event you may receive it multiple times.  If you’re not running any other AddOns, or if yours happens to be the last one loaded, you’ll only receive it once.

6)  Global saved variables are loaded.

7)  The “VARIABLES_LOADED” event fires.

8)  The “PLAYER_ENTERING_WORLD” event fires.

A brief digression while we’re on the topic of AddOn initialization:  I mention #8 because some game world objects to which your AddOns might need to refer are not guaranteed to be created when either “ADDON_LOADED” or “VARIABLES_LOADED” fires.  In these cases, the “PLAYER_ENTERING_WORLD” event might be a good alternative.  Be aware that “PLAYER_ENTERING_WORLD” can fire multiple times for your AddOn (for example, when changing instances).  If you need to use “PLAYER_ENTERING_WORLD” but multiple firings would mess up your AddOn, you should set up a flag to distinguish the first firing from subsequent ones.  Also, the Chronos library has an alternative that you might consider.  All that is beyond the scope of our battle cry AddOn, so I won’t discuss it any further. 

If you don’t need to worry about referring to game world objects, the “VARIABLES_LOADED” event looks like the one to use, right?  Unfortunately, there’s a “gotcha”.  If your AddOn uses “OnDemand” loading, you may never get “VARIABLES_LOADED” at all.  “OnDemand” loading is beyond the scope of this guide.  Briefly, it is a way for AddOns to load each other.  This might be useful if your AddOn has a very special purpose.  If the user doesn’t need it in a particular session, it doesn’t need to be loaded (thus saving memory load).  For this reason, “ADDON_LOADED” is the safer event – you’re always guaranteed to receive that one, even if your AddOn is dynamically loaded.  The only disadvantage is that global saved variables won’t be loaded yet, but we try to avoid those anyway.  We’ll use “ADDON_LOADED” in this guide.

We’re ready to modify our code to echo the battle cry when the AddOn loads.  All we need to do is register for the “ADDON_LOADED” event and, when we receive it, send the battle cry to chat.  Registering for the event is simple:

function TthAog3Load()
  -- Register for events.
  this:RegisterEvent("PLAYER_REGEN_DISABLED");
  this:RegisterEvent("ADDON_LOADED");
end

(For the moment, I’ve omitted the code that creates our slash command.  I’ll put it back in for the full listing at the end.) 

Uh-oh.  We’ve never dealt with two events before.  We’ve always just assumed that when our event handling routine was called, it was for the "PLAYER_REGEN_DISABLED" event.  Fortunately, handling multiple events is easy.  The name of the current event is stored by WoW in a variable named (appropriately) “event”.  This is technically a local variable to an internal WoW function that runs the code snippet in the OnEvent line of your XML file.  But Lua scoping rules give us access to it, so we can just treat it the same way as we do our own global variables – it is automatically there for us to use.  Your code can test this variable to see which event it is processing.  It looks like this:

function TthAog3Event()
  if (event == "ADDON_LOADED") then
    local echo = TTH_AOG3_BATTLE_CRY_IS .. TthAog3MyBattleCry;
    TthAog3OutputToChat(echo);
  elseif (event == "PLAYER_REGEN_DISABLED") then
    -- Send a message to chat.
    SendChatMessage(TthAog3MyBattleCry, "SAY");
  end
end

(The variable TTH_AOG3_BATTLE_CRY_IS holds the constant string “Battle cry is:  “.  We’ll create this variable at the top of our .lua file, like you learned in Part Two.)

When the TthAog3Event function is called, we check which event caused the call.  If you handle a lot of events, or if the events you handle require a lot of code, you might want to write a separate function to handle each of them.  The TthAog3Event (or your equivalent of it) would then be much simpler – it would just figure out which event occurred and call the appropriate event handler function.

We’ve also never used the elseif construct before.  You probably already figured out what it does – if the “if” condition isn’t met, the “elseif” condition is checked.  You can add as many “elseif” conditions as you need to.  If none of the “if”  or “elseif” conditions are met, then execution continues past the “end”.  If there are actions that you want taken only if none of the conditions are met, you can put them in an “else” at the end.  We don’t need to do that here, but I’m sure that you’ll find a use for it soon enough in your own Addons.

There’s a bug in our code.  Remember your AddOn can receive the "ADDON_LOADED" message multiple times?  We haven’t done anything to deal with that.  So we’ll indeed echo our battle cry when our AddOn loads.  But we may also do it when lots of other AddOns load, which isn’t what we want.  To solve this we check the “arguments” of the event (also called “parameters”).  These are variables that WoW sets for every event, much the same as the “event” variable.  They’re named very generically, as “arg1”, “arg2”, and so on all the way up to “arg9”.  These are generic slots shared by all events.  Their meanings change depending on the event that you’re handling, so be sure to look up the event to see what its arguments mean.  Few events use all nine arguments.  If an argument is unused, it will be the special Lua value nil, meaning “not present”. 

For the "ADDON_LOADED" event, “arg1” is set to the name of our AddOn.  We’ll hold this name in a constant string at the top of our file, like this:

local TTH_AOG3_ADDON_NAME = "TthAog3";

Here’s how we check to see whether we really want to do anything in response to "ADDON_LOADED":

  if (event == "ADDON_LOADED") then
    if (arg1 == TTH_AOG3_ADDON_NAME) then
      local echo = TTH_AOG3_BATTLE_CRY_IS .. TthAog3MyBattleCry;
      TthAog3OutputToChat(echo);
    end
  elseif (rest of function omitted for brevity)

TRYING IT OUT 

That wraps up the changes for Part Three of this guide.  The battle cry AddOn now remembers the user’s preferred battle cry between sessions, and echos it on load.  We’ve finally turned this little AddOn to something that you might actually use.  (Personally, I use it when soloing quests, just to spice things up a little.  But I turn it off if I’m in a group so that I don’t annoy my party.)

Here are the code listings.  First, the toc file (compared to Part Two, we just added one line, and changed some things to say “3” instead of “2”):

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

Next, the XML file (compared to Part Two, we just changed some names to say “3” instead of “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=" TthAog3.lua"/>
       <Frame name=" TthAog3Core">
              <Scripts>
                     <OnLoad> TthAog3Load();</OnLoad>
                     <OnEvent> TthAog3Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Finally, the LUA file:

--  Constant strings
local TTH_AOG3_ADDON_NAME = "TthAog3";
local TTH_AOG3_BATTLE_CRY_IS = "Battle cry is ";
local TTH_AOG3_BATTLE_CRY_SET_TO = "Battle cry set to:  ";
 
--  This variable holds our battle cry.
TthAog3MyBattleCry = "Not in the face!";

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

--  Called when the AddOn loads.
function TthAog3Load()
  -- Register for events.
  this:RegisterEvent("PLAYER_REGEN_DISABLED");
  this:RegisterEvent("ADDON_LOADED");

  --  Create the slash command.
  SlashCmdList["TTH_AOG3_SET_MY_BATTLE_CRY"] = TthAog3SetMyBattleCry;
  SLASH_TTH_AOG3_SET_MY_BATTLE_CRY1 = "/setmybattlecry3";
  SLASH_TTH_AOG3_SET_MY_BATTLE_CRY2 = "/smbc3";
end

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

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

--  Called to handle the events for which we’ve registered.
function TthAog3Event()
  if (event == "ADDON_LOADED") then
    if (arg1 == TTH_AOG3_ADDON_NAME) then
      local echo = TTH_AOG3_BATTLE_CRY_IS .. TthAog3MyBattleCry;
      TthAog3OutputToChat(echo);
    end
  elseif (event == "PLAYER_REGEN_DISABLED") then
    -- Send a message to chat.
    SendChatMessage(TthAog3MyBattleCry, "SAY");
  end
end

You can also download these files from <here>.

WRAP-UP

Remember that if you still have the AddOn from Part One and/or Part Two of this guide installed and active, you’ll get multiple battle cries – one from each version of the AddOn.

I should mention that only global variables can be saved.  That’s why I didn’t make TthAog2MyBattleCry local back in Part Two.  Usually this isn’t a big deal – just pick good names for your variables – but it will come back to haunt us in Part Five when we look at packages.  For now, just know that if you want a variable to be saved, make it a global variable.

One warning:  saved variables have changed several times since Blizzard introduced them.  The above describes how they work as of WoW version 1.9.  If you look on other Web pages, especially at older forum postings, you’re likely to find outdated descriptions about how saved variables once worked.  So don’t get confused.  Just be sure you know what version of WoW the author is talking about.


Go to Part 4 - Tables!


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