Eventos FiveM: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent
Como funcionam os eventos de cliente e servidor do FiveM: RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent com -1, a variável source e nomeação de eventos.
A maioria dos bugs de script entre o jogo e o servidor se reduz a eventos: um manipulador que nunca executa, um evento enviado para ninguém ou um source que aponta para o jogador errado.
Este artigo explica como funcionam os eventos de rede, como registrá-los e acioná-los nas duas direções e como nomeá-los para que dois scripts nunca colidam.
Os dois lados
Um recurso FiveM pode ter código no cliente (uma cópia por jogador) e no servidor (uma cópia). Eles se comunicam através de eventos:
| De | Para | Função |
|---|---|---|
| Cliente | Servidor | TriggerServerEvent |
| Servidor | Um cliente | TriggerClientEvent(name, playerId, ...) |
| Servidor | Todos os clientes | TriggerClientEvent(name, -1, ...) |
| Mesmo lado | Mesmo lado | TriggerEvent |
Argumentos são serializados, então você pode enviar números, strings, booleanos e tabelas. Você não pode enviar funções. Identificadores de entidade são locais para cada máquina, então envie um id de rede (NetworkGetNetworkIdFromEntity) em vez disso.
Registrando um manipulador
Um manipulador é uma função que executa quando o evento chega. Para um evento que vem da rede você também deve marcá-lo como permitido:
RegisterNetEvent('my_script:client:notify', function(message)
print(message)
end)Essa é a forma curta: RegisterNetEvent com a função como seu segundo argumento registra o evento e anexa o manipulador. A forma longa faz o mesmo em duas etapas:
RegisterNetEvent('my_script:client:notify')
AddEventHandler('my_script:client:notify', function(message)
print(message)
end)Se você apenas usar AddEventHandler, o manipulador funciona para chamadas locais de TriggerEvent, mas um TriggerServerEvent ou TriggerClientEvent do outro lado é ignorado. Essa é a razão mais comum para um evento que não faz nada.
Cliente para servidor
-- client
TriggerServerEvent('my_script:server:buy', 'water', 2)-- server
RegisterNetEvent('my_script:server:buy', function(item, amount)
local src = source
print(('player %s wants %s x %s'):format(src, amount, item))
end)No servidor, o source global mantém o id do jogador que disparou o evento. O cliente não o envia, portanto não pode ser falsificado por um cliente alterando seus argumentos.
Servidor para cliente
-- server: to one player
TriggerClientEvent('my_script:client:notify', src, 'Purchase done')
-- server: to everyone
TriggerClientEvent('my_script:client:notify', -1, 'Server restart in 5 minutes')-- client
RegisterNetEvent('my_script:client:notify', function(message)
print(message)
end)O segundo argumento de TriggerClientEvent é sempre o alvo. Se você esquecer, seu primeiro argumento de dados é lido como o id do jogador e o evento não vai para lugar nenhum.
Salve source antes de Wait
source não é um valor que você possui, é um global que o runtime define para o evento que está em execução. Se seu manipulador chamar Wait, ou chamar algo que cede como MySQL.query.await, outro evento pode rodar enquanto isso e source muda.
RegisterNetEvent('my_script:server:buy', function(item, amount)
local src = source -- copy it first
Wait(500) -- or any await
print(src) -- still the right player
print(source) -- may be someone else now
end)Faça local src = source a primeira linha de cada manipulador de servidor e use src depois disso. O mesmo se aplica dentro de um callback que você passa para outra função.
Nomeando eventos
Eventos são globais para todo o servidor: qualquer recurso pode disparar qualquer nome de evento. Se dois scripts registrarem ambos buy ou server:buy, ambos os manipuladores executam e um jogador pode acioná-los por nome.
Prefixe cada evento com o recurso e o lado:
'my_script:server:buy'
'my_script:client:updateJob'Não reutilize os nomes de eventos de outros scripts e não nomeie seu evento como algo genérico como giveMoney. Um nome legível também torna óbvio no console qual script enviou o quê.
Nunca confie no cliente
Qualquer coisa que venha através de TriggerServerEvent pode ser disparada por um trapaceiro com quaisquer argumentos, a qualquer momento. Verifique no servidor:
- que o jogador tem permissão para fazer (emprego, distância, cooldown),
- que os valores e nomes de itens são válidos,
- que você nunca tira o preço ou recompensa dos argumentos.
RegisterNetEvent('my_script:server:buy', function(item, amount)
local src = source
if type(amount) ~= 'number' or amount < 1 or amount > 10 then return end
-- look the price up on the server, do not read it from the client
end)A lista completa está em eventos de servidor seguro. Para um resultado que deve voltar ao chamador, um callback é mais limpo do que dois eventos: veja server callbacks no ESX, QBCore e ox_lib.
Eventos grandes ou frequentes
Cada evento é uma mensagem de rede. Não dispare uma a cada frame e não envie uma tabela muito grande para cada jogador. Se o console do servidor imprime sobre eventos de rede confiáveis transbordando, você está enviando muito, muito rápido: veja reliable network event overflow.
Lista de verificação
| Sintoma | Correção |
|---|---|
| Manipulador nunca executa do outro lado | Use RegisterNetEvent, não apenas AddEventHandler |
TriggerClientEvent alcança ninguém |
Passe um id de jogador, ou -1, como o segundo argumento |
source é nil ou o jogador errado |
local src = source como a primeira linha, antes de qualquer Wait |
| Dois scripts reagem ao mesmo evento | Prefixe nomes com o recurso: my_script:server:buy |
| Cliente pode se dar itens ou dinheiro | Valide no servidor e procure os preços lá |
| Spam de evento no console ou lag | Envie menos, menos frequentemente: veja reliable network event overflow |
Respostas rápidas
Qual é a diferença entre RegisterNetEvent e AddEventHandler?
RegisterNetEvent permite que o evento seja disparado do outro lado da rede. AddEventHandler apenas anexa uma função. Um manipulador para um evento de rede precisa de ambos, ou RegisterNetEvent com a função como seu segundo argumento.
Como envio um evento para cada jogador?
No servidor, TriggerClientEvent('my_script:client:thing', -1, data). O -1 significa todos os clientes conectados.
Por que source está errado no meu evento de servidor?
source é um global que mantém o jogador que disparou o evento atual. Se você chamar Wait e outro evento rodar enquanto isso, ele muda. Copie-o em um local primeiro: local src = source.
Scripts sem esse problema
Advanced BoostingBoosting de veículos pelo tablet: contratos da classe D à S+, crews e fila ao vivo.Ver script →
Drug Dealer AppVendas na rua como app do lb-phone: zonas, compradores NPC, níveis e alertas para a polícia.Ver script →
Shop CreatorMonte uma loja em menos de um minuto — donos, funcionários, cofres e assaltos inclusos.Ver script →