Greatest wow gold guide service

WOW Hunter Guide: How To Choose The Right Pet


Hunters are like warriors in the World of Warcraft, they are strong, dependable, and have great skills to move up the levels swiftly.Here is a article about hunter’s pet.The pet is very important to hunter.How To Choose The Right Pet? Read follow information..

 

While a hunter and warrior are alike in many different ways, the hunter is also kin to the warlock in the fact that they can have a pet to use as a tank to draw the mob’s damage off of them. But, unlike a warlock, the hunter can train and use just about anything for their pet.

 

This is great in the fact that it will allow you to train all sorts of different animals, reptiles, and monsters to use as pets in different areas of the game.

 

With each different type of pet you train, you will gain a new ability that you can then pass on to your main pet. Some of the different areas of training that you can use for your pet include: bite rank and claw rank.

 

Like the warlock, you can keep a couple of pets at a time. To do this, you should find one main pet that you really like to use and when you want to train something else to learn a new skill, you can stable this pet so you have the ability to go out and train another. Most larger cities will have stables where you can “park” your main pet in order to train a new one. (These are usually located in an open space in the center of the city, near the mail box or the flight master.)

 

But, unlike the warlock, when you are training a new pet and using one for a tank, you will need to feed it. Different pets prefer different foods, such as meat, fruit, and eggs, all of which you can find through vendors in each town. When you are training your pet, you’ll need to make sure that you have enough food for the both of you so neither of you suffer. Because the two of you will need food, cooking is a must have for the hunter so you can use the raw meat that you loot from mobs to help feed both you and your pet.

 

As you level through the game, you’ll find that with each different level you hit, you’ll gain new spells and abilities. From tracking to marksmanship, each different ability will enhance your skills and allow you to send your pet in to tank, while you use your weapons from afar to kill the mob.

 

By practicing this skill, you’ll be able to gain more xp without gaining too much damage that you will have to heal on yourself. You should start to practice sending in your pet as soon as you gain the ability to train a pet.

 
To choose the right pet, you should try all sorts of different ones. Some players stick to cats due to their swiftness and strength and their ability to heal fast. Other players prefer boars for the same reasons. And, there are those that prefer reptiles, such as the scorpids, for other reasons. No matter what your personal preference is, you should always train new pets throughout your time in the World of Warcraft to get a full range of the different abilities and skills that come with each different type of pet.

 

While you are training different animals and reptiles for your pets, keep mental notes about which one’s you like better than others and why. (This will help you to choose your main pet later in the game.) If you find a pet that you like along the way, keep it and stable it when you need to train other types of animals for their skills. By doing this, you will ensure that you have the perfect pet for you.

 

When you start to run instances with a group, you should find the best and most powerful pet for that instance. Cats, boars, and bears are always great choices for instances due to their strength and speed in a fight.

 

You can choose which you prefer, as cats are great for instances where you need stealth to sneak on mobs, bears are great for brute strength for large mobs, and boars are great for their toughness and speed as well.

 

As for which is the best pet, there is no sure fire way to tell, which is why there are so many options in the game for hunters. Try training several different pets and just see which one works best for you as you quest and grind your way through the game. No one can tell you the perfect pet for you, you must learn this on your own through trial and error to see which pet is best for your character.
-Rhiley

If you play a WoW hunter and have a guide that you have written and you would like to see it on this website please feel free to drop me an e-mail to get it listed on this site. Be sure to bookmark this page and check out the other guides to improve your hunter and other World of Warcraft characters.
Sometimes, you don’t have time to earn gold or  level your character, you can buy that at our website.And you can conncet our customer service anytime.. Here is the detailed information, click here:WOW gold and WOW powerleveling.


WOW inscription guides


This is free wow gold guide,enjoy it.

We know inscription is a new profession ,so we need some guides about it.The following guide will introduce WOW Inscription 1-450 guide.It will be continually evolving as it is refined with experience. If you are  interested in it ,just look through.Hopefully the article can help you. I believe you will learn something from it.

 
As of this writting Inscription trainers are only available in Northrend, as soon as the next patch is released the location of trainers in the older lands will be posted. One can assume however that there will most likely be trainers located in capital cities.

 

The pigments used in the guide are obtained by using the milling ability on herbs. It averages about 2-3 pigments for every stack of 5 herbs. The following herbs are used to create pigments. You can use any of them, but you need a stack of 5 to be able to mill them.

 

Alabaster Pigment: Bloodthistle, Peacebloom, Silverleaf, Earthroot, Mageroyal.
Dusky Pigment: Briarthorn, Swiftthistle, Bruiseweed, Stranglekelp.
Golden Pigment: Wild Steelbloom, Grave Moss, Kingsblood, Liferoot.
Emerald Pigment: Fadeleaf, Goldthorn, Khadgar’s Whiskers, Wintersbite.
Violet Pigment: Firebloom, Purple Lotus, Arthas’ Tears, Sungrass, Blindweed, Ghost Mushroom, Gromsblood.
Silvery Pigment: Golden Sansam, Dreamfoil, Mountain Silversage, Plaguebloom, Icecap.
Nether Pigment: From All Outland Herbs.
Azure Pigment: From All Northrend Herbs.

 
Trainers:

Alliance:
Elise Brightletter - Ironforge at 60,44(Inscription Trainer)
Thargen Heavyquill - Ironforge at 60,44(Inscription Supplies)
Feyden Darkin - Darnassus at 58,15(Inscription Trainer)
Illianna Moonscribe - Darnassus at 58,15(Inscription Supplies)
Catarina Stanford - Stormwind at 49,74(Inscription Trainer)
Stanly McCormick - Stormwind at 49,74(Inscription Supplies)
Tink Brightbolt - Valiance Keep at 57,71 in Borean Tundra (Grand Master Trainer)
Mindri Dinkles - Valgarde at 58,62 in Howling Fjord (Grand Master Trainer)
Horde:
Lelorian - Silvermoon City at 69,23(Inscription Supplies)
Zantasia - Silvermoon City at 69,24(Inscription Trainer)
Jo’mah - Orgrimmar at 59,46(Inscription Trainer)
Xantili - Orgrimmar at 56,46(Inscription Supplies)
Ickabod Pimlen - Undercity at 61,57(Inscription Supplies)
Margaux Parchley - Undercity at 61,57(Inscription Trainer)
Adelene Sunlance - Warsong Hold at 41,53in Borean Tundra (Grand Master Trainer)
Booker Kells - Vengence Landing at 79,29 in Howling Fjord (Grand Master Trainer)

 

Neutral:
Librarian Ingram - Borean Tundra at 46,33(Inscription Supplies)
Larana Drome - Dalaran at 41,36(Inscription Supplies)
Professor Palin - Dalaran at 41,36(Grand Master Trainer)

 

Materials you will need:

50 x Alabaster Class Herbs
50 x Dusky Class Herbs
140 x Golden Class Herbs
140 x Emerald Class Herbs
90 x Violet Class Herbs
80 x Silvery Class Herbs
180 x Nether Class Herbs
180 x Azure Class Herbs

60 x Light Parchment

115 x Common Parchment

125 x Heavy Parchment
130 x Resilient Parchment

 

 

Inscription 1-25
Buy a Virtuoso Inking Set from the Inscription Supplies NPC.
Create 25 Ivory Ink

You will need:
25 x Alabaster Pigment (25 x Peacebloom)

Inscription 26-40
Create 15 Scroll of Stamina

You will need:
15 x Ivory Ink
15 x Light Parchment

Inscription 41-50
Create 10 Moonglow Ink

You will need:

20 x Alabaster Pigment (20 x Peacebloom or otherAlabaster Pigment herb. See list above.)
Inscription 51-75
Create 25 Armor Vellum

You will need:
25 x Moonglow Ink
25 x Light Parchment

Inscription 75-80
Create 5 Midnight Ink

You will need:
10 x Dusky Pigment

Inscription 81-100
Create 20 Glyph of Frost Nova

You will need:
20 x Midnight Ink
20 x Light Parchment

Inscription 101-105
Create 5 Lion’s Ink

You will need:
10 x Golden Pigment

Inscription 106-115
Create 10 Glyph of Fade

You will need:
10 x Lion’s Link
10 x Common Parchment

Inscription 116-125
Create 10 Glyph of Arcane Missiles

You will need:
10 x Lion’s Ink
10 x Common Parchment

