IT/Guida al Debug

From Multi Theft Auto: Wiki
Jump to navigation Jump to search

Scriptando ti capiteranno presto problemi che non si notano subito. Questa pagina ha lo scopo di elencare qualche strategia di base per trovare l'errore.

Console di debug

MTA possiede una console ingame per il debug. Puoi aprirla digitando debugscript x nella console, dove x è il livello di debug:

  • 1: solo gli errori
  • 2: gli errori e i warning
  • 3: gli errori, i warning e le info

Perciò, digitando debugscript 3 tutti i messaggi saranno visibili, anche se il livello 2 è raccomandato per la maggior parte dei casi. Dovresti avere il debugscript attivato la maggior parte del tempo che scripti, ti aiuterà a trovare errori di digitazione, insieme ad altri piccoli problemi e a risolverli facilmente.

Esempio

Questo frammento di esempio ha due errori:

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

Quando lo script tenterà di caricare questa parte di codice, nella console di debug dovrebbe apparire qualcosa del genere:

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

Significa che c'è un errore di sintassi. Il debugscript mostra il percorso dello script che genera l'errore, in modo da conoscere la resource ('myResource' in questo caso) e ovviamente il nome dello script. Dopo il nome del file mostra la linea dell'errore e poi il tipo di errore. A questo punto è facile, abbiamo dimenticato il 'then':

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

Ora lo script caricherà senza errori e sembrerà funzionare bene, ma quando un player di nome 'Fedor' raggiungerà questa parte dello script, il debugscript darà questo errore:

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

Significa che la funzione chiamata non esiste: è facilmente spiegabile, visto che il nome esatto della funzione è outputChatBox (con la B maiuscola):

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

Ovviamente è solo un esempio, potrebbe esserci un'infinità di altre situazioni, ma dovreste esservi fatti un'idea.

Log del debug

Puoi anche attivare la creazione di un file log del debug modificando il file coreconfig.xml nella tua cartella MTA all'interno di quella di GTA. Devi cercare la riga seguente:

<debugfile/>

E rimpiazzarla con questa, scegliendo la path che preferisci, relativa alla cartella di GTA:

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

Tutti i messaggi di debug da ora in poi verranno aggiunti al file log. Per bloccare questo processo, ripristina la riga precedente.

Strategie di debug

Esistono svariate strategie per trovare gli errori, la maggior parte delle quali prevede l'uso dei messaggi di debug, con informazioni differenti a seconda della situazione.

Funzioni utili

Prima di tutto ecco alcune funzioni comode per il debug.

Creare messaggi di debug per verificare se, quando o quante volte è eseguita una parte di codice

Un esempio tipico potrebbe essere verificare se la sezione di codice dentro un if sia eseguita o meno. Per far questo, aggiungi un messaggio qualsiasi (che in seguito riconoscerai) nella sezione dentro l'if.

if (variabile1 == variabile2) then
	outputDebugString("if eseguito")
	-- fai qualsiasi cosa
end

Un altro uso potrebbe essere verificare se il valore di una variabile è modficato. Prima cerca tutti i punti in cui questo valore è modificato e aggiungi un messaggio di debug proprio sotto di essi.

Creare messaggi di debug per verificare il valore di una variabile

Diciamo che vuoi creare un marker, ma non sembra essere nella posizione dove dovrebbe trovarsi. La prima cosa che potresti voler fare è controllare se la funzione createMarker è eseguita. Ma nel fare ciò, puoi anche verificare le variabili usate nel createMarker in un colpo solo.

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

Questo mostrerà le 3 variabili usate come coordinate per creare il marker. Assumendo che tu le 'legga' da un file map, puoi controllare in questo modo il loro valore. La funzione tostring() permette di unire con certezza i valori delle variabili in una stringa, anche se per esempio sono dei booleani.

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