• This is a read only backup of the old Emudevs forum. If you want to have anything removed, please message me on Discord: KittyKaev

Lua tips and tricks

Rochet2

Moderator / Eluna Dev
luathread
I thought to post a little thread about scripting with lua.
Posting some special things you can, should and should not do and why.

I apologize if I appear messy or not understandable at some points : |

This thread has nice comments and tips in the replies as well!
Do remember to read them as well :)



NEVER save an ?object? overtime! (boss = unit is a no no)
Ok, .. there are some exceptions.. (see next post for an example of a nice workaround :))
But basically you should never save a player for example to a variable you are not using instantly.
When the player logs out, the variable wont become nil. On C++ side the player is a pointer, which will be "valid" (not NULL), but it points to an area that no longer has player data in it.
This means that when a player logs out and you use the player object still after that, there is a possibility for crashing and having bad method reads and more.
This problem can be avoided in certain ways, but I would rather not use them as they would disable certain functionality and possibilities. Be smart, dont do it :)
Should use guids instead. (GetGUID)

Use GetGUIDLow! not GetGUID
This may sound a little like I am contradicting myself with what I said before..
But GUID is a (number as a) string, and as such, it is "worse" for tables as a key.
It is also bigger than GUIDLow, and it also can not and should not be used in the database.
When handling stuff in the code, it is fine to use GUID instead of GUIDLow though. GUIDLow is the ID used in database tables for player, creature etc!
Check out the differences with GUID and low GUID :) Also see the global methods for them

Collision in scripts is seen very often.
Note that the script is not bound to a creature. It is a global script.
This means that if you use a variable like:
local spawned = false
It is just one variable. This means that multiple NPCs that run an event to change or check that variable use the same variable.
So, for example if a boss script that sets that variable to true when spawning adds, other instances of the same raid or dungeon where the boss is wont get adds at all, since the variable says that they are already spawned when checked by the script.
You should solve this problem for example by saving data to a lua table with the instance ID or creature guid low for example (boss guid)

Localize your variables and functions!
You should always localize your variables. Mostly you can just say local x = 2 and so, but sometimes you may need to declare a variable local before using it.
This can be achieved by doing for example:
Code:
local testvar
if(condition) then
 testvar = true
else
 testvar = false
end
This code will set a local variable, but you wont hit a problem with the variable being local like when trying similar to this:
Code:
if(condition) then
 local testvar = true
else
 local testvar = false
end
-- testvar is nil here :(
Localizing your functions may be important.
You can do this in a few ways. One of the ways is to just declare that it is local when defining or declaring the function like so:
local function testFunction()
With this way you may run into problems when trying to use the function that is local before it is defined though.
You can solve this problem by using the option 2 or by declaring the functions local before using them, like this:
Code:
local testFunc
-- code
function testFunction() -- code
Another way is to store all functions (and variables) inside a table, which then can be made local.
Code:
local T = {}
function T.MyFunction()
   -- code
end
T.SomeVar = false
Localizing variables and functions is needed to prevent them from being overwritten or from overwriting functions and variables with the same name.
They also wont take up memory by being global when used just in one function for example.
Often scripts tend to use unique function names and as such, mostly people do not use local functions. Local functions are said to be faster though. (source?)

What if I need to pass more variables than a function can receive :(?
In some situations you might want to pass additional arguments to functions.
This is often needed with timed events. This is also a useful trick for some situations :)
Code:
local function myFunction(eventId, delay, calls, customVar)
    print(customVar) -- prints 123
end
CreateLuaEvent(function(a,b,c) myFunction(a,b,c, 123) end, 5000, 0)
Lua allows you to create functions inside a function. You can also store functions to a variable and tables and so.
Actually the function defining syntax is just a syntactic sugar to:
local myFunction = function(eventId, delay, calls, customVar) print(customVar) end
 
Last edited:
Can just use (for example, not what I use exactly) something like Player[ID].WorldObjectVar = nil when they logout, like I do similarly, but okay. Lua gets rid of nil variables from memory so setting all player-linked variables to nil when the player logs out should be fine.

The rest I pretty much already do but thanks for the advice, glad to hear I'm doing things right.
 

Rochet2

Moderator / Eluna Dev
Well sure you can do that i guess.. Hmm.
Not sure if there are some exceptions to logging off.

Sadly we cant exactly implement that as a default thing to do.
Or we /might/ possibly in some way .. hmmmm This would mean that when calling a method on the object the object would be nil.
We would likely need to implement this to destructor of object to make it work nicely.
But it wouldnt really be efficient. Best way is to do as you suggested or use guids if you really need to save a player overtime.
 
Last edited:

efonius

Enthusiast
Great write up, just to see some solid example of how to localize, could you tell me what you would have to change to localize this example script to work in a way that multiple people could run/do it at the same time?
 

Rochet2

Moderator / Eluna Dev
Great write up, just to see some solid example of how to localize, could you tell me what you would have to change to localize this example script to work in a way that multiple people could run/do it at the same time?
Well, as said in my main post, the easy ways would be to create a table and then using T.FuncName instead of FuncName everywhere.
And then have the table local.
Or then you could make a list of all functions to the top of the script like here on line 23:
http://pastebin.com/uqctudvj
See LoadDB for example. It is declared local at the top, but then when defined a few rows below, it is used normally.

Another thing are the variables like currentWP.
You should create a table and store those for the creature.
local T = {}
Then when used in code:
T[creature:GetGUIDLow()] = currentWP+1;

Hmm, we should probably update those scripts .. they are terribly outdated :p
They wouldnt work at all on the engine today.
 
Last edited:

Rochet2

Moderator / Eluna Dev
Know you hooks!
Hooks trigger at different parts of the code.
So for example XP receive hook will trigger BEFORE adding the XP to the player.
This is due to C++ being able to edit the XP given to player in the script (coming to eluna soon :))
There are some hooks that trigger before some event, some trigger after it and some trigger in the middle of the code.