Inscription 126-130
Create 5 Glyph of Fear

You will need:
10 x Lion’s Ink
5 x Common Parchment

Inscription 131-135
Create 5 Glyph of Blink

You will need:
10 x Lion’s Ink
5 x Common Parchment

Inscription 136-140
Create 5 Glyph of Inner Fire

You will need:
10 x Lion’s Ink
5 x Common Parchment

Inscription 141-150
Create 10 Glyph of Imp

You will need:
20 x Lion’s Ink
10 x Common Parchment

Inscription 151-155
Create 5 Jadefire Ink

You will need:
10 x Emerald Pigment
Inscription 156-160
Create 5 Scroll of Stamina III

You will need:
5 x Jadefire Ink
10 x Common Parchment

Inscription 161-165
Create 5 Scroll of Spirit III

You will need:
5 x Jadefire Ink
10 x Common Parchment

Inscription 166-170
Create 5 Scroll of Intellect III

You will need:
5 x Jadefire Ink
10 x Common Parchment

Inscription 171-175
Create 5 Scroll of Strength III

You will need:
5 x Jadefire Ink
10 x Common Parchment

Inscription 176-180
Create 5 Scroll of Agility III

You will need:
5 x Jadefire Ink
10 x Common Parchment

Inscription 181-185
Create 5 Glyph of Cleansing

You will need:
10 x Jadefire Ink
5 x Common Parchment

Inscription 186-190
Create 5 Glyph of Frost Shock

You will need:
10 x Jadefire Ink
5 x Common Parchment

Inscription 191-200
Create 10 Glyph of Revenge

You will need:
20 x Jadefire Ink
10 x Common Parchment

Inscription 201-205
Create 5 Celestial Ink

You will need:
10 x Violet Pigment

Inscription 206-215
Create 10 Scroll of Recall

You will need:
10 x Moonglow Ink
10 x Light Parchment

Inscription 216-220
Create 5 Scroll of Intellect IV

You will need:
5 x Celestial Ink
10 x Heavy Parchment

Inscription 221-225
Create 5 Scroll of Strength IV

You will need:
5 x Celestial Ink
10 x Heavy Parchment

Inscription 226-230
Create 5 Scroll of Agility IV

You will need:
5 x Celestial Ink
10 x Heavy Parchment

Inscription 231-235
Create 5 Glyph of Dispel Magic

You will need:
5 x Celestial Ink
5 x Heavy Parchment

Inscription 236-240
Create 5 Glyph of Slice and Dice

You will need:
10 x Celestial Ink
5 x Heavy Parchment

Inscription 241-250
Create 10 Weapon Vellum II

You will need:
10 x Fiery Ink
10 x Celestial Ink
10 x Heavy Parchment

Inscription 251-255
Create 5 Shimmering Ink

You will need:
10 x Silvery Pigment

Inscription 256-260
Create 5 Scroll of Spirit V

You will need:
5 x Shimmering Ink
10 x Heavy Parchment

Inscription 261-265
Create 5 Scroll of Intellect V

You will need:
5 x Shimmering Ink
10 x Heavy Parchment

Inscription 266-270
Create 5 Scroll of Strength V

You will need:
5 x Shimmering Ink
10 x Heavy Parchment

Inscription 271-275
Create 5 Scroll of Agility V

You will need:
5 x Shimmering Ink
10 x Heavy Parchment

Inscription 276-280
Create 5 Glyph of Shadowburn

You will need:
5 x Shimmering Ink
5 x Heavy Parchment

Inscription 281-285
Create 5 Glyph of Icy Touch

You will need:
5 x Shimmering Ink
5 x Heavy Parchment

Inscription 286-300
Create 5 Glyph of Death Grip

You will need:
5 x Shimmering Ink
5 x Heavy Parchment

Inscription 301-305
Create 5 Ethereal Ink

You will need:
10 x Nether Pigment

Inscription 306-310
Create 5 Scroll of Intellect VI

You will need:
5 x Etheral Ink
10 x Heavy Parchment

Inscription 311-315
Create 5 Scroll of Strength VI

You will need:
5 x Etheral Ink
10 x Heavy Parchment

Inscription 316-320
Create 5 Scroll of Agility VI

You will need:
5 x Etheral Ink
10 x Heavy Parchment

Inscription 321-325
Create 5 Glyph of Blood Strike

You will need:
5 x Etheral Ink
5 x Resilient Parchment

Inscription 326-330
Create 5 Glyph of Mage Armor

You will need:
10 x Etheral Ink
5 x Resilient Parchment

Inscription 331-335
Create 5 Glyph of Scourge Strike

You will need:
10 x Etheral Ink
5 x Resilient Parchment

Inscription 336-340
Create 5 Glyph of Arcane Power

You will need:
10 x Etheral Ink
5 x Resilient Parchment

Inscription 341-345
Create 5 Glyph of Ambush

You will need:
10 x Etheral Ink
5 x Resilient Parchment

Inscription 346-350
Create 5 Glyph of Whirlwind

You will need:
5 x Etheral Ink
5 x Resilient Parchment

Inscription 350-360
Create 10 Glyph of Frost Trap

You will need:
20 x Etheral Ink
10 x Resilient Parchment

Inscription 360-375
Create 15 Glyph of Turn Evil

You will need:
15 x Ink of the Sea
15 x Resilient Parchment

Inscription 376-380
Create 5 Glyph of Felhunter

You will need:
5 x Ink of the Sea
5 x Resilient Parchment

Inscription 381-385
Create 5 Glyph of Felhunter

You will need:
5 x Ink of the Sea
5 x Resilient Parchment

Inscription 386-400
Create 15 Glyph of Chain Lightening

You will need:
15 x Ink of the Sea
15 x Resilient Parchment

Inscription 401-415
Create 15 Glyph of Water Elemental

You will need:
15 x Ink of the Sea
15 x Resilient Parchment

Inscription 416-420
Create 5 Glyph of Mind Soothe

You will need:
5 x Ink of the Sea
5 x Resilient Parchment

Inscription 421-430
Create 10 Glyph of Aspect of the Beast

You will need:
10 x Ink of the Sea
10 x Resilient Parchment

Inscription 431-445
Create 15 Glyph of Crippling Poison

You will need:
15 x Ink of the Sea
15 x Resilient Parchment

Inscription 446-450
Create 5 Glyph of Circle of Healing

You will need:
5 x Ink of the Sea
5 x Resilient Parchment

 

Besides inscription guides, our website also provide WOW gold and WOW power leveling. If you are interested in that, go to the related page and check the detailed information. Goldceo is your best choice.


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.


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.)


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.


WOW UI Mod Guide - Chapter 7


There is much that can go astray when working with AddOns.  Unfortunately the error messages provided by WoW aren’t always helpful.  Here are a few tips to troubleshooting AddOns:

- Watch out for version changes.  Remember that the .toc file has the expected version of WoW at the top.  This must match the actual version of WoW for your AddOn to load.  Be sure that when WoW upgrades, you adjust the version number in your .toc file.  Here’s an article on getting the version number:  http://www.wowwiki.com/HOWTO:_Get_Current_Interface_Number
Simpler is to find the version number from another AddOn’s .toc file.  Also, you can just ask in the forums on this site.

- Check your names carefully.  Case matters.  Underscores matter.  The names “MyAddOn”, “MyAddon”, and “My_AddOn” are all different.

- Watch out for smart quotations.  Many editors and word processors (such as Microsoft Word) like to replace normal quotation marks with “smart quotes”.  In virtually all cases, you’ll want normal quotation marks.  These tend to be displayed as two parallel lines running straight up and down, while smart quotes tend to be angled and sometimes curved.

- Make sure there are no spaces after “.xml” in your .toc files.  If there are, WoW will consider the spaces to be part of the file name.  This will cause WoW to fail to find the XML file, preventing your AddOn from running. 

- Make sure to initialize all variables with safe default values.  Even if you expect to normally use a saved value, you can’t count on it.  The first time your AddOn runs, there won’t be anything saved.  And the user could always have deleted the saved variables file.  Safe defaults eliminate a whole category of tricky bugs.

- Write lots of comments.  If your code isn’t working, you’ll often find that improving the comments causes you to realize what is wrong.  But looking at this the other way, don’t assume just because a comment says your code does something, that the code actually does it.  Sometimes you’ll find that what your comments say happens and what really happens are entirely different things.

