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.


Code:
|      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;


Code:
{}
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.


Code:
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;

Code:
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:

Code:
print(t[1])
Will print the following to the console

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

Code:
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,



Code:
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()



Code:
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;



Code:
--[[
	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.





Code:
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.

Code:
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.

Code:
 
 
: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


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



Code:
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.

Code:
Unit:SetUInt32Value(58, 26)
The above code will make the NPC unselectable from the client.
To make him attackable again, you use this code:


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)



Code:
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.


Code:
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;


Code:
	InsertUnitHere = Unit