MMO-Champion - News

Sunday, January 16, 2011

Creating Your Own WoW Addon

So you want to create your very own addon do you? Here's a few things you should know before you start.

  1. It is not going to be a quick thing
  2. It will most likely not be a do it and forget kind of thing, unless you only plan on using it yourself
  3. It is programming. If you don't like programming you probably won't like creating an addon
  4. You WILL have to debug your addon. Getting it right the first time is very rare



If you're fine with those things, then let's get started. This guide will go over quite a few things, everything from getting started and some basic Lua programming information, to more complex topics like storing data between user sessions. As a result, I will be splitting up this guide into multiple sections with a table of contents to make browsing this guide a little bit easier (I may miss things, and I plan on updating this, so feel free to let me know in a PM if I missed something)

What this guide will cover, and what it won't
This guide is intended to be for beginning to moderate addon authors/programmers. It will go over just enough to help you get started with creating and working with your addon. It won't go over (somewhat) advanced topics like graphical interfaces or profiles or the like.

Table of Contents
  1. Getting Started
  2. Lua Basics
  3. The Core Addon
  4. Making Your Addon Do Something Useful
  5. Storing Information
  6. Working with Libraries



Getting Started

This section will go over getting your computer set up to create your addon.



So first things first:
Setting up your environment:

You need two main things. 1, a program in which to code your addon, and 2, a good WoW environment in which to code your addon.

1. Programs:

There are many programs you can use to program. Personally I like to use Notepad++ as it's lightweight and easy to install (LINK)
You can also use regular notepad if you would rather, or even larger programs like Adobe Dreamweaver.
Any of these will work, but if you use notepad you have to remember when you're saving your code to switch the save as file type to All Files from .txt so you can save your file as a .lua file.

2. A Good WoW Environment:

I personally like to have a completely separate WoW installation specifically for creating and debugging my addon. That way my coding doesn't affect my playing of the game, and overall I think it makes it simpler. The easiest way to do this (if you have space) is to just copy your whole WoW folder over to another folder. (If you want to save time, I would strongly recommend you don't copy over your Interface and Cache folders. They are created the first time you start the game and it will save a TON of time in copying)

2.1. Semi-Essential Addons:

In order to debug and other things that make coding easier, I find it's best to have the following:
1. Prat 3.0 or your favorite chat mod
2. BugSack and BugGrabber

Beyond that it is up to personal preference, but keep in mind that the more addons you have, the longer it will take to reload your UI and the longer it will take to debug your addon

Thats it for Getting Started. Feel free to go on if you have the time or are still interested.





Lua Basics

This section will go over some of the basics for Lua programming.


Lua Programming is similar to other languages, so if you already know a few it should come fairly quickly. If not, you'll get there.

Some basic things to know
Please note that I'm only going over the extreme basics here. http://www.lua.org/pil/index.html is a full list and explanation for pretty much everything you need to know about Lua programming.
Lua is a very flexible programming language compared to some. It's main functionality comes from five main things: Tables, Functions, Variables, if statements and loops. Those five things are the main things you need to know, and are just what I'll go over now.



Functions:
A function is a bit of code which is run whenever the name of the function is called somewhere in code. There are two ways to define a function. Way One:
Code:
local function print_f(arguments)
 --do stuff
end
Way Two:
Code:
local print_f = function (arguments)
        --do stuff
end
Both ways are equally valid. Way one is defined more like you would define a function in VB whereas way two is defined more like a variable would be defined.