- Keep your XML and Lua separate.  If you’re having problems and you’ve let Lua creep into your XML, try cleaning things up.  You may run across the source of the problem in the process.

- Watch out for old “saved variable” values.  If you already have a saved variable value stored for your AddOn and you change assumptions about that data, or if a buggy version of your AddOn stored bad data, you can run into trouble.  You can remove these by hand (see the sections in this guide on saved variables for details on where these values are stored). 

- As with many programming languages, the line number reported in an error message isn’t necessarily the line containing the actual error.


WOW UI Mod Guide - Chapter 6 Localization


Viva la WoW!  Despite two years of sleeping through high school Spanish, I can barely order at Taco Bell.  But I think that means “Long Live WoW!”.  This brings up a question:  what about the many WoW players who don’t speak English?  All the text embedded into our AddOn has thus far been in English.  What can we do so that our AddOns work just as well in all languages supported by WoW?  Enter the magic of “localization”, the process of making your AddOns seamlessly change language to match the user.

 

Part Six of this guide will describe localization.  Compared to Part Five, this will be a short installment.  I originally planned to discuss tables, packages, and localization all in Part Four.  But tables took enough space to justify their own section, then packages did the same.  This leaves us knowing almost everything necessary to localize our AddOn already.  Cool.

HOW DO WE KNOW WHAT LANGUAGE THE USER PREFERS?

The first thing we need to know is how WoW tells us which language we should present to the user.  WoW is available in the following languages:  English, French, German, and Korean.  WoW provides the GetLocale() function to return a code identifying the current language.  These codes are simply strings.  The possible values are:

            enUS = English

            frFR = French

            deDE = German

            koKR = Korean

 

As of this writing (March 2006), a Spanish version has been officially announced but is still in development (maybe I shouldn’t have slept through high school Spanish after all!).  I heard a rumor that the code will be esES, but that’s not confirmed.  I’ll update this guide once I’m sure.

 

In any case, once our code supports two languages it is very easy to extend to more:  our code can call GetLocale() to see which language is in effect, then return the correct string.  Simple enough! 

 

HOW DO WE PRESENT MULTIPLE LANGAGUES TO THE USER?

 

Given the GetLocale() routine, there are a bunch of ways you could localize your AddOn.  For example, you could just scatter if/else/elseif throughout your code, testing for the locale every time you want to use a constant string.  But that would quickly degenerate into a rat’s nest. 

 

We’ve been careful all along to put our constant strings at the top of our .lua files.  Now let’s take that a step further.  In Part Five, we placed all our saved variables into a dedicated saved variables table.  We’ll take a similar approach to localization.  We’ll create one “constant strings” table for each language, each in its own .lua file.  Then we’ll arrange these files so that only one table is actually in use throughout the AddOn.  First, here’s the English version, which we’ll call localization.lua:

 

–  English constant strings

TthCs6 = {};

 

TthCs6.BATTLE_CRIES_ARE = “Battle cries are:  “;

TthCs6.BATTLE_CRY_ADDED = “Battle cry added:  “;

TthCs6.BATTLE_CRIES_CLEARED = “Battle cries cleared.”;

 

Now, here’s the French version, which we’ll call localization.fr.lua:

 

–  French constant strings

if ( GetLocale() == “frFR” ) then

  TthCs6 = {};

 

  TthCs6.BATTLE_CRIES_ARE = “Les cris de guerre sont:  “;

  TthCs6.BATTLE_CRY_ADDED = “Cri de guerre ajouté:  “;

  TthCs6.BATTLE_CRIES_CLEARED = “Cris de guerre effacés.”;

end

 

Notice that in the English version, we didn’t call GetLocale(), but in the French version we did.  If the user is running the English version of WoW, the English version of the TthCs6 (Cs for “Constant strings”) table will be created and we’ll use it.  If the user is running the French version of WoW, the English version will still be created, but the French one will replace it.  We’ll end up using the French one. 

 

To make this happen, we need to add these new .lua files to our .xml.  Here are the <Script> elements after adding the new files:

 

       <Script file=”localization.lua”/>

       <Script file=”localization.fr.lua”/>

       <Script file=”TthSv6.lua”/>

       <Script file=”TthBc6.lua”/>

       <Script file=”TthAog6.lua”/>

 

Simple enough!

 

HOW DO WE CREATE THE LANGUAGE-SPECIFIC LUA FILES?

 

Computer representation of character sets is a complex and (to those of us with twisted minds) fascinating topic.  I won’t even attempt to describe it in this guide.  Suffice it to say that there is an alphabet soup of terms and acronyms like ASCII, Extended-ASCII, EBCDIC (anyone else old enough to remember that one?), UTF-8, WCHAR, Unicode, and so on.  The important one for us is “Unicode”.  That’s the computer industry’s current best hope for representing characters in all languages in a consistent way.  And fortunately, WoW speaks Unicode. 

 

Practically speaking, here’s the one thing you need to know for creating your localized .lua files:  use a Unicode editor to create the file.  Like I said, I don’t speak anything besides English.  (After reading my writing for six installments of this guide, you’re probably wondering if I even speak that! J)  So to create the French translation of the battle cry AddOn, I emailed my localization.lua file to a friend who speaks French.  She translated the strings, using an Unicode editor, and sent them back to me.  Even though I still don’t speak a word of French, my AddOn now does.  Way cool.

DRESSING UP THE .TOC FILE

There’s one more nice touch we can add.  In our .toc file, we can localize the Title and Notes fields as well.  We do this by including translated versions directly in the .toc file.  The language code is appended to the field name (with a dash in between).  WoW then knows to automatically pick up the translated version.  If we hadn’t done this, WoW would still have displayed the default (English) Title and Notes, even for users running the French version of WoW.  See below for the full code listing, which includes these changes.

THE CODE

Our little AddOn is up to seven files now.  Here they are:

 

TthAog6.toc:

 

## Interface: 11000

## Title: TenTonHammer Guide Part 6

## Title-frFR: TenTonHammer Guide 6e partie

## Author:  TenTonHammer.com

## Notes:  Makes your character scream a randomized battle cry when combat begins

## Notes-frFR:  Permet à votre personnage de crier un cri de guerre choisi au hasard lorsque le combat commence

## SavedVariablesPerCharacter: TthAog6Sv

TthAog6.xml

 

TthAog6.xml:

 

<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=”localization.lua”/>

       <Script file=”localization.fr.lua”/>

       <Script file=”TthSv6.lua”/>

       <Script file=”TthBc6.lua”/>

       <Script file=”TthAog6.lua”/>

       <Frame name=”TthAog6Core”>

              <Scripts>

                     <OnLoad>TthAog6:Load();</OnLoad>

                     <OnEvent>TthAog6:Event();</OnEvent>

              </Scripts>

       </Frame>

</Ui>

 

TthAog6.lua:

 

–  TenTonHammer.com AddOn Guide Version 6

TthAog6 = {};

 

TthAog6.ADDON_NAME = “TthAog6″;

 

–  Called when the AddOn loads.

function TthAog6:Load()

  — Register for events.

  this:RegisterEvent(”PLAYER_REGEN_DISABLED”);

  this:RegisterEvent(”ADDON_LOADED”);

 

  –  Create the slash commands.

  SlashCmdList["TTH_AOG6_ADDNEWBATTLECRY"] = function(msg)

    TthBc6:AddNew(msg);

  end

  SLASH_TTH_AOG6_ADDNEWBATTLECRY1 = “/addnewbattlecry6″;

  SLASH_TTH_AOG6_ADDNEWBATTLECRY2 = “/anbc6″;

 

  SlashCmdList["TTH_AOG6_CLEARALLBATTLECRIES"] = function(msg)

    TthBc6:ClearAll();

  end

  SLASH_TTH_AOG6_CLEARALLBATTLECRIES1 = “/clearallbattlecries6″;

  SLASH_TTH_AOG6_CLEARALLBATTLECRIES2 = “/cabc6″;

end

 

–  Called to handle the events for which we’ve registered.

function TthAog6:Event()

  if (event == “ADDON_LOADED”) then

    if (arg1 == self.ADDON_NAME) then

      — Handle saved variable defaults

      for key, value in pairs(TthAog6SvDefaults) do

        if (not TthAog6Sv[key]) then

          TthAog6Sv[key] = value;

        end

      end

 

      TthBc6:AddOnLoaded();

    end

  elseif (event == “PLAYER_REGEN_DISABLED”) then

    TthBc6:SendRandomCry();

  end

