A arte de depurar scripts FiveM
A depuração de scripts FiveM é fundamentalmente diferente da depuração de aplicativos padrão. Você está lidando com uma arquitetura cliente-servidor dividida, onde o código é executado em dois tempos de execução separados, os eventos viajam pela rede e o estado do jogo muda a cada quadro. Quando algo dá errado, o erro pode ter origem no servidor, mas se manifestar no cliente ou vice-versa. Desenvolver uma abordagem sistemática para depuração economizará inúmeras horas e tornará seu fluxo de trabalho de desenvolvimento muito mais rápido. Este guia cobre as ferramentas, técnicas e padrões essenciais que os desenvolvedores profissionais do FiveM usam diariamente.
Usando declarações impressas de maneira eficaz
A ferramenta de depuração mais fundamental no FiveM é a função print(), mas usá-la de forma eficaz requer mais do que apenas descartar variáveis. Estruture sua saída de depuração com prefixos que identificam o script, o lado (cliente ou servidor) e a função onde ocorre a impressão. Use códigos de cores para destacar mensagens importantes no console. Crie um utilitário de depuração que você possa ativar e desativar sem remover linhas de depuração do seu código.
-- shared/debug.lua
local DEBUG_ENABLED = GetConvar('myresource_debug', 'false') == 'true'
local RESOURCE_NAME = GetCurrentResourceName()
function DebugLog(module, message, ...)
if not DEBUG_ENABLED then return end
local side = IsDuplicityVersion() and 'SERVER' else 'CLIENT'
local formatted = type(message) == 'string' and message:format(...) or tostring(message)
local timestamp = os.date('%H:%M:%S')
print(('[^3%s^0][^5%s^0][^2%s^0] %s'):format(
timestamp, RESOURCE_NAME, side .. ':' .. module, formatted
))
end
function DebugTable(module, tbl, depth)
if not DEBUG_ENABLED then return end
depth = depth or 0
local indent = string.rep(' ', depth)
if type(tbl) ~= 'table' then
DebugLog(module, '%s%s', indent, tostring(tbl))
return
end
for k, v in pairs(tbl) do
if type(v) == 'table' then
DebugLog(module, '%s%s = {', indent, tostring(k))
DebugTable(module, v, depth + 1)
DebugLog(module, '%s}', indent)
else
DebugLog(module, '%s%s = %s (%s)', indent, tostring(k), tostring(v), type(v))
end
end
end
Usando o utilitário de depuração
Com esse utilitário instalado, você pode adicionar log de depuração em todos os seus scripts, o que é completamente silencioso na produção. Habilite-o adicionando set myresource_debug true à configuração do seu servidor quando precisar solucionar problemas. A saída estruturada facilita a navegação pelos logs do console e a localização exata do que você está procurando.
-- server/jobs.lua
RegisterNetEvent('myresource:startJob', function(jobName)
local src = source
DebugLog('jobs', 'Player %d attempting to start job: %s', src, jobName)
local playerData = GetPlayerData(src)
DebugTable('jobs', playerData)
if not playerData then
DebugLog('jobs', 'ERROR: No player data found for source %d', src)
return
end
if playerData.job == jobName then
DebugLog('jobs', 'Player %d already has job %s, skipping', src, jobName)
return
end
DebugLog('jobs', 'Job %s assigned to player %d successfully', jobName, src)
end)
Erros e soluções comuns do FiveM
ERRO DE SCRIPT: tentativa de indexar um valor nulo
Este é o erro Lua mais frequente no desenvolvimento FiveM. Significa que você está tentando acessar uma propriedade ou método em uma variável que é nil. As causas mais comuns são acessar os dados do jogador antes de carregá-los, fazer referência a uma função do framework que não existe na versão que você está usando ou ler um valor de configuração com um erro de digitação na chave. Sempre verifique se há nulo antes de acessar propriedades aninhadas.
-- BAD: Will crash if GetPlayerData returns nil
local name = GetPlayerData(src).charinfo.firstname
-- GOOD: Defensive nil checks
local playerData = GetPlayerData(src)
if not playerData then
print('[ERROR] Player data is nil for source: ' .. src)
return
end
local charinfo = playerData.charinfo
if not charinfo then
print('[ERROR] charinfo missing for source: ' .. src)
return
end
local name = charinfo.firstname or 'Unknown'
ERRO DE SCRIPT: tentativa de chamar um valor nulo
Este erro ocorre quando você tenta chamar uma função que não existe. No FiveM, isso geralmente acontece quando você chama uma exportação de um recurso que ainda não foi iniciado, usa uma função de framework que foi renomeada em uma atualização ou se esquece de carregar um arquivo compartilhado em seu manifesto. Verifique seu fxmanifest.lua para ter certeza de que todos os arquivos necessários estão listados e na ordem correta.
-- Safely calling an export that might not be available
local function SafeExport(resource, exportName, ...)
local success, result = pcall(function(...)
return exports[resource][exportName](...)
end, ...)
if not success then
print(('[^1ERROR^0] Failed to call export %s:%s - %s'):format(
resource, exportName, tostring(result)
))
return nil
end
return result
end
-- Usage
local inventory = SafeExport('ox_inventory', 'GetInventory', src)
O evento não foi registrado
Quando você aciona um evento para o qual nenhum script registrou um manipulador, FiveM o descarta silenciosamente, sem erros no console. Isso pode ser enlouquecedor para depurar porque tudo parece correto, mas nada acontece. Use uma função auxiliar para verificar se os eventos estão registrados antes de acioná-los e adicione log em torno dos gatilhos de eventos durante o desenvolvimento.
-- server/debug_events.lua
-- Wrap TriggerClientEvent to log when events fire
local originalTrigger = TriggerClientEvent
if GetConvar('myresource_debug', 'false') == 'true' then
TriggerClientEvent = function(eventName, target, ...)
print(('[^3EVENT^0] TriggerClientEvent: %s -> target: %s'):format(
eventName, tostring(target)
))
return originalTrigger(eventName, target, ...)
end
end
Depuração NUI com DevTools
Para scripts com interfaces NUI, os Chromium DevTools integrados são inestimáveis. Abra-os com o console F8 digitando nui_devtools para acessar o inspetor completo do Chrome. Isso fornece o painel Elementos para inspecionar a estrutura do DOM, o Console para erros de JavaScript, a guia Rede para carregamento de recursos e o painel Fontes para definir pontos de interrupção. Para problemas de comunicação NUI, registre ambos os lados da ponte de mensagens.
// nui/js/debug.js
// Log all incoming NUI messages
window.addEventListener('message', (event) => {
if (event.data && event.data.action) {
console.log(
'%c[NUI Received]%c ' + event.data.action,
'background: #2dd4bf; color: #000; padding: 2px 6px; border-radius: 3px;',
'color: #94a3b8;',
event.data
);
}
});
// Wrap fetch to log NUI callbacks
const originalFetch = window.fetch;
window.fetch = function(url, options) {
const body = options?.body ? JSON.parse(options.body) : null;
console.log(
'%c[NUI Callback]%c ' + url,
'background: #8b5cf6; color: #fff; padding: 2px 6px; border-radius: 3px;',
'color: #94a3b8;',
body
);
return originalFetch.apply(this, arguments);
};
Criação de perfil com Resmon e Timing
Além do monitoramento básico resmon, você pode criar instrumentação de tempo precisa em seus scripts. Meça quanto tempo levam operações específicas e registre avisos quando excederem limites aceitáveis. Isto é especialmente importante para consultas de banco de dados, cálculos complexos e loops que processam muitas entidades.
-- shared/profiler.lua
local Profiler = {}
function Profiler.Start(label)
return {
label = label,
startTime = GetGameTimer()
}
end
function Profiler.Stop(timer, warnThresholdMs)
local elapsed = GetGameTimer() - timer.startTime
warnThresholdMs = warnThresholdMs or 5
if elapsed >= warnThresholdMs then
print(('[^1PERF WARNING^0] %s took %dms (threshold: %dms)'):format(
timer.label, elapsed, warnThresholdMs
))
elseif GetConvar('myresource_debug', 'false') == 'true' then
print(('[^2PERF^0] %s completed in %dms'):format(timer.label, elapsed))
end
return elapsed
end
-- Usage in a server event
RegisterNetEvent('myresource:heavyOperation', function(data)
local timer = Profiler.Start('heavyOperation')
-- ... expensive processing ...
local result = ProcessLargeDataSet(data)
Profiler.Stop(timer, 10) -- warn if over 10ms
end)
Depuração de saco de estado
As bolsas estaduais são um recurso poderoso, mas às vezes confuso. Quando os valores do pacote de estado não são atualizados conforme o esperado, geralmente é porque você os está configurando na entidade errada, o manipulador não está capturando o nome do pacote correto ou a replicação está atrasada. Crie um comando de inspetor de bagagem de estado que despeje todo o estado de uma determinada entidade.
-- server/debug_statebags.lua
RegisterCommand('debugstate', function(source, args)
local targetId = tonumber(args[1])
if not targetId then
print('Usage: debugstate [playerId]')
return
end
local playerPed = GetPlayerPed(targetId)
if playerPed == 0 then
print('Player not found: ' .. targetId)
return
end
local entityState = Player(targetId).state
print(('[^3STATE BAGS^0] Player %d:'):format(targetId))
-- Print known state keys (state bags don't have an iterator)
local keysToCheck = {'job', 'gang', 'duty', 'dead', 'phone', 'inventory'}
for _, key in ipairs(keysToCheck) do
local val = entityState[key]
if val ~= nil then
print((' %s = %s (%s)'):format(key, tostring(val), type(val)))
end
end
end, true)
Lista de verificação essencial para depuração
- Verifique ambos os consoles.Sempre verifique se há erros no console do servidor (txAdmin ou terminal) e no console do cliente (F8). Um erro de um lado geralmente explica o comportamento incorreto do outro.
- Verifique o estado do recurso.Use
ensurepara reiniciar seu recurso erestartpara reiniciar um único recurso. Verifiqueresmonpara ter certeza de que o recurso está realmente em execução. - Teste em um ambiente limpo.Desative outros scripts que interagem com os mesmos sistemas. Muitos bugs vêm de conflitos entre recursos, e não de bugs em um único script.
- Leia o rastreamento da pilha de erros com atenção.Os rastreamentos de pilha Lua mostram o arquivo exato e o número da linha. Leia-os de baixo para cima para entender a cadeia de chamadas que levou ao erro.
- Use pcall para operações arriscadas.Envolva consultas de banco de dados, chamadas de exportação e decodificação JSON em
pcallpara capturar erros normalmente, em vez de deixá-los travar seu script. - Versão de suas configurações.Quando os jogadores relatarem bugs, pergunte qual versão eles estão executando. Muitos problemas decorrem de arquivos de configuração desatualizados após uma atualização.
