https://wiki.multitheftauto.com/api.php?action=feedcontributions&user=Ali+salah&feedformat=atomMulti Theft Auto: Wiki - User contributions [en]2024-03-28T15:43:50ZUser contributionsMediaWiki 1.39.3https://wiki.multitheftauto.com/index.php?title=Hibakeres%C3%A9s&diff=43149Hibakeresés2014-12-04T10:24:45Z<p>Ali salah: /* مثال */</p>
<hr />
<div>When scripting you will often come across problems that are not immediately apparent. This page tries to point out some basic strategies to locate the error.<br />
<br />
==وحدة تحكم التصحيح==<br />
<br />
<br />
MTA يتميز حدة التصحيح المدمج في أن يظهر الناتج رسائل التصحيح من وظائف MTA أو من البرامج النصية. ابقائها تمانع أنه يجب أن يكون وصول المسؤول (إلا إذا كنت تعديل ACL لتغيير هذا)، يمكنك فتح وحدة التصحيح عن طريق كتابة debugscript * س * في وحدة التحكم، حيث X هو مستوى التصحيح:<br />
1: الأخطاء فقط<br />
2: الأخطاء والتحذيرات<br />
3: الأخطاء والتحذيرات ورسائل المعلومات<br />
عن طريق كتابة debugscript 3 كل الرسائل مرئية. المستوى 3 والمستوى 2 ويوصى لمعظم المناسبات. يجب أن يكون لديك debugscript تمكين كلما كنت <br />
اختبار البرامج النصية الخاصة بك لأنها سوف تساعدك بسهولة اكتشاف وحل الأخطاء المطبعية وغيرها من القضايا.<br />
<br />
===مثال===<br />
This example code has two errors:<br />
<syntaxhighlight lang="lua"><br />
وظيفة SayHello ( الرسالة، لاعب ) <br />
إذا ( getPlayerName ( لاعب ) == "فيدور" ) <br />
outputChatbox ( "مرحبا فيدور" ) <br />
نهاية <br />
نهاية <br />
addEventHandler ( "onChatMessage" ، الجذر، SayHello )end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
عندما البرنامج النصي في هو حاول هذه القطعة من التعليمات البرمجية إلى أن يتم تحميل، debugscript سيتم إخراج شيء مماثلة لهذه:<br />
INFO: النصي تحميل فشل: myResource \ script.lua: 2: 'ثم' يتوقع قرب'outputChatbox "<br />
وهذا يعني أنه لا يمكن تحليل السيناريو لأنه كان هناك خطأ في بناء جملة. فإنه يدل على السيناريو مسار نسبة إلى الدليل الموارد حتى تتمكن من التعرف إلى البرنامج النصي حيث نشأت الخطأ من. بعد اسم الملف فإنه يظهر القولون وعدد، ويعرض هذا العدد رقم السطر - وهذا هو حتى تتمكن من معرفة مكان في السيناريو الخاص بك الخطأ - فإنه من المفيد جدا للمخطوطات كبيرة. بعد ذلك هو رسالة الخطأ، والتي تختلف تبعا للخطأ بها. تبحث في رسالة الخطأ، يمكننا بسهولة تحديد أن خطأ نشأت من المورد myResource وسطرين من الملف النصي script.lua . رسالة الخطأ هو واضح جدا، ونحن نسيت "ثم"! نحن يمكن اصلاحها بسهولة أن!<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatbox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
Now the script will load fine and won't output any errors until a player with the name 'Fedor' says something in the chat. At that moment, the debug console will output:<br />
:{{Debug error|myResource\script.lua:2: attempt to call global 'outputChatbox' (a nil value)}}<br />
This error means that the function '''outputChatbox''' is a nil value, that is - it doesn't exist! This is because the function is actually called [[outputChatBox]], not [[outputChatbox]] - take care of that capital B.:<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatBox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
<br />
Of course, this is just an example and I've only skimmed the surface of error messages. There are plenty of other messages and scenarios, but you should get the general idea.<br />
<br />
==Server & client debug logging==<br />
====Server====<br />
Navigate to: ''(MTA root folder)>server>mods>deathmatch''<br />
<br />
There are two nearly identical files:<br />
<br />
*The local.conf is the settings for the "host game" menu item in the main menu of MTA. This is a quick and easy way of starting a temporary server from inside the client. When the client is turned off the server will also turn off.<br />
<br />
*The mtaserver.conf is used when "MTA Server.exe" is executed from (MTA root folder)>server. This runs the server in its own console window which does not need the client to exist or to be run. This is can be useful if you're interested in hosting servers for a longer time period or if you want to experiment with a real world console.<br />
<br />
Depending on which way you host, you will want to edit those files. The settings in question are:<br />
<br />
<syntaxhighlight lang="xml"><!-- Specifies the location and name of the debugscript log file. If left blank, server won't be saving this file. --><br />
<scriptdebuglogfile>logs/scripts.log</scriptdebuglogfile> <br />
<br />
<!-- Specifies the level of the debugscript log file. Available values: 0, 1, 2, 3. When not set, defaults to 0. --><br />
<scriptdebugloglevel>0</scriptdebugloglevel></syntaxhighlight><br />
<br />
You want to make sure you have a log name specified. Also you want to specify what level of errors will be logged. If it is 0 nothing will be logged. The levels were explained at the top of this article. If the logging level was changed to 3, then in this case all '''serverside''' script errors log to (MTA root folder)>server>mods>deathmatch>logs>scripts.log<br />
<br />
====Client====<br />
Navigate to: ''(MTA root folder)>server>clientscript.log''<br />
<br />
This file logs all '''clientside''' script errors. This is set to log by default, no setup required.<br />
<br />
==Debug strategies==<br />
There are several strategies that support finding errors, apart from going through the code. Most of them include outputting debug messages, with have different information depending on the situtation.<br />
<br />
===Useful functions===<br />
There are some functions that may come in handy for debugging.<br />
* [[outputDebugString]] or [[outputChatBox]] for outputting any information (use outputDebugString for technical output)<br />
* [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] on a variable to turn the value into a string. Useful if the value is not a number or string.<br />
* [[getElementType]] to check the type of the MTA element.<br />
* [[isElement]] to check if the MTA element exists.<br />
<br />
===Add debugmessages to check ''if'', ''when'' or ''how often'' a section of code is executed===<br />
A typical example would be verify whether an ''if''-section is executed or not. To do that, just add any message you will recognize later within the ''if''-section.<br />
<syntaxhighlight lang="lua"><br />
if (variable1 == variable2) then<br />
outputDebugString("variable1 is the same as variable2!")<br />
-- do anything<br />
end<br />
</syntaxhighlight><br />
<br />
Another application would be to check when variable values are modified. First search for all occurences of the variable being edited and add a message just beside it.<br />
<br />
===Add debugmessages to check the ''value'' of a variable===<br />
Let's say you want to create a marker, but it doesn't appear at the position you expect it to be. The first thing you might want to do is check if the [[createMarker]] function is executed. But while doing this, you can also check the values being used in the [[createMarker]] function in one run.<br />
<syntaxhighlight lang="lua"><br />
outputChatBox("posX is: "..x.." posY is: "..y.." posZ is: "..z)<br />
createMarker(x,y,z)<br />
</syntaxhighlight><br />
This would output all three variables that are used as coordinates for the marker. Assuming you read those from a map file, you can now compare the debug output to the desired values. The [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] will ensure that the values of the variables can be concatenated (put together) as a string, even if it's a boolean value.<br />
<br />
==Example==<br />
Imagine you created a [[Colshape|collision shape]] somewhere and you want an action to perform after the player stays for ten seconds inside it, you would do this:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave", root, colShapeLeave)<br />
</syntaxhighlight><br />
When a player enters the colshape, debugscript outputs the following message:<br />
:{{Debug error|..[path]: attempt to index global 'colshapeTimer' (a nil value)}}<br />
This means you tried to index a table that does not exist (because the table is a nil value, it doesn't exist). In the example above, this is done when storing the timer id in the table. We need to add a check if the table exists and if it does not exist we should create it.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",root,colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we will still receive a warning when a player enters the colshape, waits for the message and leaves it again:<br />
<br />
:{{Debug warning|[..]: Bad argument @ 'killTimer' Line: ..}}<br />
<br />
Except for that (we will talk about that later) everything seems to work fine. A player enters the colshape, the timer is started, if he stays the message occurs, if he leaves the timer is killed.<br />
<br />
===A more inconspicuous error===<br />
But for some reason the message gets outputted twice when you stay in the colcircle while in a vehicle. Since it would appear some code is executed twice, we add debug messages to check this.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message<br />
outputDebugString("colShapeHit")<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message<br />
outputDebugString("colShapeLeave")<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we notice that both handler functions get executed twice when we are in a vehicle, but only once when we are on-foot. It would appear the vehicle triggers the colshape as well. To confirm this theory, we ensure that the ''player'' variable actually holds a reference to a player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
The debug messages tell us that one of the ''player'' variables is a player and that the other one is a vehicle element. Since we only want to react when a player enters the colshape, we add an ''if'' that will end the execution of the function if it's '''not''' an player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now the script should work as desired, but it will still output the warning mentioned above. This happens because the timer we try to kill when a player leaves the colshape will not exist anymore when it has reached the 10 seconds (and therefore executed after the 10th second has completed). There are different ways to get rid of that warning (since you know that the timer might not exist anymore and you only want to kill it if it exists). One way would be to check if the timer referenced in the table really exists. To do this, we need to use [[isTimer]], which we will use when we kill the timer:<br />
<syntaxhighlight lang="lua"><br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
</syntaxhighlight><br />
<br />
So the complete working code would be:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
<br />
==Debugging Performance Issues==<br />
<br />
If your server is using up more resources than it should or you just want to make sure your scripts are efficient, you can find the source of the issue by using a great tool that comes with the default resource package called [[Resource:Performancebrowser|performancebrowser]]. You can start it with ''''start performancebrowser''''. If it doesn't exist then you can get the latest resources from the [http://code.google.com/p/mtasa-resources Google Code repository]. This tool provides an incredible amount of information for performance debugging. Memory leaks, element leaks and CPU intensive scripts are all easily findable via performancebrowser. If you use the ''''d'''' option in Lua timing you can see which functions are using up the CPU.<br />
<br />
To access performancebrowser you will need to go to your web browser and enter the address: http://serverIPHere:serverHTTPPortHere/performancebrowser/ Note that the / at the end is required. So for example: http://127.0.0.1:22005/performancebrowser/ You will then need to login with an in-game admin account or any account that has access to ''''resource.performancebrowser.http'''' and ''''resource.ajax.http''''. Most of the information you will need are in the categories Lua timing and Lua memory, look for values that are much higher than other values.<br />
<br />
===Examples of scripts that could cause performance problems===<br />
<br />
Adding data to a table but never removing it. This would take months/years before it causes a problem though.<br />
<syntaxhighlight lang="lua"><br />
local someData = {}<br />
<br />
function storeData()<br />
someData[source] = true<br />
-- There is no handling for when a player quits, this is considered a memory leak<br />
-- Using the Lua timing tab you can detect the RAM usage of each resource.<br />
end<br />
addEventHandler("onPlayerJoin", root, storeData)<br />
</syntaxhighlight><br />
<br />
Element leaking is possible if you use temporary colshapes for whatever reason and may not destroy them. This would cause bandwidth, CPU and memory performance issues over time.<br />
<syntaxhighlight lang="lua"><br />
function useTemporaryCol()<br />
local col = createColCircle(some code here)<br />
if (normally this should happen) then<br />
destroyElement(col)<br />
end<br />
-- But sometimes it didn't so the script ended but the collision area remained and over time<br />
-- you may end up with hundreds to thousands of pointless collision areas. <br />
-- The Lua timing tab allows you to see the amount of elements each script has created.<br />
end<br />
</syntaxhighlight><br />
<br />
High CPU usage resulting in the server FPS dropping so much that the server is unplayable. In under 24 hours this can create havoc on a very busy server. The amount of "refs" in the Lua timing detect this type of build up, surprisingly the Lua timing tab didn't help in this case but Lua memory did.<br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onPlayerJoin", root, function()<br />
-- Code for joiner<br />
addEventHandler("onPlayerQuit", root, function()<br />
-- Code for when they have quit<br />
-- See the problem? It's bound to root which the event handler is being added again and again and again<br />
end)<br />
end)<br />
</syntaxhighlight><br />
<br />
A function uses up a lot of your CPU because whatever it does takes a long time. This is just some function that takes a long time to complete. Without performancebrowser you'd have no idea its the cause but with performancebrowser you can see that a resource is using lots of CPU in the Lua timing tab. If you then enter: ''''d'''' into the options edit box it will even tell you what file name and first line of the function that is using up so much CPU.<br />
<syntaxhighlight lang="lua"><br />
function someDodgyCode()<br />
for i=1, 100000 do<br />
-- some code<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
[[it:Guida al Debug]]<br />
[[Category:Scripting Concepts]]<br />
[[ru:Debugging]]<br />
[[Category:Tutorials]]</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Hibakeres%C3%A9s&diff=43148Hibakeresés2014-12-04T10:22:52Z<p>Ali salah: /* Example */</p>
<hr />
<div>When scripting you will often come across problems that are not immediately apparent. This page tries to point out some basic strategies to locate the error.<br />
<br />
==وحدة تحكم التصحيح==<br />
<br />
<br />
MTA يتميز حدة التصحيح المدمج في أن يظهر الناتج رسائل التصحيح من وظائف MTA أو من البرامج النصية. ابقائها تمانع أنه يجب أن يكون وصول المسؤول (إلا إذا كنت تعديل ACL لتغيير هذا)، يمكنك فتح وحدة التصحيح عن طريق كتابة debugscript * س * في وحدة التحكم، حيث X هو مستوى التصحيح:<br />
1: الأخطاء فقط<br />
2: الأخطاء والتحذيرات<br />
3: الأخطاء والتحذيرات ورسائل المعلومات<br />
عن طريق كتابة debugscript 3 كل الرسائل مرئية. المستوى 3 والمستوى 2 ويوصى لمعظم المناسبات. يجب أن يكون لديك debugscript تمكين كلما كنت <br />
اختبار البرامج النصية الخاصة بك لأنها سوف تساعدك بسهولة اكتشاف وحل الأخطاء المطبعية وغيرها من القضايا.<br />
<br />
===مثال===<br />
This example code has two errors:<br />
<syntaxhighlight lang="lua"><br />
وظيفة SayHello ( الرسالة، لاعب ) <br />
إذا ( getPlayerName ( لاعب ) == "فيدور" ) <br />
outputChatbox ( "مرحبا فيدور" ) <br />
نهاية <br />
نهاية <br />
addEventHandler ( "onChatMessage" ، الجذر، SayHello )end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
When the script this piece of code is in is tried to be loaded, debugscript will output something similiar to this:<br />
:{{Debug info|Loading script failed: myResource\script.lua:2: 'then' expected near ´outputChatbox'}}<br />
This means the script could not be parsed because there was a syntax error. It shows the script path relative to the resource directory so you can identify to script where the error originated from. After the filename it shows a colon and a number, this number presents the line number - this is so you can find out where in your script the error is - it is very helpful for large scripts. After that is the error message, which varies depending on the error made. Looking at the error message, we can easily determine that the error originated from the resource '''myResource''' and line two of the script file '''script.lua'''. The error message is very clear, we forgot the "then"! We can easily fix that!<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatbox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
Now the script will load fine and won't output any errors until a player with the name 'Fedor' says something in the chat. At that moment, the debug console will output:<br />
:{{Debug error|myResource\script.lua:2: attempt to call global 'outputChatbox' (a nil value)}}<br />
This error means that the function '''outputChatbox''' is a nil value, that is - it doesn't exist! This is because the function is actually called [[outputChatBox]], not [[outputChatbox]] - take care of that capital B.:<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatBox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
<br />
Of course, this is just an example and I've only skimmed the surface of error messages. There are plenty of other messages and scenarios, but you should get the general idea.<br />
<br />
==Server & client debug logging==<br />
====Server====<br />
Navigate to: ''(MTA root folder)>server>mods>deathmatch''<br />
<br />
There are two nearly identical files:<br />
<br />
*The local.conf is the settings for the "host game" menu item in the main menu of MTA. This is a quick and easy way of starting a temporary server from inside the client. When the client is turned off the server will also turn off.<br />
<br />
*The mtaserver.conf is used when "MTA Server.exe" is executed from (MTA root folder)>server. This runs the server in its own console window which does not need the client to exist or to be run. This is can be useful if you're interested in hosting servers for a longer time period or if you want to experiment with a real world console.<br />
<br />
Depending on which way you host, you will want to edit those files. The settings in question are:<br />
<br />
<syntaxhighlight lang="xml"><!-- Specifies the location and name of the debugscript log file. If left blank, server won't be saving this file. --><br />
<scriptdebuglogfile>logs/scripts.log</scriptdebuglogfile> <br />
<br />
<!-- Specifies the level of the debugscript log file. Available values: 0, 1, 2, 3. When not set, defaults to 0. --><br />
<scriptdebugloglevel>0</scriptdebugloglevel></syntaxhighlight><br />
<br />
You want to make sure you have a log name specified. Also you want to specify what level of errors will be logged. If it is 0 nothing will be logged. The levels were explained at the top of this article. If the logging level was changed to 3, then in this case all '''serverside''' script errors log to (MTA root folder)>server>mods>deathmatch>logs>scripts.log<br />
<br />
====Client====<br />
Navigate to: ''(MTA root folder)>server>clientscript.log''<br />
<br />
This file logs all '''clientside''' script errors. This is set to log by default, no setup required.<br />
<br />
==Debug strategies==<br />
There are several strategies that support finding errors, apart from going through the code. Most of them include outputting debug messages, with have different information depending on the situtation.<br />
<br />
===Useful functions===<br />
There are some functions that may come in handy for debugging.<br />
* [[outputDebugString]] or [[outputChatBox]] for outputting any information (use outputDebugString for technical output)<br />
* [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] on a variable to turn the value into a string. Useful if the value is not a number or string.<br />
* [[getElementType]] to check the type of the MTA element.<br />
* [[isElement]] to check if the MTA element exists.<br />
<br />
===Add debugmessages to check ''if'', ''when'' or ''how often'' a section of code is executed===<br />
A typical example would be verify whether an ''if''-section is executed or not. To do that, just add any message you will recognize later within the ''if''-section.<br />
<syntaxhighlight lang="lua"><br />
if (variable1 == variable2) then<br />
outputDebugString("variable1 is the same as variable2!")<br />
-- do anything<br />
end<br />
</syntaxhighlight><br />
<br />
Another application would be to check when variable values are modified. First search for all occurences of the variable being edited and add a message just beside it.<br />
<br />
===Add debugmessages to check the ''value'' of a variable===<br />
Let's say you want to create a marker, but it doesn't appear at the position you expect it to be. The first thing you might want to do is check if the [[createMarker]] function is executed. But while doing this, you can also check the values being used in the [[createMarker]] function in one run.<br />
<syntaxhighlight lang="lua"><br />
outputChatBox("posX is: "..x.." posY is: "..y.." posZ is: "..z)<br />
createMarker(x,y,z)<br />
</syntaxhighlight><br />
This would output all three variables that are used as coordinates for the marker. Assuming you read those from a map file, you can now compare the debug output to the desired values. The [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] will ensure that the values of the variables can be concatenated (put together) as a string, even if it's a boolean value.<br />
<br />
==Example==<br />
Imagine you created a [[Colshape|collision shape]] somewhere and you want an action to perform after the player stays for ten seconds inside it, you would do this:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave", root, colShapeLeave)<br />
</syntaxhighlight><br />
When a player enters the colshape, debugscript outputs the following message:<br />
:{{Debug error|..[path]: attempt to index global 'colshapeTimer' (a nil value)}}<br />
This means you tried to index a table that does not exist (because the table is a nil value, it doesn't exist). In the example above, this is done when storing the timer id in the table. We need to add a check if the table exists and if it does not exist we should create it.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",root,colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we will still receive a warning when a player enters the colshape, waits for the message and leaves it again:<br />
<br />
:{{Debug warning|[..]: Bad argument @ 'killTimer' Line: ..}}<br />
<br />
Except for that (we will talk about that later) everything seems to work fine. A player enters the colshape, the timer is started, if he stays the message occurs, if he leaves the timer is killed.<br />
<br />
===A more inconspicuous error===<br />
But for some reason the message gets outputted twice when you stay in the colcircle while in a vehicle. Since it would appear some code is executed twice, we add debug messages to check this.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message<br />
outputDebugString("colShapeHit")<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message<br />
outputDebugString("colShapeLeave")<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we notice that both handler functions get executed twice when we are in a vehicle, but only once when we are on-foot. It would appear the vehicle triggers the colshape as well. To confirm this theory, we ensure that the ''player'' variable actually holds a reference to a player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
The debug messages tell us that one of the ''player'' variables is a player and that the other one is a vehicle element. Since we only want to react when a player enters the colshape, we add an ''if'' that will end the execution of the function if it's '''not''' an player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now the script should work as desired, but it will still output the warning mentioned above. This happens because the timer we try to kill when a player leaves the colshape will not exist anymore when it has reached the 10 seconds (and therefore executed after the 10th second has completed). There are different ways to get rid of that warning (since you know that the timer might not exist anymore and you only want to kill it if it exists). One way would be to check if the timer referenced in the table really exists. To do this, we need to use [[isTimer]], which we will use when we kill the timer:<br />
<syntaxhighlight lang="lua"><br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
</syntaxhighlight><br />
<br />
So the complete working code would be:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
<br />
==Debugging Performance Issues==<br />
<br />
If your server is using up more resources than it should or you just want to make sure your scripts are efficient, you can find the source of the issue by using a great tool that comes with the default resource package called [[Resource:Performancebrowser|performancebrowser]]. You can start it with ''''start performancebrowser''''. If it doesn't exist then you can get the latest resources from the [http://code.google.com/p/mtasa-resources Google Code repository]. This tool provides an incredible amount of information for performance debugging. Memory leaks, element leaks and CPU intensive scripts are all easily findable via performancebrowser. If you use the ''''d'''' option in Lua timing you can see which functions are using up the CPU.<br />
<br />
To access performancebrowser you will need to go to your web browser and enter the address: http://serverIPHere:serverHTTPPortHere/performancebrowser/ Note that the / at the end is required. So for example: http://127.0.0.1:22005/performancebrowser/ You will then need to login with an in-game admin account or any account that has access to ''''resource.performancebrowser.http'''' and ''''resource.ajax.http''''. Most of the information you will need are in the categories Lua timing and Lua memory, look for values that are much higher than other values.<br />
<br />
===Examples of scripts that could cause performance problems===<br />
<br />
Adding data to a table but never removing it. This would take months/years before it causes a problem though.<br />
<syntaxhighlight lang="lua"><br />
local someData = {}<br />
<br />
function storeData()<br />
someData[source] = true<br />
-- There is no handling for when a player quits, this is considered a memory leak<br />
-- Using the Lua timing tab you can detect the RAM usage of each resource.<br />
end<br />
addEventHandler("onPlayerJoin", root, storeData)<br />
</syntaxhighlight><br />
<br />
Element leaking is possible if you use temporary colshapes for whatever reason and may not destroy them. This would cause bandwidth, CPU and memory performance issues over time.<br />
<syntaxhighlight lang="lua"><br />
function useTemporaryCol()<br />
local col = createColCircle(some code here)<br />
if (normally this should happen) then<br />
destroyElement(col)<br />
end<br />
-- But sometimes it didn't so the script ended but the collision area remained and over time<br />
-- you may end up with hundreds to thousands of pointless collision areas. <br />
-- The Lua timing tab allows you to see the amount of elements each script has created.<br />
end<br />
</syntaxhighlight><br />
<br />
High CPU usage resulting in the server FPS dropping so much that the server is unplayable. In under 24 hours this can create havoc on a very busy server. The amount of "refs" in the Lua timing detect this type of build up, surprisingly the Lua timing tab didn't help in this case but Lua memory did.<br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onPlayerJoin", root, function()<br />
-- Code for joiner<br />
addEventHandler("onPlayerQuit", root, function()<br />
-- Code for when they have quit<br />
-- See the problem? It's bound to root which the event handler is being added again and again and again<br />
end)<br />
end)<br />
</syntaxhighlight><br />
<br />
A function uses up a lot of your CPU because whatever it does takes a long time. This is just some function that takes a long time to complete. Without performancebrowser you'd have no idea its the cause but with performancebrowser you can see that a resource is using lots of CPU in the Lua timing tab. If you then enter: ''''d'''' into the options edit box it will even tell you what file name and first line of the function that is using up so much CPU.<br />
<syntaxhighlight lang="lua"><br />
function someDodgyCode()<br />
for i=1, 100000 do<br />
-- some code<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
[[it:Guida al Debug]]<br />
[[Category:Scripting Concepts]]<br />
[[ru:Debugging]]<br />
[[Category:Tutorials]]</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Hibakeres%C3%A9s&diff=43147Hibakeresés2014-12-04T10:21:06Z<p>Ali salah: /* Debug console */</p>
<hr />
<div>When scripting you will often come across problems that are not immediately apparent. This page tries to point out some basic strategies to locate the error.<br />
<br />
==وحدة تحكم التصحيح==<br />
<br />
<br />
MTA يتميز حدة التصحيح المدمج في أن يظهر الناتج رسائل التصحيح من وظائف MTA أو من البرامج النصية. ابقائها تمانع أنه يجب أن يكون وصول المسؤول (إلا إذا كنت تعديل ACL لتغيير هذا)، يمكنك فتح وحدة التصحيح عن طريق كتابة debugscript * س * في وحدة التحكم، حيث X هو مستوى التصحيح:<br />
1: الأخطاء فقط<br />
2: الأخطاء والتحذيرات<br />
3: الأخطاء والتحذيرات ورسائل المعلومات<br />
عن طريق كتابة debugscript 3 كل الرسائل مرئية. المستوى 3 والمستوى 2 ويوصى لمعظم المناسبات. يجب أن يكون لديك debugscript تمكين كلما كنت <br />
اختبار البرامج النصية الخاصة بك لأنها سوف تساعدك بسهولة اكتشاف وحل الأخطاء المطبعية وغيرها من القضايا.<br />
<br />
===Example===<br />
This example code has two errors:<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor")<br />
outputChatbox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
When the script this piece of code is in is tried to be loaded, debugscript will output something similiar to this:<br />
:{{Debug info|Loading script failed: myResource\script.lua:2: 'then' expected near ´outputChatbox'}}<br />
This means the script could not be parsed because there was a syntax error. It shows the script path relative to the resource directory so you can identify to script where the error originated from. After the filename it shows a colon and a number, this number presents the line number - this is so you can find out where in your script the error is - it is very helpful for large scripts. After that is the error message, which varies depending on the error made. Looking at the error message, we can easily determine that the error originated from the resource '''myResource''' and line two of the script file '''script.lua'''. The error message is very clear, we forgot the "then"! We can easily fix that!<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatbox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
Now the script will load fine and won't output any errors until a player with the name 'Fedor' says something in the chat. At that moment, the debug console will output:<br />
:{{Debug error|myResource\script.lua:2: attempt to call global 'outputChatbox' (a nil value)}}<br />
This error means that the function '''outputChatbox''' is a nil value, that is - it doesn't exist! This is because the function is actually called [[outputChatBox]], not [[outputChatbox]] - take care of that capital B.:<br />
<syntaxhighlight lang="lua"><br />
function SayHello(message, player)<br />
if (getPlayerName(player) == "Fedor") then<br />
outputChatBox("Hello Fedor")<br />
end<br />
end<br />
addEventHandler("onChatMessage", root, SayHello)<br />
</syntaxhighlight><br />
<br />
Of course, this is just an example and I've only skimmed the surface of error messages. There are plenty of other messages and scenarios, but you should get the general idea.<br />
<br />
==Server & client debug logging==<br />
====Server====<br />
Navigate to: ''(MTA root folder)>server>mods>deathmatch''<br />
<br />
There are two nearly identical files:<br />
<br />
*The local.conf is the settings for the "host game" menu item in the main menu of MTA. This is a quick and easy way of starting a temporary server from inside the client. When the client is turned off the server will also turn off.<br />
<br />
*The mtaserver.conf is used when "MTA Server.exe" is executed from (MTA root folder)>server. This runs the server in its own console window which does not need the client to exist or to be run. This is can be useful if you're interested in hosting servers for a longer time period or if you want to experiment with a real world console.<br />
<br />
Depending on which way you host, you will want to edit those files. The settings in question are:<br />
<br />
<syntaxhighlight lang="xml"><!-- Specifies the location and name of the debugscript log file. If left blank, server won't be saving this file. --><br />
<scriptdebuglogfile>logs/scripts.log</scriptdebuglogfile> <br />
<br />
<!-- Specifies the level of the debugscript log file. Available values: 0, 1, 2, 3. When not set, defaults to 0. --><br />
<scriptdebugloglevel>0</scriptdebugloglevel></syntaxhighlight><br />
<br />
You want to make sure you have a log name specified. Also you want to specify what level of errors will be logged. If it is 0 nothing will be logged. The levels were explained at the top of this article. If the logging level was changed to 3, then in this case all '''serverside''' script errors log to (MTA root folder)>server>mods>deathmatch>logs>scripts.log<br />
<br />
====Client====<br />
Navigate to: ''(MTA root folder)>server>clientscript.log''<br />
<br />
This file logs all '''clientside''' script errors. This is set to log by default, no setup required.<br />
<br />
==Debug strategies==<br />
There are several strategies that support finding errors, apart from going through the code. Most of them include outputting debug messages, with have different information depending on the situtation.<br />
<br />
===Useful functions===<br />
There are some functions that may come in handy for debugging.<br />
* [[outputDebugString]] or [[outputChatBox]] for outputting any information (use outputDebugString for technical output)<br />
* [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] on a variable to turn the value into a string. Useful if the value is not a number or string.<br />
* [[getElementType]] to check the type of the MTA element.<br />
* [[isElement]] to check if the MTA element exists.<br />
<br />
===Add debugmessages to check ''if'', ''when'' or ''how often'' a section of code is executed===<br />
A typical example would be verify whether an ''if''-section is executed or not. To do that, just add any message you will recognize later within the ''if''-section.<br />
<syntaxhighlight lang="lua"><br />
if (variable1 == variable2) then<br />
outputDebugString("variable1 is the same as variable2!")<br />
-- do anything<br />
end<br />
</syntaxhighlight><br />
<br />
Another application would be to check when variable values are modified. First search for all occurences of the variable being edited and add a message just beside it.<br />
<br />
===Add debugmessages to check the ''value'' of a variable===<br />
Let's say you want to create a marker, but it doesn't appear at the position you expect it to be. The first thing you might want to do is check if the [[createMarker]] function is executed. But while doing this, you can also check the values being used in the [[createMarker]] function in one run.<br />
<syntaxhighlight lang="lua"><br />
outputChatBox("posX is: "..x.." posY is: "..y.." posZ is: "..z)<br />
createMarker(x,y,z)<br />
</syntaxhighlight><br />
This would output all three variables that are used as coordinates for the marker. Assuming you read those from a map file, you can now compare the debug output to the desired values. The [http://www.lua.org/manual/5.1/manual.html#pdf-tostring tostring()] will ensure that the values of the variables can be concatenated (put together) as a string, even if it's a boolean value.<br />
<br />
==Example==<br />
Imagine you created a [[Colshape|collision shape]] somewhere and you want an action to perform after the player stays for ten seconds inside it, you would do this:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave", root, colShapeLeave)<br />
</syntaxhighlight><br />
When a player enters the colshape, debugscript outputs the following message:<br />
:{{Debug error|..[path]: attempt to index global 'colshapeTimer' (a nil value)}}<br />
This means you tried to index a table that does not exist (because the table is a nil value, it doesn't exist). In the example above, this is done when storing the timer id in the table. We need to add a check if the table exists and if it does not exist we should create it.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit", root, colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",root,colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we will still receive a warning when a player enters the colshape, waits for the message and leaves it again:<br />
<br />
:{{Debug warning|[..]: Bad argument @ 'killTimer' Line: ..}}<br />
<br />
Except for that (we will talk about that later) everything seems to work fine. A player enters the colshape, the timer is started, if he stays the message occurs, if he leaves the timer is killed.<br />
<br />
===A more inconspicuous error===<br />
But for some reason the message gets outputted twice when you stay in the colcircle while in a vehicle. Since it would appear some code is executed twice, we add debug messages to check this.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message<br />
outputDebugString("colShapeHit")<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message<br />
outputDebugString("colShapeLeave")<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now we notice that both handler functions get executed twice when we are in a vehicle, but only once when we are on-foot. It would appear the vehicle triggers the colshape as well. To confirm this theory, we ensure that the ''player'' variable actually holds a reference to a player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
The debug messages tell us that one of the ''player'' variables is a player and that the other one is a vehicle element. Since we only want to react when a player enters the colshape, we add an ''if'' that will end the execution of the function if it's '''not''' an player element.<br />
<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
killTimer(colshapeTimer[player])<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
Now the script should work as desired, but it will still output the warning mentioned above. This happens because the timer we try to kill when a player leaves the colshape will not exist anymore when it has reached the 10 seconds (and therefore executed after the 10th second has completed). There are different ways to get rid of that warning (since you know that the timer might not exist anymore and you only want to kill it if it exists). One way would be to check if the timer referenced in the table really exists. To do this, we need to use [[isTimer]], which we will use when we kill the timer:<br />
<syntaxhighlight lang="lua"><br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
</syntaxhighlight><br />
<br />
So the complete working code would be:<br />
<syntaxhighlight lang="lua"><br />
function colShapeHit(player)<br />
if (colshapeTimer == nil) then<br />
colshapeTimer = {}<br />
end<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeHit "..getElementType(player))<br />
-- set a timer to output a message (could as well execute another function)<br />
-- store the timer id in a table, using the player as index<br />
colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")<br />
end<br />
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)<br />
<br />
function colShapeLeave(player)<br />
-- add a check for the element type<br />
if (getElementType(player) ~= "player") then return end<br />
-- add a debug message, with the element type<br />
outputDebugString("colShapeLeave "..getElementType(player))<br />
-- kill the timer when the player leaves the colshape<br />
if (isTimer(colshapeTimer[player])) then<br />
killTimer(colshapeTimer[player])<br />
end<br />
end<br />
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)<br />
</syntaxhighlight><br />
<br />
<br />
==Debugging Performance Issues==<br />
<br />
If your server is using up more resources than it should or you just want to make sure your scripts are efficient, you can find the source of the issue by using a great tool that comes with the default resource package called [[Resource:Performancebrowser|performancebrowser]]. You can start it with ''''start performancebrowser''''. If it doesn't exist then you can get the latest resources from the [http://code.google.com/p/mtasa-resources Google Code repository]. This tool provides an incredible amount of information for performance debugging. Memory leaks, element leaks and CPU intensive scripts are all easily findable via performancebrowser. If you use the ''''d'''' option in Lua timing you can see which functions are using up the CPU.<br />
<br />
To access performancebrowser you will need to go to your web browser and enter the address: http://serverIPHere:serverHTTPPortHere/performancebrowser/ Note that the / at the end is required. So for example: http://127.0.0.1:22005/performancebrowser/ You will then need to login with an in-game admin account or any account that has access to ''''resource.performancebrowser.http'''' and ''''resource.ajax.http''''. Most of the information you will need are in the categories Lua timing and Lua memory, look for values that are much higher than other values.<br />
<br />
===Examples of scripts that could cause performance problems===<br />
<br />
Adding data to a table but never removing it. This would take months/years before it causes a problem though.<br />
<syntaxhighlight lang="lua"><br />
local someData = {}<br />
<br />
function storeData()<br />
someData[source] = true<br />
-- There is no handling for when a player quits, this is considered a memory leak<br />
-- Using the Lua timing tab you can detect the RAM usage of each resource.<br />
end<br />
addEventHandler("onPlayerJoin", root, storeData)<br />
</syntaxhighlight><br />
<br />
Element leaking is possible if you use temporary colshapes for whatever reason and may not destroy them. This would cause bandwidth, CPU and memory performance issues over time.<br />
<syntaxhighlight lang="lua"><br />
function useTemporaryCol()<br />
local col = createColCircle(some code here)<br />
if (normally this should happen) then<br />
destroyElement(col)<br />
end<br />
-- But sometimes it didn't so the script ended but the collision area remained and over time<br />
-- you may end up with hundreds to thousands of pointless collision areas. <br />
-- The Lua timing tab allows you to see the amount of elements each script has created.<br />
end<br />
</syntaxhighlight><br />
<br />
High CPU usage resulting in the server FPS dropping so much that the server is unplayable. In under 24 hours this can create havoc on a very busy server. The amount of "refs" in the Lua timing detect this type of build up, surprisingly the Lua timing tab didn't help in this case but Lua memory did.<br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onPlayerJoin", root, function()<br />
-- Code for joiner<br />
addEventHandler("onPlayerQuit", root, function()<br />
-- Code for when they have quit<br />
-- See the problem? It's bound to root which the event handler is being added again and again and again<br />
end)<br />
end)<br />
</syntaxhighlight><br />
<br />
A function uses up a lot of your CPU because whatever it does takes a long time. This is just some function that takes a long time to complete. Without performancebrowser you'd have no idea its the cause but with performancebrowser you can see that a resource is using lots of CPU in the Lua timing tab. If you then enter: ''''d'''' into the options edit box it will even tell you what file name and first line of the function that is using up so much CPU.<br />
<syntaxhighlight lang="lua"><br />
function someDodgyCode()<br />
for i=1, 100000 do<br />
-- some code<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
[[it:Guida al Debug]]<br />
[[Category:Scripting Concepts]]<br />
[[ru:Debugging]]<br />
[[Category:Tutorials]]</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Meta.xml&diff=43146Meta.xml2014-12-04T10:13:13Z<p>Ali salah: /* Tags */</p>
<hr />
<div>The ''meta.xml'' file presents MTA with a set of metadata, such as the resource's name, the scripts to include, and which files to precache for sending to clients among other things. It is also the scope of "elements". It is written in XML, which is based on HTML and is the parent of XHTML.<br />
<br />
=ملف ميتا=<br />
XML is a textual data format which is widely used for the representation of data. MTA uses an XML-based language to describe the metadata for resources by using the tags below:<br />
<br />
*'''<وصف بسيط />''' Information about this resource, possible parameters include (any arbitrary parameters can be used and read using [[getResourceInfo]]):<br />
** '''صانع السكربت:''' The author of this resource<br />
** '''اصدار السكربت:''' The version of this resource<br />
** '''اسم السكربت:''' The name of this resource<br />
** '''dوصف بسيط للسكربت:''' A brief description of this resource<br />
** '''نوع السكربت:''' توجد عدة انواع للسكربت مثلا "قيم مود(لعب"سباق)", "مود", "ماب" or "misc".<br />
*'''<script />''' Source code for this resource, possible parameters are:<br />
** '''ملفات السكربت:''' هنا ملفات السكربت <br />
** '''type:''' The type of source code: "client", "server" or "shared".<br />
**'''cache:''' When the script file type is "client", this setting controls whether the file is saved on the clients' hard drive. Default is "true". Using "false" will mean the file is not saved.<br />
**'''validate:''' If set to "false", compatibility checks are skipped.<br />
*'''<map />''' The map for a gamemode, possible parameters are:<br />
**'''src:''' .map file name (can be path too eg. "maps/filename.map")<br />
**'''dimension:''' Dimension in which the map will be loaded (optional)<br />
*'''<file />''' A client-side file. Generally these are images, .txd, .col, .dff or .xml files. They'll be downloaded by clients when the resources is started (or on join)<br />
**'''src:''' client-side file name (can be path too eg. "images/image.png")<br />
**'''download:''' Whether or not to be sent to the client automatically (optional). Default is "true". Using "false" will mean they are not sent on resource start but could later be used by [[downloadFile]] (from version 1.4)<br />
*'''<include />''' Include resources that this resource will use<br />
**'''resource:''' Resource name that you want to start with this resource<br />
**'''minversion:''' Minimum version that '''resource''' needs to be (optional)<br />
**'''maxversion:''' Maximum version that '''resource''' needs to be (optional)<br />
*'''<config />''' Config file (.xml) can be accessed by resource, possible parameters are:<br />
**'''src:''' The file name of the config file<br />
**'''type:''' The type of the config file: "client" or "server"<br />
*'''<export />''' This exports functions from this resource, so other resources can use them with [[call]]<br />
**'''function:''' The function name<br />
**'''type''' Whether function is exported server-side or client-side (valid values are: "client", "server" and "shared")<br />
**'''http:''' Can the function be called via HTTP (true/false)<br />
*'''<html />'''<br />
**'''src:''' The filename for the HTTP file (can be a path)<br />
**'''default:''' The html file is one that is shown by default when visiting /resourceName/ on the server. Only one html can be default, the rest are ignored. (true/false)<br />
**'''raw:''' The html file is not parsed by the Lua interpreter and is treated as binary data. Must be used for binary files (images mainly) (true/false)<br />
*'''<settings> <setting name="" value=""/> </settings>:''' Most gamemodes use [[settings system]] to let server admins to configure it how they like. For instance you could set round time and then use [[get]] and [[set]] to get the value or change it, respectively.<br />
*'''<min_mta_version />''' Minimum version requirements for this resource to run correctly. When authoring resources, the minimum version should usually be set to the current released version of MTA:SA (which at the moment is "{{Current Version|full}}"). See example for example.<br />
**'''client:''' The minimum client version<br />
**'''server:''' The minimum server version<br />
*'''<aclrequest />''' A list of [[Access_Control_List|ACL]] rights this resource will need.<br />
{{New items|3.0132|1.3.1 r4141|<br />
*'''<sync_map_element_data />''' Controls whether map [[Element_data|element data]] such as "PosX" and "DoubleSided" are transferred to the client. This data is usually not required by most gamemodes or resources. (Map Editor and Interiors require this to be not set to false to work). When set in a gamemode meta.xml, the setting will apply to all maps loaded by that resource.<br />
**'''false:''' Disable transfer of map element data for all resources. This can reduce map download times considerably.<br />
**'''true:''' Enable transfer of map element data for all resources. (If '''false''' and '''true''' are set in different resources, true will have priority and all resources will transfer map element data)<br />
}}<br />
{{New items|3.0140|1.4.0 r5313|<br />
*'''<oop/>''' OOP - Please refer to [[OOP]] for documentation.<br />
**'''false:''' Disable OOP.<br />
**'''true:''' Enable OOP.<br />
}}<br />
<br />
== Example ==<br />
Heres an example of a meta file using some of the tags mentioned:<br />
{{#tag:code |<br />
<meta><br />
<info author="Slothman" type="gamemode" name="Stealth" /><br />
<config src="help.xml" type="client"/><br />
<br />
<min_mta_version client="{{Current Version|full}}" server="{{Current Version|full}}" /><br />
<br />
<sync_map_element_data>false</sync_map_element_data><br />
<br />
<script src="stealthmain_server.lua" /><br />
<script src="noiseblip.lua" /><br />
<script src="mission_timer.lua" /><br />
<script src="gadgets_server.lua" /><br />
<script src="gadgets_client.lua" type="client"/><br />
<script src="stealthmain_client.lua" type="client" validate="true"/><br />
<script src="noisebar.lua" type="client"/><br />
<script src="spycam.lua" type="client"/><br />
<script src="riemann_z_demonstration.lua" type="client" cache="false"/><br />
<map src="base.map" dimension="1"/><br />
<br />
<file src="riot_shield.txd" /><br />
<file src="riot_shield.dff" /><br />
<file src="riot_shield.col" /><br />
<file src="armor.png" download="true"/><br />
<file src="camera.png" download="false"/><br />
<file src="cloak.png" /><br />
<file src="goggles.png" /><br />
<file src="mine.png" /><br />
<file src="radar.png" /><br />
<file src="shield.png" /><br />
<br />
<include resource="scoreboard" /><br />
<include resource="killmessages" /><br />
<include resource="maplimits" /><br />
<br />
<export function="exampleExport1" type="server" /><br />
<export function="exampleExport2" type="client" /><br />
<export function="exampleExport3" type="shared" /><br />
<br />
<settings><br />
<setting name="roundlimit" value="[6]" /> <!-- round length in minutes --><br />
<setting name="teamdamage" value="[1]" /> <!-- 0 for team protection off, 1 for team protection on --><br />
<setting name="teambalance" value="[1]" /> <!-- difference limit of amount of players between teams --><br />
<setting name="spazammo" value="[25]" /> <!-- ammo amounts --><br />
<setting name="m4ammo" value="[100]" /><br />
<setting name="shotgunammo" value="[25]" /><br />
<setting name="sniperammo" value="[20]" /><br />
<setting name="ak47ammo" value="[120]" /><br />
<setting name="rifleammo" value="[40]" /><br />
<setting name="deserteagleammo" value="[45]" /><br />
<setting name="pistolammo" value="[132]" /><br />
<setting name="uziammo" value="[150]" /><br />
<setting name="tec9ammo" value="[150]" /><br />
<setting name="silencedammo" value="[65]" /><br />
<setting name="grenadeammo" value="[4]" /><br />
<setting name="satchelammo" value="[4]" /><br />
<setting name="teargasammo" value="[4]" /><br />
<setting name="molatovammo" value="[4]" /><br />
<setting name="isAllowedToShoot" value="true" /><br />
</settings><br />
<br />
<aclrequest><br />
<right name="function.startResource" access="true" /><br />
<right name="function.stopResource" access="true" /><br />
<right name="function.setPlayerMuted" access="true" /><br />
</aclrequest><br />
</meta><br />
|lang="xml"}}<br />
[[Category:Scripting Concepts]]<br />
[[cs:Meta.xml]]<br />
[[de:Meta.xml]]<br />
[[es:Sobre el archivo "meta.xml"]]<br />
[[it:Meta.xml]]<br />
[[pl:Meta.xml]]<br />
[[ru:Meta.xml]]</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Introduction_to_Scripting_the_GUI&diff=43145Introduction to Scripting the GUI2014-12-04T10:06:53Z<p>Ali salah: /* Logging in */</p>
<hr />
<div><!-- place holder --><br />
One important feature in MTA:SA is the ability to script customized GUI (Graphic User Interface). The GUI consists of windows, button, edit boxes, check boxes... Almost every standard form components in graphical environments. They can be displayed while the user is in game, and used for inputs and outputs in place of traditional commands. <br />
<br />
[[Image:AdminGUI.png|thumb|Admin Console GUI]]<br />
<br />
==A tutorial to make a login window==<br />
In this tutorial we'll make a simple login window, with two input boxes and a button. The window appears when the player joins the game, and once the button is clicked, the player is spawned. The tutorial will continue the gamemode we made in [[Scripting Introduction|Introduction to Scripting]] ''(If you have used the [[Scripting Introduction|Introduction to Scripting]], you will need to remove or comment the [[spawnPlayer]] line in the "joinHandler" function in your code, as we will be replacing it with a gui alternative in this tutorial)''. We'll also take a look at client-side scripting. <br />
<br />
===رسم الواجهة===<br />
All the GUI must be made client side. It is also a good practice to keep all the client scripts in a separate folder. <br />
<br />
Browse to /Your MTA Server/mods/deathmatch/resources/myserver/ directory, and create a folder named "client". Under /client/ directory, create a text file and name it "gui.lua". <br />
<br />
In this file we will write a funtion that draws the window. To create a window we will use [[guiCreateWindow]]:<br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
-- define the X and Y positions of the window<br />
local X = 0.375<br />
local Y = 0.375<br />
-- define the width and height of the window<br />
local Width = 0.25<br />
local Height = 0.25<br />
-- create the window and save its element value into the variable 'wdwLogin'<br />
-- click on the function's name to read its documentation<br />
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)<br />
end<br />
</syntaxhighlight><br />
<br />
===Relative and Absolute===<br />
Note that the final argument passed to guiCreateWindow in the above example is ''true''. This indicates that the coordinates and dimensions of the window are '''relative''', meaning they are a ''percentage'' of the total screen size. This means that if the far left side of the screen is 0, and the far right is 1, an X position of 0.5 would represent the centre point of the screen. Similarly, if the top of the screen is 0 and the bottom is 1, a Y position of 0.2 would be 20% of the way down the screen. The same principles apply to both Width and Height as well (with a Width value of 0.5 meaning the window will be half as wide as the screen).<br />
<br />
The alternative to using relative values is using '''absolute''' (by passing ''false'' instead of true to guiCreateWindow). Absolute values are calculated as the total number of pixels from the top-left corner of the parent (if no gui element parent is specified, the parent is the screen itself). If we assume a screen resolution of 1920x1200, the far left side of the screen being 0 pixels and the far right being 1920 pixels, an X position of 960 will represent the centre point of the screen. Similarly, if the top of the screen is 0 pixels and the bottom is 1200, a Y position of 20 would be 20 pixels down from the top of the screen. The same principles apply to both Width and Height as well (with a Width value of 50 meaning the window will be 50 pixels wide). ''You can use [[guiGetScreenSize]] and a little maths to calculate certain absolute positions.''<br />
<br />
The differences between using relative and absolute values is quite simple; gui created using absolute values will always remain exactly the same pixel size and position, while gui created using relative values will always be a percentage of its parent's size.<br />
<br />
Absolute is generally easier to maintain when editing code by hand, however your choice of type depends on the situation you are using it for.<br />
<br />
For the purposes of this introduction we will be using relative values.<br />
<br />
===Adding the components===<br />
Next, we'll add the text labels (saying "username:" and "password:"), edit boxes (for entering your data) and a button to log in.<br />
<br />
To create buttons we use [[guiCreateButton]] and to create edit boxes use [[guiCreateEdit]]:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
local X = 0.375<br />
local Y = 0.375<br />
local Width = 0.25<br />
local Height = 0.25<br />
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)<br />
<br />
-- define new X and Y positions for the first label<br />
X = 0.0825<br />
Y = 0.2<br />
-- define new Width and Height values for the first label<br />
Width = 0.25<br />
Height = 0.25<br />
-- create the first label, note the final argument passed is 'wdwLogin' meaning the window<br />
-- we created above is the parent of this label (so all the position and size values are now relative to the position of that window)<br />
guiCreateLabel(X, Y, Width, Height, "Username", true, wdwLogin)<br />
-- alter the Y value, so the second label is slightly below the first<br />
Y = 0.5<br />
guiCreateLabel(X, Y, Width, Height, "Password", true, wdwLogin)<br />
<br />
<br />
X = 0.415<br />
Y = 0.2<br />
Width = 0.5<br />
Height = 0.15<br />
edtUser = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
Y = 0.5<br />
edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
-- set the maximum character length for the username and password fields to 50<br />
guiEditSetMaxLength(edtUser, 50)<br />
guiEditSetMaxLength(edtPass, 50)<br />
<br />
X = 0.415<br />
Y = 0.7<br />
Width = 0.25<br />
Height = 0.2<br />
btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)<br />
<br />
-- make the window invisible<br />
guiSetVisible(wdwLogin, false)<br />
end<br />
</syntaxhighlight><br />
Note that every GUI component created is a child of the window, this is done by specifying the parent element (wdwLogin, in this case) when creating the component. <br />
<br />
This is very useful because not only does it mean that all the components are attached to the window and will move with it, but also that any changes done to the parent window will be applied down the tree to these child components. For example, we can now hide all of the GUI we just created by simply hiding the window:<br />
<syntaxhighlight lang="lua"><br />
guiSetVisible(wdwLogin, false) --hides all the GUI we made so we can show them to the player at the appropriate moment. <br />
</syntaxhighlight><br />
<br />
===Using the function we wrote===<br />
The createLoginWindow function is now complete, but it won't do anything until we call it. It is recommended to create all GUI when the client resource starts, hide them, and show them to the player later when needed. Therefore, we'll write an event handler for "[[onClientResourceStart]]" to create the window:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event handler to the root element of the resource<br />
-- this means it will only trigger when its own resource is started<br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
createLoginWindow()<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
As this is a log in window, we now need to show the window when the player joins the game. <br />
This can be done using the same event, "[[onClientResourceStart]]", so we can modify the above code to include showing the window:<br />
<br />
'''Note that we are now writing more code for our existing 'onClientResourceStart' handler. This is not a new event handler and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
-- create the log in window and its components<br />
createLoginWindow()<br />
<br />
-- output a brief welcome message to the player<br />
outputChatBox("Welcome to My MTA:SA Server, please log in.")<br />
<br />
-- if the GUI was successfully created, then show the GUI to the player<br />
if (wdwLogin ~= nil) then<br />
guiSetVisible(wdwLogin, true)<br />
else<br />
-- if the GUI hasnt been properly created, tell the player<br />
outputChatBox("An unexpected error has occurred and the log in GUI has not been created.")<br />
end <br />
<br />
-- enable the players cursor (so they can select and click on the components)<br />
showCursor(true)<br />
-- set the input focus onto the GUI, allowing players (for example) to press 'T' without the chatbox opening<br />
guiSetInputEnabled(true)<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
Note that we have a simple security check before making the window visible, so in the unlikely event that the window has not been created, meaning wdwLogin is not a valid element, we don't get an error and just inform the player what has happened. <br />
In the next step, we will create the button functionality for the log in button.<br />
<br />
==Scripting the button==<br />
Now that we have created our GUI and shown it to the player, we need to make it work. <br />
<br />
===Detecting the click===<br />
When the player clicks on any part of the GUI, the event "[[onClientGUIClick]]" will be triggered for the GUI component you clicked on. This allows us to easily detect any clicks on the GUI elements we want to use.<br />
For example, we can attach the event to the btnLogin button to catch any clicks on it:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event onClientGUIClick to btnLogin and set it to trigger the 'clientSubmitLogin' function<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
'''Note the final argument passed is "false". This indicates that the event will only trigger directly on btnLogin, not if the event has propagated up or down the tree. Setting this to "true" while attaching to gui elements will mean that clicking on any element in the same branch will trigger this event.'''<br />
<br />
This line of code can now be added inside the createLoginWindow function. It is a common mistake to try and attach events to non-existant GUI elements, so make sure you always attach your events '''after''' the gui element (in this case, the button) has been created:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
-- create all our GUI elements<br />
...<br />
<br />
-- now add our onClientGUIClick event to the button we just created<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
<br />
===Managing the click===<br />
Now that we can detect when the player clicks on the button, we need to write code to manage what happens when they do.<br />
In our [[onClientGUIClick]] event handle, we told it to call the function clientSubmitLogin whenever btnLogin is clicked.<br />
Therefore, we can now use the function clientSubmitLogin to control what happens when the button is clicked:<br />
<syntaxhighlight lang="lua"><br />
-- create the function and define the 'button' and 'state' parameters<br />
-- (these are passed automatically by onClientGUIClick)<br />
function clientSubmitLogin(button,state)<br />
-- if our login button was clicked with the left mouse button, and the state of the mouse button is up<br />
if button == "left" and state == "up" then<br />
-- move the input focus back onto the game (allowing players to move around, open the chatbox, etc)<br />
guiSetInputEnabled(false)<br />
-- hide the window and all the components<br />
guiSetVisible(wdwLogin, false)<br />
-- hide the mouse cursor<br />
showCursor(false)<br />
end<br />
end<br />
</syntaxhighlight><br />
Now, when the button is clicked, the window will be hidden and all controls will be returned to the player. Next, we will tell the server to allow the player to spawn.<br />
<br />
===Triggering the server===<br />
Triggering the server can be done using [[triggerServerEvent]]. This allows you to trigger a specified event on the server from the client. The same can be done in reverse using [[triggerClientEvent]].<br />
Here, we use the [[triggerServerEvent]] function to call our own custom event on the server, named "submitLogin", which will then control the spawning of the player serverside.<br />
<br />
'''Note that we are now writing more code for our existing 'clientSubmitLogin' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function clientSubmitLogin(button,state)<br />
if button == "left" and state == "up" then<br />
-- get the text entered in the 'username' field<br />
local username = guiGetText(edtUser)<br />
-- get the text entered in the 'password' field<br />
local password = guiGetText(edtPass)<br />
<br />
-- if the username and password both exist<br />
if username and password then<br />
-- trigger the server event 'submitLogin' and pass the username and password to it<br />
triggerServerEvent("submitLogin", getRootElement(), username, password)<br />
<br />
-- hide the gui, hide the cursor and return control to the player<br />
guiSetInputEnabled(false)<br />
guiSetVisible(wdwLogin, false)<br />
showCursor(false)<br />
else<br />
-- otherwise, output a message to the player, do not trigger the server<br />
-- and do not hide the gui<br />
outputChatBox("Please enter a username and password.")<br />
end<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
===Creating the serverside event===<br />
At this point we now have all the code needed on the client side, so open up your serverside 'script.lua' file (from the [[Scripting Introduction|Introduction to Scripting]]) or another suitable serverside file to work with.<br />
<br />
On the server side, recall that we are spawning the player as soon as they login.<br />
So, first of all, we will need to define the custom event that we used before on the client. This can be done using [[addEvent]] and [[addEventHandler]].<br />
<syntaxhighlight lang="lua"><br />
-- create our loginHandler function, with username and password parameters (passed from the client gui)<br />
function loginHandler(username,password)<br />
<br />
end<br />
<br />
-- define our custom event, and allow it to be triggered from the client ('true')<br />
addEvent("submitLogin",true)<br />
-- add an event handler so that when submitLogin is triggered, the function loginHandler is called<br />
addEventHandler("submitLogin",root,loginHandler)<br />
</syntaxhighlight><br />
<br />
وظيفة loginHandler ( اسم المستخدم، كلمة المرور ) <br />
- التحقق من أن اسم المستخدم وكلمة المرور الصحيحة <br />
إذا اسم المستخدم == "المستخدم" و كلمة المرور == "تفاحة" ثم <br />
- لاعب وتسجيل بنجاح، حتى تفرخ لهم <br />
إذا ( العميل ) ثم <br />
spawnPlayer ( العميل ، 1959.55 ، - 1714.46 ، 10 ) <br />
fadeCamera ( العميل ، صحيح ) <br />
setCameraTarget ( العميل ، العميل ) <br />
outputChatBox ( "مرحبا بكم في بلدي الخادم." ، العميل ) <br />
إنهاء <br />
آخر <br />
- إذا كان اسم المستخدم أو كلمة المرور غير صحيحة، خرج رسالة لاعب <br />
outputChatBox ( "اسم المستخدم وكلمة المرور غير صالحة. الرجاء إعادة الاتصال وحاول مرة أخرى." ، العميل ) <br />
نهاية <br />
نهاية<br />
<br />
addEvent ( "submitLogin" ، أوفياء ) <br />
addEventHandler ( "submitLogin" ، الجذر، loginHandler )</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Introduction_to_Scripting_the_GUI&diff=43144Introduction to Scripting the GUI2014-12-04T10:04:35Z<p>Ali salah: /* Draw the window */</p>
<hr />
<div><!-- place holder --><br />
One important feature in MTA:SA is the ability to script customized GUI (Graphic User Interface). The GUI consists of windows, button, edit boxes, check boxes... Almost every standard form components in graphical environments. They can be displayed while the user is in game, and used for inputs and outputs in place of traditional commands. <br />
<br />
[[Image:AdminGUI.png|thumb|Admin Console GUI]]<br />
<br />
==A tutorial to make a login window==<br />
In this tutorial we'll make a simple login window, with two input boxes and a button. The window appears when the player joins the game, and once the button is clicked, the player is spawned. The tutorial will continue the gamemode we made in [[Scripting Introduction|Introduction to Scripting]] ''(If you have used the [[Scripting Introduction|Introduction to Scripting]], you will need to remove or comment the [[spawnPlayer]] line in the "joinHandler" function in your code, as we will be replacing it with a gui alternative in this tutorial)''. We'll also take a look at client-side scripting. <br />
<br />
===رسم الواجهة===<br />
All the GUI must be made client side. It is also a good practice to keep all the client scripts in a separate folder. <br />
<br />
Browse to /Your MTA Server/mods/deathmatch/resources/myserver/ directory, and create a folder named "client". Under /client/ directory, create a text file and name it "gui.lua". <br />
<br />
In this file we will write a funtion that draws the window. To create a window we will use [[guiCreateWindow]]:<br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
-- define the X and Y positions of the window<br />
local X = 0.375<br />
local Y = 0.375<br />
-- define the width and height of the window<br />
local Width = 0.25<br />
local Height = 0.25<br />
-- create the window and save its element value into the variable 'wdwLogin'<br />
-- click on the function's name to read its documentation<br />
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)<br />
end<br />
</syntaxhighlight><br />
<br />
===Relative and Absolute===<br />
Note that the final argument passed to guiCreateWindow in the above example is ''true''. This indicates that the coordinates and dimensions of the window are '''relative''', meaning they are a ''percentage'' of the total screen size. This means that if the far left side of the screen is 0, and the far right is 1, an X position of 0.5 would represent the centre point of the screen. Similarly, if the top of the screen is 0 and the bottom is 1, a Y position of 0.2 would be 20% of the way down the screen. The same principles apply to both Width and Height as well (with a Width value of 0.5 meaning the window will be half as wide as the screen).<br />
<br />
The alternative to using relative values is using '''absolute''' (by passing ''false'' instead of true to guiCreateWindow). Absolute values are calculated as the total number of pixels from the top-left corner of the parent (if no gui element parent is specified, the parent is the screen itself). If we assume a screen resolution of 1920x1200, the far left side of the screen being 0 pixels and the far right being 1920 pixels, an X position of 960 will represent the centre point of the screen. Similarly, if the top of the screen is 0 pixels and the bottom is 1200, a Y position of 20 would be 20 pixels down from the top of the screen. The same principles apply to both Width and Height as well (with a Width value of 50 meaning the window will be 50 pixels wide). ''You can use [[guiGetScreenSize]] and a little maths to calculate certain absolute positions.''<br />
<br />
The differences between using relative and absolute values is quite simple; gui created using absolute values will always remain exactly the same pixel size and position, while gui created using relative values will always be a percentage of its parent's size.<br />
<br />
Absolute is generally easier to maintain when editing code by hand, however your choice of type depends on the situation you are using it for.<br />
<br />
For the purposes of this introduction we will be using relative values.<br />
<br />
===Adding the components===<br />
Next, we'll add the text labels (saying "username:" and "password:"), edit boxes (for entering your data) and a button to log in.<br />
<br />
To create buttons we use [[guiCreateButton]] and to create edit boxes use [[guiCreateEdit]]:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
local X = 0.375<br />
local Y = 0.375<br />
local Width = 0.25<br />
local Height = 0.25<br />
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)<br />
<br />
-- define new X and Y positions for the first label<br />
X = 0.0825<br />
Y = 0.2<br />
-- define new Width and Height values for the first label<br />
Width = 0.25<br />
Height = 0.25<br />
-- create the first label, note the final argument passed is 'wdwLogin' meaning the window<br />
-- we created above is the parent of this label (so all the position and size values are now relative to the position of that window)<br />
guiCreateLabel(X, Y, Width, Height, "Username", true, wdwLogin)<br />
-- alter the Y value, so the second label is slightly below the first<br />
Y = 0.5<br />
guiCreateLabel(X, Y, Width, Height, "Password", true, wdwLogin)<br />
<br />
<br />
X = 0.415<br />
Y = 0.2<br />
Width = 0.5<br />
Height = 0.15<br />
edtUser = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
Y = 0.5<br />
edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
-- set the maximum character length for the username and password fields to 50<br />
guiEditSetMaxLength(edtUser, 50)<br />
guiEditSetMaxLength(edtPass, 50)<br />
<br />
X = 0.415<br />
Y = 0.7<br />
Width = 0.25<br />
Height = 0.2<br />
btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)<br />
<br />
-- make the window invisible<br />
guiSetVisible(wdwLogin, false)<br />
end<br />
</syntaxhighlight><br />
Note that every GUI component created is a child of the window, this is done by specifying the parent element (wdwLogin, in this case) when creating the component. <br />
<br />
This is very useful because not only does it mean that all the components are attached to the window and will move with it, but also that any changes done to the parent window will be applied down the tree to these child components. For example, we can now hide all of the GUI we just created by simply hiding the window:<br />
<syntaxhighlight lang="lua"><br />
guiSetVisible(wdwLogin, false) --hides all the GUI we made so we can show them to the player at the appropriate moment. <br />
</syntaxhighlight><br />
<br />
===Using the function we wrote===<br />
The createLoginWindow function is now complete, but it won't do anything until we call it. It is recommended to create all GUI when the client resource starts, hide them, and show them to the player later when needed. Therefore, we'll write an event handler for "[[onClientResourceStart]]" to create the window:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event handler to the root element of the resource<br />
-- this means it will only trigger when its own resource is started<br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
createLoginWindow()<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
As this is a log in window, we now need to show the window when the player joins the game. <br />
This can be done using the same event, "[[onClientResourceStart]]", so we can modify the above code to include showing the window:<br />
<br />
'''Note that we are now writing more code for our existing 'onClientResourceStart' handler. This is not a new event handler and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
-- create the log in window and its components<br />
createLoginWindow()<br />
<br />
-- output a brief welcome message to the player<br />
outputChatBox("Welcome to My MTA:SA Server, please log in.")<br />
<br />
-- if the GUI was successfully created, then show the GUI to the player<br />
if (wdwLogin ~= nil) then<br />
guiSetVisible(wdwLogin, true)<br />
else<br />
-- if the GUI hasnt been properly created, tell the player<br />
outputChatBox("An unexpected error has occurred and the log in GUI has not been created.")<br />
end <br />
<br />
-- enable the players cursor (so they can select and click on the components)<br />
showCursor(true)<br />
-- set the input focus onto the GUI, allowing players (for example) to press 'T' without the chatbox opening<br />
guiSetInputEnabled(true)<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
Note that we have a simple security check before making the window visible, so in the unlikely event that the window has not been created, meaning wdwLogin is not a valid element, we don't get an error and just inform the player what has happened. <br />
In the next step, we will create the button functionality for the log in button.<br />
<br />
==Scripting the button==<br />
Now that we have created our GUI and shown it to the player, we need to make it work. <br />
<br />
===Detecting the click===<br />
When the player clicks on any part of the GUI, the event "[[onClientGUIClick]]" will be triggered for the GUI component you clicked on. This allows us to easily detect any clicks on the GUI elements we want to use.<br />
For example, we can attach the event to the btnLogin button to catch any clicks on it:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event onClientGUIClick to btnLogin and set it to trigger the 'clientSubmitLogin' function<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
'''Note the final argument passed is "false". This indicates that the event will only trigger directly on btnLogin, not if the event has propagated up or down the tree. Setting this to "true" while attaching to gui elements will mean that clicking on any element in the same branch will trigger this event.'''<br />
<br />
This line of code can now be added inside the createLoginWindow function. It is a common mistake to try and attach events to non-existant GUI elements, so make sure you always attach your events '''after''' the gui element (in this case, the button) has been created:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
-- create all our GUI elements<br />
...<br />
<br />
-- now add our onClientGUIClick event to the button we just created<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
<br />
===Managing the click===<br />
Now that we can detect when the player clicks on the button, we need to write code to manage what happens when they do.<br />
In our [[onClientGUIClick]] event handle, we told it to call the function clientSubmitLogin whenever btnLogin is clicked.<br />
Therefore, we can now use the function clientSubmitLogin to control what happens when the button is clicked:<br />
<syntaxhighlight lang="lua"><br />
-- create the function and define the 'button' and 'state' parameters<br />
-- (these are passed automatically by onClientGUIClick)<br />
function clientSubmitLogin(button,state)<br />
-- if our login button was clicked with the left mouse button, and the state of the mouse button is up<br />
if button == "left" and state == "up" then<br />
-- move the input focus back onto the game (allowing players to move around, open the chatbox, etc)<br />
guiSetInputEnabled(false)<br />
-- hide the window and all the components<br />
guiSetVisible(wdwLogin, false)<br />
-- hide the mouse cursor<br />
showCursor(false)<br />
end<br />
end<br />
</syntaxhighlight><br />
Now, when the button is clicked, the window will be hidden and all controls will be returned to the player. Next, we will tell the server to allow the player to spawn.<br />
<br />
===Triggering the server===<br />
Triggering the server can be done using [[triggerServerEvent]]. This allows you to trigger a specified event on the server from the client. The same can be done in reverse using [[triggerClientEvent]].<br />
Here, we use the [[triggerServerEvent]] function to call our own custom event on the server, named "submitLogin", which will then control the spawning of the player serverside.<br />
<br />
'''Note that we are now writing more code for our existing 'clientSubmitLogin' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function clientSubmitLogin(button,state)<br />
if button == "left" and state == "up" then<br />
-- get the text entered in the 'username' field<br />
local username = guiGetText(edtUser)<br />
-- get the text entered in the 'password' field<br />
local password = guiGetText(edtPass)<br />
<br />
-- if the username and password both exist<br />
if username and password then<br />
-- trigger the server event 'submitLogin' and pass the username and password to it<br />
triggerServerEvent("submitLogin", getRootElement(), username, password)<br />
<br />
-- hide the gui, hide the cursor and return control to the player<br />
guiSetInputEnabled(false)<br />
guiSetVisible(wdwLogin, false)<br />
showCursor(false)<br />
else<br />
-- otherwise, output a message to the player, do not trigger the server<br />
-- and do not hide the gui<br />
outputChatBox("Please enter a username and password.")<br />
end<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
===Creating the serverside event===<br />
At this point we now have all the code needed on the client side, so open up your serverside 'script.lua' file (from the [[Scripting Introduction|Introduction to Scripting]]) or another suitable serverside file to work with.<br />
<br />
On the server side, recall that we are spawning the player as soon as they login.<br />
So, first of all, we will need to define the custom event that we used before on the client. This can be done using [[addEvent]] and [[addEventHandler]].<br />
<syntaxhighlight lang="lua"><br />
-- create our loginHandler function, with username and password parameters (passed from the client gui)<br />
function loginHandler(username,password)<br />
<br />
end<br />
<br />
-- define our custom event, and allow it to be triggered from the client ('true')<br />
addEvent("submitLogin",true)<br />
-- add an event handler so that when submitLogin is triggered, the function loginHandler is called<br />
addEventHandler("submitLogin",root,loginHandler)<br />
</syntaxhighlight><br />
<br />
===Logging in===<br />
Now we have a function that is called through the custom event 'submitLogin', we can start to work on logging in and spawning the player, using our 'loginHandler' function:<br />
<syntaxhighlight lang="lua"><br />
function loginHandler(username,password)<br />
-- check that the username and password are correct<br />
if username == "user" and password == "apple" then<br />
-- the player has successfully logged in, so spawn them<br />
if (client) then<br />
spawnPlayer(client, 1959.55, -1714.46, 10)<br />
fadeCamera(client, true)<br />
setCameraTarget(client, client)<br />
outputChatBox("Welcome to My Server.", client)<br />
end<br />
else<br />
-- if the username or password are not correct, output a message to the player<br />
outputChatBox("Invalid username and password. Please re-connect and try again.",client)<br />
end <br />
end<br />
<br />
addEvent("submitLogin",true)<br />
addEventHandler("submitLogin",root,loginHandler)<br />
</syntaxhighlight><br />
'''For the purposes of this tutorial, a very basic username and password system is shown. For a more comprehensive alternative, you can use the Account System or a MySQL database.'''<br />
<br />
Also note the use of the variable "client", it's an internal variable used by MTA to identify the player who triggered the event. <br />
<br />
<br />
Finally, do not forget to include the new gui.lua file in the meta.xml of the main resource, and label it as a client script:<br />
<syntaxhighlight lang="xml"><br />
<script src="client/gui.lua" type="client" /><br />
</syntaxhighlight><br />
<br />
<br />
At this point, we now have a basic login window that checks the player's username and password when the login button is clicked. If they are correct, the player is automatically spawned.<br />
<br />
For further help with GUI, see the [[:Category:GUI_Tutorials|GUI tutorials]].<br />
<br />
[[Category:GUI_Tutorials]]<br />
[[it:Introduzione_allo_scripting_della_GUI]]<br />
[[ru:Introduction to Scripting the GUI]]<br />
[[es:Introducción a la Programación de GUI]]</div>Ali salahhttps://wiki.multitheftauto.com/index.php?title=Introduction_to_Scripting_the_GUI&diff=43142Introduction to Scripting the GUI2014-12-04T09:36:34Z<p>Ali salah: /* Draw the window */</p>
<hr />
<div><!-- place holder --><br />
One important feature in MTA:SA is the ability to script customized GUI (Graphic User Interface). The GUI consists of windows, button, edit boxes, check boxes... Almost every standard form components in graphical environments. They can be displayed while the user is in game, and used for inputs and outputs in place of traditional commands. <br />
<br />
[[Image:AdminGUI.png|thumb|Admin Console GUI]]<br />
<br />
==A tutorial to make a login window==<br />
In this tutorial we'll make a simple login window, with two input boxes and a button. The window appears when the player joins the game, and once the button is clicked, the player is spawned. The tutorial will continue the gamemode we made in [[Scripting Introduction|Introduction to Scripting]] ''(If you have used the [[Scripting Introduction|Introduction to Scripting]], you will need to remove or comment the [[spawnPlayer]] line in the "joinHandler" function in your code, as we will be replacing it with a gui alternative in this tutorial)''. We'll also take a look at client-side scripting. <br />
<br />
===رسم النافذة===<br />
ويجب بذل كل واجهة المستخدم الرسومية جانب العميل. بل هو أيضا ممارسة جيدة للحفاظ على جميع النصوص العميل في مجلد منفصل.<br />
تصفح ل/ لديك MTA الخادم / تعديل / الموت / موارد / MYSERVER / الدليل، وإنشاء مجلد باسم "العميل". تحت / العميل / الدليل، إنشاء ملف نصي وتسميته "gui.lua".<br />
في هذا الملف وسوف نكتب Funtion والذي يرسم النافذة. لإنشاء إطار سوف نستخدم guiCreateWindow :<br />
وظيفة createLoginWindow ( ) <br />
- تحديد مواقع X و Y من النافذة <br />
المحلي X = 0.375 <br />
المحلي Y = 0.375 <br />
- تحديد العرض والارتفاع من النافذة <br />
المحلي العرض = 0.25 <br />
المحلي الطول = 0.25 <br />
- خلق النافذة وحفظ عنصرها قيمة في المتغير 'wdwLogin " <br />
- انقر على اسم الدالة لقراءة الوثائق الخاصة به <br />
wdwLogin = guiCreateWindow ( X، Y، العرض، الارتفاع، "الرجاء الدخول في" ، الحقيقية ) <br />
نهاية<br />
</syntaxhighlight><br />
<br />
===Relative and Absolute===<br />
Note that the final argument passed to guiCreateWindow in the above example is ''true''. This indicates that the coordinates and dimensions of the window are '''relative''', meaning they are a ''percentage'' of the total screen size. This means that if the far left side of the screen is 0, and the far right is 1, an X position of 0.5 would represent the centre point of the screen. Similarly, if the top of the screen is 0 and the bottom is 1, a Y position of 0.2 would be 20% of the way down the screen. The same principles apply to both Width and Height as well (with a Width value of 0.5 meaning the window will be half as wide as the screen).<br />
<br />
The alternative to using relative values is using '''absolute''' (by passing ''false'' instead of true to guiCreateWindow). Absolute values are calculated as the total number of pixels from the top-left corner of the parent (if no gui element parent is specified, the parent is the screen itself). If we assume a screen resolution of 1920x1200, the far left side of the screen being 0 pixels and the far right being 1920 pixels, an X position of 960 will represent the centre point of the screen. Similarly, if the top of the screen is 0 pixels and the bottom is 1200, a Y position of 20 would be 20 pixels down from the top of the screen. The same principles apply to both Width and Height as well (with a Width value of 50 meaning the window will be 50 pixels wide). ''You can use [[guiGetScreenSize]] and a little maths to calculate certain absolute positions.''<br />
<br />
The differences between using relative and absolute values is quite simple; gui created using absolute values will always remain exactly the same pixel size and position, while gui created using relative values will always be a percentage of its parent's size.<br />
<br />
Absolute is generally easier to maintain when editing code by hand, however your choice of type depends on the situation you are using it for.<br />
<br />
For the purposes of this introduction we will be using relative values.<br />
<br />
===Adding the components===<br />
Next, we'll add the text labels (saying "username:" and "password:"), edit boxes (for entering your data) and a button to log in.<br />
<br />
To create buttons we use [[guiCreateButton]] and to create edit boxes use [[guiCreateEdit]]:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
local X = 0.375<br />
local Y = 0.375<br />
local Width = 0.25<br />
local Height = 0.25<br />
wdwLogin = guiCreateWindow(X, Y, Width, Height, "Please Log In", true)<br />
<br />
-- define new X and Y positions for the first label<br />
X = 0.0825<br />
Y = 0.2<br />
-- define new Width and Height values for the first label<br />
Width = 0.25<br />
Height = 0.25<br />
-- create the first label, note the final argument passed is 'wdwLogin' meaning the window<br />
-- we created above is the parent of this label (so all the position and size values are now relative to the position of that window)<br />
guiCreateLabel(X, Y, Width, Height, "Username", true, wdwLogin)<br />
-- alter the Y value, so the second label is slightly below the first<br />
Y = 0.5<br />
guiCreateLabel(X, Y, Width, Height, "Password", true, wdwLogin)<br />
<br />
<br />
X = 0.415<br />
Y = 0.2<br />
Width = 0.5<br />
Height = 0.15<br />
edtUser = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
Y = 0.5<br />
edtPass = guiCreateEdit(X, Y, Width, Height, "", true, wdwLogin)<br />
-- set the maximum character length for the username and password fields to 50<br />
guiEditSetMaxLength(edtUser, 50)<br />
guiEditSetMaxLength(edtPass, 50)<br />
<br />
X = 0.415<br />
Y = 0.7<br />
Width = 0.25<br />
Height = 0.2<br />
btnLogin = guiCreateButton(X, Y, Width, Height, "Log In", true, wdwLogin)<br />
<br />
-- make the window invisible<br />
guiSetVisible(wdwLogin, false)<br />
end<br />
</syntaxhighlight><br />
Note that every GUI component created is a child of the window, this is done by specifying the parent element (wdwLogin, in this case) when creating the component. <br />
<br />
This is very useful because not only does it mean that all the components are attached to the window and will move with it, but also that any changes done to the parent window will be applied down the tree to these child components. For example, we can now hide all of the GUI we just created by simply hiding the window:<br />
<syntaxhighlight lang="lua"><br />
guiSetVisible(wdwLogin, false) --hides all the GUI we made so we can show them to the player at the appropriate moment. <br />
</syntaxhighlight><br />
<br />
===Using the function we wrote===<br />
The createLoginWindow function is now complete, but it won't do anything until we call it. It is recommended to create all GUI when the client resource starts, hide them, and show them to the player later when needed. Therefore, we'll write an event handler for "[[onClientResourceStart]]" to create the window:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event handler to the root element of the resource<br />
-- this means it will only trigger when its own resource is started<br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
createLoginWindow()<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
As this is a log in window, we now need to show the window when the player joins the game. <br />
This can be done using the same event, "[[onClientResourceStart]]", so we can modify the above code to include showing the window:<br />
<br />
'''Note that we are now writing more code for our existing 'onClientResourceStart' handler. This is not a new event handler and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
addEventHandler("onClientResourceStart", getResourceRootElement(getThisResource()), <br />
function ()<br />
-- create the log in window and its components<br />
createLoginWindow()<br />
<br />
-- output a brief welcome message to the player<br />
outputChatBox("Welcome to My MTA:SA Server, please log in.")<br />
<br />
-- if the GUI was successfully created, then show the GUI to the player<br />
if (wdwLogin ~= nil) then<br />
guiSetVisible(wdwLogin, true)<br />
else<br />
-- if the GUI hasnt been properly created, tell the player<br />
outputChatBox("An unexpected error has occurred and the log in GUI has not been created.")<br />
end <br />
<br />
-- enable the players cursor (so they can select and click on the components)<br />
showCursor(true)<br />
-- set the input focus onto the GUI, allowing players (for example) to press 'T' without the chatbox opening<br />
guiSetInputEnabled(true)<br />
end<br />
) <br />
</syntaxhighlight><br />
<br />
Note that we have a simple security check before making the window visible, so in the unlikely event that the window has not been created, meaning wdwLogin is not a valid element, we don't get an error and just inform the player what has happened. <br />
In the next step, we will create the button functionality for the log in button.<br />
<br />
==Scripting the button==<br />
Now that we have created our GUI and shown it to the player, we need to make it work. <br />
<br />
===Detecting the click===<br />
When the player clicks on any part of the GUI, the event "[[onClientGUIClick]]" will be triggered for the GUI component you clicked on. This allows us to easily detect any clicks on the GUI elements we want to use.<br />
For example, we can attach the event to the btnLogin button to catch any clicks on it:<br />
<syntaxhighlight lang="lua"><br />
-- attach the event onClientGUIClick to btnLogin and set it to trigger the 'clientSubmitLogin' function<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
'''Note the final argument passed is "false". This indicates that the event will only trigger directly on btnLogin, not if the event has propagated up or down the tree. Setting this to "true" while attaching to gui elements will mean that clicking on any element in the same branch will trigger this event.'''<br />
<br />
This line of code can now be added inside the createLoginWindow function. It is a common mistake to try and attach events to non-existant GUI elements, so make sure you always attach your events '''after''' the gui element (in this case, the button) has been created:<br />
<br />
'''Note that we are now writing more code for our existing 'createLoginWindow' function.''' <br />
<syntaxhighlight lang="lua"><br />
function createLoginWindow()<br />
-- create all our GUI elements<br />
...<br />
<br />
-- now add our onClientGUIClick event to the button we just created<br />
addEventHandler("onClientGUIClick", btnLogin, clientSubmitLogin, false)<br />
</syntaxhighlight><br />
<br />
===Managing the click===<br />
Now that we can detect when the player clicks on the button, we need to write code to manage what happens when they do.<br />
In our [[onClientGUIClick]] event handle, we told it to call the function clientSubmitLogin whenever btnLogin is clicked.<br />
Therefore, we can now use the function clientSubmitLogin to control what happens when the button is clicked:<br />
<syntaxhighlight lang="lua"><br />
-- create the function and define the 'button' and 'state' parameters<br />
-- (these are passed automatically by onClientGUIClick)<br />
function clientSubmitLogin(button,state)<br />
-- if our login button was clicked with the left mouse button, and the state of the mouse button is up<br />
if button == "left" and state == "up" then<br />
-- move the input focus back onto the game (allowing players to move around, open the chatbox, etc)<br />
guiSetInputEnabled(false)<br />
-- hide the window and all the components<br />
guiSetVisible(wdwLogin, false)<br />
-- hide the mouse cursor<br />
showCursor(false)<br />
end<br />
end<br />
</syntaxhighlight><br />
Now, when the button is clicked, the window will be hidden and all controls will be returned to the player. Next, we will tell the server to allow the player to spawn.<br />
<br />
===Triggering the server===<br />
Triggering the server can be done using [[triggerServerEvent]]. This allows you to trigger a specified event on the server from the client. The same can be done in reverse using [[triggerClientEvent]].<br />
Here, we use the [[triggerServerEvent]] function to call our own custom event on the server, named "submitLogin", which will then control the spawning of the player serverside.<br />
<br />
'''Note that we are now writing more code for our existing 'clientSubmitLogin' function. This is not a new function and is meant to replace what you already have.''' <br />
<syntaxhighlight lang="lua"><br />
function clientSubmitLogin(button,state)<br />
if button == "left" and state == "up" then<br />
-- get the text entered in the 'username' field<br />
local username = guiGetText(edtUser)<br />
-- get the text entered in the 'password' field<br />
local password = guiGetText(edtPass)<br />
<br />
-- if the username and password both exist<br />
if username and password then<br />
-- trigger the server event 'submitLogin' and pass the username and password to it<br />
triggerServerEvent("submitLogin", getRootElement(), username, password)<br />
<br />
-- hide the gui, hide the cursor and return control to the player<br />
guiSetInputEnabled(false)<br />
guiSetVisible(wdwLogin, false)<br />
showCursor(false)<br />
else<br />
-- otherwise, output a message to the player, do not trigger the server<br />
-- and do not hide the gui<br />
outputChatBox("Please enter a username and password.")<br />
end<br />
end<br />
end<br />
</syntaxhighlight><br />
<br />
===Creating the serverside event===<br />
At this point we now have all the code needed on the client side, so open up your serverside 'script.lua' file (from the [[Scripting Introduction|Introduction to Scripting]]) or another suitable serverside file to work with.<br />
<br />
On the server side, recall that we are spawning the player as soon as they login.<br />
So, first of all, we will need to define the custom event that we used before on the client. This can be done using [[addEvent]] and [[addEventHandler]].<br />
<syntaxhighlight lang="lua"><br />
-- create our loginHandler function, with username and password parameters (passed from the client gui)<br />
function loginHandler(username,password)<br />
<br />
end<br />
<br />
-- define our custom event, and allow it to be triggered from the client ('true')<br />
addEvent("submitLogin",true)<br />
-- add an event handler so that when submitLogin is triggered, the function loginHandler is called<br />
addEventHandler("submitLogin",root,loginHandler)<br />
</syntaxhighlight><br />
<br />
===Logging in===<br />
Now we have a function that is called through the custom event 'submitLogin', we can start to work on logging in and spawning the player, using our 'loginHandler' function:<br />
<syntaxhighlight lang="lua"><br />
function loginHandler(username,password)<br />
-- check that the username and password are correct<br />
if username == "user" and password == "apple" then<br />
-- the player has successfully logged in, so spawn them<br />
if (client) then<br />
spawnPlayer(client, 1959.55, -1714.46, 10)<br />
fadeCamera(client, true)<br />
setCameraTarget(client, client)<br />
outputChatBox("Welcome to My Server.", client)<br />
end<br />
else<br />
-- if the username or password are not correct, output a message to the player<br />
outputChatBox("Invalid username and password. Please re-connect and try again.",client)<br />
end <br />
end<br />
<br />
addEvent("submitLogin",true)<br />
addEventHandler("submitLogin",root,loginHandler)<br />
</syntaxhighlight><br />
'''For the purposes of this tutorial, a very basic username and password system is shown. For a more comprehensive alternative, you can use the Account System or a MySQL database.'''<br />
<br />
Also note the use of the variable "client", it's an internal variable used by MTA to identify the player who triggered the event. <br />
<br />
<br />
Finally, do not forget to include the new gui.lua file in the meta.xml of the main resource, and label it as a client script:<br />
<syntaxhighlight lang="xml"><br />
<script src="client/gui.lua" type="client" /><br />
</syntaxhighlight><br />
<br />
<br />
At this point, we now have a basic login window that checks the player's username and password when the login button is clicked. If they are correct, the player is automatically spawned.<br />
<br />
For further help with GUI, see the [[:Category:GUI_Tutorials|GUI tutorials]].<br />
<br />
[[Category:GUI_Tutorials]]<br />
[[it:Introduzione_allo_scripting_della_GUI]]<br />
[[ru:Introduction to Scripting the GUI]]<br />
[[es:Introducción a la Programación de GUI]]</div>Ali salah