end

 

TthBc6.lua:

 

–  TenTonHammer.com Battle Cry Version 6

TthBc6 = {};

 

–  Utility function for printing to chat area

function TthBc6:OutputToChat(msg)

  if (DEFAULT_CHAT_FRAME) then

    DEFAULT_CHAT_FRAME:AddMessage(msg);

  end

end

 

–  Called when the ADDON_LOADED event occurs for our AddOn

function TthBc6:AddOnLoaded()

  self:OutputToChat(TthCs6.BATTLE_CRIES_ARE);

  for i=1,table.getn(TthAog6Sv.Cries) do

    self:OutputToChat(TthAog6Sv.Cries[i]);

  end

end

 

–  Sends a random battle cry to chat (if there are any to send)

function TthBc6:SendRandomCry()

  local numBattleCries = table.getn(TthAog6Sv.Cries);

  if (numBattleCries > 0) then

    local randomKey = math.random(numBattleCries);

    SendChatMessage(TthAog6Sv.Cries[randomKey], TthAog6Sv.OutputType);

  end

end

 

–  Called when the associated slash command executes.

function TthBc6:AddNew(msg)

  — Add the new battle cry, for use later.

  local newKey = table.getn(TthAog6Sv.Cries) + 1;

  TthAog6Sv.Cries[newKey] = msg;

 

  — Echo the new battle cry for confirmation.

  local echo = TthCs6.BATTLE_CRY_ADDED .. msg;

  self:OutputToChat(echo);

end

 

–  Called when the associated slash command executes.

function TthBc6:ClearAll()

  –  assign to an empty table for quick and easy clearing.

  TthAog6Sv.Cries = {};

 

  — Echo the new battle cry for confirmation.

  self:OutputToChat(TthCs6.BATTLE_CRIES_CLEARED);

end

 

TthSv6.lua:

 

–  Saved variables table

TthAog6Sv = {

  –  This table holds our battle cries.

  Cries = {},

  OutputType

};

 

TthAog6SvDefaults = {

  Cries = {

    “Not in the face!”,

    “Not in the groin!”,

    “Not in the kneecaps!”

  },

  OutputType = “SAY”

};

 

localization.lua:

 

–  English constant strings

TthCs6 = {};

 

TthCs6.BATTLE_CRIES_ARE = “Battle cries are:  “;

TthCs6.BATTLE_CRY_ADDED = “Battle cry added:  “;

TthCs6.BATTLE_CRIES_CLEARED = “Battle cries cleared.”;

 

localization.fr.lua:

 

–  French constant strings

if (GetLocale() == “frFR”) then

  TthCs6 = {};

 

  TthCs6.BATTLE_CRIES_ARE = “Les cris de guerre sont:  “;

  TthCs6.BATTLE_CRY_ADDED = “Cri de guerre ajouté:  “;

  TthCs6.BATTLE_CRIES_CLEARED = “Cris de guerre effacés.”;

end

 

One final point:  notice that in TthAog6.lua we didn’t localize the name of our AddOn.  Some constant strings, such as this one, are truly constant across all languages.  We can continue to treat these as we have all along.

 

Whew!  We’ve pushed through some pretty heavy topics in the last few parts of this guide.  Ready for some fun?  Catch your breath, then move on to Part Seven where we (finally!) get to add a graphical user interface to our AddOn (under construction).


WOW UI Mod Guide - Chapter 5 Packages


Part Five of this guide is going to be a little different.  We won’t add any new features to the battle cry AddOn that we created over the first four parts of this guide.  Instead, we’ll focus exclusively on making it better from a programmer’s perspective.

If there’s no benefit to the user, why bother?  Here’s the reason:  our AddOn has grown and the code has become more complicated.  If we kept adding features without doing anything to manage the complexity, our code would soon become hard to work with.  Programmers use the term “spaghetti code” to refer to code with poor organization.  This kind of code is notoriously hard to read, debug, and improve. 

First, we’ll look at how to split our code into multiple .lua files.  This goes a long way toward managing complexity.  We’ll then examine packages, which helps even more.  Because these changes affect our code as a whole, we’ll list the full code of our AddOn three times in the course of all this.  I’m only going to make the final version available for download, but I thought it was important for you to see the intermediate stages. 

MULTIPLE .LUA FILES

Until now, our AddOn has had only three files:  one .toc file, one .xml file, and one .lua file.  Our .lua file has grown, and is a now mix of boilerplate code (really just plumbing), and the code that performs the logic of our AddOn.  Splitting this into two .lua files would help in two ways.  First, each file is shorter so there’s less code to wade through at once.  Second, each file focuses on one particular thing.  The programming term for this is “cohesion”.  By grouping functions and variables that are closely related (have high cohesion), things stay much more organized and easier to maintain.  While some real AddOns get by with one .lua file, many have two, three, or even more.

To see how this works, we’ll split our AddOn into two files.  For this incarnation of our AddOn, the main code file is TthAog5.lua.  Let’s create a second .lua file, named TthBc5.lua.  The “Bc” part, of course, stands for “battle cry”.  This new file goes into the same folder at TthAog5.lua.  It starts out empty, then we cut-and-paste the battle-cry specific code into it.  The TthBc5.lua file should end up with the following variables:
TTH_AOG5_BATTLE_CRIES_ARE;
TTH_AOG5_BATTLE_CRY_ADDED;
TTH_AOG5_BATTLE_CRIES_CLEARED;
TthAog5MyBattleCries

And the following functions:
TthAog5OutputToChat
TthAog5AddNewBattleCry
TthAog5ClearAllBattleCries

That leaves the following variable in the TthAog5.lua file:
TTH_AOG5_ADDON_NAME

And the following functions:
TthAog5Load
TthAog5Event

WoW doesn’t automatically know how to find our new file; we have to tell it.  We do this by adding a line to our.xml file.  We already have this line:
            <Script file=”TthAog5.lua”/>
Now, we add one new line, directly above it:
            <Script file=”TthBc5.lua”/>

That’s it!  We could stop here and our AddOn would already be better-arranged than before.  But we won’t stop, of course.  Mostly because Part Five is still too short, and I’m getting paid by the word.  (Just kidding!) 

Before we move on, let’s take a look at the full contents of our files at this point:

First, the TthAog5.toc file:

## Interface: 11000
## Title: TenTonHammer Guide Part 5
## Author:  TenTonHammer.com
## Notes:  Makes your character scream a randomized battle cry when combat begins
## SavedVariablesPerCharacter: TthAog5MyBattleCries
TthAog5.xml

Next, the TthAog5.xml file:

<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=”TthBc5.lua”/>
       <Script file=”TthAog5.lua”/>
       <Frame name=”TthAog5Core”>
              <Scripts>
                     <OnLoad>TthAog5Load();</OnLoad>
                     <OnEvent>TthAog5Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Now the TthAog5.lua file:

local TTH_AOG5_ADDON_NAME = “TthAog5″;

–  Called when the AddOn loads.
function TthAog5Load()
  — Register for events.
  this:RegisterEvent( “PLAYER_REGEN_DISABLED”);
  this:RegisterEvent( “ADDON_LOADED”);

  –  Create the slash commands.
  SlashCmdList["TTH_AOG5_ADDNEWBATTLECRY"] = TthAog5AddNewBattleCry;
  SLASH_TTH_AOG5_ADDNEWBATTLECRY1 = “/addnewbattlecry5″;
  SLASH_TTH_AOG5_ADDNEWBATTLECRY2 = “/anbc5″;

  SlashCmdList["TTH_AOG5_CLEARALLBATTLECRIES"] = TthAog5ClearAllBattleCries;
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES1 = “/clearallbattlecries5″;
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES2 = “/cabc5″;
end

–  Called to handle the events for which we’ve registered.
function TthAog5Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == TTH_AOG5_ADDON_NAME) then
      TthAog5AddonLoaded();
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    — Send a random message to chat (if there are any to send)
    local numBattleCries = table.getn(TthAog5MyBattleCries);
    if (numBattleCries > 0) then
      local randomKey = math.random(numBattleCries);
      SendChatMessage(TthAog5MyBattleCries[randomKey], “SAY”);
    end
  end
