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.
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
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)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.Wait(0)é necessário logo apósdefer(). Chamadas paraupdate,doneoupresentCardno mesmo tick quedeferpodem não funcionar.deferrals.update(message)substitui o texto que o jogador vê na tela de conexão. Use para mostrar progresso.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
sourceem uma local no início. ApósWaitou uma requisição HTTP, o globalsourcepode apontar para outro jogador.
Rejeitando um jogador
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.
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.
-- 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:
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
doneduas vezes, ou chamarupdatedepois dedone. Usereturnpara 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.
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.

