attempt to index a nil value (local 'xPlayer') : correction ESX

xPlayer est nil dans votre script ESX ? Pourquoi ESX.GetPlayerFromId(source) retourne nil : joueur non chargé, source perdu après Wait, ids en string, playerDropped. Avec des modèles de garde.

La console serveur montre :

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'a trouvé aucun joueur pour cet id et a retourné nil, et la ligne suivante l'a indexé. L'appel est correct. L'id que vous lui avez donné ne correspond pas à un joueur ESX chargé. Voici les raisons habituelles et comment vous protéger contre chacune.

1. Le joueur n'est pas encore chargé

Un joueur est connecté au serveur avant que ESX ait fini de charger son personnage. Pendant ce laps de temps, et pendant qu'un écran multi-caractères est ouvert, ESX.GetPlayerFromId(source) retourne nil.

Cela affecte le code qui s'exécute tôt, comme les événements FiveM génériques playerJoining ou playerConnecting, ou un thread qui démarre au moment où votre ressource démarre.

Utilisez l'événement ESX qui se déclenche quand le joueur est prêt, sur le serveur :

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

Sur le client, le même nom d'événement dit à votre script client que le joueur peut être utilisé :

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

Si votre script peut être redémarré pendant que les joueurs sont en ligne, cet événement a déjà été déclenché pour eux. Au démarrage, bouclez sur les joueurs qu'ESX a déjà :

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 changé après un Wait

source est une globale spéciale que FiveM définit pour l'événement en cours d'exécution. Si vous attendez à l'intérieur du gestionnaire, un autre événement peut s'exécuter entre-temps et source n'appartient plus à votre joueur.

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

Stockez-le dans une locale avant toute autre chose :

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

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

La même chose s'applique à l'intérieur d'un rappel ou d'une fonction à laquelle vous passez source plus tard : passez la valeur stockée, pas la globale.

3. L'id est mauvais

ESX.GetPlayerFromId a besoin de l'id serveur en tant que nombre d'un joueur qui est en ligne.

  • Un id en string. Les arguments de commande sont du texte. ESX.GetPlayerFromId(args[1]) peut retourner nil où ESX.GetPlayerFromId(tonumber(args[1])) fonctionne.
  • Le mauvais id du client. Sur le client, PlayerId() est un index local, pas l'id serveur. L'id serveur est GetPlayerServerId(PlayerId()). Envoyer le mauvais donne un joueur qui n'existe pas, ou le mauvais joueur.
  • La console. Une commande tapée dans la console serveur a source égal à 0, ce qui n'est pas un joueur.
  • Un id d'une recherche d'identificateur. ESX.GetPlayerFromIdentifier(identifier) ne trouve que les joueurs qui sont en ligne. Pour un joueur hors ligne, lisez la base de données à la place, voir le guide des requêtes 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)

Ne faites pas confiance à un id envoyé du client pour quelque chose qui importe. Utilisez source, qui ne peut pas être falsifié, comme expliqué dans Sécuriser les événements serveur.

4. Le joueur est parti

Si le joueur se déconnecte pendant que votre code attend, par exemple dans une longue boucle ou un minuteur, GetPlayerFromId retourne nil quand il s'exécute finalement. C'est un cas normal, pas un bug, et il a besoin de la même garde.

À l'intérieur de playerDropped, la situation est moins claire, car ESX gère aussi cet événement pour enregistrer et supprimer le joueur, et l'ordre des gestionnaires n'est pas quelque chose sur lequel compter. ESX Legacy déclenche aussi son propre événement esx:playerDropped, mais vérifiez que votre version l'a. L'approche la plus robuste est de garder les données vous-même pendant que le joueur est en ligne :

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)

Le modèle de garde

Commencez chaque gestionnaire serveur qui utilise un joueur de la même manière :

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 même vérification va à l'intérieur des rappels serveur enregistrés avec ESX.RegisterServerCallback, où le premier argument est la source du joueur :

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)

Vérifiez aussi le côté client : il doit accepter une réponse nil du rappel.

Astuce : si l'erreur est attempt to index a nil value (global 'ESX') à la place, le problème est l'objet ESX, pas le joueur. Voir ESX is nil: fixing esx:getSharedObject.

Liste de vérification

Symptôme Correction
Nil juste après que le joueur se joint Attendez esx:playerLoaded au lieu de playerJoining
Nil après un Wait Stockez local src = source d'abord et utilisez src
Nil d'une commande Convertissez l'argument avec tonumber ; source est 0 dans la console
Nil avec un id envoyé par le client Utilisez source ; sur le client l'id serveur est GetPlayerServerId(PlayerId())
Nil dans playerDropped Gardez les données dont vous avez besoin dans votre propre table pendant que le joueur est en ligne
Nil pour un joueur hors ligne GetPlayerFromId ne trouve que les joueurs en ligne ; interrogez la base de données
N'importe quel nil Ajoutez if not xPlayer then return end en haut du gestionnaire

Réponses rapides

Pourquoi xPlayer est-il nil dans ESX ?

ESX.GetPlayerFromId(source) retourne nil quand ESX n'a pas de joueur chargé pour cet id. Le joueur peut ne pas avoir fini de charger, l'id peut être mauvais ou une string, ou le joueur a déjà quitté.

Dois-je vérifier xPlayer dans chaque événement ?

Oui. N'importe quel événement serveur peut être déclenché avec un joueur qui n'est pas chargé, donc commencez le gestionnaire avec if not xPlayer then return end. Cela ne coûte rien et prévient l'erreur.

Puis-je utiliser xPlayer à l'intérieur de playerDropped ?

Ce n'est pas sûr de s'y fier, car ESX peut avoir déjà supprimé le joueur. Gardez les données dont vous avez besoin dans votre propre table pendant que le joueur est en ligne, et lisez-les de là.

Des scripts sans ce problème

Shop CreatorCréez un magasin en moins d’une minute — propriétaires, employés, coffres et braquages inclus.Voir le script →Pawn Shop AppUn marché de prêt sur gage entre joueurs, directement dans lb-phone.Voir le script →Drug Dealer AppLa vente de rue en app lb-phone : zones, acheteurs PNJ, niveaux et alertes police.Voir le script →

À lire aussi