end

Finally, the new TthBc5.lua file:

–  This table holds our battle cries.
TthAog5MyBattleCries = {
  “Not in the face!”,
  “Not in the groin!”,
  “Not in the kneecaps!”
};

local TTH_AOG5_BATTLE_CRIES_ARE = “Battle cries are:  “;
local TTH_AOG5_BATTLE_CRY_ADDED = “Battle cry added:  “;
local TTH_AOG5_BATTLE_CRIES_CLEARED = “Battle cries cleared.”;

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

–  Called when the ADDON_LOADED event occurs for our AddOn
function TthAog5AddOnLoaded()
  TthAog5OutputToChat(TTH_AOG5_BATTLE_CRIES_ARE);
  for i=1,table.getn(TthAog5MyBattleCries) do
    TthAog5OutputToChat(TthAog5MyBattleCries[i]);
  end
end

–  Called when the associated slash command executes.
function TthAog5AddNewBattleCry(msg)
  — Add the new battle cry, for use later.
  local newKey = table.getn(TthAog5MyBattleCries) + 1;
  TthAog5MyBattleCries[newKey] = msg;

  — Echo the new battle cry for confirmation.
  local echo = TTH_AOG5_BATTLE_CRY_ADDED ” .. msg;
  TthAog5OutputToChat(echo);
end

–  Called when the associated slash command executes.
function TthAog5ClearAllBattleCries()
  –  assign to an empty table for quick and easy clearing.
  TthAog5MyBattleCries = {};

  — Echo the new battle cry for confirmation.
  TthAog5OutputToChat(TTH_AOG5_BATTLE_CRIES_CLEARED);
end

You can see that TthBc5.lua contains nothing but the core logic related to battle cries.  TthAog5.lua, on the other hand, contains only code that sits between WoW and this core logic.  This is what I meant by cohesion:  everything in each file is closely related and serves a single, easily stated purpose. 

Now let’s take it a step further and look at packages.

A LOOK AT PACKAGES

Imagine that you’re running a bunch of AddOns, all of which store all their information in global variables.  There could be literally thousands of global variables for WoW to manage.  If even two of these have the same name, at least one of the AddOns probably won’t work right.  We’ve seen this problem before, and we dealt with it by adorning all of our variable and function names with ugly prefixes like “TthAog5”.  That works, but there’s a better way.  Enter packages.

A Lua “package” is a way to group related variables and functions “underneath” a single name.  Lua doesn’t have language-level support for packages in the same way many other languages do.  Instead, we leverage the flexibility of Lua tables to achieve much the same thing.  That’s why we spent so much time looking at tables in Part Four.  Without knowing tables, you can’t understand packages. 

To summarize what we’re about to do, a package is really just a table.  Some of its entries are variables, and some are functions.  Although this may seem odd at first, it is a life-saver in big AddOns.  Our example AddOn is still small enough to be manageable without packages, but I’m not going to make it huge just to show you the technique.  Keep in mind that even small AddOns have a tendency to grow over time, so using packages is a good habit even when you start simple. 

One note:  because Lua is so flexible, there are almost as many ways to write code that uses packages as there are Lua programmers.  I’m going to show you the method that I prefer.  If you look at a different guide or a forum post, the author may advocate a slightly different method.  That’s okay.  I’m not claiming that my preference is the best.  Use the method that makes the most sense to you. 

The first step in converting our AddOn to use packages is to decide how many packages we’ll need and what the purpose of each will be.  By separating our AddOn into two files we’ve already done this.  We’ll have two packages.  One will be called TthAog5 and will be devoted to boilerplate code.  The other will be called TthBc5 and will be devoted to the battle cry logic.  Package names don’t have to match file names, but it is usually convenient and I suggest you use that as a convention.

The next step is to create our package.  We’ll use the TthAog5.lua file as an example.  Remember, a package is just a table.  So we can create one like this:

TthAog5 = {};

Next, let’s add the one variable in that file to the package:

TthAog5.ADDON_NAME = “TthAog5″;

As we saw in Part Four, this notation adds an entry named “ADDON_NAME” to the table and assigns it the value “TthAog5”, which is the name of our AddOn.  You can see already that there’s nothing magical about packages – we’re just doing stuff we already know about.

Note that we still used all-caps for the variable name portion of our entry.  This is because we still consider this variable to be a constant string.  The all-caps are a reminder of this anywhere we use the variable.

Now let’s convert the TthAog5Event function: 

function TthAog5:Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == self.ADDON_NAME) then
      TthBc5:AddOnLoaded();
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    TthBc5:SendRandomCry();
  end
end

At a glance, this doesn’t look much different from before.  We’ve stuffed a colon (:) into the function name and added “self.” instead of “TthAog5” at the front when we access the ADDON_NAME variable.  But what is actually occurring is that both the ADDON_NAME variable and the Event function now “live inside” the table.  That’s why we use the special “self.” to access the variable from within the function.  Saying “self.” inside a function is just a shorthand way of saying “the variable in the same table as me”.

Notice also that instead of calling the TthAog5SendRandomCry function, we use this notation:

    TthBc5:SendRandomCry();

That assumes we’ve also converted the TthBc5.lua file to use a package named TthBc5, and that package contains a function named SendRandomCry.  I won’t bore you to tears by walking through all that, but now you know what it looks like to call a function in a different package.  You’re pretty familiar with this AddOn by now, so I’ll present the full code as it currently stands.  Look it over, then we’ll talk about one final issue.

First, the TthAog5.toc file (nothing changed here):

## Interface: 11000
## Title: TenTonHammer Guide Part 5
## Author:  TenTonHammer.com
## Notes:  Makes your character scream a randomized battle cry when combat begins
## SavedVariablesPerCharacter: TthAog5MyBattleCries
TthAog5.xml

Next, the TthAog5.xml file (the OnLoad and OnEvent blocks elements have changed):

<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=”TthBc5.lua”/>
       <Script file=”TthAog5.lua”/>
       <Frame name=”TthAog5Core”>
              <Scripts>
                     <OnLoad>TthAog5:Load();</OnLoad>
                     <OnEvent>TthAog5:Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Now the TthAog5.lua file:

–  TenTonHammer.com AddOn Guide Version 5
TthAog5 = {};

TthAog5.ADDON_NAME = “TthAog5″;

–  Called when the AddOn loads.
function TthAog5:Load()
  — Register for events.
  this:RegisterEvent( “PLAYER_REGEN_DISABLED”);
  this:RegisterEvent( “ADDON_LOADED”);

  –  Create the slash commands.
  SlashCmdList["TTH_AOG5_ADDNEWBATTLECRY"] = function(msg)
    TthBc5:AddNew(msg);
  end
  SLASH_TTH_AOG5_ADDNEWBATTLECRY1 = “/addnewbattlecry5″;
  SLASH_TTH_AOG5_ADDNEWBATTLECRY2 = “/anbc5″;

  SlashCmdList["TTH_AOG5_CLEARALLBATTLECRIES"] = function(msg)
    TthBc5:ClearAll();
  end
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES1 = “/clearallbattlecries5″;
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES2 = “/cabc5″;
end

–  Called to handle the events for which we’ve registered.
function TthAog5:Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == self.ADDON_NAME) then
      TthBc5:AddOnLoaded();
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    TthBc5:SendRandomCry();
  end
end

Finally, the TthBc5.lua file:

–  TenTonHammer.com Battle Cry Version 5
TthBc5 = {};

TthBc5.BATTLE_CRIES_ARE = “Battle cries are:  “;
TthBc5.BATTLE_CRY_ADDED = “Battle cry added:  “;
TthBc5.BATTLE_CRIES_CLEARED = “Battle cries cleared.”;

–  This table holds our battle cries.
TthAogMyBattleCries = {
  “Not in the face!”,
  “Not in the groin!”,
  “Not in the kneecaps!”
};

–  Utility function for printing to chat area
function TthBc5:OutputToChat(msg)
  if (DEFAULT_CHAT_FRAME) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
  end
end

–  Called when the ADDON_LOADED event occurs for our AddOn
function TthBc5:AddOnLoaded()
  self:OutputToChat(self.BATTLE_CRIES_ARE);
  for i=1,table.getn(TthAogMyBattleCries) do
    self:OutputToChat(TthAogMyBattleCries [i]);
  end
end

