PDA

View Full Version : (Share)[Lua] The Ultimate Tutorial: For beginners and advanced users alike.



Noblebeastx
17-03-10, 09:28 PM
The Ultimate Lua Tutorial


Introduction.
Hello everyone, and welcome to my Ultimate Lua Tutorial. This tutorial is aimed at those of you that wish to learn Lua but cannot seem to find a way to do it easily and reliably. Make no mistakes, however, this tutorial will also cover some advanced features of the programming language.

WARNING. THIS THREAD SPANS 5 POSTS. IF YOU DO NOT LIKE WALL OF TEXTS, PLEASE FIND ANOTHER TUTORIAL. TO BE EXACT, THIS TUTORIAL CONTAINS 40223 CHARACTERS.

Introduction to Lua.
Presumably you have come to learn about Lua, and as such you already have a keen understanding of what Lua is capable of. If you're able to script Lua, you are probably going to able to apply for high-ranked jobs in Private Servers, notably Developer spots, as Lua is a very powerful, lightweight language. With the addition of certain Lua Engines (Notably, LuaHypArc, created by 'hypersniper' of WoW-V.com), Lua can almost do as much as C++, and it's much, much easier to understand and write.

Pros and Cons of learning Lua.
Whilst things have their upside, everything has a downside.

Pros

Lightweight.
Powerful.
Simple.
Supported by all ArcEmu servers by default.
It's power is limitless.

"It's power is limitless" - Well, yes it is. Before you smart-asses decide to say "No it's not, <insert reason about C here>", allow me to point out that Lua's coding is based on C. So, as long as you code it in C beforehand, you can do it in Lua.

Cons

Limits you only to ArcEmu servers (Unless Mangos and Trinitycore decide to support Lua, god forbid.)
Although I said it is limitless, you are limited by what has been coded.
It has a C base, so you're better off learning C in the end.

Lua makes for a great first programming language, as it's simple heirachy (Structure) allows for easy construction of complex scripts. The reason I learnt Lua instead of jumping the gun for C++ is that it is easier to learn Lua and then C++ due to the fact that... well, let's consider Lua as pre-school books. It introduces you to the basics of programming and computer interpretation. It's not great, but at the same time, it's a good supplement for HTML.

The Basics.
This Topic is the lowest level you can get inside this forum topic. This topic consists of:

Low-level functions
Arguments
Gossip NPCs
If statements
Else & Elseif
Commenting
Variables

This tutorial assumes you are looking for ArcEmu placements, and therefore we will be focusing on the ArcEmu way of scripting. In this tutorial, I will teach you how to make a Gossip NPC. Before I start, I believe you should have these things before setting off to learn Lua:


A repack/custom compiled core that supports LuaHypArc.
Notepad++.
Some good music (I'm not talking about Miley Cyrus here. I mean AFI, All That Remains, Three Days Grace; etc).
The ArcEmu command list loaded up as a reference.

If you do not have any of these, do not worry! It is not necessary to learn Lua, but it sure will make it a heck of a lot easier. You can find Notepad++ and the ArcEmu command list by pressing the links below.

ArcEmu Default Lua Command List. (http://www.arcemu.info/wiki/index.php?title=Unit_Commands)
Notepad++ Installer (http://sourceforge.net/projects/notepad-plus/files/notepad%2B%2B%20releases%20binary/npp%205.5.1%20bin/npp.5.5.1.Installer.exe/download)

Don't be alarmed! Both of these items are open source and free (As in "Free Speech" and "Free Beer"), which means you cannot be sued (At least, by the creators) for downloading them.

Setting out into the wilderness.
The first thing I always add to my script is a block comment. This allows me to place, at the top of the script (So it is easily readable) what was on my mind when I was creating the script, what I want it to be able to do, and what needs to be done to fix it.

Comments are lines of the code that are not read by the Lua Engine. It completely and utterly ignores them. There are two types of comments; a single line comment, and a block comment.

Single Line comments
A single line comment does what it says on the tin; it blocks out the rest of the line (from where you used the comment identifier). Anything after the so-called comment identifier is completely ignored by the Lua Engine, and subsequently, ArcEmu-World. A single line comment is indentified by two hyphens placed right next to each other. An example can be seen below.


Stuff() -- This is a comment!

What this does, is that it ensures that anything after the -- is ignored by the Lua Engine. Anything placed after it is considered white space by the handler. This is a great method for ensuring that your thoughts are placed on paper.. er, virtual paper, so that you remember them later. It's all very well creating the best script known to man (Good luck with that ^^), but it's useless if you cannot remember what it does.
But what if this comment spans more than one line? Surely, it'd be messy to do this;



-- This
-- Needs
-- To
-- Be
-- Neater
-- ...!

So what in the world do we do? Well..

Block comments
A block comment is a comment that spans multiple lines until you add the closing symbol. Anything between the opening and closing identifiers are completely ignored. This is great for introductory paragraphs at the top of a script or generally indicating a gap in date/time of thought.

A block comment looks like this:


--[[
This is a block comment
It can span multiple lines!
:D
]]

You may see some scripts use this:


--]]

Instead of


]]