Variables:
Variables are a object that you assign a value that you can change or read at any time. You can think of it like a coin. A coin would have two values, Heads or Tails which you can read by looking at which side is face up. You can also change the value that you read by changing which side is facing up manually.
There is one thing you have to do before you can read the value of a variable, and that is setting it's default value. You can do that by the following:
Code:
local variable = value
You can then change the value of that variable by setting variable to a new value. Example:
Code:
variable = new_value
You can also read the value of the variable by calling the variables name directly and without an equal sign. Example:
Code:
print(variable)
the above would print the value of the variable into the main chat frame (General if you have the default set up). Note that the variable can be called whatever you like, and does not have to be a real word. Gmeta is just as acceptable. (There are some limitations, but I won't get into that here)

Variables, when defined, are given a type based on the value you give it. Enclosing text in quotation marks will identify the value as a string (and it will be read as a string value, hence letting you use any string-based functions on it, such as substr.) Variables which are numbers are defined by setting the variable equal to a number (with no quotes)

Variables can also be set to nil, which indicates that you no longer wish to use that variable and it will be cleared from the system memory. If you try to read the value of a nil or undefined function, it will return nil.


Tables:
Tables are a way to contain multiple pieces of information. It's very helpful to use tables to organize information in a way which makes logical sense, rather than having a thousand different variables with unique names. (Note that tables are very similar to arrays in other programming languages, but there are some key differences)

First, you have to declare your table. You can do that by the following:
Code:
local table_name = {}
Now, table_name is a table with no values. In order to give a table values you have to do the following.
Code:
table_name["variable"] = value
Now table_name has a value under the key "variable", and it can be read by referenceing table_name["variable"]
You can also give other values to other keys in the table. IE, table_name["var2"] = "hey mom" is completely separate from table_name["variable"] and will return different things when read.

There is also a different way to give values to a table, and also read values from a table, and often this can be easier.

Code:
table_name.variable = value
table_name.variable is equivalent to table_name[“variable”].
Thanks to Adirelle for the following clarification:

Code:
local mytable = {}
mytable.someval = 2
print(mytable["someval"]) -- prints 2
Not to be confused by using a variable as an index :

Code:
local mytable = {}
local myindex = "someval"
mytable[myindex] = 2 -- stores 2 in mytable["someval"]
mytable.myindex = "BAM!" -- stores "BAM!" in mytable["myindex"]
print(mytable[myindex]) -- prints 2
print(mytable["someval"]) -- prints 2
print(mytable["myindex"]) -- prints BAM!
print(mytable.myindex) -- prints BAM!

If Statements:
If statements let you do something based on the value or relative value of one or more variables.
They follow the following form.
Code:
if variable [operator] variable2 then
      --Do Stuff
end
An operator can be many things. Here's a list:
  • == variable is equal to variable2
  • >= variable is greater than or equal to variable2
  • <= variable is less than or equal to variable2
  • < variable is less than (but not equal to) variable2
  • > variable is greater than (but not equal to) variable2
  • ~= variable is not equal to variable2
For a full list (as well as limitations and more complex ideas), check out the Lua Manual and the sections on Operators


Loops:
There are two types of loops, a while loop, and a for loop.

A while loop executes it's code while the logic you give it returns true. (I.E. 1 > 0 returns true always) After executing the code once, it will check whether the logic provided by you returns true or false. If it's true, it executes the code again. If it's false, it will exit the loop and go on to the next thing.
Code:
while variable [operator] variable2 do
      -- do stuff while true
end
A for loop is similar but it has a few differences. A for loop while do something while a variable that is defined in the for loop is true. Example:
Code:
for var=default,check_var,step do
      -- do something
end
This for loop will execute it's code until the value of var equals check_var. Every time it executes the code, it increases the value of var by step. (Note that step is optional. It's default value is +1, IE it will increase the value of var by 1 per execution)

There is an additional form of for loop which is very useful for traversing tables. An example is below:
Code:
for key,value in pairs(table) do
     -- Stuff to do with keys and values
end
This is a fairly complex statement. pairs() is a function which takes an argument table and then returns an array of keys and values from that table. By using that with the for loop, you can step through each key,value pair and do stuff with that information. (Example: Stepping through a table of stored dates and events, and finding the date that an event happened. (the event would be the value, and the date would be the key in this example))

