FiveM playerConnecting e deferrals: defer, update, done spiegati

Come funziona AddEventHandler('playerConnecting') con deferrals: defer, Wait(0), update, done(reason), adaptive cards, e perché i giocatori rimangono bloccati quando dimentichi di chiamare done.

Vuoi eseguire qualcosa prima che un giocatore sia autorizzato a entrare: un controllo whitelist, una lista dei ban, una ricerca nel database, una scheda di benvenuto. Il posto per quello è l'evento playerConnecting, e lo strumento è deferrals. Usato male, i giocatori rimangono sulla schermata di connessione per sempre, quindi i dettagli contano.

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    -- runs when a player starts connecting
end)

Quali sono gli argomenti

  • name: il nome del giocatore.
  • setKickReason(reason): una funzione che rifiuta la connessione con un messaggio. Esiste per rifiuti semplici e immediati.
  • deferrals: un oggetto con funzioni che ti permettono di mettere in pausa la connessione e parlare con il giocatore mentre lavori.

Dentro l'handler, source è l'id di connessione del giocatore.

Le quattro chiamate deferrals

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local src = source          -- save it now

    deferrals.defer()           -- 1. pause the connection
    Wait(0)                     -- 2. let the engine register it

    deferrals.update('Checking your account...')   -- 3. show a message

    -- do your checks here

    deferrals.done()            -- 4. let the player in
end)
  1. deferrals.defer() dice a FiveM di trattenere il giocatore fino a quando decidi. Senza di esso, il giocatore è lasciato entrare non appena il tuo handler ritorna, anche se hai avviato un compito asincrono.
  2. Wait(0) è necessario subito dopo defer(). Le chiamate a update, done o presentCard nello stesso tick di defer potrebbero non funzionare.
  3. deferrals.update(message) sostituisce il testo che il giocatore vede sulla schermata di connessione. Usalo per mostrare i progressi.
  4. deferrals.done(reason) termina l'attesa. Nessun argomento significa che il giocatore è accettato. Una stringa significa che è rifiutato e vede quella stringa.

Suggerimento: copia source in una variabile locale all'inizio. Dopo Wait o una richiesta HTTP, il source globale può puntare a un altro giocatore.

Rifiutare un giocatore

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local src = source
    deferrals.defer()
    Wait(0)

    local license
    for _, id in ipairs(GetPlayerIdentifiers(src)) do
        if id:sub(1, 8) == 'license:' then license = id break end
    end

    if not license then
        return deferrals.done('No Rockstar license found. Restart the game and try again.')
    end

    deferrals.done()
end)

return deferrals.done(...) termina l'handler allo stesso tempo, il che ti impedisce di chiamare done due volte.

Controlli asincroni

I deferrals sono fatti per il lavoro che richiede tempo: una query al database o una chiamata HTTP. Mantieni la connessione differita e chiama done nel callback.

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local src = source
    deferrals.defer()
    Wait(0)
    deferrals.update('Looking you up...')

    local license = GetPlayerIdentifierByType(src, 'license')

    MySQL.scalar('SELECT 1 FROM whitelist WHERE license = ?', { license }, function(found)
        if found then
            deferrals.done()
        else
            deferrals.done('You are not whitelisted.')
        end
    end)
end)

GetPlayerIdentifierByType esiste nelle build server attuali. Se il tuo artefatto è vecchio, fai un loop su GetPlayerIdentifiers invece, come sopra. Vedi la guida oxmysql per la sintassi della query. Un esempio completo con ruoli Discord è in Discord whitelist per FiveM.

L'errore comune: dimenticare done

I giocatori bloccati sulla schermata di connessione quasi sempre significano che un percorso del codice non raggiunge mai deferrals.done.

lua
-- Bad: nothing happens when the query returns no row
MySQL.single('SELECT * FROM bans WHERE license = ?', { license }, function(row)
    if row then
        deferrals.done('You are banned.')
    end
end)

Se row è nil, il giocatore è tenuto per sempre. La soluzione è un else:

lua
MySQL.single('SELECT * FROM bans WHERE license = ?', { license }, function(row)
    if row then
        deferrals.done('You are banned.')
    else
        deferrals.done()
    end
end)

Tre altri modi per rimanere bloccati:

  • Un errore di script dentro l'handler prima di done. L'handler si interrompe e il giocatore aspetta. Leggi la console. Vedi lettura di un errore di script.
  • Una chiamata HTTP che non risponde mai. Aggiungi un timeout, per esempio tracciando il tempo di inizio e terminando con un messaggio se non è tornato nulla.
  • Chiamare done due volte, o chiamare update dopo done. Usa return per lasciare l'handler dopo ogni conclusione.

Più handler contemporaneamente

Molte risorse ascoltano playerConnecting, e ognuna che chiama defer aggiunge all'attesa. Il giocatore è lasciato entrare solo quando tutti loro finiscono. Un handler lento o rotto in uno script blocca tutti, quindi testa con solo quella risorsa per prima quando le connessioni rimangono bloccate.

Adaptive cards

deferrals.presentCard mostra un modulo o una schermata di regole da un Adaptive Card, che è un layout JSON. Il giocatore può premere un pulsante, e il tuo callback riceve il risultato.

lua
local card = {
    type = 'AdaptiveCard',
    version = '1.3',
    body = {
        { type = 'TextBlock', text = 'Server rules', weight = 'Bolder', size = 'Large' },
        { type = 'TextBlock', text = 'Be respectful and no cheating.', wrap = true },
    },
    actions = {
        { type = 'Action.Submit', title = 'I agree', data = { accepted = true } },
    },
}

AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    deferrals.defer()
    Wait(0)

    deferrals.presentCard(json.encode(card), function(data)
        if data and data.accepted then
            deferrals.done()
        else
            deferrals.done('You must accept the rules.')
        end
    end)
end)

Mantieni le schede semplici: testo, un'immagine e uno o due pulsanti. Il callback deve ancora terminare in done.

Checklist

Sintomo Soluzione
Giocatore bloccato sulla connessione Un percorso non chiama mai deferrals.done; aggiungi l'else mancante
update o done non ha effetto Chiama Wait(0) subito dopo deferrals.defer()
Giocatore sbagliato interessato Salva local src = source per primo
Giocatore lasciato entrare prima che il controllo finisca Hai dimenticato deferrals.defer()
Rifiuta con un messaggio deferrals.done('reason')
Accetta il giocatore deferrals.done() senza argomento
Blocco solo con molti script L'handler playerConnecting di un'altra risorsa è lento o rotto

Risposte rapide

Perché ho bisogno di Wait(0) dopo deferrals.defer()?

Il deferral è attivo solo una volta che il motore ha fatto un tick. Chiamare deferrals.update o deferrals.done nello stesso tick può essere ignorato, quindi fai un yield prima con Wait(0).

Come posso rifiutare un giocatore?

Chiama deferrals.done('your reason') con una stringa. Il giocatore vede il testo e non è autorizzato a entrare. Chiamare deferrals.done() senza argomento lo lascia connettere.

Cosa succede se non chiamo mai deferrals.done?

Il giocatore rimane sulla schermata di connessione fino al timeout della connessione. Ogni percorso del codice nel tuo handler deve terminare in deferrals.done.

Script senza questo problema

Tebex TemplateUn tema premium senza codice per il tuo negozio Tebex, modificabile interamente dal pannello Tebex.Vedi script →Mic PhoneUn telefono pieghevole che si apre in un tablet e arriva sul telefono vero del giocatore.Vedi script →

Continua a leggere