–  Sends a random battle cry to chat (if there are any to send)
function TthBc5:SendRandomCry()
  local numBattleCries = table.getn(TthAogMyBattleCries);
  if (numBattleCries > 0) then
    local randomKey = math.random(numBattleCries);
    SendChatMessage(TthAogMyBattleCries[randomKey], “SAY”);
  end
end

–  Called when the associated slash command executes.
function TthBc5:AddNew(msg)
  — Add the new battle cry, for use later.
  local newKey = table.getn(TthAogMyBattleCries) + 1;
  TthAogMyBattleCries[newKey] = msg;

  — Echo the new battle cry for confirmation.
  local echo = self.BATTLE_CRY_ADDED .. msg;
  self:OutputToChat(echo);
end

–  Called when the associated slash command executes.
function TthBc5:ClearAll()
  –  assign to an empty table for quick and easy clearing.
  TthAog5MyBattleCries = {};

  — Echo the new battle cry for confirmation.
  self:OutputToChat(self.BATTLE_CRIES_CLEARED);
end

This code still all looks very familiar.  If you’re accustomed to programming in any sort of object-oriented language or any language that supports namespaces, it probably looks a lot cleaner to you than when we started.  If not, you may still be wondering what the big deal is.  Like I say, there’s no obligation to use packages.  But once you get used to them, you won’t want to go back.

SAVED VARIABLES AND PACKAGES

Remember at the end of Part Three (Saved Variables), when I said that only global variables could be saved, and that this would come back to haunt us in Part Five?  Well, we’re in Part Five now.  Consider yourself haunted.

In our above code listing, notice that TthAog5MyBattleCries is still a global variable.  It sits in the TthBc5.lua file.  Logically speaking, it looks like it should be in that package.  I didn’t forget to put it there – I left it as a global for a reason.  Here’s the reason:  you can’t put a saved variable into a package.  “What?”, I hear you say, “You spent all that time showing us packages and now you tell us that packages don’t apply to our most important variable?”.  Slow down.  I didn’t say they didn’t apply, I just said you couldn’t put a saved variable inside a package. 

What we can do is save a table as a saved variable.  We’ve been doing that for a while now with the TthAog5MyBattleCries table.  And although we haven’t done it yet, it is no problem if the table we save happens to contain other tables.  We can use this to our advantage by creating a TthAog5Sv table (Sv for “Saved variables) and putting all our saved variables into that.  Effectively, this is a package that contains no functions.  This gives us a consistent place to put all our saved variables.  Any time we use one of these variables, we’ll be reminded by the table name that it is in fact a saved variable.

We currently only have one saved variable.  Just to make this more realistic, let’s add another.  All along, we’ve been using “SAY” to output our battle cry to other players close to us.  The other good choice would be “PARTY”, to output our battle cry to other players in our party.  To do this, we could add a saved variable to store the user’s preference, and a slash command or two for setting it.  For brevity, I’m just going to add the variable.  Feel free to add the slash commands yourself if you like – you learned how in Part Two.

We’ll put our saved variable table in its own file, named “TthSv5.lua” (again, Sv for Saved variables).  Of course, that means we’ll need a corresponding “Script” element in our TthAog5.lua file and we’ll need to modify the TthAog5.toc file.  You can guess how the new line in the .xml will look:
            <Script file=”TthSv5.lua”/>

Here’s how the “SavedVariablesPerCharacter” line looks in the .toc:
## SavedVariablesPerCharacter: TthAog5Sv

The TthSv5.lua file is short.  Here are the full contents:

–  Saved variables table
TthAog5Sv = {
  –  This table holds our battle cries.
  Cries = {},
  OutputType
};

TthAog5SvDefaults = {
  Cries = {
    “Not in the face!”,
    “Not in the groin!”,
    “Not in the kneecaps!”
  },
  OutputType = “SAY”
};

I bet you’re wondering why I listed two tables for saved variables, when I’ve only mentioned one.  The reason is that out old way of assigning default values no longer works.  This is a little twisted, so hang on tight.  From the .toc file, you can see that we don’t have two saved variables (Cries and OutputType).  We have one saved variable, named TthAog5Sv.  From WoW’s perspective, we asked to save one variable, a table, and that’s exactly what it will do.  WoW has no special knowledge about what entries should or should not be in that table.

Here’s the problem:  if WoW finds a saved value for our (TthAog5Sv), it completely discards the original value of our table and creates a new table based on the saved variables file.  Suppose we assigned our default values when we originally created TthAog5Sv table.  They’d be completely lost when WoW loaded the saved variables.  Normally, that’s fine – we want to use the values from the saved variables file anyway.  But what happens when we come up with a great new way of improving our AddOn?  If our improvement requires us to add a new entry to the TthAog5Sv table, we don’t have a good way to assign a default value to the new entry.  If our AddOn were popular, thousands of people might have saved values in their saved variables files.  Because we’re good programmers, we want the upgrade to be seamless:  the existing settings for each of these people should be retained, and the new value should be introduced with a suitable default value.  But when WoW loaded TthAog5Sv, there wouldn’t be an entry for the new value.  Any requests within our code for the new value would return nil.  So now, we have to check for nil everywhere in our code, or we have to write special-purpose code to check for the new value and assign a default if a saved value wasn’t found.  Yuck.

When I first learned of this problem, it almost turned me off of using tables for saved variables entirely.  Fortunately, I saw a neat solution in a post on the official WoW forums by “Iriel”.  (Credit where credit is due!)  The trick is to put your defaults into a second table.  Then, in the ADDON_LOADED event, we do the following:

      for key, value in pairs(TthAog5SvDefaults) do
        if (not TthAog5Sv[key]) then
          TthAog5Sv[key] = value;
        end
      end

If you like, you can read the Lua documentation to help understand the complete details.  Basically, this runs through the “defaults” table and, for each entry, checks to see if there’s a matching entry in the main saved variables table.  If not, it creates one based on the defaults.  This still isn’t as clean as when we just used a global variable.  But it does offer the benefit of grouping all the saved variables into their own table, which I like.

Now, when we need to use the value of a saved variable, we treat it just like a variable from a package.  For example, here’s the code to add a new battle cry:
  local newKey = table.getn(TthAog5Sv.Cries) + 1;
  TthAog5Sv.Cries[newKey] = msg;

I wish I could tell you that everything in creating AddOns was clean, simple, and neat.  I can’t.  Mixing saved variables and packages is just a little messy, no matter how hard you try to overcome the limitations.  I’ve shown you how to use a saved variables table to retain some of the benefits, but I don’t deny that it is far from ideal.  If you choose not to use a saved variables table and instead just use globals for your saved variables, I won’t fault you.  But I think the saved variables table is worth it, and we’ll use it for the remainder of this guide.

At long last, here is the final full code listing for our AddOn, which has now grown to five files.  (The code is also available for download <here>.)

First, the TthAog5.toc file:

## Interface: 11000
## Title: TenTonHammer Guide Part 5
## Author:  TenTonHammer.com
## Notes:  Makes your character scream a randomized battle cry when combat begins
## SavedVariablesPerCharacter: TthAog5Sv
TthAog5.xml

Next, the TthAog5.xml file:

<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=”TthSv5.lua”/>
       <Script file=”TthBc5.lua”/>
       <Script file=”TthAog5.lua”/>
       <Frame name=”TthAog5Core”>
              <Scripts>
                     <OnLoad>TthAog5:Load();</OnLoad>
                     <OnEvent>TthAog5:Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Now, the TthAog5.lua file (containing the boilerplate code):

–  TenTonHammer.com AddOn Guide Version 5
TthAog5 = {};

TthAog5.ADDON_NAME = “TthAog5″;

–  Called when the AddOn loads.
function TthAog5:Load()
  — Register for events.
  this:RegisterEvent( “PLAYER_REGEN_DISABLED”);
  this:RegisterEvent( “ADDON_LOADED”);

  –  Create the slash commands.
  SlashCmdList["TTH_AOG5_ADDNEWBATTLECRY"] = function(msg)
    TthBc5:AddNew(msg);
  end
  SLASH_TTH_AOG5_ADDNEWBATTLECRY1 = “/addnewbattlecry5″;
  SLASH_TTH_AOG5_ADDNEWBATTLECRY2 = “/anbc5″;

  SlashCmdList["TTH_AOG5_CLEARALLBATTLECRIES"] = function(msg)
    TthBc5:ClearAll();
  end
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES1 = “/clearallbattlecries5″;
  SLASH_TTH_AOG5_CLEARALLBATTLECRIES2 = “/cabc5″;