The for loop is very powerful and while somewhat complex, is very useful once you understand what it can do. (Note that a for loop is identical to a while loop that sets a default value and increases the value in it's execution)


Differences Between Local and Global Tables, Functions and Variables
In the code examples I've given up to this point, I've kept everything local, and there is a pretty good reason to doing so, but there are some limitations as a result.
When WoW loads all of it's addons, it does so once at a time and executes all of the code in every addon before you even enter the game-world. As a result, you do not know whether your addon is loaded first, or whether it is loaded last.

The advantage to local Tables Functions and Variables is that you do not have to worry that your code will be overwritten by other addons, and thus it reduces the chance of incompatibility between addons. The limitation is that these local functions variables and tables can only be called by your addon, and cannot be accessed by other addons, or by the game.
The advantage with global variables is that, assuming you give it a unique name, you can access it with other addons. That would enable you to print the value of your variables to your chat frame to help make debugging easier. It also enables limited communication between addons. (And can also increase the extensibility of your addon with limited work on your part)

An important point: (Thanks Treeston)
In addition, by defining your functions inside a global variable unique to your addon, you can gain access to the advantages of global variables while still preventing the disadvantages of using global variables.

For example, by defining a global variable WhyHelloThar at the beginning of my code, and then adding all of my functions to that global variable, I can reference those functions through other addons/code outside of my addon:

Code:
WhyHelloThar = {}

function WhyHelloThar:myFunction() 
    -do stuff
end
By putting a colon between the global variable for the addon, and the function name, the function is defined as a method, allowing it to reference the self variable, which contains the WhyHelloThar global variable, among a few other nice things methods bring.



That's it for Lua Basics. If you want to learn more, either experiment or browse the Lua manual. If you need help with Lua coding, the WoWInterface.com forums can be a good place to ask for help, or even our very own Mmo-Champ Interface & Macros forum can be useful. Both places contain plenty of knowledgeable people. Don't be afraid to ask for help. Not everyone is out to make you look stupid




The Core Addon
The core addon contains two files. The .toc file (or table of contents) and the .lua file.

The ToC File
The toc file is what WoW looks for and reads from when initializing addons, and is required for your addon to function. (Note that it should also be the same name as the name of your addon and the folder that it is contained in. Both for ease of use and because it may be required [I'm not sure off the top of my head. It's just good practice])

The ToC file contains a bunch of different lines, some of which are optional and some of which are required. (An example TOC file)
Code:
## Interface: 40000
## Title: WhyHelloThar
## Author: Brusalk
## Dependencies: Ace3
## OptDeps: BugSack, !Swatter
## SavedVariables: mySavedVar

WhyHelloThar.lua
The first line defines which version of WoW this addon is intended for. The 40000 means that this addon was designed to be used in WoW Version 4.0, but may work for more recent versions.

The Title line defines the name of the addon as it is referenced both in game and in the list of addons on the character selection menu. Make sure this is unique to your addon.

The author line defines the name of the person who made the addon.

The dependencies line defines the names of addons which are required in order for the addon to work properly (and without which the addon won't run at all)

The OptDeps line defines the names (in a comma separated list) of addons which should be loaded before the addon in the load order. This is a great place to include libraries.

The SavedVariables line defines the name of a variable in which the values will be saved between sessions. (This is how addons remember information like configuration settings or position of frames, etc)

After those lines are done, WoW will then load all of the following lines in the order in which they appear. WhyHelloThar.lua would be the first and only file to be loaded and read into memory for this addon. Any additional files in the addon directory won't be loaded.

Note that WoW, when the user reloads their UI, will reload any Lua or xml files already referenced in the TOC file. It will not however reload the ToC file. Therefore for testing/debugging, you can reloadUI as long as you don't add any new files to the toc file.

The Lua File

The Lua file contains all of the lua code in which WoW will execute when the addon is loaded. This is the file which you will be mainly changing. (And will probably contain most of your errors )




Making Your Addon Do Something Useful
This section will focus on working with Events in WoW and providing a service to the user of the addon. To do so I'll be working with an example addon which will post a Welcome to the game message.

Events:
In WoW, events are one way to provide functionality to the user. (List of All Events and When They Are Called)

Looking through the list of events, and given the action which I want to do, I notice there are a few events which I could potentially use.
  • PLAYER_LOGIN
  • PLAYER_ENTERING_WORLD
Looking at the description for player_login I see that it fires whenever a player logs into the game, or when they manually reload the UI. Player_Entering_World fires every time the loading screen finishes. As such, I'd rather use PLAYER_LOGIN as it fires just when I want it to, and not more often, making my code simpler.

Know that I know the event I want, I need a frame which checks for when that event is fired. WoW provides the function CreateFrame which returns a reference to the frame which was created.
Here's that code:
Code:
local EventFrame = CreateFrame("Frame")
I can now reference the frame by using the local variable EventFrame.

I now need to register the event I want so that I can do what I need it to. I do that by calling the built in function RegisterEvent and SetScript:
Code:
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:SetScript("OnEvent", function(self,event,...) 
     --Do Stuff when player logs in
end)
Now, whenever the player logs into the game, the addon will execute the function defined in the SetScript. (Note that without first registering the event, the OnEvent will never fire.)
You can also register multiple events to the same frame, just keep in mind that the function will be called everytime any of the events happen.


Now that I have my function being called, I can begin to actually provide functionality to the addon. To do what I want to right now, I'm simply going to change the above code to include a message being added to the main chat frame.
The Complete Code:
Code:
local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:SetScript("OnEvent", function(self,event,...) 
      ChatFrame1:AddMessage('WhyHelloThar ".. UnitName("Player"))
end)
Pop Up Image

Thats it for this first part. The basics are quite simple:
  • Identify What You Want To Do
  • Research on some events or methods in which to do what you wish
  • Do it




Storing Information
This next section will go over storing information between sessions.

The Basic Concept
As you got a hint of in the section in this guide about the ToC file, there is a variable defined there that WoW will save the value of between user sessions. WoW saves the content of this variable to a file upon logout and restores the value of this variable upon login. Thus, any information you wish to save between sessions, whether it be configuration information, or something else, should be stored to that one variable.

The important thing to note here is that WoW will save tables, if the variable is a table. Therefore if we set all of the information we want into one table, we can save and use the information contained in that table the next time the user logs into the game.

Application to the WhyHelloThar Addon
I'm going to use this saved variable to save information on the characters the addon has already seen, and also how often the addon has seen that character. It will then output that information in the welcome message. Keep in mind that you don't have to do it the same way I do it, and in fact there are probably more efficient ways of doing it


More Specific Plans
I know that I need to save two pieces of information, the names of the characters that the user logs on with, and the number of times that they log on with that character. Because of this I'll use a table because I need to save more than one piece of information. Also, because I know that a table can be referenced associatively, or in other words I can reference a value out of the table my name, I can make the name the name of the character, and the number of times they log onto the character the value.

To do this I make use of my knowledge of for loops to step through my saved variable that contains all of my information. In the following code, I step through the saved variable and check to see if the name of the character the player logged in on is already in the saved variable. If it is, I auto increment it by 1 value to indicate that I've seen the character again. If it isn't then I add it to the table and set it's default value to 1 to indicate that I've seen it for the first time. (Setting Defaults)

Code:
local found = 0
for name,number in pairs(mySavedVar) do 
        if UnitName("Player") == name then
                mySavedVar[name] = mySavedVar[name] + 1
                found = 1
        end
end
if found == 0 then
        mySavedVar[UnitName("Player")] = 1
end

The only problem with using this logic is that I have to have a table, and one of the limitations of a table is that it first has to be given a default and defined. Therefore, when the player logs into the game, I need to check whether my table exists/actually is a table. and if not, then I have to set it's defaults.
Code:
if type(mySavedVar) ~= "table" then  
  mySavedVar = {}
  mySavedVar[UnitName("Player")] = 1
  ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". I do believe this is the first time we've met. Nice to meet you!")
 end
Because I know that my saved var needs to be a table, I can use the built-in Lua function type() to check the type of the saved var. If it doesn't exist type() would not return "table", thus I can just check if the type of mySavedVar is a table. If it is, I know that it's already been defined. If not, I need to define it and set it's default value.

Now that I have all of the pieces together, I can stitch them together to form a complete addon that will tell the user customized messages based on the number of times the user has logged on per character.

Complete Code:
Code:
local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:SetScript("OnEvent", function(self,event,...) 
 if type(mySavedVar) ~= "table" then  --  I know it doesn't exist. so set it's default
  mySavedVar = {}
  mySavedVar[UnitName("Player")] = 1
  ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". I do believe this is the first time we've met. Nice to meet you!")
 else -- It's already set
  if mySavedVar[UnitName("Player")] == 1 then
   ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". How nice to see you again. I do believe I've seen you " .. mySavedVar[UnitName('Player')] .. " time before.")
  else
   ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". How nice to see you again. I do believe I've seen you " .. mySavedVar[UnitName('Player')] .. " times before.")
  end
  local found = 0
                for name,number in pairs(mySavedVar) do 
                         if UnitName("Player") == name then
                                mySavedVar[name] = mySavedVar[name] + 1
                                found = 1
                         end
                end
                if found == 0 then
                         mySavedVar[UnitName("Player")] = 1
                end
 end
