FiveM playerConnecting et deferrals : defer, update, done expliqués

Comment AddEventHandler('playerConnecting') fonctionne avec deferrals : defer, Wait(0), update, done(reason), cartes adaptatives, et pourquoi les joueurs attendent quand vous oubliez d'appeler done.

Vous voulez exécuter quelque chose avant qu'un joueur soit autorisé à entrer : une vérification de liste blanche, une liste d'interdiction, une recherche en base de données, une carte de bienvenue. L'endroit pour cela est l'événement playerConnecting, et l'outil est deferrals. Mal utilisées, les joueurs restent sur l'écran de connexion à jamais, les détails comptent donc.

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

Quels sont les arguments

  • name : le nom du joueur.
  • setKickReason(reason) : une fonction qui rejette la connexion avec un message. Elle existe pour les rejets simples et immédiats.
  • deferrals : un objet avec des fonctions qui vous permettent de mettre en pause la connexion et de parler au joueur pendant que vous travaillez.

À l'intérieur du gestionnaire, source est l'ID de connexion du joueur.

Les quatre appels de report

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() dit à FiveM de garder le joueur jusqu'à ce que vous décidiez. Sans lui, le joueur est laissé entrer dès que votre gestionnaire revient, même si vous avez commencé une tâche asynchrone.
  2. Wait(0) est nécessaire juste après defer(). Les appels à update, done ou presentCard dans la même tick que defer peuvent ne pas fonctionner.
  3. deferrals.update(message) remplace le texte que le joueur voit sur l'écran de connexion. Utilisez-le pour afficher la progression.
  4. deferrals.done(reason) termine l'attente. Aucun argument signifie que le joueur est accepté. Une chaîne signifie qu'il est rejeté et voit cette chaîne.

Conseil : copiez source dans une variable locale au tout début. Après Wait ou une requête HTTP, le global source peut pointer vers un autre joueur.

Rejeter un joueur

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(...) termine également le gestionnaire en même temps, ce qui vous empêche d'appeler done deux fois.

Vérifications asynchrones

Les reports sont conçus pour les travaux qui prennent du temps : une requête de base de données ou un appel HTTP. Gardez la connexion reportée et appelez done dans le 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 existe dans les versions actuelles du serveur. Si votre artefact est ancien, bouclez plutôt sur GetPlayerIdentifiers, comme ci-dessus. Consultez le guide oxmysql pour la syntaxe des requêtes. Un exemple complet avec les rôles Discord se trouve dans liste blanche Discord pour FiveM.

L'erreur courante : oublier done

Les joueurs bloqués sur l'écran de connexion signifient presque toujours qu'un chemin de code n'atteint jamais 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)

Si row est nil, le joueur est retenu à jamais. La correction est une 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)

Trois autres façons de rester bloqué :

  • Une erreur de script à l'intérieur du gestionnaire avant done. Le gestionnaire s'arrête et le joueur attend. Lisez la console. Consultez lire une erreur de script.
  • Un appel HTTP qui ne répond jamais. Ajoutez un délai d'expiration, par exemple en suivant l'heure de début et en terminant avec un message si rien n'est revenu.
  • Appeler done deux fois, ou appeler update après done. Utilisez return pour quitter le gestionnaire après chaque fin.

Plusieurs gestionnaires à la fois

De nombreuses ressources écoutent playerConnecting, et chacun qui appelle defer s'ajoute à l'attente. Le joueur n'est laissé entrer que quand tous les deux finissent. Un gestionnaire lent ou cassé dans un script retient tout le monde, alors testez d'abord avec seulement cette ressource quand les connexions s'accrochent.

Cartes adaptatives

deferrals.presentCard affiche un formulaire ou un écran de règles à partir d'une Adaptive Card, qui est une mise en page JSON. Le joueur peut appuyer sur un bouton, et votre callback reçoit le résultat.

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)

Gardez les cartes simples : texte, une image et un ou deux boutons. Le callback doit toujours se terminer par done.

Liste de contrôle

Symptôme Correction
Joueur bloqué sur la connexion Un chemin n'appelle jamais deferrals.done ; ajoutez le else manquant
update ou done n'a aucun effet Appelez Wait(0) juste après deferrals.defer()
Mauvais joueur affecté Enregistrez d'abord local src = source
Joueur laissé entrer avant la fin de la vérification Vous avez oublié deferrals.defer()
Rejeter avec un message deferrals.done('reason')
Accepter le joueur deferrals.done() sans argument
L'attente concerne seulement plusieurs scripts Le gestionnaire playerConnecting d'une autre ressource est lent ou cassé

Réponses rapides

Pourquoi ai-je besoin de Wait(0) après deferrals.defer() ?

Le report n'est actif qu'une fois que le moteur a avancé d'une tick. Appeler deferrals.update ou deferrals.done dans la même tick peut être ignoré, donc cédez d'abord une fois avec Wait(0).

Comment rejeter un joueur ?

Appelez deferrals.done('votre raison') avec une chaîne. Le joueur voit le texte et ne peut pas entrer. Appeler deferrals.done() sans argument lui permet de se connecter.

Que se passe-t-il si je n'appelle jamais deferrals.done ?

Le joueur reste sur l'écran de connexion jusqu'à ce que la connexion expire. Chaque chemin de code dans votre gestionnaire doit se terminer par deferrals.done.

Des scripts sans ce problème

Tebex TemplateUn thème premium sans code pour votre boutique Tebex, entièrement modifiable depuis le panel Tebex.Voir le script →Mic PhoneUn téléphone pliable qui se déplie en tablette et se prolonge jusqu’au vrai téléphone du joueur.Voir le script →

À lire aussi