FiveM playerConnecting y deferrals: defer, update, done explicado

Cómo funciona AddEventHandler('playerConnecting') con deferrals: defer, Wait(0), update, done(reason), tarjetas adaptables, y por qué los jugadores se quedan colgados cuando olvidas llamar a done.

Quieres ejecutar algo antes de que se permita que un jugador entre: una comprobación de lista blanca, una lista de prohibiciones, una búsqueda en la base de datos, una tarjeta de bienvenida. El lugar para eso es el evento playerConnecting, y la herramienta es deferrals. Si se usa incorrectamente, los jugadores permanecen en la pantalla de conexión para siempre, así que los detalles importan.

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

Cuáles son los argumentos

  • name: el nombre del jugador.
  • setKickReason(reason): una función que rechaza la conexión con un mensaje. Existe para rechazos simples e inmediatos.
  • deferrals: un objeto con funciones que te permiten pausar la conexión y hablar con el jugador mientras trabajas.

Dentro del manejador, source es el id de conexión del jugador.

Las cuatro llamadas de aplazamiento

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() le dice a FiveM que retenga al jugador hasta que decidas. Sin él, el jugador es dejado entrar tan pronto como tu manejador se devuelve, incluso si comenzaste una tarea asincrónica.
  2. Wait(0) es necesario justo después de defer(). Las llamadas a update, done o presentCard en el mismo tick que defer pueden no funcionar.
  3. deferrals.update(message) reemplaza el texto que el jugador ve en la pantalla de conexión. Úsalo para mostrar progreso.
  4. deferrals.done(reason) termina la espera. Sin argumento significa que el jugador es aceptado. Una cadena significa que son rechazados y ven esa cadena.

Consejo: copia source en una variable local al inicio. Después de Wait o una solicitud HTTP, el source global puede apuntar a otro jugador.

Rechazar a un jugador

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 el manejador al mismo tiempo, lo que te impide llamar a done dos veces.

Comprobaciones asincrónicas

Los aplazamientos están hechos para trabajo que toma tiempo: una consulta de base de datos o una llamada HTTP. Mantén la conexión aplazada y llama a done en la devolución de llamada.

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 en compilaciones de servidor actuales. Si tu artefacto es antiguo, itera sobre GetPlayerIdentifiers en su lugar, como arriba. Consulta la guía de oxmysql para la sintaxis de consulta. Un ejemplo completo con roles de Discord está en Lista blanca de Discord para FiveM.

El error común: olvidar done

Los jugadores atrapados en la pantalla de conexión casi siempre significan que una ruta de código nunca alcanza 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 es nil, el jugador es retenido para siempre. La solución es 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)

Tres otras formas de quedar atrapado:

  • Un error de script dentro del manejador antes de done. El manejador se detiene y el jugador espera. Lee la consola. Consulta leer un error de script.
  • Una llamada HTTP que nunca responde. Añade un tiempo de espera, por ejemplo, rastreando la hora de inicio y terminando con un mensaje si nada regresó.
  • Llamar a done dos veces, o llamar a update después de done. Usa return para salir del manejador después de cada final.

Varios manejadores a la vez

Muchos recursos escuchan playerConnecting, y cada uno que llama a defer se suma a la espera. El jugador es dejado entrar solo cuando todos ellos terminan. Un manejador lento o roto en un script detiene a todos, así que prueba con solo ese recurso primero cuando las conexiones se cuelguen.

Tarjetas adaptables

deferrals.presentCard muestra un formulario o una pantalla de reglas desde una Tarjeta Adaptable, que es un diseño JSON. El jugador puede presionar un botón, y tu devolución de llamada recibe el resultado.

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)

Mantén las tarjetas simples: texto, una imagen y uno o dos botones. La devolución de llamada aún necesita terminar en done.

Lista de verificación

Síntoma Solución
Jugador atrapado en la conexión Una ruta nunca llama a deferrals.done; añade el else faltante
update o done no tiene efecto Llama a Wait(0) justo después de deferrals.defer()
Jugador incorrecto afectado Guarda local src = source primero
Jugador dejado entrar antes de que termine la comprobación Olvidaste deferrals.defer()
Rechazar con un mensaje deferrals.done('reason')
Aceptar al jugador deferrals.done() sin argumento
Cuelgue solo con muchos scripts El manejador playerConnecting de otro recurso es lento o está roto

Respuestas rápidas

¿Por qué necesito Wait(0) después de deferrals.defer()?

El aplazamiento solo está activo una vez que el motor ha avanzado un tick. Llamar a deferrals.update o deferrals.done en el mismo tick puede ignorarse, así que antes cede una vez con Wait(0).

¿Cómo rechazo a un jugador?

Llama a deferrals.done('tu razón') con una cadena. El jugador ve el texto y no se le permite entrar. Llamar a deferrals.done() sin argumento los deja conectar.

¿Qué sucede si nunca llamo a deferrals.done?

El jugador permanece en la pantalla de conexión hasta que la conexión agota el tiempo. Cada ruta de código en tu manejador debe terminar en deferrals.done.

Scripts que evitan este problema

Tebex TemplateUn tema premium para tu tienda Tebex, sin código y editable desde el panel.Ver script →Mic PhoneUn móvil plegable que se abre en tablet y llega al móvil real del jugador.Ver script →

Sigue leyendo