For example, the OnFirstLogin hook executes a bit in the middle of the code, right before OnFirstLogin flag is removed from the player. Not before login, not after it.
Character create hook also gives you access to the player object, but dont expect for example to be able to send messages to the player. The player is only a temporary object and has not yet logged in even.

This means that if some method or code doesnt work ..(for example I think salja tried to add creature kills to a quest on quest accept and the script didnt work since the hook probably executes before the player has the quest)
.. test a bit and think of what you are trying to do and when.
 
Made a "function library" for other scripts to use, went something like this:
Code:
PF = {};
local PV = {};

PF.Connect = function(Name)
	local PlayerID = 0;
	
	for Player2ID, PlayerTable in pairs(PV) do
		if(not(PlayerTable.Connected)) then
			PlayerID == Player2ID;
			break;
		else
			if(PlayerTable.Name == Name) then
				return Player2ID;
			end
		end
	end
	
	if(PlayerID == 0) then
		PlayerID = (#PV + 1);
	end
	
	local GameID = nil;
	
	GameID = GetPlayerByName(Name); -- Eluna Function:: Returns nil when the player is changing maps.
	
	if(GameID == nil) then -- This will bug if the player is changing maps.
		print("ConnectPlayer error! The player specified was not found");
		return 0;
	end
	-- Other Eluna functions to get player information.
	
	if(PV[PlayerID] == nil) then
		PV[PlayerID] = {};
	end

	PV[PlayerID].Connected = true;	
	PV[PlayerID].GameID = GameID;
	PV[PlayerID].Name = Name;
	-- Other table variables, such as MaxHealth, Gold, etc.
	
	if(type(PlayerConnected) == "function") then
		PlayerConnected(PlayerID);
	end
	return PlayerID;
end

PF.Disconnect = function(PlayerID, Kick)
	if(PV[PlayerID] == nil or not(PV[PlayerID].Connected)) then
		return false;
	end
	
	if(Kick == nil or Kick) then
		Kick(PV[PlayerID].GameID);
	end
	
	if(type(PlayerDisconnected) == "function") then
		PlayerDisconnected(PlayerID);
	end
	
	PV[PlayerID] = {}; -- Set the entire table to nil.
	PV[PlayerID].Connected = false; -- So PV.Connect can reuse the table.
	return true;
end
I did this just now in 5minutes, don't mimic it's untested.

Now, I'm kinda sortof doing the same thing but with C++, using Eluna for TrinityCore as a base.
 
Last edited:

Rochet2

Moderator / Eluna Dev
Here is a new tip for the thread.
I originally saw @Laurea doing this in his script here : http://pastebin.com/siQHVciK
I suggest you check it out as well.
Update: Eluna is now supported, and some general optimizations have been performed. The value backup feature for Eluna isn't implemented yet, but working on it.
This script allows you to easily attach variables to players, creatures, gameobjects, maps and instances (Eluna also supports items, groups and guilds) without using a table and the tostring method.
The purpose is to make collision-free scripting easier, especially for beginners, and keep more advanced code clean.

Anyways. The idea is that Player and Unit and so on are tables that hold the methods for lua.
This means that you can add in your own methods :)
And variables and so on .. just like a normal table pretty much.
When making a method, you use : like in this example code. This enables you to use the function as a method to the player and use "self" variable for the player object using it:
Code:
local function OnChat(event, player, msg, lang, typ, misc)
    if (msg == "test") then
        player:Custom()
    end
end

RegisterPlayerEvent(18, OnChat)

function Player:Custom()
    self:SendAreaTriggerMessage("Hi :)")
end





[MENTION=409]SkittlesAreFalling[/MENTION]
-- Eluna Function:: Returns nil when the player is changing maps.
While this is true, you can still use an existing saved player pointer.
Example:
Code:
        guid = player:GetGUID()
        CreateLuaEvent(function(a,b,c) player:SendBroadcastMessage("TestPointer") print(player:GetName(), GetPlayerByGUID(guid)) end, 500, 0)
In this code you commented that GetPlayerByGUID (or by name) returns nil on map change. While it does that, player:GetName() will still return valid name.
Also the BroadcastMessage will succeed after changing maps (obviously the player cant see the message when loading a new map)
A code like this should be enough to control players:
http://pastebin.com/3C7EFm3M
It of course doesnt do what your script does, but this is what I had in mind when you said about saving the player to var and setting to nil on logout.

ps. Pretty much the only difference between getting a player with GetPlayerByGUID vs using a system like that are that player isnt available when changing maps.
This is why if you need to do this, I suggest using GetPlayerByGUID instead. You may need a system if you have special needs like player stats while he is off, like skittles might have had.
The above system only describes a simple example of storing data for player (or the player). It can be extended to save various information about player.
 
Last edited:

Rochet2

Moderator / Eluna Dev
Lua is fast and C++ is fast.
The swapping from lua to C++ and C++ to lua is slow.

When using a variable from C++ twice, for example if you would need the health of an NPC twice,
save it to a local variable. This will be a lot faster.
We wrote an extension that automatically does this already for methods that should return static data, like GetGUID().

If you want to be more efficient, I suggest googling for lua efficiency, even though that will mostly be just fine tuning your lua scripts.
Optimizing Getting values from C++ is a lot more important.


As mentioned in another topic, lua automatically casts units etc.
So if a method would return a world object for example, lets say C++ has a function GetUnit()
When the return value is Unit, it will in lua be player or creature, since it is casted automatically to the right value.
 
Top