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

xPlayer è nil nel tuo script ESX? Perché ESX.GetPlayerFromId(source) restituisce nil: giocatore non caricato, source perso dopo Wait, id string, playerDropped. Con modelli di protezione.

La console del server 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 non ha trovato un giocatore per quell'id e ha restituito nil, e la riga successiva l'ha indicizzato. La chiamata va bene. L'id che gli hai dato non corrisponde a un giocatore ESX caricato. Ecco le solite ragioni e come proteggere se stesso da ognuna.

1. Il giocatore non è ancora caricato

Un giocatore è connesso al server prima che ESX abbia finito di caricare il suo personaggio. Durante quel gap, e mentre uno schermo multicarattere è aperto, ESX.GetPlayerFromId(source) restituisce nil.

Questo colpisce il codice che viene eseguito in anticipo, come gli eventi FiveM generici playerJoining o playerConnecting, o un thread che si avvia nel momento in cui la tua risorsa si avvia.

Usa l'evento ESX che si attiva quando il giocatore è pronto, sul server:

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

Sul client, lo stesso nome di evento dice al tuo script client che il giocatore può essere utilizzato:

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

Se il tuo script può essere riavviato mentre i giocatori sono online, quell'evento ha già sparato per loro. All'avvio, scorre i giocatori che ESX ha già:

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 è cambiato dopo un Wait

source è un globale speciale che FiveM imposta per l'evento che è in esecuzione. Se aspetti dentro il handler, un altro evento può essere eseguito nel frattempo e source non appartiene più al tuo giocatore.

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

Salvalo in una variabile locale prima di tutto il resto:

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 stesso vale dentro un callback o una funzione a cui passi source più tardi: passa il valore memorizzato, non il globale.

3. L'id è sbagliato

ESX.GetPlayerFromId ha bisogno dell'id del server come numero di un giocatore che è online.

  • Un id string. Gli argomenti del comando sono testo. ESX.GetPlayerFromId(args[1]) può restituire nil dove ESX.GetPlayerFromId(tonumber(args[1])) funziona.
  • L'id sbagliato dal client. Sul client, PlayerId() è un indice locale, non l'id del server. L'id del server è GetPlayerServerId(PlayerId()). Inviare quello sbagliato dà un giocatore che non esiste, o il giocatore sbagliato.
  • La console. Un comando digitato nella console del server ha source uguale a 0, che non è un giocatore.
  • Un id da una ricerca di identificatore. ESX.GetPlayerFromIdentifier(identifier) trova solo giocatori che sono online. Per un giocatore offline, leggi invece il database, vedi la guida alle query 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)

Non fidarti di un id inviato dal client per qualcosa che importa. Usa source, che non può essere falsificato, come spiegato in protezione degli eventi del server.

4. Il giocatore se ne è andato

Se il giocatore si disconnette mentre il tuo codice aspetta, ad esempio in un ciclo lungo o un timer, GetPlayerFromId restituisce nil quando finalmente viene eseguito. È un caso normale, non un bug, e ha bisogno della stessa protezione.

Dentro playerDropped la situazione è meno chiara, perché ESX gestisce anche quell'evento per salvare e rimuovere il giocatore, e l'ordine dei handler non è qualcosa su cui fare affidamento. ESX Legacy attiva anche il suo proprio evento esx:playerDropped, ma controlla che la tua versione lo abbia. L'approccio più robusto è mantenere i dati tu stesso mentre il giocatore è 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)

Il modello di protezione

Inizia ogni handler del server che usa un giocatore nello stesso modo:

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)

Lo stesso controllo va dentro i callback del server registrati con ESX.RegisterServerCallback, dove il primo argomento è la source del giocatore:

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)

Controlla anche il lato client: deve accettare una risposta nil dal callback.

Suggerimento: se l'errore è attempt to index a nil value (global 'ESX') invece, il problema è l'oggetto ESX, non il giocatore. Vedi ESX è nil: correzione di esx:getSharedObject.

Lista di controllo

Sintomo Soluzione
Nil subito dopo che il giocatore si unisce Aspetta invece esx:playerLoaded invece di playerJoining
Nil dopo un Wait Memorizza local src = source prima e usa src
Nil da un comando Converti l'argomento con tonumber; source è 0 nella console
Nil con un id inviato dal client Usa source; sul client l'id del server è GetPlayerServerId(PlayerId())
Nil in playerDropped Mantieni i dati di cui hai bisogno nella tua tabella mentre il giocatore è online
Nil per un giocatore offline GetPlayerFromId trova solo giocatori online; interroga il database
Qualsiasi nil Aggiungi if not xPlayer then return end in cima al handler

Risposte rapide

Perché xPlayer è nil in ESX?

ESX.GetPlayerFromId(source) restituisce nil quando ESX non ha un giocatore caricato per quell'id. Il giocatore potrebbe non aver finito di caricarsi, l'id potrebbe essere sbagliato o una stringa, oppure il giocatore se n'è già andato.

Devo controllare xPlayer in ogni evento?

Sì. Qualsiasi evento server può essere attivato con un giocatore che non è caricato, quindi inizia il handler con if not xPlayer then return end. Non costa nulla e previene l'errore.

Posso usare xPlayer dentro playerDropped?

Non è sicuro fare affidamento su di esso, perché ESX potrebbe aver già rimosso il giocatore. Mantieni i dati di cui hai bisogno nella tua tabella mentre il giocatore è online, e leggili da lì.

Script senza questo problema

Shop CreatorCrea un negozio in meno di un minuto — proprietari, dipendenti, casseforti e rapine inclusi.Vedi script →Pawn Shop AppUn banco dei pegni tra giocatori dentro lb-phone.Vedi script →Drug Dealer AppSpaccio di strada come app per lb-phone: zone, acquirenti NPC, livelli e allerte alla polizia.Vedi script →

Continua a leggere