Please note that this is purely aesthetical and has no effect whatsoever on the code.

Anywho, knowing how to comment things is great!... right? Well, yes, but you may as well just make a text file with no symbols in it if you're just going to make a text filled lua file. So, we move onto the actual beef of the script.

Functions.

Functions are the bit (In ArcEmu) that make the script work. It's like petrol to a car. It's the meat of your script. OK, enough analogies. A function is started with the keyword function and ended with the keyword end. After the word 'function', there is a space and there you can put your function name, followed by a pair of parenthesis - (). The parenthesis are known as arguments. They basically decide which bit of the data has been passed onto us from ArcEmu-World. Here is a sample function;


function FunctionName(Unit, Event)
-- This is a function!
end

Note that the keyword 'function' is case-sensitive. Using a capital 'f' (or any other letter, for that matter) will make the compiler not recognise that it is, indeed, a function. Same goes for Function Names, Arguments and.. er, pretty much everything, really.

So, what are these bracketty things..? Well, as mentioned before, they are arguments. In pretty much every function you use as a Lua Scripter, you will have stuff inside these parenthesis. There are different arguements for different events. Here are the most common:


GeneralCreatureEvent(Unit, Event[, pMisc])
GossipOnTalkEvent(Unit, Event, player)
GossipOnSelectEvent(Unit, Event, player, id, intid, code, pMisc)


function HelloWorld()
print("Hello, World!")
print("This is my first Lua Script!")
end

HelloWorld()

Wait, what did I just do there? Well, first of all, I created the function HelloWorld with no arguments. Note that I still added a pair of parenthesis. This is mandatory for every function you create. The first line, function HelloWorld() does nothing more than tell the Lua Engine that we are beginning a new function, which we have designated to be called 'HelloWorld', and that it has no arguments.

The print statement is followed by an opening bracket, and then quotation marks (Unless you want to print a variable). Inside these quotation marks, you type whatever you want to be printed.

If you save this code as HelloWorld.lua and place it inside your /scripts/ folder, and then open up ArcEmu-World, it will print this to the console:


Hello, World!
This is my first Lua Script!

Kinda boring huh? Oh yeah, and it gets better. It'll loop itself over and over and o-.. You get the idea. It's not helpful, so we'll move onto something a bit more.. productive.

Your first Gossip Script.

Whoa, gossip?



WTF is Gossip!?


Well, you know in WoW, when you talk to an NPC it gives you options? Such as, 'Yes, I'm ready', etcetera? Well, that is gossip. And that is what we are going to create now. First of all, you need to create an NPC... go on, I'll wait.

