attempt to index a nil value (local 'xPlayer'): solución ESX

¿xPlayer es nil en tu script ESX? Por qué ESX.GetPlayerFromId(source) devuelve nil: jugador no cargado, source perdido después de Wait, ids de string, playerDropped. Con patrones de guardia.

La consola del servidor muestra:

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 no encontró ningún jugador para ese id y devolvió nil, y la siguiente línea lo indexó. La llamada está bien. El id que le diste no coincide con un jugador ESX cargado. Aquí están las razones habituales y cómo guardar contra cada una.

1. El jugador aún no se ha cargado

Un jugador está conectado al servidor antes de que ESX haya terminado de cargar su personaje. Durante esa brecha, y mientras una pantalla de múltiples personajes está abierta, ESX.GetPlayerFromId(source) devuelve nil.

Esto toca código que se ejecuta temprano, como los eventos genéricos de FiveM playerJoining o playerConnecting, o un hilo que se inicia en el momento en que tu recurso se inicia.

Usa el evento ESX que se dispara cuando el jugador está listo, en el servidor:

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

En el cliente, el mismo nombre de evento le dice a tu script de cliente que se puede usar el jugador:

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

Si tu script puede ser reiniciado mientras los jugadores están en línea, ese evento ya se ha disparado para ellos. Al iniciar, recorre los jugadores que ESX ya tiene:

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 cambió después de un Wait

source es una global especial que FiveM establece para el evento que se está ejecutando. Si esperas dentro del manejador, otro evento puede ejecutarse mientras tanto y source ya no pertenece a tu jugador.

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

Guárdalo en una local antes de nada:

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

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

Lo mismo se aplica dentro de una devolución de llamada o una función a la que pasas source después: pasa el valor almacenado, no la global.

3. El id es incorrecto

ESX.GetPlayerFromId necesita el id del servidor como número de un jugador que está en línea.

  • Un id de string. Los argumentos de comandos son texto. ESX.GetPlayerFromId(args[1]) puede devolver nil donde ESX.GetPlayerFromId(tonumber(args[1])) funciona.
  • El id incorrecto del cliente. En el cliente, PlayerId() es un índice local, no el id del servidor. El id del servidor es GetPlayerServerId(PlayerId()). Enviar el incorrecto da un jugador que no existe, o el jugador incorrecto.
  • La consola. Un comando escrito en la consola del servidor tiene source igual a 0, que no es un jugador.
  • Un id de una búsqueda de identificador. ESX.GetPlayerFromIdentifier(identifier) solo encuentra jugadores que están en línea. Para un jugador sin conexión, lee la base de datos en su lugar, ve la oxmysql queries guide.
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)

No confíes en un id enviado por el cliente para algo que importa. Usa source, que no se puede falsificar, como se explica en securing server events.

4. El jugador se ha ido

Si el jugador se desconecta mientras tu código espera, por ejemplo en un bucle largo o un temporizador, GetPlayerFromId devuelve nil cuando finalmente se ejecuta. Este es un caso normal, no un error, y necesita la misma guardia.

Dentro de playerDropped la situación es menos clara, porque ESX también maneja ese evento para guardar y eliminar al jugador, y el orden de los manejadores no es algo en lo que confiar. ESX Legacy también dispara su propio evento esx:playerDropped, pero comprueba que tu versión lo tiene. El enfoque más robusto es mantener los datos tú mismo mientras el jugador está en línea:

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)

El patrón de guardia

Comienza cada manejador del servidor que usa un jugador de la misma manera:

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)

La misma comprobación va dentro de devoluciones de llamada del servidor registradas con ESX.RegisterServerCallback, donde el primer argumento es la fuente del jugador:

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)

También comprueba el lado del cliente: debe aceptar una respuesta nil de la devolución de llamada.

Consejo: si el error es attempt to index a nil value (global 'ESX') en su lugar, el problema es el objeto ESX, no el jugador. Ve ESX is nil: fixing esx:getSharedObject.

Lista de verificación

Síntoma Solución
Nil justo después de que el jugador se une Espera a esx:playerLoaded en lugar de playerJoining
Nil después de un Wait Almacena local src = source primero y usa src
Nil de un comando Convierte el argumento con tonumber; source es 0 en la consola
Nil con un id enviado por el cliente Usa source; en el cliente el id del servidor es GetPlayerServerId(PlayerId())
Nil en playerDropped Mantén los datos que necesitas en tu propia tabla mientras el jugador está en línea
Nil para un jugador sin conexión GetPlayerFromId solo encuentra jugadores en línea; consulta la base de datos
Cualquier nil Añade if not xPlayer then return end en la parte superior del manejador

Respuestas rápidas

¿Por qué es xPlayer nil en ESX?

ESX.GetPlayerFromId(source) devuelve nil cuando ESX no tiene un jugador cargado para ese id. El jugador puede no haber terminado de cargar, el id puede ser incorrecto o una cadena, o el jugador ya se fue.

¿Debo comprobar xPlayer en cada evento?

Sí. Cualquier evento del servidor puede ser activado por un jugador que no está cargado, así que comienza el manejador con if not xPlayer then return end. No cuesta nada y previene el error.

¿Puedo usar xPlayer dentro de playerDropped?

No es seguro confiar en ello, porque ESX puede haber eliminado ya al jugador. Mantén los datos que necesitas en tu propia tabla mientras el jugador está en línea, y léelos desde allí.

Scripts que evitan este problema

Shop CreatorCrea una tienda en menos de un minuto — dueños, empleados, caja fuerte y atracos.Ver script →Pawn Shop AppUn mercado de empeños entre jugadores dentro de lb-phone.Ver script →Drug Dealer AppVenta callejera como app de lb-phone: zonas, compradores NPC, niveles y avisos a la policía.Ver script →

Sigue leyendo