tentativa de indexar um valor nil (local 'xPlayer'): correção ESX

xPlayer é nil em seu script ESX? Por que ESX.GetPlayerFromId(source) retorna nil: jogador não carregado, source perdido após Wait, ids de string, playerDropped. Com padrões de proteção.

O console do servidor mostra:

text
SCRIPT ERROR: @my_script/server/main.lua:23: attempt to index a nil value (local 'xPlayer')
lua
local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addMoney(100)   -- xPlayer is nil

ESX.GetPlayerFromId não encontrou jogador para aquele id e retornou nil, e a próxima linha o indexou. A chamada está boa. O id que você deu não corresponde a um jogador ESX carregado. Aqui estão as razões usuais e como se proteger contra cada uma.

1. O jogador ainda não está carregado

Um jogador está conectado ao servidor antes do ESX ter terminado de carregar seu personagem. Durante essa lacuna, e enquanto uma tela multicaráter está aberta, ESX.GetPlayerFromId(source) retorna nil.

Isso atinge código que é executado cedo, como os eventos genéricos do FiveM playerJoining ou playerConnecting, ou uma thread que inicia no momento em que seu recurso inicia.

Use o evento ESX que dispara quando o jogador está pronto, no servidor:

lua
AddEventHandler('esx:playerLoaded', function(playerId, xPlayer, isNew)
    print(('%s is loaded'):format(xPlayer.getName()))
end)

No cliente, o mesmo nome de evento diz ao seu script cliente que o jogador pode ser usado:

lua
RegisterNetEvent('esx:playerLoaded', function(xPlayer)
    PlayerLoaded = true
end)

Se seu script pode ser reiniciado enquanto os jogadores estão online, esse evento já disparou para eles. Na inicialização, faça um loop sobre os jogadores que ESX já tem:

lua
CreateThread(function()
    for _, playerId in ipairs(GetPlayers()) do
        local xPlayer = ESX.GetPlayerFromId(tonumber(playerId))
        if xPlayer then
            -- set up the player
        end
    end
end)

2. source mudou após um Wait

source é um global especial que FiveM define para o evento que está em execução. Se você esperar dentro do manipulador, outro evento pode rodar enquanto isso e source não pertence mais ao seu jogador.

lua
RegisterNetEvent('my_script:buy', function(item)
    Wait(500)
    local xPlayer = ESX.GetPlayerFromId(source)   -- source may be someone else, or invalid
end)

Armazene-o em um local antes de qualquer outra coisa:

lua
RegisterNetEvent('my_script:buy', function(item)
    local src = source
    Wait(500)

    local xPlayer = ESX.GetPlayerFromId(src)
    if not xPlayer then return end
end)

O mesmo se aplica dentro de um callback ou de uma função que você passa source para depois: passe o valor armazenado, não o global.

3. O id está errado

ESX.GetPlayerFromId precisa do server id como um número de um jogador que está online.

  • Um id de string. Argumentos de comando são texto. ESX.GetPlayerFromId(args[1]) pode retornar nil onde ESX.GetPlayerFromId(tonumber(args[1])) funciona.
  • O id errado do cliente. No cliente, PlayerId() é um índice local, não o server id. O server id é GetPlayerServerId(PlayerId()). Enviar o errado dá um jogador que não existe ou o jogador errado.
  • O console. Um comando digitado no console do servidor tem source igual a 0, que não é um jogador.
  • Um id de uma busca de identificador. ESX.GetPlayerFromIdentifier(identifier) apenas encontra jogadores que estão online. Para um jogador offline, leia o banco de dados em vez disso, veja o guia de consultas oxmysql.
lua
RegisterCommand('givecash', function(source, args)
    local target = tonumber(args[1])
    local amount = tonumber(args[2])
    if not target or not amount then
        return print('Usage: /givecash [id] [amount]')
    end

    local xTarget = ESX.GetPlayerFromId(target)
    if not xTarget then
        return print('Player not found or not loaded')
    end

    xTarget.addMoney(amount)
end, true)