end

–  Called to handle the events for which we’ve registered.
function TthAog5:Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == self.ADDON_NAME) then
      — Handle saved variable defaults
      for key, value in pairs(TthAog5SvDefaults) do
        if (not TthAog5Sv[key]) then
          TthAog5Sv[key] = value;
        end
      end

      TthBc5:AddOnLoaded();
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    TthBc5:SendRandomCry();
  end
end

Then, the TthSv5.lua file (containing the saved variable table and its defaults):

–  Saved variables table
TthAog5Sv = {
  –  This table holds our battle cries.
  Cries = {},
  OutputType
};

TthAog5SvDefaults = {
  Cries = {
    “Not in the face!”,
    “Not in the groin!”,
    “Not in the kneecaps!”
  },
  OutputType = “SAY”
};

Finally, the TthBc5.lua file (containing the core battle cry logic):

–  TenTonHammer.com Battle Cry Version 5
TthBc5 = {};

TthBc5.BATTLE_CRIES_ARE = “Battle cries are:  “;
TthBc5.BATTLE_CRY_ADDED = “Battle cry added:  “;
TthBc5.BATTLE_CRIES_CLEARED = “Battle cries cleared.”;

–  Utility function for printing to chat area
function TthBc5:OutputToChat(msg)
  if (DEFAULT_CHAT_FRAME) then
    DEFAULT_CHAT_FRAME:AddMessage(msg);
  end
end

–  Called when the ADDON_LOADED event occurs for our AddOn
function TthBc5:AddOnLoaded()
  self:OutputToChat(self.BATTLE_CRIES_ARE);
  for i=1,table.getn(TthAog5Sv.Cries) do
    self:OutputToChat(TthAog5Sv.Cries[i]);
  end
end

–  Sends a random battle cry to chat (if there are any to send)
function TthBc5:SendRandomCry()
  local numBattleCries = table.getn(TthAog5Sv.Cries);
   if (numBattleCries > 0) then
    local randomKey = math.random(numBattleCries);
    SendChatMessage(TthAog5Sv.Cries[randomKey], TthAog5Sv.OutputType);
  end
end

–  Called when the associated slash command executes.
function TthBc5:AddNew(msg)
  — Add the new battle cry, for use later.
  local newKey = table.getn(TthAog5Sv.Cries) + 1;
  TthAog5Sv.Cries[newKey] = msg;

  — Echo the new battle cry for confirmation.
  local echo = self.BATTLE_CRY_ADDED .. msg;
  self:OutputToChat(echo);
end

–  Called when the associated slash command executes.
function TthBc5:ClearAll()
  –  assign to an empty table for quick and easy clearing.
  TthAog5Sv.Cries = {};

  — Echo the new battle cry for confirmation.
  self:OutputToChat(self.BATTLE_CRIES_CLEARED);
end

I began Part Five by saying that we were going to manage the growing complexity in our AddOn.  You may look at the above code listings and feel like we’ve only made things more complex, not less.  I can’t argue.  But we’ve now established an infrastructure that divides our code into chunks based on their logical function.  If our AddOn grew to a hundred times its current size, we could deal with it.  Without packages, that would likely cause a descent into spaghetti code from which we might never escape.

One final word:  there are people who have pushed Lua to do things much closer to true object-oriented programming (OOP) than we’ve done here.  I’m a big fan of OOP, and use it on a daily basis in most of my projects.  Thus far, though, I haven’t written any AddOns that were complex enough to really require it.  From what I can see, the real benefit of OOP would be in writing libraries for use by other AddOn authors, much like Swing in Java or MFC in C++.  That’s beyond the scope of this document, but feel free to do some Internet research if you like.  And if you come up with a really spiffy library, drop me a line.  I’d love to give it a test drive!

That’s it for packages.  Next, we’ll put these concepts to further use in Part Six, which talks about localizing your AddOn into different languages.


WOW UI Mod Guide - Chapter 4 Tables


Are you bored with your battle cry yet?  I know I am.  I can use our slash commands to change it, or turn it on and off, but it would be nice to have some variety without that much effort.  After all, I still have four more parts to this guide left to write (counting this one).  So as I test the code, I’m gonna be seeing my battle cry a lot.

Here’s an idea:  let’s let the user enter a bunch of battle cries and have the AddOn randomly display one of them when a fight starts.  Not only will this improve our AddOn, but it is a good time to talk about Lua tables in more depth.  And we’ll need to know tables for the changes we’ll be making in Part Five and Part Six.

SO, WHAT ARE Lua TABLES?

I promised in Part One of this guide that I wouldn’t try to teach you Lua coding.  And I mean it, I really do.  But some concepts are too fundamental to avoid.  Trying to learn how to cleanly write a complex AddOn without knowing tables would be kinda like trying to spec your Feral Druid before you know what Talents are.  (And if you don’t know that, close this guide and go play some WoW already!)

Lua tables are incredibly flexible data structures.  If you’ve used arrays in strongly typed languages such as C++, you’re in for a treat.  Lua tables are entirely different beasts, with mind-bending capabilities. 

At a basic level, a Lua table is just a place to store a bunch of stuff, combined with convenient ways to find the stuff later.  Recall that in Part Two we said a Lua variable was like a big empty box?  Lua tables are like a bunch of big empty boxes, all connected in a row.  These boxes (I’ll also call them slots or entries) can contain just about anything:  numbers, strings, functions, other tables, etc.  And they don’t all have to contain the same kind of thing.  Within a single table, one box could contain a number, one a string, and one a function. 

Now here comes the really cool part.  Imagine each box has an index card taped to it.  On each index card is written something that identifies the box.  This identifier (or key) can be anything:  a number, a string, a function, another table, etc.  And like the box contents, the keys don’t have to all be the same type.  Wow.  (Pun intended. J)

Let’s see how tables look in code.  Here’s how to create an empty table in Lua, then assign it some values:

MyTable = {};
MyTable[1] = 1;
MyTable["cow"] = “two”;
MyTable[5] = 3.5;

Notice that we’re mixing types for both keys and values, and even when we used numbers as keys, they didn’t have to be consecutive. 

When we want to access the values (peek in the boxes to see what they hold) we do this:

someVariable1 = MyTable[1];
someVariable2 = MyTable["cow"];
someVariable3 = MyTable[5];
someVariable4 = MyTable["oops"];

Oops, we didn’t store a value with a key of “oops”.  This will cause someVariable4 to end up with the special value nil, which means “no value” or “not present”.

MULTIPLE BATTLE CRIES

Next, let’s use tables to store a set of battle cries.  We can create our table like this:

TthAog4MyBattleCries = {
  “Not in the face!”,
  “Not in the groin!”,
  “Not in the kneecaps!”
};

This is just a shorthand way of creating the table and assigning it some values all in one statement.  When values are added to the table this way, they automatically use consecutive integers for keys, starting at 1.
 
Now that we have multiple battle cries, our old slash command isn’t good enough any more.  Instead, we’ll have two slash commands.  One command will add a battle cry to the table, and the other command will clear all battle cries.  (In Part Seven we’ll create a more powerful interface to make up for things these slash commands obviously lack, such as the ability to delete a specific battle cry.)  We looked atslash commands in detail in Part Two, so I won’t bore you with all the details.  Take a look at the code listing at the end if you like.  Suffice it to say that we’ll end up with four things the user can type to manage our battle cries:

/addnewbattlecry4
/anbc4
/clearallbattlecries4
/cabc4

The interesting part happens in the functions that are called in response to these slash commands:

function TthAog4AddNewBattleCry(msg)
  — Add the new battle cry, for use later.
  local newKey = table.getn(TthAog4MyBattleCries) + 1;
  TthAog4MyBattleCries[newKey] = msg;

  — Echo the new battle cry for confirmation.
  local echo = TTH_AOG4_BATTLE_CRY_ADDED .. msg;
  TthAog4OutputToChat(echo);
end

This function gets the number of entries in the table, adds one to it, and inserts a new battle cry in the new slot.  Then it echoes the new battle cry (TTH_AOG4_BATTLE_CRY_ADDED is a constant string we’ll declare at the top of our file.)  Notice that the table size isn’t fixed when it is created.  In keeping with Lua’s trademark flexibility, all we have to do to expand the table to hold a new entry is assign a value to the entry.  The table grows automatically to accommodate the new data.  Also notice this function call:

