此站点大量使用JavaScript。
请在您的浏览器中启用JavaScript。
正式服
PTR
10.2.7
PTR
10.2.6
Beta
Comprehensive Beginner's Guide for WoW Addon Coding in Lua
来自 Armak
[Last Updated]:
2024/03/21
变更日志
补丁:10.2.6
目录
评分:
1 Introduction
Hi, I am Armak from EU-Frostwolf. Being into programming since 2001 and into WoW since 2006 it was only a question of time that I started looking into WoW addon programming. What held me back a couple of times was the relatively high hurdle on starting because it is not very well-documented. There are several websites which document the ingame API, but most of the things you still have to find out for yourself.
I want to provide such an introduction to those highly motivated players with their own ideas for tons of addons. I will start from the very beginning with some basic explanations and from then on we will move forward into Lua. If you are still interested, we will create a small example addon.
This is nothing you will be able to fully understand on a single evening if you have never been in touch with programming before. Always come back to it if you have time and motivation and you will begin to understand more and more.
I would like to provide an introduction to WoW Lua coding, which puts you in a position to gather the rest of the information yourself. You cannot know every method or return value by heart, but if you know the basics you can quickly look them up at almost no time and start using them.
For those wanting to read about advanced Lua techniques: I might do another guide in the future about optimising code, object oriented programming in Lua and other techniques. But this should not be treated in this beginner's guide and is not necessary for successfully writing well-performing addons.
If you have any questions, suggestions or corrections, feel free to leave a comment or catch me on battle.net via Michi#2174. Since I am not a native speaker, you are also welcome to improve my phrasing. But please do not use the comment section for this matter.
Note for Classic WoW players:
The ingame Lua API did not change much over the years. The biggest differences are that some parts of the API have been removed, because they were too powerful or could be exploited. Over the years many new features got included in the API, like the Mount and Pet Journal. Most of this guide is very basic and does not make use of these new features. Therefore, it should work inside Classic WoW.
2 Resources
Your most visited websites being an addon author are probably some of these:
https://lua.org/pil/contents.html
- Lua documentation from 2003. Most of it is still relevant.
https://wowwiki.wikia.com/wiki/WoW_AddOn
- A Wiki with pretty much everything about WoW addons. It is mostly written comprehensively but often lacks background information on a certain topic and often does not cover every important aspect of a subject.
https://wowprogramming.com
- A website containing everything you can use within your addons to interact with the game. It is quite complete in its listing but lacks documentation and good examples.
https://wow.gamepedia.com/World_of_Warcraft_API
- Another API Website.
https://authors.curseforge.com/forums
- A message board for WoW related Lua questions.
https://curseforge.com/wow/addons
- One of several websites to upload your addons to (in my opinion the most relevant).
https://twitch.tv/tomcat
- Addon developer regularly streaming on Twitch.
https://wago.tools
- Lua files and more ready to be searched which were extracted from the different WoW builds. Here you can look up the basic UI's functionality which can help you alter the game's systems.
3 Preparation
To start writing a WoW addon you do not need much. Some time and a good text editor like
notepad++
should be enough. Basic knowledge about programming is useful, but Lua in combination with WoW is a good opportunity to start from scratch because it is very lightweight.
A good WoW addon helping you experiment with Lua is
WowLua
. With its help you can easily edit and run code ingame. Its last version was designed for BFA, but it works in SL and Classic as well.
Last but not least you should activate the reporting of Lua errors. Do that by typing
/console scriptErrors 1
into the chat. This will give you a small window with more information about any Lua errors. You should put that back to
0
while actually playing WoW because it can be quite annoying having those errors pop up.
3.1 How To Execute Lua Code In WoW
There are a few possibilities. The easiest one is to write
/run <code>
into the chat frame, but this has the same text length limitations as if you were writing to a friend. You can also put this in a macro, limited to 255 characters. But if done right, these 255 characters can do a lot.
The next one is the
WoWLua
addon mentioned above. It provides a text editor without character limitation. You can write down what you want and execute it. This is your best friend when testing new code snippets you would like to incorporate into your addon later.
You can also execute code with
WeakAuras
, although this is a whole new and different thing to learn.
The last possibility - of course, is to write an addon. No character limitation, an easy way to add sound and texture files, but you always have to do an interface reload in WoW to make your latest changes go live.
4 Addon Basics
In this section I am going to provide some basic information about how addons work. First of all you should know that addons are represented via simple folders in
C:\Program Files\World of Warcraft\_retail_\Interface\AddOns
or wherever your copy of WoW is located. These folders contain every piece of information about your addon. This can be:
The actual code files
A file with meta information about your addon, like its full name and author
Images
Sound files
Additional information like a
readme.md
or a PDF document. WoW ignores these kind of files.
After starting your WoW client, every addon inside the
World of Warcraft\_retail_\Interface\AddOns
folder is loaded into memory. It is the only time this is done, so this is the reason why you have to restart the game if you install a new addon. The game never checks for new addons in this folder again while running.
In detail, WoW remembers the file names to load of all your addons. If you do a
/reload
ingame, it reloads all these files it identified when you started WoW. If you update an addon while the game is running and the new version of the addon only made some changes to its existing files, reloading the UI would be sufficient to have the new version running. If this version added some new files, it would likely result in a couple of errors because the updated files which already existed may now include references to the new files WoW does not know about yet.
With the pre patch of Shadowlands Blizzard finally changed the addon loading process. Now you can do a
/reload
ingame and it re-scans your addon directory. Any new addon should be detected instantly and shown ingame afterwards.
With your addon you have the possibility to save some data. This data is called
SavedVariables
and can be found in two locations:
World of Warcraft\_retail_\WTF\Account\<account_name>\SavedVariables\<addon_name>.lua
which can be read by all your characters
World of Warcraft\_retail_\WTF\Account\<account_name>\<server_name>\<character_name>\SavedVariables\<addon_name>.lua
which can be read by this specific character
This file is automatically created and always has the same name as your addon. You cannot change the way WoW saves your data, you can only tell WoW
what
you want to save and it does the job for you.
You should know that these files are only refreshed when reloading the UI, relogging (which reloads the UI) or closing the game. Before that they are only in your computer's memory. This is why when WoW crashes, you will not keep settings changes or data collected by your addons. WoW terminated abrubtly and therefore cannot initiate its "save addon data routine". This is why e.g.
Details!
loses all your fight data since your last reload/relog/regular game quit if WoW crashes.
5 Terms And Definitions
I should explain some of the terms which I will probably use a lot.
API - Application Programming Interface
In the case of WoW you will work with its API to get the desired results. If you want to make your character yell something, you have to tell WoW to do so in your code.
How
you do that is defined by the API. It is basically like:
WoW: If you want your character to say something, use SendChatMessage, tell me what you want to say and in which chat type you want to say that.
You: SendChatMessage("Hello Azeroth!", "yell")
You cannot do anything which is not defined in the WoW API. So for example you are not allowed to create a file on your computer and put some text in it. This would ultimately lead to serious security concerns and therefore is not supported by WoW's API.
6 About Lua
Lua is a programming language, more specific a scripting language. Being a scripting language has the characteristic that you can write down your code, press
Run
and immediately get the result. No need to compile your files. Lua is very popular for being embedded into other programs as an extension. There are for example image editing programs with their own API which you can access via Lua. You would then be able to do actions like programatically create 1,000 rectangles with a random background colour, put them side by side and save the resulting image.
In WoW you can change and enhance many aspects of the game. The WoW developers allow you to use many things they use internally to make the game run. Some examples:
You are able to customise the user interface: its graphical representation and its functionality.
You can read and evaluate the combat log.
You can react to all kind of events (when a player dies, someone writes a message into the guild chat, you kill a mob, someone trades you, you start gaining fatigue ...).
Combining these things allows you to create mighty addons which have been with us for many years, like Deadly Boss Mods, Weakauras2, and Recount.
How you do these things in WoW is prescribed by the WoW API.
Of course, Lua in WoW has its limitations. You can, with a small exception, not change any file of your computer's system. One reason for this is: You could export data from inside WoW to the outside and immediately react to this with another program of yours. You cannot control the movement of your character. You cannot automate most actions of your character which require clicking. So no code like
if rogue has six combo points, use Run Through
. As far as I know, there has not been a major loophole since years and WoW does a pretty good job to keep a lid on its API.
Some time ago, Blizzard introduced new dialog windows when trading and when sending mail. Before that you could write an addon which sent all your gold to another character when opening your mailbox. You still can do that, but the game now asks for your permission if the recipient is not one of your own characters. In addition to this they added a gold cap for a single mail.
Over the years people came up with creative uses of the API, which led Blizzard to change it or to completely remove the misused features. Back in WotLK there were addons telling you where to go to avoid mechanics by inserting arrows into the world. Blizzard removed that. Remember those radars on the Archimonde fight? Blizzard removed some of the API parts responsible for this mighty addon.
7 Some Addons And How They Work
To get a feeling about addons and how they achieve to do what they do it is a good idea to look at some examples of popular addons.
The most important thing for this addon is the game's combat log.
Every important combat action you, your party members and your enemies do is logged there. DBM gets told by the API when a boss fight starts and receives an encounter ID (e.g. 2051 for Kil'Jaeden in Legion) and its difficulty. Then they can activate this encounter in their addon. Through a lot of testing, the people behind DBM know the rules a boss follows. If they know that a boss immediately starts casting
恶意切割
and after that every 20 seconds, they would start a 20 seconds timer after its first cast and display it on its well-known timer bars.
The DBM pulltimer works as follows: Addons have the opportunity to communicate between each other silently through an
addon chat channel
not visible to the user. If player A types
/dbm pull 10
, this is broadcast to everyone in the raid/party and those with DBM will see the timer as well. Every addon is allowed to read this channel, so this is why other boss mod addons might also display a pull timer, although initiated from a different addon.
DBM's feature to tell you when your guild started a boss fight works similarly, then the addon message is broadcast to every guild member.
Prat is a chat enhancing addon. Let us look at some features:
Name colouring: Prat saves name, class and level of players you meet. When you receive a chat message, let us say in the guild chat, it will intercept this message, search for known player names in it and colour them accordingly. After you have seen a lot of players, Prat tends to noticeably slow down the chat if you e.g. send 20 messages at once with a script because Prat goes through your list of seen players everytime you receive a chat message.
URL copying: Prat searches chat messages for URLs in it, e.g.
levelupgilde.de
. It then makes this part of the chat clickable and displays an input box with the URL in it, already selected and ready to be copied.
Chat history: If activated, Prat saves the last
x
messages of your chat frames. When you log back into the game, it prepopulates your chat frames with those saved messages.
Among others, Pawn enhances your equipment tooltip by showing the improvement this particular item would give you when equipped.
To make it work properly, you have to feed Pawn your stat weights. Stat weights usually take your primary stat as a reference (weighted "1") and calculate the other stats accordingly. If crit is worth 0.75, it is at 3/4 the worth of your primary stat.
How it works: When hovering over an item, Pawn retrieves the stats of this item and updates the tooltip. It multiplies the amount of mastery on this item with mastery's stat weight, continues with the rest of the stats and accumulates the results. The end result is a number perfectly usable to compare items with only stats on them (procs and use effects are usually not taken into account).
Updating the tooltip and getting information about stats on an item is part of the API.
Details! is a damage meter, showing each player's total damage done and DPS value. It can do much more like showing graphs, deaths, dispels, interrupts and damage taken.
It is heavily powered by the combat log. The combat log gives you every information needed. A few examples:
When a player starts casting a spell. You get additional information like the name and ID of the spell.
When a player successfully cast a spell.
When a player aborts casting a spell.
When a player deals damage. Among other data you can get the amount of damage done, the target, the spell which did the damage, the name of the player.
When damage is absorbed.
With these information you can comprehend almost every aspect of a fight. Details! graphically summarises this data, rendering it more readable to humans.
If you look at
Warcraftlogs
, which is powered by WoW's combat log as well, you will see that it even has a replay function. With its help you see every player's actions, movements and positionings in a boss fight. The position data is an example of WoW's internal API limitation - you cannot access this ingame. But if you activate combat logging with
/combatlog
, all of the combat log including player's positions will get logged into a text file in your WoW log folder.
To give you an idea of how much happens in such a combat log: After an evening of extensive raiding this text file can grow to a couple of hundred megabytes.
8 Basic Programming Rules, Conventions And Structures
In this chapter we will discuss programming basics. Keep in mind that the elemental instructions are rather simple. What makes them really powerful is the ability to combine different instructions and create algorithms and tasks which a computer should execute. You can use any tutorial you want to get informed about your first steps in programming, but I will try to explain it directly related to WoW and Lua.
I will give you examples you can insert into an online
Lua interpreter
or into the
WowLua
addon I recommended earlier. If there is WoW specific code in an example, this cannot be run in the online Lua interpreter. It most certainly does not know what a Mount Journal is and has most certainly never heard of Hogger!
Every code snippet will have symbols next to it which indicate where you can run it.
8.1 Variables
Variables are one of the most elemental but also most useful helpers of any programming language. Inside a variable you can store data. To declare and initialise a variable you do the following:
text = "Hello Azeroth."
variable name: May consist of letters, digits and underscores. It may not start with a digit.
assignment operator: This equals sign assigns the incoming value from the right to the variable on the left.
variable content: This value will be stored in the variable.
There are several value types, let us take a look at the three most basic ones:
Any type of text. They have to start and end with a quotation mark (" or '). Else it would be difficult for the interpreter to determine the beginning and ending of the string. Notice the
\"
in the following example. Our string contains a quotation, which is surrounded by " in the English language. Unfortunately, this is the same character we use for surrounding our string. If we wrote
string1 = "She said, "Oh, wow!"."
, Lua would be confused by all these quotation marks and as soon as it finds the second one throw an error because for Lua the assignment is now done, but there are still unexpected characters coming. To bypass this, we put a backslash (\) in front of the quotation mark. This is called
escaping
. With a backslash in front, Lua recognises this quotation mark as normal text and not as its text delimiter. Another workaround for this would have been writing
string1 = 'She said, "Oh, wow!".'
. By using single quotation marks, the problem is gone. Using single quotation marks inside
this
string would make escaping them necessary again:
\'
.
string1 = "She said, \"Oh, wow!\"."
string2 = "abc"
string3 = 'She said, "Oh, wow!".'
print(string1)
print(string2)
print(string3)
They can be integer or have a fractional part. No need for quotation marks here, although in most situations, it works with them as well. You should try to avoid this, though, since it is error-prone.
number1 = 100
number2 = 100.54
number3 = number1 + number2
print(number1)
print(number2)
print(number3)
A special kind of variable named after
George Boole
. A boolean value can either be
true
or
false
. We will see later what we can do with that.
playerIsDead = false
luaIsCool = true
print(playerIsDead)
print(luaIsCool)
8.2 Expressions
One job of
expressions
is the ability to combine several tasks to get a result. An example of this is a calculation. It follows basic math rules (point before line calculation) and you can also use parenthesis.
calculation1 = 100 * 0.75 - 50
calculation2 = 100 * (0.75 - 50)
print(calculation1)
print(calculation2)
This code will result in
25.0
resp.
-4925.0
being assigned to
calculation1/2
.
Another action which can be done is
string concatenation
:
name1 = "Michael"
name2 = "Brad"
greeting = "Hello " .. name1 .. ", hello " .. name2 .. "!"
print(greeting)
What might look a bit confusing at first sight is nothing special at all. Under normal circumstances
greeting = "Hello "
would be a complete string definition, because it starts and ends with quotation marks. In the last chapter we talked about Lua throwing an error if after the second " comes something else. We can prevent this from happening by adding two dots (
..
) to tell Lua:
Wait, there is more to come! And just append it to our string!
In this case, we append the variables
name1
and
name2
to our string, mixed with some "normal text".
name1 = "Michael"
name2 = "Brad"
names = name1 .. name2
print(names)
The above code results in
names
containing
MichaelBrad
.
You can also mix strings and calculations by doing:
sentence = "I am " .. 50 * 0.58 .. " years old"
print(sentence)
The only important thing is to interrupt the string and add your calculation via string concatenation. If you simply did
sentence = "I am 50 * 0.58 years old"
print(sentence)
it would be treated just as text. We do not want to stress Lua with having to search through all our text for possible calculations, do we?
8.3 Functions
Functions are very important for a programming language. Up to this point, we defined variables, but we had no opportunity to actually use them. Functions usually do something and can be re-used as many times as you want. Let us start with a basic Lua function: print().
print("Hey, Azeroth!")
It prints the text to a predefined place, mostly called
console
. If you execute this command at the online Lua interpreter, the text gets displayed in the output section. If you use WowLua, it will get displayed in its built-in console. If you type
/run print("Hey, Azeroth!")
into WoW's chat, it will get displayed in your standard chat frame (without anyone except you being able to see it).
Dissection:
print("Hey, Azeroth!")
function name
parenthesis surrounding the argument(s)
argument
You usually need to provide all needed information for a function to be executed. This information is called
arguments
. In the case of
print
we only need to specify
what
should be printed. Another function is
RandomRoll
. It takes two arguments:
RandomRoll(1, 54)
It does the same as typing
/rnd 1-54
into the chat, but you can use it in your addons. The first argument here is
1
, the minimum number to roll, the second one is
54
, the maximum number to roll.
Both
print()
and
RandomRoll()
are predefined by Lua/WoW and ready to use. But we can also create our own functions. Functions are borrowed from mathematics. In maths we have functions like
f(x) = x ^ 2
or
y = x ^ 2
which implies: If you provide an
x
value, you can calculate the
y
value and vice versa. Translated into Lua this would be:
function square(x)
return x * x
end
We have a few important semantic structures here. First is the keyword
function
, which tells Lua that a function is being defined. Second is the name of the function, here
square
. It has the same naming restrictions as a variable. Third is the
(x)
which is our list of parameters, here only one. Then follows the function body which contains everything the function does. Often, functions use the keyword
return
- we will see what that does below. The function definition is completed via the keyword
end
.
Now your function is ready to use just like any other prior discussed function:
function square(x)
return x * x
end
print(square(2)) -- prints '4'
print(square(4)) -- prints '16'
If you want to assign the result of the function to a variable, just do it:
y = square(2)
The reason this works is
return
. Without that, the function would not be of any use because it does not tell you its result. You could print your calculated value inside the function via
print(x * x)
, but most probably you do not always want to have the function's result printed to the console. So it makes sense to seperate calculation logic from output logic here and not handle both of them inside your function. The smaller your functions are (and the less complex), the greater is the possibility of reusing them somewhere else in your code. In addition to this, smaller functions are also a lot more readable and understandable.
A function in Lua can have
multiple
return values. See this code:
function dummy()
return "Hey", 54.54
end
string1, number1 = dummy()
print(string1)
print(number1)
string1
is assigned
Hey
,
number1
is assigned
54.54
.
I am going to provide a real world WoW example with a user defined function and some functions defined by the API involved. It is a player data tool. We are going to make use of the following WoW API functions:
UnitName()
UnitLevel()
GetSpecialization()
GetSpecializationInfo()
You should take some time and read the documentation pages so that you get used to their style and see what these functions do in detail.
function getPlayerInformation()
playerName = UnitName("player")
playerLevel = UnitLevel("player")
specId, specName = GetSpecializationInfo(GetSpecialization())
return "Hey, I'm " .. playerName .. " (Level " .. playerLevel .. "). I'm currently in spec " .. specName .. "."
end
print(getPlayerInformation())
Explanation:
UnitName()
accepts a
unitID
as a parameter. This can be e.g.
"player"
(for the current player) or
"target"
(for your target) or
"group2"
(for the second player in the player's group).
UnitName()
returns two values, but since we are only interested in the first here, we can omit the second.
UnitLevel()
returns the level of the specified
unitID
.
GetSpecializationInfo()
, among others, has the spec name of the player as the second return value. It needs to get passed a
specID
as an argument. We get this
specID
via
GetSpecialization()
.
GetSpecialization()
returns the position of your active spec in the Specs & Talents window. For Rogues Assassination is at the top (1), Outlaw in the middle (2) and Subtlety at the bottom (3).
8.4 Conditional Statements, Boolean Values, Relational Operators
Before we continue, we should take a look at the aforementioned boolean values. A boolean variable can be of two states:
true
and
false
. With this you can design complex expressions which either evaluate to a single
true
or a
false
. Example: You want to output a text, but only if the following conditions are met: The player is dead, must be a level 97 Warrior and inside a guild. The text should not be shown on Sundays.
To link up these conditions, we have the following two
logical connectives
in Lua:
AND
OR
We also have the possibility to compare variables or expressions. For this we need
relational operators
or
comparison operators
, which evaluate to
true
or
false
:
< (less than)
<= (less than or equals)
> (greater than)
>= (greater than or equals)
== (equals)
~= (not equals)
These operators and logical connectives can be used in
if statements
, which, depending on their evaluation, execute code (or not).
A simple
if statement
would be:
theWowApiRules = true
if(theWowApiRules == true) then
print("The WoW API rules!")
end
You can almost always read
if statements
as a sentence:
If the WoW API rules, then print "The WoW API rules!".
You can also do calculations in
if statements
and append an alternative part to execute, if our condition was not met:
a = 500
b = 200
if(a * b < 50) then
print(a .. " * " .. b .. " is smaller than 50")
else
print(a .. " * " .. b .. " is greater or equal than 50")
end
With
elseif
you can check for as many conditions as you want:
pl = UnitLevel("player")
if(pl == 60) then
print("Hey, you're max level Vanilla and SL at the same time! Not confusing at all!")
elseif(pl == 70) then
print("Hey, you're max level BC!")
elseif(pl == 80) then
print("Hey, you're max level WotLK!")
elseif(pl == 90) then
print("Hey, you're max level MOP!")
elseif(pl == 100) then
print("Hey, you're max level WoD!")
elseif(pl == 110) then
print("Hey, you're max level Legion!")
elseif(pl == 120) then
print("Hey, you're max level BFA!")
elseif(pl > 120) then
print("Hey, you're from the future!")
else
print("Hey, you're not max level at all!")
end
More examples:
overall, equipped = GetAverageItemLevel()
if(equipped < 9000) then
print("Not viable for Razorfen Downs!")
end
See
GetAverageItemLevel()
.
if(UnitLevel("player") > 1) then
print("You at least levelled up once!")
end
weekday, month, day, year = CalendarGetDate()
if(weekday == 1) then
print("Happy Sunday!")
end
See
CalendarGetDate()
.
if(UnitName("target") ~= "Hodor") then
print("Your target is not Hodor!")
end
Back to boolean values, we should learn the evaluation of basic logical operations:
A
Logical operator
B
C (result)
true
AND
true
true
true
AND
false
false
false
AND
true
false
false
AND
false
false
Read: The expression
A AND B
only evaluates to true, if both A and B are
true
. Reverse conclusion: If either of A or B is
false
, they can't be both
true
any more, which is required for
A AND B
to evaluate to
true
.
A
Logical operator
B
C (result)
true
OR
true
true
true
OR
false
true
false
OR
true
true
false
OR
false
false
Read:
A OR B
only evaluates to false, if both A and B are false. Reverse conclusion: It is enough for one of A or B to be
true
to evaluate
A OR B
to
true
.
So
AND
only evaluates to
true
if both conditions are true,
OR
evaluates to
true
if at least one of the two conditions is
true
. With only these two comparisons and one more element you can already do complex evaluations. The element missing is
NOT
. With
NOT
you can turn around the meaning of a partial expression.
NOT true AND NOT true
requires both partial expressions not to be
true
(i.e. to be
false
) to evaluate to
true
.
You cannot really explain this very well via words, you have to think about it logically on your own to fully understand.
NOT A
Logical operator
B
C (result)
true
AND
true
false
true
AND
false
false
false
AND
true
true
false
AND
false
false
Read:
NOT A AND B
evaluates to
true
, if A is
false
and B is
true
.
NOT A
Logical operator
B
C (result)
true
OR
true
true
true
OR
false
false
false
OR
true
true
false
OR
false
true
Read:
NOT A OR B
evaluates to
false
, only if the exact opposite is the case.
NOT A
Logical operator
NOT B
C (result)
true
AND
true
false
true
AND
false
false
false
AND
true
false
false
AND
false
true
Read:
NOT A AND NOT B
only evaluates to
true
if both of them are
false
. This is a reversed
A AND B
, which evaluates to
true
, if both A and B are
true
.
NOT A
Logical operator
NOT B
C (result)
true
OR
true
false
true
OR
false
true
false
OR
true
true
false
OR
false
true
Read:
NOT A OR NOT B
evaluates to
true
if either of them is
false
.
You can easily test this in Lua. Just write:
print(true and false)
You can keep working with A and B as well:
a = true
b = false
print(a and b)
If you create more complex expressions, you should know that
AND
has a priority over
OR
.
print(true or true and false)
The code above outputs
true
. Lua first evaluates the partial expression
true and false
to
false
and after that the remaining expression is:
print(true or false)
which is
true
. To make that clearer to the reader or if you want to change the order you can put in parenthesis:
print((true or true) and false)
evaluates to
false
. Now Lua first evaluates the partial expression
true or true
, which is
true
, and then the remaining expression
true and false
, which is
false
.
Small exercise: Find out what the following expression does:
(NOT A AND B) OR (A AND NOT B)
Solution
This is called
mutual exclusion
or
exclusive or
. In some programming languages you can write this via a simple
A XOR B
. For this to evaluate to
true
either A
or
B has to be
true
, but not both. The parenthesis would not be needed here, but they increase readability:
a = true
b = false
print((not a and b) or (a and not b))
So let us assume we have the following variables and let us assume we have determined their values prior to this (for the sake of this example we set the variables to true or false directly).
playerIsDead = true
playerIsWarrior = true
playerIsLevel97 = true
playerIsInGuild = true
isSunday = false
if(playerIsDead == true and playerIsWarrior == true and playerIsLevel97 == true and playerIsInGuild == true and isSunday == false) then
print("Whatever.")
end
Another (shorter) representation of this would be:
playerIsDead, playerIsWarrior, playerIsLevel97, playerIsInGuild, isSunday = true, true, true, true, false
if(playerIsDead and playerIsWarrior and playerIsLevel97 and playerIsInGuild and not isSunday) then
print("Whatever.")
end
As an exercise you could try to get these boolean values from the WoW API instead of assigning dummy values as we just did.
8.5 Loops
While programming, you will often need a possibility to execute a piece of code a certain amount of times. This is done via loops. The standard loop is the
for loop
.
for var = start, end, increment do
-- code
end
You need to provide three values:
start
- A number to start your loop with.
end
- A number at which your loop should end.
increment
- A number your start value should be incremented by with every step. If omitted, this defaults to
1
.
for i = 1, 10, 1 do
print(i)
end
The above code outputs 1 to 10 in the console. It starts counting at 1, ends at 10 and in each step increments the
i variable
by 1.
Another form of loop is the
while ... do
loop:
while(condition) do
-- code
end
As long as
condition
evaluates to true, this loop is done over and over again. Example:
i = 1
while(i <= 10) do
print(i)
i = i + 1
end
So as long as
i
is less than or equal to ten it gets printed and
i
gets incremented by 1 via
i = i + 1
. Do not forget to put that in or else you would have an infinite loop, because
i
always stays at 1 and the condition never evaluates to
false
.
There is a variation of this loop,
repeat ... until
:
repeat
-- code
until(condition)
The difference to
while ... do
is that its code gets at least executed once because the condition gets evaluated
after
the code execution.
loop = false
repeat
print("Hey.")
until(not loop)
This leads to the code above printing
Hey.
once, although
loop
was set to
false
right at the beginning.
8.6 Tables
Tables
are a special kind of variables. With their help we can assign more than a simple "Hello Azeroth!" to a variable. We could save many more greetings in a table. Lua does not know of
arrays
, like many other languages do, instead it uses
tables
.
Let me give a basic example:
greetings = {}
greetings = "Throm-Ka"
greetings = "Chronakai Kristor!"
greetings.Thalassian = "Bal'a dash, malanore" -- Another method to fill the table
Notice the difference in declaration in the last line.
We initialise our table by writing
greetings = {}
. We can then add
indices
or
keys
(the
is called an
index
or
key
) to put entries into the table. Tables are often used to be filled dynamically and to organise code structure.
print(greetings)
would output
Throm-Ka
. There is an alternative way to fill tables with data, let us call it inline filling:
greetings = {
= "Throm-Ka",
= "Chronokai Kristor!",
= "Bal'a dash, malanore"
}
This would result in the exact same table as above, but maybe a bit more clearly arranged. Let us combine a few things we have learnt before and some new things to create a table that can be filled by a function.
greetings = {}
function addGreeting(index, content)
greetings = content
end
function getGreeting(index)
return greetings
end
addGreeting("Orcish", "Throm-Ka")
addGreeting("Draenei", "Chronokai Kristor!")
addGreeting("Thalassian", "Bal'a dash, malanore")
print(getGreeting("Draenei"))
With our function
addGreeting()
we fill the
greetings table
with values, with
getGreetings()
we retrieve items from the table. In reality, you would not do it this way, because the method mentioned above (writing
greetings = "xyz"
) results in less code. You cannot write
greetings.index = content
inside
addGreeting
; this way you would only create and override the same table entry with the literal key
index
over and over again.
But what if we do not know what is inside our table, because e.g. we received it from a WoW API function as a return value? Then we can iterate through a table with a special for loop construct:
-- Let us pretend we do not know the content of the following table and thus we also do not know how to access the values by their key names
greetings = {
= "Throm-Ka",
= "Chronokai Kristor!",
= "Bal'a dash, malanore"
}
for key, value in pairs(greetings) do
print("Language: " .. key .. ", greeting: " .. value)
end
But we cannot only give strings as indices, we can also populate a table with numeric indices:
number = 1
potencies = {}
while(number < 4096) do
number = number * 2
table.insert(potencies, number)
end
print("Our table has " .. #potencies .. " entries.")
print("The tenth potency of 2 is " .. potencies .. ".")
print("The first " .. #potencies .. " potencies of 2:")
for i = 1, #potencies, 1 do
print(potencies
)
end
In this piece of code we fill a table with the first 12 potencies of 2.
We first declare our empty and to-be-filled table potencies
. With a
while loop
and the
table.insert()
function we fill it. Notice how we do not provide an index at all. After that we can access the size of the table (its number of elements) via
#<tablename>
. We can access single elements by their automatic, incrementally assigned numeric keys (e.g.
potencies
for the eighth potency of 2). In the end we loop through the table, starting from the first element, ending at the last element (which is the size of the table) and incrementing by one.
If you have read this guide carefully and did some coding yourself, you should now understand this piece of code pretty quickly.
Last but not least, let us look at an example in WoW:
numCollectedMounts = 0
mountIDs = C_MountJournal.GetMountIDs()
for i = 1, #mountIDs, 1 do
creatureName, spellID, icon, active, isUsable, sourceType, isFavorite, isFactionSpecific, faction, hideOnChar, isCollected, mountID = C_MountJournal.GetMountInfoByID(mountIDs
)
if(isCollected == true and faction ~= 1) then
numCollectedMounts = numCollectedMounts + 1
end
end
print(numCollectedMounts)
Used functions:
C_MountJournal.GetMountIDs()
,
C_MountJournal.GetMountInfoByID
. C_MountJournal.GetMountIDs()
returns a table with every mount ID in the game. We can then walk through this table with a
for loop
and get additional information about this mount ID by passing it to
C_MountJournal.GetMountInfoByID()
as an argument. Via its return values we can check if the mount with the given ID is collected and if its faction is not
1
(Alliance). So we will get the number of mounts collected for a Horde character as a result and print it to the chat.
8.7 Commenting Code
After all this heavy stuff let us do something refreshingly easy. You can, and should, should, should! comment your code. While coding you might think that you are doing obvious things, but if it starts getting more complex it certainly is not as obvious any more, especially not after a couple of months. It is a big load of work if you then have to learn your code again.
And if at sometime another person wants to work with your code, it can be tremendously difficult for them to understand what you did. So try your best to describe what you are doing and why you are doing it. There are two possibilities to comment:
-- One line comment
--]
After a bit of practice you will make better and better decisions on what to comment or not and how.
-- Add b to a
sum = a + b
This is something you should not do, because it
really
is self explanatory. If you feel the urge to write something about this line, describe
why
you add these two numbers.
9 Variable Scoping
Before we ultimately start with our example addon, you should know about
variable scoping
. All WoW addons share the same execution environment. A variable or function defined by your addon can be read/used by every other addon installed if you do not make use of scoping. You could do this on purpose, to give other addons the possibility to read some data (e. g. table data) of your addon. But then you should make sure that your variable or function has a unique name. A variable is automatically defined
global
when writing:
playerScore = 1337
If another addon uses this variable name as well, also defined globally, it most possibly leads to unpredicted behaviour. Two addons using the same variable leads into chaos; you cannot predict the content of the variable any more.
You
could use it as a storage for numbers, but what if the other addon overrides it with a string? This will lead to Lua errors on your side while trying to calculate something with your variable.
It is the same with functions. A user-defined rounding function
round(number, decPlaces)
is named in such a standard way, that, if you have many addons installed, there is a high chance of another addon declaring the same function. If it is not an exact copy of your rounding function, defining the same parameters and return values, this is going to result in errors, either at your side or on the side of the other addon. So, what to do? Either name your variables and functions in such a way that no other addon will interfere or make them
local
.
For the first solution a good approach is prefixing your object names with your addon's name: AddonName_playerScore. The second solution introduces the keyword
local
:
local n1 = 1
local function add(p1, p2)
local a = "b"
return p1 + p2
end
print(add(n1, 5))
print(a)
Here, the variable
n1
and the function
add
are declared as
local
. Only the lua file it is in can use them, no other addons can access it. If your addon consists of more than one lua file, something that we are not going to look at in this guide, you cannot access this variable from the other files. Note that the second variable declaration,
a
, is only available inside its code block (the function). Local variables always want to be the least visible. The
print(a)
at the end will result in
nil
, because
a
is not accessible outside of its "home zone".
The function
add
, which has a very common name, is also declared
local
, so nothing interferes with it. A good article about scoping which goes a lot more into detail can be found under
http://et.worldofwarcraft.wikia.com/wiki/Lua_variable_scoping
.
10 Example Addon
In this chapter we are going to establish our first real connections between WoW and Lua by developing a simple addon.
10.1 Events
All of WoW is based on so called
events
. An event is
something, that happens
. Type
/etrace
(short for eventtrace) into the chat and you will see a list of events which are happening in your environment. If you start scrolling the list, it stops automatically scrolling to the bottom and you can have a closer look.
Some events that you can identify easily this way:
UNIT_SPELL_CAST_START - When someone around you casts a spell
UPDATE_MOUSEOVER_UNIT - When you hover over a unit
PLAYER_STARTED_MOVING - When you start moving
PLAYER_DIED - When you die
CHAT_MSG_WHISPER - When you receive a whisper
CHAT_MSG_MONSTER_SAY - When an NPC says something
And there are many more events ticking without you even doing anything.
"Why are events so important?", you might ask. Well, they tell you what is going on. They give you the possibility to actually
react
to something that just happened in the game.
You want to send the infamous "Congratulation for this Achievement!" into the guild chat? You need to react to ACHIEVEMENT_EARNED.
You want to automatically
/bow
to a player after a duel? Listen to DUEL_FINISHED.
An (I think) complete list of events can be found here:
https://wowwiki.wikia.com/wiki/Events_A-Z_(Full_List)
The only thing you have to do is to subscribe to an event and then you will get noticed when it happens. Alongside this notification comes important data, which depends on the event. If you get noticed about someone writing in the guild chat, there is a chance that you will want to know which player wrote this and what he wrote. After this you can execute your code. We will look deeper into it as we continue writing our first addon.
10.2 Getting Started
Create a folder
TestAddon
in
C:\Program Files\World of Warcraft\_retail_\Interface\AddOns
with two text files inside:
TestAddon.toc
and
TestAddon.lua
. This is everything needed to let your addon show up ingame and to make it work. If you start WoW now, you can already see your addon inside the addon manager.
10.3 .toc file
For a good reference about the .toc file see
https://wow.gamepedia.com/TOC_format
.
A perfectly valid .toc file for our addon would be:
## Interface: 80250
## Title: Test Addon
## Notes: Just playin' around.
## Version: 0.1
TestAddon.lua
toc
is short for
table of contents
and the file provides meta information about your project. The
Interface
tag states the WoW version your addon was designed for. If this is lower than your WoW's version, your addon could be marked as deprecated by WoW (but is in most cases still perfectly usable).
Title
is the name of your addon. It can be different from the folder and file name of your addon. Since addon folders cannot have spaces in them, we can add them here.
The
Notes
text is shown as a tooltip when hovering over your addon inside WoW's addon manager.
Version
is your addon's version. It can be any text like
0.1
or
1.0.3
or
8.2 build 1
or
v1.0
.
After the tags comes a list of files used by our addon. WoW will load them in the order of appearance from top to bottom. In our case we only have one file. Restart WoW to see the changes in the addon manager.
10.4 .lua File
The .lua file we created now basically is our addon. The code in this file gets executed everytime the addon is loaded, right after entering the game or reloading. Write down a simple
print("Hello, Azeroth!")
and start your WoW client. After logging in, you should see the output of your print command in the chat window. Now change your string-to-print to something else and do a
/reload
. You will see your updated output. Note that we do not need to use
/run
here, because we are already inside an addon. Everything here is treated as code.
10.5 Addon Idea
Before you start developing your addon, you should think about what features it should have in its first version. It is perfectly viable to start with a small and working set of features, knowing that you can expand functionality in future versions. For our example addon we want to do the following: Our addon should react to messages in the raid chat. If the right command was issued to the raid chat, we want every user of the addon to output his/her gear durability. For this we need to listen to the events
CHAT_MSG_RAID
and
CHAT_MSG_RAID_LEADER
. The raid leader indeed causes a different event to fire when issueing a raid message compared to normal raid members. When handling these events, we somehow need to gain access to the durability information of a player's gear. Let us start with the event handling.
10.6 Frames
To listen to an event, we first need to create a
Frame
. Almost everything in WoW is a frame. Type in
/fstack
(framestack) and you will see a popup outputting debug information about the frame you are currently hovering over with your mouse. If you hover over an action bar, you will see that its buttons are named
ActionButton{n}
, where
{n}
is a sequential number. Type in
/run ActionButton4:Hide()
and it will hide your fourth action button.
:Show()
will make it visible again. You can do this with almost every frame you explore via
/fstack
.
Frames are nested. The "master" frame is called
UIParent
. Every other frame of the WoW UI was put explicitly into UIParent (or implicitly, by putting it into a frame which already is inside UIParent). Type in
/run UIParent:Hide()
and your whole interface disappears (just like when you press ALT + y). So hiding a frame hides the frame itself and every other frame inside it. And since UIParent is the master frame every other frame was put into, everything gets hidden.
So the WoW UI consists of a frame hierarchy. Open your character menu. The whole menu is a frame by itself. But you can already guess that probably also the close button and the title bar at the top with your character name and title are both frames which were put into the character frame. Every gear slot is a frame, too. The frame debug output always shows the complete frame hierarchy of your hovered frame. The specific frame you are hovering is displayed at the top of the frame list, UIParent is at the bottom. You will also see groups like
MEDIUM
,
TOOLTIP
or
BACKGROUND
inside the debug output. Frames follow a basic layout system, called
FrameStrata
. These settings establish the frame order management, i. e. which frame should overlap another frame when shown at the same time. Choosing the wrong frame strata for your graphical UI components could lead in e. g. your frames overlapping the full-screen map (when pressing
m
) which is probably not what you wanted them to do.
10.7 Addon Loading Process
Clear your
TestAddon.lua
and insert the following code:
PlayerFrame:Hide()
. Reload. Your character frame showing your portrait and resources is now hidden.
Now choose
ChatFrame1:Hide()
and reload. It is still visible. What went wrong? At the time our addon code gets executed (as soon as it is loaded), the UI is not fully available yet or modified afterwards by Blizzard's code: We hide the frame, but apparently Blizzard's code makes it visible again afterwards. This does not have to occur every time, but we should not make the functionality of our addon dependent on chance. When logging in or reloading, a fixed order of events are fired.
Here
is a very good and detailed article about it. The first event fired is
ADDON_LOADED
, but
PLAYER_ENTERING_WORLD
sounds more interesting, due to "Most information about the game world should now be available to the UI.". So let us try listening to
PLAYER_ENTERING_WORLD
and hiding our frame inside the event handler:
local EventFrame = CreateFrame("frame", "EventFrame")
EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
EventFrame:SetScript("OnEvent", function(self, event, ...)
if(event == "PLAYER_ENTERING_WORLD") then
ChatFrame1:Hide()
end
end)
On line 1 we create a frame with
CreateFrame()
. The first argument states that we just want to create a basic frame, the second is a name of our choice. As I said before, almost everything in WoW coding is a frame. The frame we have just created does not show up in the UI, it is just a helper frame for us which makes it possible to listen to events. In the second line we register our event. The frame now subscribes to the event and is able to react to it. We could also register multiple events here to listen to by duplicating line 2 and altering its argument. Line 4 says: If any event the frame was registered to occurs ("OnEvent"), execute the following function. This sort of function is called an anonymous function. We cannot call it manually; it gets executed everytime our event frame gets the notification about an event. The parameters
self
(a reference to our event frame) and
event
(the name of the event which was fired) are always provided. Then follows
...
which is a variable amount of parameters depending on our event. Since we only care about the event firing itself and not about those parameters, we can ignore them for now. Inside the anonymous function comes a simple
if statement
checking our event name. Since our frame only subscribed to one event, this can never be a different value. In this case we could omit the
if statement
. But if you listened to more than one event you would have to make this differentiation. Inside the
if code block
finally comes the code for our event. Reload. Still, nothing is hidden. Apparently, we have found an edge case here. The ChatFrame1 seems to be altered by Blizzard even
after
PLAYER_ENTERING_WORLD
. Now we need to be creative. A simple
C_Timer.After(1, function() ChatFrame1:Hide() end)
seems do to the job.
C_Timer.After()
executes a function after a set amount of seconds. We use another anonymous function for executing our
Hide()
. Another variant of this would be:
local function HideOurFrame()
ChatFrame1:Hide()
end
local EventFrame = CreateFrame("frame", "EventFrame")
EventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
EventFrame:SetScript("OnEvent", function(self, event, ...)
if(event == "PLAYER_ENTERING_WORLD") then
C_Timer.After(1, HideOurFrame)
end
end)
Instead of inserting an anonymous function, we now add a reference pointing to our HideOurFrame() function. Because this is a reference, which is technically not a function call, we are not supposed to add
()
here.
There are probably better ways to do this. When experiencing such an issue, you have to do some trial and error. Another solution I can think of would consist of adding an
OnShow script
to the ChatFrame1 which gets executed everytime the frame is shown. Inside the handler you could then hide the frame again. This translates to: Each time the frame shows up, hide it.
Since this was a very technical chapter, I'll provide a TL;DR:
If you want to execute code right after entering or reloading the game, have the
addon loading process
in the back of your mind. Not every information inside WoW is instantly available or fully processed like some UI elements or API functions which provide access to ingame data (mounts, pets, achievements ...).
10.8 Our Main Event Handler
With the knowledge from the previous chapters we can write the following code:
local EventFrame = CreateFrame("frame", "EventFrame")
EventFrame:RegisterEvent("CHAT_MSG_RAID")
EventFrame:RegisterEvent("CHAT_MSG_RAID_LEADER")
EventFrame:SetScript("OnEvent", function(self, event, ...)
if(event == "CHAT_MSG_RAID" or event == "CHAT_MSG_RAID_LEADER") then
print("Raid Message Event Captured!")
end
end)
We subscribe to the two raid chat events and state inside our event handler that when these events occur,
Raid Message Event Captured!
should be printed to the chat. Try it out. Now that we took this first hurdle, we can go into detail. We do not want to post durability information on every raid message received, because this would lead to serious spamming. So we want to tie our program to a specific text message, let us say
Durability Check!
. To make that possible, we need to know the text content which was sent into the raid chat. Now it is time for the three dots (
...
) to shine. When you look at
https://wow.gamepedia.com/CHAT_MSG_RAID
, many different variables are shown: "text", "playerName", "languageName", "channelName", "playerName2", "specialFlags", zoneChannelID, channelIndex, "channelBaseName", unused, lineID, "guid", bnSenderID, isMobile, isSubtitle, hideSenderInLetterbox, supressRaidIcons We call this the event payload. All these variables are represented by the three dots. How to extract them? Like this:
local text, playerName, languageName, channelName, playerName2, specialFlags, zoneChannelID, channelIndex, channelBaseName, unused, lineID, guid, bnSenderID, isMobile, isSubtitle, hideSenderInLetterbox, supressRaidIcons = ...
Fortunately, the very first variable is what we need, so we can just write:
local text = ...
If we also needed
playerName
, we had to write:
local text, playerName = ...
or
local _, playerName = ...
In the above case the
text
variable would be skipped and not assigned.
Now we can continue with our checking:
local text = ...
if(text == "Durability Check!") then
print("Starting Durability Check...")
end
Test it by reloading and typing
Durability Check!
into the raid chat. Now it is time to actually implement our durability check. If you do not know the Lua API functions for this yet, you have to search the websites I mentioned at the beginning of this guide or consult a web search engine. For this last time, I will do the work for you. We need
GetInventoryItemDurability(slotIndex)
. It takes a numerical
inventory slot index
as an argument. Unfortunately, we should not use those IDs directly because they could change in future WoW versions. So if at the current patch the head slot has the ID 10, it could be 7 in a future patch. Instead, we have to use the constants stated
here
. These constants are always mapped to the correct slotId. We put all the constants inside a table:
local inventorySlotConstants = {
INVSLOT_HEAD,
INVSLOT_SHOULDER,
INVSLOT_CHEST,
INVSLOT_WAIST,
INVSLOT_LEGS,
INVSLOT_FEET,
INVSLOT_WRIST,
INVSLOT_HAND,
INVSLOT_MAINHAND,
INVSLOT_OFFHAND
}
These slots are the ones with a durability attached to them. We can now walk through this table each time a durability check is initiated and do our calculations:
local curTotal = 0
local maxTotal = 0
for key, value in pairs(inventorySlotConstants) do
local current, maximum = GetInventoryItemDurability(value)
curTotal = curTotal + current
maxTotal = maxTotal + maximum
end
Here we iterate through our table and call
GetInventoryItemDurability()
for each slot. Then we add our current and maximum durability of all items. With these information we can calculate a percentage and send it to the raid chat:
local message = curTotal / maxTotal * 100 .. " %"
SendChatMessage(message, "raid")
You now have a fully working addon. There are still many things you can do to improve its usability: The average durability of all items is a good thing to know, but what if every gear slot durability is at 100% except for your weapon, which is at 5 %? You could identify the lowest durability item and include it in the output. You will also see that the percent output will lead to huge numbers, due to their fractional part. You will need a rounding function. See
here
, for example.
This is our complete code:
local EventFrame = CreateFrame("frame", "EventFrame")
EventFrame:RegisterEvent("CHAT_MSG_RAID")
EventFrame:RegisterEvent("CHAT_MSG_RAID_LEADER")
local inventorySlotConstants = {
INVSLOT_HEAD,
INVSLOT_SHOULDER,
INVSLOT_CHEST,
INVSLOT_WAIST,
INVSLOT_LEGS,
INVSLOT_FEET,
INVSLOT_WRIST,
INVSLOT_HAND,
INVSLOT_MAINHAND,
INVSLOT_OFFHAND
}
EventFrame:SetScript("OnEvent", function(self, event, ...)
if(event == "CHAT_MSG_RAID" or event == "CHAT_MSG_RAID_LEADER") then
local text = ...
if(text == "Durability Check!") then
local curTotal = 0
local maxTotal = 0
for key, value in pairs(inventorySlotConstants) do
local current, maximum = GetInventoryItemDurability(value)
curTotal = curTotal + current
maxTotal = maxTotal + maximum
end
local message = curTotal / maxTotal * 100 .. " %"
SendChatMessage(message, "raid")
end
end
end)
11 Conclusion
I hope I could give you an understandable entry into programming with Lua inside WoW. This was only a small fraction of what you can do. There are many possibilities to combine the things you have learnt. There are endless possibilities to combine the things you have learnt with the things you still can explore. We did not look at UI design in WoW at all. This could be part of another guide. But if you are interested in WoW coding and mostly understood everything in this guide, I am sure you can also get the information by yourself.
Things we also did not look into:
Object oriented programming with Lua:
https://www.lua.org/pil/16.html
Make use of slash commands (like
/addonname
):
https://wowwiki.fandom.com/wiki/Creating_a_slash_command
Regular expressions:
http://lua-users.org/wiki/PatternsTutorial
Saved Variables (basically just a table which WoW can save and load for you):
https://wowwiki.fandom.com/wiki/Saving_variables_between_game_sessions
Ace3: A library helping you with creating addons. You should probably not make use of it if you are still learning:
https://wow.gamepedia.com/WelcomeHome_-_Your_first_Ace3_Addon
Invisible/silent communication between addon users:
https://wow.gamepedia.com/API_C_ChatInfo.SendAddonMessage
What you should be able to do now is to create addons which react to chat messages and all kind of events. You can then do calculations or other program logic to get the desired result. Graphical output can be very nice but at the same time very time consuming. Focus on learning the WoW API first, then look into UI programming. You could also work with the existing UI and extract information from it or alter it. Use
/fstack
to get the names of your objects of interest. You can then explore their content with
/dump ObjectName
. You can already automate many things if you know the names of buttons, option frames (like the ones which are given to you by quest givers) and other frames and play around.
Looking at the guide now it became quite huge. I started writing more than two and a half years ago. Originally, I wanted to include a lot more but then decided to prefer publishing it over making it grow even more. If this guide manages to help a lot of people, I will write separate and smaller ones on other topics.
Have a lot of fun and keep exploring!
[Get Wowhead]
高级会员
[$2]
[A Month]
[Enjoy an ad-free experience, unlock premium features, & support the site!]
评论
评论来自
Vizions77
Thanks so much for the post! I know very little about coding and Lua, but with your guide I do feel a little more confident in learning. Would love to see a part two. Great work!
评论来自
Anderphil
Thanks a lot, I was able to make my first functional addon from this. Great write up!
评论来自
bando
I'm a python developer with no lua experience. This guide is extremely useful!
I've occasionally glanced at some of the code in addons and while it sometimes made sense, there were times I had no idea what it was doing. After reading this guide, I now see why.
Object.key = variable
The above is appending to a table! (rather than assigning an attribute of the object)
Or even the fact that arrays don't exist.
Also, understanding that things happen inside a frame makes so much more sense now.
Superb job!
I'd be curious if you have recommendations on an IDE for beginners. (something akin to JetBrains with excellent intellisense to help newbies from making mistakes)
评论来自
Edglar
I am so incredibly grateful you put this together. I will echo a previous poster in saying I would absolutely love to see part 2 and beyond! I am no programmer, yet this guide was an excellent stepping stone towards creating simple addons...something I've been looking for forever. Bravo!
评论来自
SpaceBarBob
Surely someone reading this can create a super basic "hide minimap during combat" addon in a spare minute and post a link <3
评论来自
sconley
<> is not a valid operator in WOW lua. You have to use ~=. I tested this with the wowlua addon and with lua for windows for lua 5.1.
评论来自
SubbyDK
I know it's way to late, but will post it anyway.
-- Register events
MinimapCluster:RegisterEvent("PLAYER_ENTERING_WORLD")
MinimapCluster:RegisterEvent("PLAYER_REGEN_DISABLED")
MinimapCluster:RegisterEvent("PLAYER_REGEN_ENABLED")
-- Call function based on event.
MinimapCluster:SetScript("OnEvent", function(self, event, ...)
-- Check to see if we are in combat.
if UnitAffectingCombat("player") then
-- We are in combat.
self:Hide()
else
-- We are not in combat.
self:Show()
end
end)
评论来自
zxeltor
A great introduction. Thanks for taking the time to do this.
评论来自
Arenvalde
I have watched and read about a dozen tutorials for wow addon coding over the last... 5 hours.
I still cannot get a ****ing WINDOW to appear in game. I do know how to send chat messages or play sounds, and I've read all about events, which I don't yet care about, but I literally cannot get a single visible element to show up. The only tutorial that seems remotely useful was made for 6.2, and even updated to what I can find in modern API, it does not do what the creator said it does.
Why is this so bloody frustrating?
评论来自
Mvin
Coming from a webdev and Java/C# background, this guide is so very helpful. Compared to most other languages and libraries I've dealt with, the WoW Addon Api kind of seems like the Wild West. There are exhaustive references for all the available functions, but little explanation what they do, with many of them being deprecated or non-functional. On top of that, where to even start? Its hard to find a coherent guide out there that just lays out the basics of what is needed for a "Hello World"-addon. Obviously, your guide does this and more and its a godsend! With the info presented here, I'm confident I can already build what I need to gather some info about player collections.
You said in another comment that an additional chapter for building addon UIs is in the works, which would be the cherry on top of this already great guide.
Thanks for the work!
评论来自
Taraezor
Nice job!
I'm a very old school programmer (any heard of Apple][ Machine Code or CList or JCL?), a journeyman if you like, and I love LUA. Sadly, I am well and truly trapped in a Structured Programming mindset and thus I find a lot of LUA code, which appears to have been shoe horned into a so-called OOP paradigm, to be unreadable.
One more thing... scoping or passing by reference and/or value in LUA... it's not entirely consistent and still tricks me up. Watch out!
评论来自
Wraiyth
This is an amazing guide - thank you so much!
As a complete and utter novice and a definite member of the target audience there is one piece of crucial info missing that had me scratching my head for a good hour. If your TestAddon doesn't load in game read on:
Depending on how you create the text files in your TestAddon folder, even when naming them with the .toc and .lua file extensions they may still save as .txt files. In this case nothing will work and you may be staring blankly at the screen as you recreate the files and reload WoW repeatedly wondering what could be wrong.
When you create the .toc and .lua be certain to "Save As" and ensure it's set to "All File Types" and not just text files like mine was, this should be reflected in windows explorer as they will then correctly show as LUA/TOC under the Type column rather than as txt file.
Took me an embarrassingly long time to figure this out so hopefully it helps someone else at some point :)
评论来自
TornJK
I tried to recreating "SAY" channel instead of Raid as I was too lazy to open a Raid for the addon testing.
However I always receive a error when I try to get the durability to print to chat.
AddOn 'MyExampleAddon' tried to call the protected function 'UNKNOWN()'.
:480: in function <Interface/AddOns/!BugGrabber/BugGrabber.lua:480>
"]: ?
"]: in function `SendChatMessage'
:30: in function <Interface/AddOns/MyExampleAddon/MyExampleAddon.lua:17>
I can use the function SendChatMessage from WowLua or /script just fine. Something I'm overlooking - is "SAY" treated differently than raid?
评论来自
Tjark
This is a very helpful guide! Thank you so much for the introduction. I wanted to get into Lua but it was pretty daunting but this made for a great first stepping stone.
评论来自
Sockemboffer
Not sure if this is a dumb question but does each major version of wow target a new version of Lua (pre and post Shadowlands), if so could that info be placed in a relevant section for environment setup? This guide is amazing, thank you!
评论来自
DerpadinMcknight
I REALLY appreciate this guide as I am trying to develop (hopefully) some functions to a controller-based style of play (an addition to ConsolePort). Thank you for going out of your way to share this information! I would not mind (if you had a Patreon) supporting something! Is it possible some information is coming about frames and custom textures? I'd like to fiddle with the nameplates quite a lot...
评论来自
OrghalPL
Hello,
thank you very much, I am reading and reading and moving forward. Absolutely the best article and manual as I found in last months. Perfect good Job, if you're still in wow it be nice to meeto you someday.
Now... back to learning hahaha
Orghal
评论来自
OrghalPL
Only one small problem to the tutorial and cannot find out what is going on
Trying use
SendChatMessage
but it works only with my self (whisper)
always error if I try send something to SAY or directly to other player - nickname checked, e.g.
SendChatMessage(msg, "WHISPER", nil, "Luicidia")
what to do?
贡献
在发表评论前,请留心以下提示:
您的评论必须为简体中文,否则将会被删除。
不知道如何发评论?参考我们的
格式指南
!
发表前最好先自行校对一次。
有问题可以访问我们的
论坛
来寻求帮助。
发表评论
你没有登录。
请登录
或者
注册账号
来添加你的评论。
使用下面的表格浏览您的截屏。
[Screenshots containing UI elements are generally declined on sight, the same goes for screenshots from the modelviewer or character selection screen.]
质量越高越好!
[Please review our
Screenshot Guidelines
before submitting!]
您没有登录。请
登录
后提交截屏。
将视频URL输入下列表格即可。
URL:
支持:仅限 YouTube
说明:您的视频需通过审核才能在站点上显示。
我们用
Wowhead 客户端
保证数据库的及时更新,向您提供额外的有趣的功能!
两大目的:
它还维护WoW的一个插件
Wowhead Looter
, 在您游戏时采集数据!
它将
采集所得数据
上传至Wowhead,保证数据库时刻掌握最新信息!
您可以用它追踪完成的任务、配方、坐骑、伙伴宠物以及头衔!
您还在等什么?立即
下载客户端
整装待发吧。
我们用 Wowhead 客户端保证数据库的及时更新,向您提供额外的有趣的功能!
两大目的:
您可以用它追踪完成的任务、配方、坐骑、伙伴宠物以及头衔!
您还在等什么?立即 下载客户端 整装待发吧。