Não confie em um id enviado do cliente para algo que importa. Use source, que não pode ser falsificado, conforme explicado em eventos de servidor seguro.

4. O jogador saiu

Se o jogador se desconectar enquanto seu código espera, por exemplo em um loop longo ou um timer, GetPlayerFromId retorna nil quando finalmente é executado. Este é um caso normal, não um bug, e precisa da mesma proteção.

Dentro de playerDropped a situação é menos clara, porque ESX também trata esse evento para salvar e remover o jogador, e a ordem dos manipuladores não é algo em que se possa contar. ESX Legacy também dispara seu próprio evento esx:playerDropped, mas verifique se sua versão o tem. A abordagem mais robusta é manter os dados você mesmo enquanto o jogador está online:

lua
local sessions = {}

AddEventHandler('esx:playerLoaded', function(playerId, xPlayer)
    sessions[playerId] = {
        identifier = xPlayer.identifier,
        job = xPlayer.job.name,
    }
end)

AddEventHandler('playerDropped', function()
    local data = sessions[source]
    if data then
        print(('%s left, job %s'):format(data.identifier, data.job))
        sessions[source] = nil
    end
end)

O padrão de proteção

Inicie cada manipulador de servidor que usa um jogador da mesma forma:

lua
RegisterNetEvent('my_script:sell', function(item, count)
    local src = source
    local xPlayer = ESX.GetPlayerFromId(src)
    if not xPlayer then return end

    -- from here xPlayer is safe to use
    xPlayer.addMoney(100)
end)

A mesma verificação entra dentro de callbacks de servidor registrados com ESX.RegisterServerCallback, onde o primeiro argumento é a source do jogador:

lua
ESX.RegisterServerCallback('my_script:getJob', function(source, cb)
    local xPlayer = ESX.GetPlayerFromId(source)
    if not xPlayer then return cb(nil) end

    cb(xPlayer.job.name)
end)

Verifique também o lado do cliente: ele deve aceitar uma resposta nil do callback.

Dica: se o erro for attempt to index a nil value (global 'ESX') em vez disso, o problema é o objeto ESX, não o jogador. Veja ESX é nil: corrigindo esx:getSharedObject.

Lista de verificação

Sintoma Correção
Nil logo após o jogador ingressar Aguarde esx:playerLoaded em vez de playerJoining
Nil após um Wait Armazene local src = source primeiro e use src
Nil de um comando Converta o argumento com tonumber; source é 0 no console
Nil com um id enviado pelo cliente Use source; no cliente o server id é GetPlayerServerId(PlayerId())
Nil em playerDropped Mantenha os dados que você precisa em sua própria tabela enquanto o jogador está online
Nil para um jogador offline GetPlayerFromId apenas encontra jogadores online; consulte o banco de dados
Qualquer nil Adicione if not xPlayer then return end no topo do manipulador

Respostas rápidas

Por que xPlayer é nil no ESX?

ESX.GetPlayerFromId(source) retorna nil quando ESX não tem um jogador carregado para aquele id. O jogador pode não ter terminado de carregar, o id pode estar errado ou ser uma string, ou o jogador já saiu.

Devo verificar xPlayer em cada evento?

Sim. Qualquer evento de servidor pode ser disparado com um jogador que não está carregado, então inicie o manipulador com if not xPlayer then return end. Custa nada e previne o erro.

Posso usar xPlayer dentro de playerDropped?

Não é seguro contar com ele, porque ESX pode já ter removido o jogador. Mantenha os dados que você precisa em sua própria tabela enquanto o jogador está online e leia-os de lá.

Scripts sem esse problema

Shop CreatorMonte uma loja em menos de um minuto — donos, funcionários, cofres e assaltos inclusos.Ver script →Pawn Shop AppUma casa de penhores entre jogadores dentro do lb-phone.Ver script →Drug Dealer AppVendas na rua como app do lb-phone: zonas, compradores NPC, níveis e alertas para a polícia.Ver script →

Continue lendo