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:
SCRIPT ERROR: @my_script/server/main.lua:23: attempt to index a nil value (local 'xPlayer')local xPlayer = ESX.GetPlayerFromId(source)
xPlayer.addMoney(100) -- xPlayer is nilESX.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:
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:
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:
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.
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:
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 dondeESX.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 esGetPlayerServerId(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
sourceigual a0, 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.
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:
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:
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:
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 →