table.getn(TthAog4MyBattleCries)

This is our first encounter with using “dot notation” to call a function, but it won’t be our last.  Lua provides a library named “table”, which contains a bunch of functions related to tables.  One of these functions is “getn”, which returns the number of items in the table.  By writing “table.getn” we’re saying “call the getn function in the table library.  We’ll do something very similar with our own functions when we add packages to our AddOn in Part Five. 

Next let’s look at the other slash command function:

function TthAog4ClearAllBattleCries()
  –  assign to an empty table for quick and easy clearing.
  TthAog4MyBattleCries = {};

  — Echo the new battle cry for confirmation.
  TthAog4OutputToChat(TTH_AOG4_BATTLE_CRIES_CLEARED);
end

This one clears all existing battle cries and echoes the constant string contained in the TTH_AOG4_BATTLE_CRIES_CLEARED variable message to chat for confirmation.  To clear the table, all we did was assign a new, empty table to the TthAog4MyBattleCries variable.  Boom, all the old battle cries are gone in one simple statement.  If you’re coming to Lua from a language that requires manual memory management, be aware that Lua is garbage collected so there is no need to deallocate the old table.  If that sentence caused your eyes to glaze over and roll back in your head, just skip it and move along.

The TthAog4Event function has grown a bit.  Here’s how it looks now:

function TthAog4Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == TTH_AOG4_ADDON_NAME) then
      TthAog4OutputToChat(TTH_AOG4_BATTLE_CRIES_ARE);
       for i=1,table.getn(TthAog4MyBattleCries) do
        TthAog4OutputToChat(TthAog4MyBattleCries[i]);
      end
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    — Send a random message to chat (if there are any to send)
    local numBattleCries = table.getn(TthAog4MyBattleCries);
    if (numBattleCries > 0) then
      local randomKey = math.random(numBattleCries);
      SendChatMessage(TthAog4MyBattleCries[randomKey], “SAY”);
    end
  end
end

We’ve changed the handling of both ADDON_LOADED and PLAYER_REGEN_DISABLED.
You can probably guess what the changes do in both cases, but we’ll look at them quickly just to be sure.  First, ADDON_LOADED:

      for i=1,table.getn(TthAog4MyBattleCries) do
        TthAog4OutputToChat(TthAog4MyBattleCries[i]);
      end

Now that we have multiple battle cries, we need to echo each one when the AddOn loads.  The for loop is just a way to execute a block of code multiple times.  The TthAog4OutputToChat function will be called once for each element in the table.  The variable i will take on values starting at 1 and ending at the last index in the table.  The way this works in that before calling TthAog4OutputToChat, Lua checks to see if “i” is bigger than the number of elements in the table, as returned by the table.getn function.  If the table is empty, “i” starts out bigger, so we never attempt to output any battle cries.  Good enough. 

Next, let’s look at PLAYER_REGEN_DISABLED:

    local numBattleCries = table.getn(TthAog4MyBattleCries);
    if (numBattleCries > 0) then
      local randomKey = math.random(numBattleCries);
      SendChatMessage(TthAog4MyBattleCries[randomKey], “SAY”);
    end

This block first gets the number of elements in the table.  It then checks to see if there actually are any, or if the table is empty.  If the table is empty, there’s no battle cry to output so we’re done.  But if there are elements in the table, we call the math.random function.  This function returns a random value ranging from one to the value of the variable that we pass to the function, in this case the number of battle cries.  We then use this random value as an index into the table, to get the battle cry associated with that index.  Done.

There’s one last thing to mention.  Our old variable to hold the battle cry is gone now, having been replaced with the table, so we need to modify our .toc file accordingly.  All we have to do is change one line:

## SavedVariablesPerCharacter: TthAog4MyBattleCries

This causes the entire table to be saved to and restored from the per-character saved variable file.  We don’t have to do anything to handle individual table elements.  Lua and WoW handle all of that.  Here’s what our saved variable file looks like after I used our slash commands to clear the default battle cries then add two new ones:

TthAog4MyBattleCries = {
       [1] = “To Battle!”,
       [2] = “ARRRGH!”,
}

We’re done!  The complete code listings follow.  The files are also available for download <here>.

Here’s the .toc file.  The only notable change is the saved variables line.

## Interface: 11000
## Title: TenTonHammer.com Guide Part 4
## Author:  TenTonHammer.com
## Notes:  Makes your character scream a randomized battle cry when combat begins
## SavedVariablesPerCharacter: TthAog4MyBattleCries
TthAog4.xml

The .xml file had no changes of substance this round.

<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=”TthAog4.lua”/>
       <Frame name=”TthAog4Core”>
              <Scripts>
                     <OnLoad>TthAog4Load();</OnLoad>
                     <OnEvent>TthAog4Event();</OnEvent>
              </Scripts>
       </Frame>
</Ui>

Finally, the .lua file, where most of the changes were made.

–  Constant strings
local TTH_AOG4_ADDON_NAME = “TthAog4″;
local TTH_AOG4_BATTLE_CRY_ADDED = “Battle cry added:  “;
local TTH_AOG4_BATTLE_CRIES_ARE = “Battle cries are:  “;
local TTH_AOG4_BATTLE_CRIES_CLEARED = “Battle cries cleared.”;
 
–  This table holds our battle cries.
TthAog4MyBattleCries = {
  “Not in the face!”,
  “Not in the groin!”,
  “Not in the kneecaps!”
};

–  Called when the AddOn loads.
function TthAog4Load()
  — Register for events.
  this:RegisterEvent( “PLAYER_REGEN_DISABLED”);
  this:RegisterEvent( “ADDON_LOADED”);

  –  Create the slash commands.
  SlashCmdList["TTH_AOG4_ADDNEWBATTLECRY"] = TthAog4AddNewBattleCry;
  SLASH_TTH_AOG4_ADDNEWBATTLECRY1 = “/addnewbattlecry4″;
  SLASH_TTH_AOG4_ADDNEWBATTLECRY2 = “/anbc4″;

  SlashCmdList["TTH_AOG4_CLEARALLBATTLECRIES"] = TthAog4ClearAllBattleCries;
  SLASH_TTH_AOG4_CLEARALLBATTLECRIES1 = “/clearallbattlecries4″;
  SLASH_TTH_AOG4_CLEARALLBATTLECRIES2 = “/cabc4″;
end

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

–  Called when the associated slash command executes.
function TthAog4AddNewBattleCry(msg)
  — Add the new battle cry, for use later.
  local newKey = table.getn(TthAog4MyBattleCries) + 1;
  TthAog4MyBattleCries[newKey] = msg;

  — Echo the new battle cry for confirmation.
  local echo = TTH_AOG4_BATTLE_CRY_ADDED .. msg;
  TthAog4OutputToChat(echo);
end

–  Called when the associated slash command executes.
function TthAog4ClearAllBattleCries()
  –  assign to an empty table for quick and easy clearing.
  TthAog4MyBattleCries = {};

  — Echo the new battle cry for confirmation.
  TthAog4OutputToChat(TTH_AOG4_BATTLE_CRIES_CLEARED);
end

–  Called to handle the events for which we’ve registered.
function TthAog4Event()
  if (event == “ADDON_LOADED”) then
    if (arg1 == TTH_AOG4_ADDON_NAME) then
      TthAog4OutputToChat(TTH_AOG4_BATTLE_CRIES_ARE);
      for i=1,table.getn(TthAog4MyBattleCries) do
        TthAog4OutputToChat(TthAog4MyBattleCries[i]);
      end
    end
  elseif (event == “PLAYER_REGEN_DISABLED”) then
    — Send a random message to chat (if there are any to send)
    local numBattleCries = table.getn(TthAog4MyBattleCries);
    if (numBattleCries > 0) then
      local randomKey = math.random(numBattleCries);
      SendChatMessage(TthAog4MyBattleCries[randomKey], “SAY”);
    end
  end
end

Now our AddOn is a little more interesting, but it is also a little bigger.  It is time to look at packages to help manage the AddOn’s growing complexity.  So when you’re ready, move on to Part Five!


WOW UI Mod Guide - Chapter 3 Saved Variables


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 d