Hibakeresés

From Multi Theft Auto: Wiki
Revision as of 16:41, 8 May 2008 by Borov (talk | contribs)
Jump to navigation Jump to search

While 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.

Debug console

MTA features a built-in debug console that shows debug messages output from MTA functions or from scripts. You can open it by typing debugscript x in console, while x is the debug level:

  • 1: only errors
  • 2: errors and warnings
  • 3: errors, warnings and info messages

Thus, by typing debugscript 3 all messages are visible, that or level 2 are recommended for most occasions. You should have debugscript enabled most of the time you are testing your scripts, this will help you detect typos or other simple issues and solve them easily.

Example

This example snippet has two errors:

if (getClientName(player) == "Fedor")
	outputChatbox("Hello Fedor")
end

When the script this piece of code is in is tried to be loaded, debugscript will output something similiar to this:

INFO: Loading script failed: C:\<server path>\mods\deathmatch\resources\myResource\script.lua:15: 'then' expected near ´outputChatbox'

This means the script could not be parsed, because there was a syntax error. It shows the path of the script, so you can also see what resource it is in ('myResource' in this case) and of course the name of the script. After the filename it shows the line number and again after that what was wrong. Easy to solve now, we just forgot the 'then' keyword:

if (getClientName(player) == "Fedor") then
	outputChatbox("Hello Fedor")
end

Now the script will load fine and won't output any errors, until a player with the name 'Fedor' enters this section of the script. Then, debugscript will output:

ERROR: C:\<server path>\mods\deathmatch\resources\d\script.lua:15: attempt to call global 'outputChatbox' (a nil value)

This means the called function does not exist, which can be easily explained since the functions' name is outputChatBox (with a capital B):

if (getClientName(player) == "Fedor") then
	outputChatBox("Hello Fedor")
end

This is of course just an example, there are plenty of other messages and scenarios, but you should get the idea.

Debug logging

You can also turn debug message logging on by editing coreconfig.xml in your GTA\MTA folder. You should find the following tag:

<debugfile/>

Replace that with a tag specifying the file you want to log messages to (file path is relative from the GTA folder):

<debugfile>MTA\debugscript.log</debugfile>

All debug messages will be appended to the specified file from now on. To turn logging off, replace that line with an empty tag again.

Debug strategies

There are several strategies that support finding errors, apart from going through the code of course. Most of them include outputting debug messages, with differing information depending on the situtation.

Useful functions

First of all some functions that may come in handy for debugging.

Add debugmessages to check if, when or how often a section of code is executed

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.

if (variable1 == variable2) then
	outputDebugString("entered if")
	-- do anything
end

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.

Add debugmessages to check the value of a variable

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.

outputChatBox(tostring(x).." "..tostring(y).." "..tostring(z))
createMarker(x,y,z)

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 tostring() will ensure that the variables' value can be put together as a string, even if it's a boolean value for example.

Example

Imagine you created a colshape (collision shape) somewhere and you want a player to stay 10 seconds in it, then perform some action.

function colShapeHit(player)
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

When a player enters the colshape, debugscript outputs the following message:

ERROR: ..[path]: attempt to index global 'colshapeTimer' (a nil value)

This means you tried to index a table that does not 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 not create it.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

Now we still get a warning when a player enters the colshape, waits for the message and leaves it again:

WARNING: [..]: Bad argument @ 'killTimer' Line: ..

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.

A more inconspicuous error

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.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- add a debug message
	outputDebugString("colShapeHit")
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- add a debug message
	outputDebugString("colShapeLeave")
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

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 check the player variable that should contain a player element.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- add a debug message, with the element type
	outputDebugString("colShapeHit "..getElementType(player))
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- add a debug message, with the element type
	outputDebugString("colShapeLeave "..getElementType(player))
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

The debug messages tell us that one of the player variables is a player, the other one 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.

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- add a check for the element type
	if (getElementType(player) ~= "player") then return end
	-- add a debug message, with the element type
	outputDebugString("colShapeHit "..getElementType(player))
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- add a check for the element type
	if (getElementType(player) ~= "player") then return end
	-- add a debug message, with the element type
	outputDebugString("colShapeLeave "..getElementType(player))
	-- kill the timer when the player leaves the colshape
	killTimer(colshapeTimer[player])
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

Now the script should work as desired, but 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 reached the 10 seconds and is executed. 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 is there). One way would be to check if the timer referenced in the table really exists. To do this, we need a little help function:

function isTimer(timer)
	local timers = getTimers()
	for k,v in ipairs(timers) do
		if (v == timer) then
			return true
		end
	end
	return false
end

Which we will use when we kill the timer:

if (isTimer(colshapeTimer[player])) then
	killTimer(colshapeTimer[player])
end

So the complete working code would be:

function colShapeHit(player)
	if (colshapeTimer == nil) then
		colshapeTimer = {}
	end
	-- add a check for the element type
	if (getElementType(player) ~= "player") then return end
	-- add a debug message, with the element type
	outputDebugString("colShapeHit "..getElementType(player))
	-- set a timer to output a message (could as well execute another function)
	-- store the timer id in a table, using the player as index
	colshapeTimer[player] = setTimer(outputChatBox,10000,1,"The player stayed 10 seconds in the colshape!")
end
addEventHandler("onColShapeHit",getRootElement(),colShapeHit)

function colShapeLeave(player)
	-- add a check for the element type
	if (getElementType(player) ~= "player") then return end
	-- add a debug message, with the element type
	outputDebugString("colShapeLeave "..getElementType(player))
	-- kill the timer when the player leaves the colshape
	if (isTimer(colshapeTimer[player])) then
		killTimer(colshapeTimer[player])
	end
end
addEventHandler("onColShapeLeave",getRootElement(),colShapeLeave)

function isTimer(timer)
	local timers = getTimers()
	for k,v in ipairs(timers) do
		if (v == timer) then
			return true
		end
	end
	return false
end