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:

lua
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:

lua
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

lua
-- client
TriggerServerEvent('my_script:server:buy', 'water', 2)
lua
-- 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

lua
-- 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')
lua
-- 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.

lua
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:

lua
'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.
lua
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 →

Continue lendo