Eventos de FiveM: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent

Cómo funcionan los eventos de cliente y servidor de FiveM: RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent con -1, la variable source y el nombre de eventos.

La mayoría de los errores de script entre el juego y el servidor se reducen a eventos: un manejador que nunca se ejecuta, un evento enviado a nadie, o un source que apunta al jugador incorrecto.

Este artículo explica cómo funcionan los eventos de red, cómo registrarlos y activarlos en ambas direcciones, y cómo nombrarlos para que dos scripts nunca choquen.

Los dos lados

Un recurso de FiveM puede tener código en el cliente (una copia por jugador) y en el servidor (una copia). Se comunican a través de eventos:

De A Función
Cliente Servidor TriggerServerEvent
Servidor Un cliente TriggerClientEvent(name, playerId, ...)
Servidor Todos los clientes TriggerClientEvent(name, -1, ...)
Mismo lado Mismo lado TriggerEvent

Los argumentos se serializan, así que puedes enviar números, cadenas, booleanos y tablas. No puedes enviar funciones. Los identificadores de entidad son locales para cada máquina, así que envía un id de red (NetworkGetNetworkIdFromEntity) en su lugar.

Registrar un manejador

Un manejador es una función que se ejecuta cuando llega el evento. Para un evento que viene de la red también debe marcarlo como permitido:

lua
RegisterNetEvent('my_script:client:notify', function(message)
    print(message)
end)

Esa es la forma corta: RegisterNetEvent con la función como su segundo argumento registra el evento y adjunta el manejador. La forma larga hace lo mismo en dos pasos:

lua
RegisterNetEvent('my_script:client:notify')
AddEventHandler('my_script:client:notify', function(message)
    print(message)
end)

Si solo usas AddEventHandler, el manejador funciona para llamadas locales de TriggerEvent, pero un TriggerServerEvent o TriggerClientEvent del otro lado se ignora. Esa es la razón más común para un evento que no hace nada.

Cliente a 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)

En el servidor, la global source contiene el id del jugador que desencadenó el evento. El cliente no lo envía, así que no puede ser falsificado por un cliente que cambie sus argumentos.

Servidor a 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)

El segundo argumento de TriggerClientEvent siempre es el destino. Si lo olvidas, tu primer argumento de datos se lee como el id del jugador y el evento no va a ningún lado.

Guarda source antes de hacer Wait

source no es un valor que poseas, es una global que el tiempo de ejecución establece para el evento que se está ejecutando. Si tu manejador llama a Wait, o llama a algo que produce rendimiento como MySQL.query.await, otro evento puede ejecutarse mientras tanto y source cambia.

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)

Haz local src = source la primera línea de cada manejador del servidor y usa src después de eso. Lo mismo se aplica dentro de una devolución de llamada que pasas a otra función.

Nombrando eventos

Los eventos son globales a todo el servidor: cualquier recurso puede activar cualquier nombre de evento. Si dos scripts registran ambos buy o server:buy, ambos manejadores se ejecutan, y un jugador puede activarlos por nombre.

Prefija cada evento con el recurso y el lado:

lua
'my_script:server:buy'
'my_script:client:updateJob'

No reutilices los nombres de los eventos de otros scripts, y no nombres tu evento algo genérico como giveMoney. Un nombre legible también hace que sea obvio en la consola qué script envió qué.

Nunca confíes en el cliente

Cualquier cosa que venga a través de TriggerServerEvent puede ser disparada por un tramposo con cualquier argumento, en cualquier momento. Comprueba en el servidor:

  • que el jugador tiene permiso para hacerlo (trabajo, distancia, tiempo de reutilización),
  • que los montos y nombres de artículos sean válidos,
  • que nunca tomes el precio o la recompensa de los 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)

La lista completa está en securing server events. Para un resultado que debe volver al llamador, una devolución de llamada es más limpia que dos eventos: ve server callbacks on ESX, QBCore and ox_lib.

Eventos grandes o frecuentes

Cada evento es un mensaje de red. No actives uno cada fotograma, y no envíes una tabla muy grande a todos los jugadores. Si la consola del servidor imprime sobre desbordamiento de eventos confiables, estás enviando demasiado, demasiado rápido: ve reliable network event overflow.

Lista de verificación

Síntoma Solución
El manejador nunca se ejecuta desde el otro lado Usa RegisterNetEvent, no solo AddEventHandler
TriggerClientEvent no llega a nadie Pasa un id de jugador, o -1, como el segundo argumento
source es nil o el jugador incorrecto local src = source como la primera línea, antes de cualquier Wait
Dos scripts reaccionan al mismo evento Prefija nombres con el recurso: my_script:server:buy
El cliente puede darse a sí mismo artículos o dinero Valida en el servidor y busca precios allí
Spam de eventos en la consola o lag Envía menos, menos a menudo: ve el desbordamiento de eventos de red confiables

Respuestas rápidas

¿Cuál es la diferencia entre RegisterNetEvent y AddEventHandler?

RegisterNetEvent permite que el evento se active desde el otro lado de la red. AddEventHandler solo adjunta una función. Un manejador para un evento de red necesita ambos, o RegisterNetEvent con la función como su segundo argumento.

¿Cómo envío un evento a todos los jugadores?

En el servidor, TriggerClientEvent('my_script:client:thing', -1, data). El -1 significa todos los clientes conectados.

¿Por qué es incorrecto source en mi evento del servidor?

source es una global que contiene el jugador que desencadenó el evento actual. Si llamas a Wait y otro evento se ejecuta mientras tanto, cambia. Cópialo en una local primero: local src = source.

Scripts que evitan este problema

Advanced BoostingBoosting de vehículos desde una tablet: contratos de clase D a S+, crews y cola en vivo.Ver script →Drug Dealer AppVenta callejera como app de lb-phone: zonas, compradores NPC, niveles y avisos a la policía.Ver script →Shop CreatorCrea una tienda en menos de un minuto — dueños, empleados, caja fuerte y atracos.Ver script →

Sigue leyendo