Done? OK, set it's flags to 1. This is the 'Gossip' flag, and enables the NPC to use Gossip. Oh, you don't know how to do that? Just hop on over to WoW-V (http://www.wow-v.com/) and create an account, and then select the 'Create' option, and then the 'Mob/NPC' option. Follow the instructions, hit the 'Advanced' section and enable gossip. Then import it into your server and, at the ArcEmu-World console, type in 'reload creature_names' and 'reload creature_proto'.

Now, create a new file and at the top of it, write this:


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = YourEntryID

-- On Triggers

-- RegisterUnitEvents

Replace 'YourEntryID' with the entry ID you used when you created your NPC. Note that the only bit the Lua Engine will pay attention to, at the moment, is the local NPC_ID = YourEntryID bit. Everything else is just comments!

But wait.. what is this local NPC_ID bit anyway? Well, that brings us onto our new section. A section within a section.. handy, huh?

Noblebeastx
17-03-10, 09:37 PM
Variables.

A variable is a byte of data, represented by a name, that stores a defined piece of data. Unlike C++, there are no variable types in Lua. Also, there is no need to worry about unsigned/signed variables.

A variable is created by typing a string into the script. This string can be almost anything; however, it cannot be keywords or function names (You'll know if it is a keyword if you're using Notepad++, as it'll be highlighted a bold blue).

Furthermore, variables can only be alphanumeric and have underscores and/or hyphens in them. You can use these characters:


a b c d e f g h i j k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
1 2 3 4 5 6 7 8 9 0
_

Note that variables are case sensitive. MY_VAR is not the same as my_var or My_VaR.

Variables can be declared with no identifier (Global) or with local infront of them. A global variable can pass from script to script, so it can cause problems. It's best to use local variables, which stay inside the current script, to reduce interference.

Good Variable Names:


My_Var = 1
myVar = 2

Invalid Variable Names:


%MyVar = 1 -- PHP Users may be used to this.
My.Var = 2
My&Var = 3

It's always a good idea to name your variables after what they are being used for.


local Var983

Is not as easy to know what it does as this is:



local Npc_Id


Anyhow, back to your first Gossip NPC!

Back to your First Gossip NPC.


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = 133713

-- On Triggers

-- RegisterUnitEvents

So, this is the code we have so far. We know what the local NPC_ID line does now, so we can start coding.
First off, let's create a new function called 'exampleGossipOnTalk(Unit, Event, player)'. These arguments are the standard ones for the OnTalk gossip event. The OnTalk gossip event is the first menu/dialog you see when you talk to the NPC.


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = YourEntryID

-- On Triggers
function exampleGossipOnTalk(Unit, Event, player)
end

-- RegisterUnitEvents


Note that I added an end below the function. We need to end every function that is created, or the Lua Engine will chuck up errors. Now that we have created our function, let's add a RegisterUnitEvent. Usually, if you are created a boss, you will use RegisterUnitEvent(), but since we are making a Gossip NPC, we use RegisterUnitGossipEvent. Note that this is case-sensitive. Add this underneath the appropriate section;


RegisterUnitGossipEvent(NPC_ID, 1, "exampleGossipOnTalk")

RegisterUnitGossipEvent() has four arguments; ID, Event ID, and Function Name. The Function Name section is in quotation marks; Don't forget that. You can use Variables for the ID and Event ID, which is why I added the NPC_ID variable in place of the ID field.


RegisterUnitGossipEvent(ID, EVENT_ID, FUNCTION_NAME)

Right now, this script will register an empty function whenever you talk to the NPC. It's something, but it's useless. It's just a placeholder, in many respects. So let's start fleshing it out.



function exampleGossipOnTalk(Unit, Event, player)
end

As we saw earlier, the statements go between the function header and terminator (Posh words for function and end), like so.


function exampleGossipOnTalk(Unit, Event, player)
-- Statements go here
end

To start creating a Gossip Menu, Lua needs to know we are creating the menu; we create a shell using the :GossipCreateMenu() statement.



:GossipCreateMenu(TEXT_ID, player, INTID)


Note that the :GossipCreateMenu() statement has a colon before it; this means it requires a Unit. By Default, the Unit is 'Unit'. This doesn't need to change unless we are dealing with multiple NPCs in one script; We'll come onto that later. Let's add our statement to the function.


function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
end

You'll see that I used '100' for the text ID. This is the default, "Hi, <name>. How can I help you?". It can be changed by looking in the appropriate table inside your Database. I set the Intid at '0' because that is what all first menus are set at; 0. It makes logical sense, too.

So now we've created a menu that displays 'Hi, <name>. How can I help you?'. It's still pretty impractical and won't help anyone in any shape, or form (Unless they want a pick-me-up). To make this NPC useful, let's add a few options to our menu by using the :GossipMenuAddItem() statement.



:GossipMenuAddItem(ICON_ID, MENU_CONTENT, INTID[, CODE])

This statement may confuse you; INTID? CODE? ICON_ID? What the heck are these? Well, the ICON_IDs are the bit you see next to the text when you open the menu. There are a few different icons, the IDs are listed below;


0 = Chat bubble
1 = Bag
2 = Wings
3 = Book
4 = Cog/Gear
5 = Cog/Gear
6 = Bag with coin
7 = Chat bubble with "..."

When you are writing in the MENU_CONTENT, you can actually colour it. You use it by putting |cf######, where the #s are your hexidecimal colour (Web Color Chart - Hexadecimal - by VisiBone (http://html-color-codes.com/) for more info), at the start of your string, and adding |r at the end.


:GossipMenuAddItem(0, "|cfFFFFFF White! |r", 1, 0)


INTID? Well, when you use the OnSelect function later, the INTID is used to determine which menu was opened. Therefore, you have to use unique IntIDs for each Menu selection, or the script will not work. Also, the IntID is in numerical form and not string.

CODE is an optional... option.. that can be omitted. It is only used when you want to load up a codebox. This will not be explained in this tutorial, and you will have to experiment to get it completely correct. Needless to say, leave it at 0 (False) unless you want it to load up, then set it to 1 (True).

I'll add a few options to our Gossip Menu..




function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
end


This adds three options to our menu; a mall teleport option, a remove resurrection sickness option, and an option to close the menu. However, this won't work. We've created the menu, but as far as ArcEmu is concerned, we haven't sent it to the player yet. For this, we add the :GossipSendMenu() command. This one has one argument; the target. The Target is the person we are sending the menu to; this is the reason we have the 'player' argument in this function. We are sending the menu to the player, so we add this to our code:


Unit:GossipSendMenu(player)

Which makes our function this:


function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
Unit:GossipSendMenu(player)
end

Let's add this to the rest of our code, leaving us with this:


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = 133713

-- On Triggers
function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
Unit:GossipSendMenu(player)
end

-- RegisterUnitEvents
RegisterUnitGossipEvent(NPC_ID, 1, "exampleGossipOnTalk")

We're getting there! However, this just shows the menu. The options won't work, you'll be able to see them; but if you click them, nothing will happen. You see, ArcEmu (sadly) doesn't 'detect' your functions for you. You need to tell it what to do. And we do this with our second function; the OnSelect function.


exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)

This means nothing to you at the moment, right? Fair enough. Let's create another function underneath our first one.


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = 133713

-- On Triggers
function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
Unit:GossipSendMenu(player)
end

function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
end

-- RegisterUnitEvents
RegisterUnitGossipEvent(NPC_ID, 1, "exampleGossipOnTalk")

And add the RegisterUnitGossipEvent hook underneath the OnTalk one.


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = 133713

-- On Triggers
function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
Unit:GossipSendMenu(player)
end

function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
end

-- RegisterUnitEvents
RegisterUnitGossipEvent(NPC_ID, 1, "exampleGossipOnTalk")
RegisterUnitGossipEvent(NPC_ID, 2, "exampleGossipOnSelect

OK, so our new function has some unknown arguments; id, code, and pMisc. Well, I'll tell you what each one does.
ID; Not a clue. :3
code; This is used if you enabled code in the Previous menu.
pMisc; this is the name of the person who started the Gossip (?).

None of these are much use to you, because you probably won't use them. At least, not in this tutorial. So, let's add the first option (of the 3) to our new function.



function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(MapID, x, y, z)
player:GossipComplete()
end
end

Wait, what? player:Teleport()? player:GossipComplete()? This makes no sense right? And what's with this 'if' thing-a-majig?!

BRAIN FREEEZE!



Err.. OK then, panic moment over. Let's work through this.. deep breaths.. in.. out..

Noblebeastx
17-03-10, 09:43 PM
if (intid == 1) then


Well, this is your new best friend, the 'if' statement. It is used to check if a certain condition is met. The thing within the brackets is the bit we are checking. I'm sure 'intid' shall come as a.. annoying, reminder to you. Remember when we added options, and we had to define a unique number? That was an intid. So, if the intid that the player selected is equal to 1 ('Teleport me to the mall.'), then we run this part of the script. Get it?


player:Teleport(MapID, x, y, z)

This is a statement, similar to the Gossip Ones a few lines up. The :Teleport() statement does what it says on the box. It teleports the Unit in question to the specified MapID, x co-ordinate, y co-ordinate and z co-ordinate. These can be found by going to the designated place you want your mall to be in, and typing '.gps' in game. For sake of example, I shall use the Undeveloped half of Ahn'Qiraj. The Co-ords for this is as follows;


MapID: 1
X: -9101.980469
Y: 1612.902832
Z: 21

So our statement will read


player:Teleport(1, -9101.980469, 1612.902832, 21)

So now we know what this does, we'll move onto the next statement; player:GossipComplete(). This one is quite simple and it doesn't need a whole paragraph to explain it. It simply closes the gossip menu. Easy. Right?


end


Wait, we have two ends? Yes, we do, well done, you can count. The 'if' statement requires an end, since it is a loop, and Lua cannot determine when it has ended on it's own.

Let's see what our function looks like now;



function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(1, -9101.980469, 1612.902832, 21)
player:GossipComplete()
end
end

So, we understand what this all does? Good. Let's move onto the other intid; Remove Resurrection Sickness.



function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(1, -9101.980469, 1612.902832, 21)
player:GossipComplete()
end
if (intid == 2) then
end
end


As you can see, we've created another if loop. This time, it only works if the intid is 2. So, in this function we want to remove the Resurrection Sickness aura ID. Well.. we don't know that, so it's time to hit our all-time friend, wowhead.com. We search in the spell we want, cut off the 5-digit number at the end of the URL and.. we have the ID: 15007. It's all good and well having the ID, but if we don't know the function to remove it, we're stuck. So let's hit the ArcEmu Wiki, using the link I provided above. We're looking for one that will check if the player has the aura, and then if he does, remove it.. hmm.. :HasAura() and :RemoveAura() look like they'll do the job. Let's try them out.



function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(1, -9101.980469, 1612.902832, 21)
player:GossipComplete()
end
if (intid == 2) then
if (player:HasAura(15007) == true) then
player:SendBroadcastMessage("Resurrection Sickness has been removed. Be careful next time!")
player:RemoveAura(15007)
player:GossipComplete()
else
player:SendBroadcastMessage("You do not have Resurrection Sickness!")
player:GossipComplete()
end
end
end

Eh what? :SendBroadcastMessage()? else? But you said we only needed :HasAura() and :RemoveAura()! And you haven't even explained what the arguments for them are either!

Well, I thought it would be self explanatory, but there we go. OK, I'll guide you through it.



:HasAura(SpellID)
:RemoveAura(SpellID)

OK, so they are easy.


:SendBroadcastMessage("message")

This one sends a message (Like the ones you see when you get an addon error, in the chat box) to the Unit specified.



else

Aha. This is part of the 'if' loop. The else operator is used to make Lua do things if the conditions are not met. It's basically saying this:


If the value of intid is 2, and the player has the aura '15007', then do this. If he doesn't have that aura, then do this.

Of course, you still need the end for the 'if' and 'function'. http://www.ac-web.org/forum/venise/smilies/smile.gif

So, now we have two intids, lets look at our function now, and add the last one.


function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(1, -9101.980469, 1612.902832, 21)
player:GossipComplete()
end
if (intid == 2) then
if (player:HasAura(15007) == true) then
player:SendBroadcastMessage("Resurrection Sickness has been removed. Be careful next time!")
player:RemoveAura(15007)
player:GossipComplete()
else
player:SendBroadcastMessage("You do not have Resurrection Sickness!")
player:GossipComplete()
end
end
if (intid == 3) then
player:GossipComplete()
end
end

We just add a player:GossipComplete() because the option was to close the window, effectively. http://www.ac-web.org/forum/venise/smilies/smile.gif

The complete script:


--[[
My First Gossip Script!
Tutorial by Neglected
]]

-- Variables
local NPC_ID = 133713

-- On Triggers
function exampleGossipOnTalk(Unit, Event, player)
Unit:GossipCreateMenu(100, player, 0)
Unit:GossipMenuAddItem(0, "Teleport me to the mall.", 1, 0)
Unit:GossipMenuAddItem(0, "Remove Resurrection Sickness.", 2, 0)
Unit:GossipMenuAddItem(0, "Never mind.", 3, 0)
Unit:GossipSendMenu(player)
end

function exampleGossipOnSelect(Unit, Event, player, id, intid, code, pMisc)
if (intid == 1) then
player:Teleport(1, -9101.980469, 1612.902832, 21)
player:GossipComplete()
end
if (intid == 2) then
if (player:HasAura(15007) == true) then
player:SendBroadcastMessage("Resurrection Sickness has been removed. Be careful next time!")
player:RemoveAura(15007)
player:GossipComplete()
else
player:SendBroadcastMessage("You do not have Resurrection Sickness!")
player:GossipComplete()
end
end
if (intid == 3) then
player:GossipComplete()
end
end

-- RegisterUnitEvents
RegisterUnitGossipEvent(NPC_ID, 1, "exampleGossipOnTalk")
RegisterUnitGossipEvent(NPC_ID, 2, "exampleGossipOnSelect")


Well done! You've created your first ever functioning script. Or at least, using this tutorial. Save it as TeleporterNPC.lua and place it in your /scripts/ folder. Spawn your mob and restart your server (Or type in #reload if you are using Alvanaar's Reload Chat Command script), and enjoy talking to your first script!

Ifs and Elseifs.

So you know how to use ifs and elses.. but, using the following is quite hard to read and monotonous;





if (var == true) then
Weee()
end
if (var == false) then
Awww()
end
if (var == nil) then
WTFITSHOULDNTBENIL()
end


Instead, you can replace this with an elseif, depending on the situation. An elseif basically removes the subsequent ifs and ends so that they are easy to read and are inside one if loop. Sound confusing? Here's a visual example.



if (var == true) then
Wee()
elseif (var == false) then
Awww()
elseif (var == nil) then
WTFITSHOULDNTBENIL()
end

As you can see, this is much easier to read than the previous ones. Quite a snappy topic, really

Noblebeastx
17-03-10, 09:50 PM
Higher-Tier Lua
This topic is comprised of some Higher-Tier features, such as tables and boss encounters. Please do not proceed unless you fully understand the previous topics.

This topic consists of:

Tables
Boss Encounters
Nil Checks
Changing the Unit

Tables

Tables are awesome. No, really. They are. I use them a lot, and I love them. They are very helpful and will probably be used in most (if not all) of your advanced scripts. They are used lots inside the Guild Housing Script (Alvanaar) and my Party v Party, Raid v Raid and Guild v Guild script (Still in development phase as of this writing).

Tables are actually used for a few statements in fact, so don't think they are useless, because they are not. They are used in such functions as :GetInRangePlayers(), :GetInRangePlayersCount, GetPlayersInWorld(), and so on, so forth. Tables are returned in two columns; Value and Key. The Key is like a unique identifier to the value. Imagine a MySQL table. The Key is the Entry ID, and the Value is the input.



| Key | Value |
|_______________|___________________|
| 1 | Neglected |
| 2 | Alvanaar |
| 3 | Paradox |



^ Crappy visual example. ^^,

Tables are defined by using curly braces. These are above the square bracket key on a QWERTY keyboard, and look like this;


{}

To define our first table, we'll name it 't'. Note that tables are defined in the same way as variables; They can be local or global.



local t = {}


Inside this table we can place anything; strings, integers, decimals, variables and even other tables. I'll start off by doing a simple name print. We'll add 3 names to our table;


local t = {"Neglected", "Alvanaar", "OnyxiaKing"}

Take note that each value is seperated by a comma (','). The keys are generated automatically. We can represent a value of a table by writing in the table name and then the key inside square brackets. For example, t[1] would refer to "Neglected". Therefore, doing the following:


print(t[1])

Will print the following to the console


Neglected

Likewise, doing print(t[1]..", "..t[2]..", and "..t[3].." are pwn.") will print the following to the console


Neglected, Alvanaar and OnyxiaKing are pwn

Note: You can only have 60 upvalues (t[1], t[2] etc) in a function. I learnt this the hard way whilst making my teleporter NPC..

But wait; what are these full stops for? Well, they are conatecation operators. If you place two of them, either side of a variable, they automatically become inserted into the string. For example, t[1]..", " will make t[1] become part of the string, and thus the string becomes "Neglected, ". It's a very powerful thing.

Anyway, back on to tables. Some statements are returned as tables in Lua. To use these, we need to arrange them into keys and values. How do we do this? Well,



for k, v in pairs(t) do
print(v)
end

Wait, what?! We just did this.. 'for'.. thing.

Yes, we did. The 'for' keyword is a loop, like the 'if' one. 'k, v' organises the table into keys and values, and assigns them k and v. So, all values inside the table are assigned to v, and all keys inside the table are assigned to k. This means, that if we use print(v), that all of the values in the 't' table will be printed to the console.

Because the 'for' keyword is a loop, we have to end it with the keyword end.

A few other notes before we move on from tables; You can change the "k, v" to anything. Just remember the first value will return as the keys and the second will return as values. "lol, rofl" would work, for example. But, print(rofl) doesn't exactly.. seem professional, does it?

Here is an example of a function that returns as a table; GetPlayersInWorld()



function GetPlayersInWorldAndKill(Unit, Event)
local plrs = GetPlayersInWorld() -- assign the table to plrs
for k, v in pairs(plrs) do -- Sort 'plrs' into keys and values (k and v)
v:CastSpell(7) -- make v (the values of plrs) cast 7 (suicide) on themselves.
end
end

Easy? I thought so too. It all goes downhill from here (;

Boss Encounters.
Oh goody, boss encounters! The bit everyone aspires to do! (/glare at Xyolexus)

Creating Boss Encounters is easy. Creating decent ones.. now, that's a challenge. However, they all follow the same structure; OnCombat, OnKilledTarget, OnLeaveCombat, and OnDeath. Sometimes you'll have OnSpawn, but that is only if people are using multiple mobs in one script. Here is the basic structure of a boss fight;



--[[
Boss Fight Structure
Ultimate Lua Tutorial
Neglected
]]

local NPC_ID = 133713

function NPC_OnSpawn(Unit, Event)
end

function NPC_OnCombat(Unit, Event)
end

function NPC_OnLeaveCombat(Unit, Event)
Unit:RemoveEvents()
end

function NPC_OnKilledTarget(Unit, Event)
end

function NPC_OnDeath(Unit, Event)
Unit:RemoveEvents()
end

RegisterUnitEvent(NPC_ID, 1, "NPC_OnCombat")
RegisterUnitEvent(NPC_ID, 2, "NPC_OnLeaveCombat")
RegisterUnitEvent(NPC_ID, 3, "NPC_OnKilledTarget")
RegisterUnitEvent(NPC_ID, 4, "NPC_OnDeath")
RegisterUnitEvent(NPC_ID, 18, "NPC_OnSpawn")

Now since you understand the concepts of functions and registering events, and NPC_IDs, and variables, I don't think I'll need to explain much to you, apart from that one statement, :RemoveEvents(). This statement removes every event from the NPC in question, which means it no longer executes OnCombat, it never Cast Spells; until you respawn it, of course. It is used in OnLeaveCombat and OnDeath.. because, well, it wouldn't exactly be a good thing if, after players killed the mob, the mob killed them with a Holy Nova.

The hunter becomes the hunted. Heh.

A key thing in Bosses is phasing. It's all well having a boss, but it's really monotonous (boring, plain) if you just have it casting a spell over and over and over again with no variation in it's spells in relation to it's health. Luckily, the 'if' loop comes to the rescue here! We can construct a condition that will only allow it to go into another phase if it has x amount of health, or even x% of health.



if (Unit:GetHealthPct() <= 50) then
Unit:RegisterEvent("NPC_UFiftyPct", 1, 1)
end

This is good, but it needs to be attached to a function. It's pretty self explanatory what it does. But the problem is, unless we attach it to it's own function and then register that function every second or so, it'll only run once. And then the Phase will probably never get a chance to register itself.


function NPC_OnCombat(Unit, Event)
Unit:RegisterEvent("NPC_CheckIfUnderFiftyPct", 1000, 0)
end

function NPC_CheckIfUnderFiftyPct(Unit, Event)
if (Unit:GetHealthPct() <= 50) then
Unit:RegisterEvent("NPC_UFiftyPct", 1, 1)
end
end

function NPC_UFiftyPct(Unit, Event)
local plrs = Unit:GetInRangePlayers()
for k, v in pairs(plrs) do
Unit:CastSpellOnTarget(5, v)
end
end

This code will do that purpose. When combat is started, it will register an event every 1000ms (1 Second). Since the repeat is set to 0, it will keep on repeating.




:RegisterEvent(FUNCTION_NAME, DELAY, REPEAT)


Every second, we will check if the Unit's Health Percent is under 50. If it is, we will register NPC_UFiftyPct. If it isn't, then the function ends.

When NPC_UFiftyPct is registered, then every player is range is killed by Death Touch


:CastSpellOnTarget(SPELL_ID, TARGET)

This is a very brief overview, because most of Boss Encounters can only be found out via Trial and Error. However, I will spare another feature with you.

Making an NPC unattackable.
What this code does is that it sets the NPC to be unattackable (but still displays it as hostile).



Unit:SetUInt32Value(58, 2)

This bit sets the NPC as unattackable. If you click him, he is displayed as hostile, but you won't see the sword when you mouse-over. You won't be able to attack him, at all.


Unit:SetUInt32Value(58, 26)

The above code will make the NPC unselectable from the client.
To make him attackable again, you use this code:


Unit:SetUInt32Value(58, 0)

Quite simple eh? It's certainly not the most ideal solution, but it is the only way to do this in Lua as of this writing.

So, to recap. SetUInt32Value(58, 2) will set the NPC as unattackable, SetUInt32Value(52, 0) will set the NPC as attackable.

Furthermore, here are all of the Unit flags (That are known as of r2850)



2 -- Client won't let you attack the mob
4 -- Makes players & NPCs attackable/unattackable
256 -- Changes attackable status
13 -- Sets PVP Flag
14 -- Silenced
15 -- Dead
17 -- Alive
18 -- Pacified
19 -- Stunned
20 -- Sets Combat Flag
21 -- Sets the same flag as mounted on a taxi (Can't cast spells)
22 -- Disarmed
23 -- Confused
24 -- Fleeing/Fear
25 -- Makes players & NPCs attackable/unattackable
26 -- Unselectable
27 -- Skinnable
30 -- Feign Death

Nil Checks.
Although nil checks are considered 'Medium-Level Lua', they are just a matter of adding another if statement to the code. Therefore, this topic will be pretty short. (Cue, "That's what she said")

A nil check is used to fool-proof a spell cast in order to make sure the script doesn't bug. It simply checks if the Unit we are aiming at exists (or is not nil) before it does anything. It's just two bits of extra code; an if statement, and an end.


function KillRandomPlayer(Unit, Event)
local tar = Unit:GetRandomPlayer(0)
if (tar ~= nil) then
Unit:CastSpellOnTarget(5, tar)
end
end

Too easy, yeah?

Changing the/Adding Units.
This is where the confusion sets in, haha. Defining Units can (and will) get very confusing, if you're a beginner at doing it. But, once you can do it, it's like riding a bike. It's simple (In fact, you're just defining a variable), but some times you can mess up and go back to the old way of things (Which you aren't allowed to do).

Remember I said earlier, in the Boss Encounter section, that OnSpawn events are only really included to define Units? Well, this is because this is the only place you can really do it. Especially for boss encounters. The thing with Defining a Unit, is that once you define a Unit, you cannot go back to using Unit:. You have to use the Unit you defined. That's the downside to it. The upside to it, is that you can have more than one mob in one script. And make them interact, of course. To define a unit, you add the following line of code to the OnSpawn event;


InsertUnitHere = Unit

Noblebeastx
17-03-10, 09:53 PM
Credits go to neglected from ac-web thxs man this helped me a little and im glad i msharing with others from other places to help them too :D

This is a share if ure gonna flame or QQ plz dont even psot if u dont got noting nice to say or helpful plz jsut leave enless ure a mod or admin thank you