end)






Alternate Method
There is another method that could be utilized that would make this even easier, and that is a SavedVariablePerCharacter. After it's defined in the ToC file, the addon can make use of a variable that contains information specific to the character. Basically, this let's WoW handle the character-specific portion of this addon. This would enable the addon to only have to store the number of times the user's logged onto that particular character by simply referencing a variable, as opposed to referencing a table associatively. Below is a modification of the addon to use the saved variable specific to the character. (Note that this is also more efficient because it eliminates the need for a for loop among other things. In this particular case, the difference is negligible, but on larger addons it can become an issue.)

Here is the complete modified code:
Code:
local EventFrame = CreateFrame("Frame")
EventFrame:RegisterEvent("PLAYER_LOGIN")
EventFrame:SetScript("OnEvent", function(self,event,...) 
 if type(CharacterVar) ~= "number" then
  CharacterVar = 1
  ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". I do believe this is the first time we've met. Nice to meet you!")
 else
  if CharacterVar == 1 then
   ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". How nice to see you again. I do believe I've seen you " .. CharacterVar .. " time before.")
  else
   ChatFrame1:AddMessage('WhyHelloThar '.. UnitName("Player")..". How nice to see you again. I do believe I've seen you " .. CharacterVar .. " times before.")
  end
  CharacterVar = CharacterVar + 1
 end
