FiveM playerConnecting e deferrals: defer, update, done explicados

Como AddEventHandler('playerConnecting') funciona com deferrals: defer, Wait(0), update, done(reason), adaptive cards, e por que os jogadores travam quando você esquece de chamar done.

Você quer executar algo antes de um jogador ser permitido: uma verificação de whitelist, uma lista de bans, uma busca no banco de dados, um cartão de boas-vindas. O lugar para isso é o evento playerConnecting, e a ferramenta é deferrals. Usado de forma errada, jogadores ficam na tela de conexão para sempre, então os detalhes importam.

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

O que os argumentos são

  • name: o nome do jogador.
  • setKickReason(reason): uma função que rejeita a conexão com uma mensagem. Existe para rejeições simples e imediatas.
  • deferrals: um objeto com funções que deixam você pausar a conexão e falar com o jogador enquanto você trabalha.

Dentro do handler, source é o id de conexão do jogador.

As quatro chamadas de deferral

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() diz ao FiveM para manter o jogador até você decidir. Sem isso, o jogador é deixado entrar assim que seu handler retorna, mesmo que você tenha iniciado uma tarefa assíncrona.
  2. Wait(0) é necessário logo após defer(). Chamadas para update, done ou presentCard no mesmo tick que defer podem não funcionar.
  3. deferrals.update(message) substitui o texto que o jogador vê na tela de conexão. Use para mostrar progresso.
  4. deferrals.done(reason) encerra a espera. Sem argumento significa que o jogador é aceito. Uma string significa que eles são rejeitados e veem aquela string.

Dica: copie source em uma local no início. Após Wait ou uma requisição HTTP, o global source pode apontar para outro jogador.

Rejeitando um jogador

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(...) encerra o handler ao mesmo tempo, o que evita você chamar done duas vezes.

Verificações assincronadas

Deferrals foram feitos para trabalho que leva tempo: uma query no banco de dados ou uma chamada HTTP. Mantenha a conexão deferred e chame done no 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 em builds de server atuais. Se seu artifact for antigo, faça um loop sobre GetPlayerIdentifiers em vez disso, como acima. Veja o oxmysql guide para a sintaxe de query. Um exemplo completo com roles do Discord está em Discord whitelist for FiveM.

O erro comum: esquecendo done

Jogadores presos na tela de conexão quase sempre significam que um caminho de código nunca atinge 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, o jogador é mantido para sempre. A solução é um 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)

Três outras formas de ficar preso:

  • Um erro de script dentro do handler antes de done. O handler para e o jogador espera. Leia o console. Veja reading a script error.
  • Uma chamada HTTP que nunca responde. Adicione um timeout, por exemplo rastreando o tempo de início e terminando com uma mensagem se nada voltar.
  • Chamar done duas vezes, ou chamar update depois de done. Use return para deixar o handler após cada fim.

Vários handlers ao mesmo tempo

Muitos recursos ouvem playerConnecting, e cada um que chama defer adiciona à espera. O jogador é deixado entrar apenas quando todos terminam. Um handler lento ou quebrado em um script afeta todos, então teste apenas com aquele recurso primeiro quando conexões travam.

Cartões adaptativos

deferrals.presentCard mostra um formulário ou uma tela de regras de um Adaptive Card, que é um layout JSON. O jogador pode pressionar um botão, e seu callback recebe o 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)

Mantenha cartões simples: texto, uma imagem e um ou dois botões. O callback ainda precisa terminar em done.

Checklist

Sintoma Solução
Jogador preso na conexão Um caminho nunca chama deferrals.done; adicione o else faltante
update ou done não tem efeito Chame Wait(0) logo após deferrals.defer()
Jogador errado afetado Salve local src = source primeiro
Jogador deixado entrar antes da verificação terminar Você esqueceu deferrals.defer()
Rejeitar com uma mensagem deferrals.done('reason')
Aceitar o jogador deferrals.done() sem argumento
Travam apenas com muitos scripts O handler playerConnecting de outro recurso é lento ou quebrado

Respostas rápidas

Por que preciso de Wait(0) depois de deferrals.defer()?

O deferral é apenas ativo uma vez que o engine passou um tick. Chamar deferrals.update ou deferrals.done no mesmo tick pode ser ignorado, então ceda uma vez com Wait(0) primeiro.

Como rejeito um jogador?

Chame deferrals.done('sua razão') com uma string. O jogador vê o texto e não é permitido entrar. Chamar deferrals.done() sem argumento deixa eles conectarem.

O que acontece se nunca chamar deferrals.done?

O jogador fica na tela de conexão até a conexão expirar. Todo caminho de código no seu handler deve terminar em deferrals.done.

Scripts sem esse problema

Tebex TemplateUm tema premium para sua loja Tebex, sem código e editado todo pelo painel da Tebex.Ver script →Mic PhoneUm celular dobrável que abre em tablet e chega ao celular de verdade do jogador.Ver script →

Continue lendo