UI Mod Guide - Chapter 5

Updated Thu, Feb 21, 2008 by bpirkle

Krimm's Guide
to World of Warcraft Addon Design

Part 5
Packages










Part 5 Index

Download Part 5 Example Files!


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.


Go to Section 6 - Localization!

BetaOptIn.jpg
Five reasons why playing the beta isn't always a good idea.
Features
Fri, Apr 18, 2014
Mem
warlords-square-banner.jpg

When Warlords of Draenor launches, Blizzard is planning a huge stat squish, where all numbers get much smaller. Is this a good or bad thing for players?

Features, Opinions
Tue, Apr 15, 2014
Messiah
warlords-hit-square-banner.jpg

With Patch 6.0 and Warlords of Draenor, Hit and Expertise will be disappearing. Is this a good or bad thing and how is it being handled, find out right here.

Features, Opinions, Guides
Mon, Apr 14, 2014
Messiah
PaladinHealing
An overview of the changes coming to healing in World of Warcraft Patch 6.0 and what it means for you, the healer.
Features, Opinions
Fri, Apr 11, 2014
Mem

News from around the 'Net