end)
Note how much simpler it is to code and read the second version than the first. Determining the most efficient way of storing information often can help the coding process, so make sure you spend time planning what you are going to do before you do it. What's the saying, Measure Twice Cut Once?



Working with Libraries
First of all, a library is a collection of code compiled into an addon that makes specific tasks simpler or more automated. There are upsides and downsides when working with libraries.
Upside
  • Can make coding easier
  • Often simplifies certain processes
Downside
  • You have to code within someone else's guidelines
  • Can make identifying bugs trickier
  • Sometimes libraries just don't do what you want them to do. IE they aren't as configurable or they just can't do something

Often times, the upsides outweigh the downsides, but it can extend your development time if the library just can't handle what you are trying to do with it.

There are two main ways that you can work with libraries with your addon, you can either define the name of the libraries as dependencies in your ToC file, or you can include the libraries as part of your addon package and include the individual Lua files in your ToC file. Either way works and many addon authors do it both ways. It's really personal preference. For the purposes of this guide, I'll show you how to do it both ways, and you can decide for yourself which way you like better. I'll also try to explain some of the benefits of doing it each way.

Method 1: Dependency
This method is pretty straight forward. In your ToC file simply include the name of the library that you wish to use. This does not include escape sequences that may appear in the name of the addon (such as changing the color of the name in the addon list or some such) This ensures that the library is loaded before your addon is, so that your addon can use the library in its code.

Example:
Code:
## Interface: 40000
## Title: WhyHelloThar
## Author: Brusalk
## Dependencies: Ace3
## SavedVariables: mySavedVar
## SavedVariablesPerCharacter: CharacterVar

WhyHelloThar.lua
This addon would now only load when AceGUI-3.0 is loaded. This enables the addon to make use of the AceGUI-3.0 library. (A Part of the Ace3 collection)

The upside of using this method is that as the author, you don't have to worry about updating the libraries yourself and it also can help reduce the load time of your addon.
The downside of using this method is that you are dependent on the user updating the libraries by themselves, which many don't bother to. This can cause some issues when you begin using functions which are defined in a newer version of your library than the user has.

Method Two: Including the Library Directly
This method means calling files that you include in your addon's package as if they were one of your lua files. By placing them above your lua files, they load first and you are sure that they are loaded such that you can make use of them.

Example:
Code:
## Interface: 40000
## Title: WhyHelloThar
## Author: Brusalk
## OptDeps: Ace3
## SavedVariables: mySavedVar
## SavedVariablesPerCharacter: CharacterVar

#@no-lib-strip@
libs\LibStub.lua
libs\AceGUI-3.0\AceGUI-3.0.xml
#@end-no-lib-strip@

WhyHelloThar.lua
This toc file loads the two libraries LibStub and AceGui-3.0 that are located in the libs sub-folder in the WhyHelloThar folder. Make sure that they are placed before your files that you use them in, otherwise they won't have loaded and you'll get all sorts of fun little errors

The upside of this method is that you always know the version of the libraries that you're using and you guarantee that you won't have any errors that are resultant from that.
The downside of this method is that it can make the load times of your user's addons longer, especially if you're loading multiple libraries. Part of the reason for this is that WoW won't identify that two libraries are the same if they are loaded from two different addons, which means that each addon that loads the same library ends up loading them multiple times.
With the number of addons that most people use, it's usually easier to work with if you call your libraries directly from your addon's folder.

I just want to make clear that it is strongly recommended that you include the libraries yourself. Most users won't install your required addons so use the OptDeps and including the library files yourself.


Conclusion
Now that you know more about working with libraries and with addons in general you can continue to research more on your own. Below I'll post some links to awesome websites and pages that I've found useful. I'll also try to include a short description of what the pages go over.





  • Ace3 - Ace3 is a pretty popular collection of libraries that can do a great many things. AceGui can help you make your GUI (or Graphical User Interface), or AceDB could help you organize your database (or table) of information, and those are only two of the libraries Ace3 has to offer. Check it out and check out some of the descriptions of the sub-libraries. They also come with a great support forum and are quite well documented.
  • WoW Programming.com - WoW Programming is a great wow programming site! They have a LOT of documentation on in-game events (linked earlier) and other APIs. This is pretty much my go-to resource when I'm coding an addon
  • WoWpedia - API - This is the resource I go to when WoW Programming is missing something (or vice-versa). WoWpedia has a lot of documentation on the different built in WoW APIs.
  • WoWInterface Forums - Lua/XML Help - If you can't figure out something or are just plain stuck, you can always trying asking a question there. Who knows, they may just answer you
  • MMO-Champ Forums - Interface & Macros - Quite a few knowledgeable people roam the MMO-Champ forums. This can be a good resource.
  • Lua-Users Wiki - Tutorial Listing - This could be a useful resource for more information on coding in Lua in general. If you don't think you quite understand my explanations of some of the basic Lua functions, check here. There's bound to be some that would be helpful
  • Your First Ace3 Addon – This is a great tutorial on how to use a quite common library, Ace3 (which I referenced previously) Could be worth reading if you want to learn more about utilizing libraries.