Événements FiveM : RegisterNetEvent, TriggerServerEvent, TriggerClientEvent

Comment fonctionnent les événements client et serveur FiveM : RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent avec -1, la variable source et la dénomination des événements.

La plupart des bugs de script entre le jeu et le serveur reviennent à des événements : un gestionnaire qui ne s'exécute jamais, un événement envoyé à personne, ou une source qui pointe vers le mauvais joueur.

Cet article explique comment fonctionnent les événements réseau, comment les enregistrer et les déclencher dans les deux sens, et comment les nommer pour que deux scripts ne se heurtent jamais.

Les deux côtés

Une ressource FiveM peut avoir du code sur le client (une copie par joueur) et sur le serveur (une copie). Ils communiquent via des événements :

De À Fonction
Client Serveur TriggerServerEvent
Serveur Un client TriggerClientEvent(name, playerId, ...)
Serveur Tous les clients TriggerClientEvent(name, -1, ...)
Même côté Même côté TriggerEvent

Les arguments sont sérialisés, vous pouvez donc envoyer des nombres, des strings, des booléens et des tables. Vous ne pouvez pas envoyer de fonctions. Les poignées d'entité sont locales à chaque machine, envoyez plutôt un id réseau (NetworkGetNetworkIdFromEntity).

Enregistrer un gestionnaire

Un gestionnaire est une fonction qui s'exécute quand l'événement arrive. Pour un événement qui vient du réseau, vous devez aussi le marquer comme autorisé :

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

C'est la forme courte : RegisterNetEvent avec la fonction comme deuxième argument enregistre l'événement et attache le gestionnaire. La forme longue fait la même chose en deux étapes :

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

Si vous utilisez seulement AddEventHandler, le gestionnaire fonctionne pour les appels TriggerEvent locaux, mais un TriggerServerEvent ou TriggerClientEvent de l'autre côté est ignoré. C'est la raison la plus courante pour un événement qui ne fait rien.

Client à serveur

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)

Sur le serveur, la globale source contient l'id du joueur qui a déclenché l'événement. Le client ne l'envoie pas, donc il ne peut pas être falsifié par un client changeant ses arguments.

Serveur à client

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)

Le deuxième argument de TriggerClientEvent est toujours la cible. Si vous l'oubliez, votre premier argument de données est lu comme l'id du joueur et l'événement n'va nulle part.

Sauvegardez source avant d'attendre

source n'est pas une valeur que vous possédez, c'est une globale que le runtime définit pour l'événement en cours d'exécution. Si votre gestionnaire appelle Wait, ou appelle quelque chose qui cède comme MySQL.query.await, un autre événement peut s'exécuter entre-temps et source change.

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)

Faites local src = source la première ligne de chaque gestionnaire serveur et utilisez src après. La même chose s'applique à l'intérieur d'un rappel que vous transmettez à une autre fonction.

Dénomination des événements

Les événements sont globaux à l'ensemble du serveur : n'importe quelle ressource peut déclencher n'importe quel nom d'événement. Si deux scripts s'enregistrent tous les deux buy ou server:buy, les deux gestionnaires s'exécutent, et un joueur peut les déclencher par nom.

Préfixez chaque événement avec la ressource et le côté :

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

Ne réutilisez pas les noms des événements d'autres scripts, et ne nommez pas votre événement quelque chose de générique comme giveMoney. Un nom lisible fait aussi qu'il est évident dans la console quel script a envoyé quoi.

Ne faites jamais confiance au client

N'importe quoi qui vient via TriggerServerEvent peut être déclenché par un cheater avec n'importe quels arguments, n'importe quand. Vérifiez sur le serveur :

  • que le joueur est autorisé à le faire (emploi, distance, délai d'attente),
  • que les montants et les noms d'articles sont valides,
  • que vous ne prenez jamais le prix ou la récompense des arguments.
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 liste complète est dans Sécuriser les événements serveur. Pour un résultat qui doit revenir à l'appelant, un rappel est plus clair que deux événements : voir rappels serveur sur ESX, QBCore et ox_lib.

Événements volumineux ou fréquents

Chaque événement est un message réseau. Ne déclenchez pas un chaque frame, et n'envoyez pas une très grande table à chaque joueur. Si la console serveur imprime à propos du débordement d'événements fiables, vous envoyez trop, trop vite : voir débordement d'événement réseau fiable.

Liste de vérification

Symptôme Correction
Le gestionnaire ne s'exécute jamais de l'autre côté Utilisez RegisterNetEvent, pas seulement AddEventHandler
TriggerClientEvent n'atteint personne Passez un id de joueur, ou -1, comme deuxième argument
source est nil ou le mauvais joueur local src = source comme première ligne, avant n'importe quel Wait
Deux scripts réagissent au même événement Préfixez les noms avec la ressource : my_script:server:buy
Le client peut se donner des articles ou de l'argent Validez sur le serveur et cherchez les prix là
Spam d'événement dans la console ou décalage Envoyez moins, moins souvent : voir débordement d'événement réseau fiable

Réponses rapides

Quelle est la différence entre RegisterNetEvent et AddEventHandler ?

RegisterNetEvent permet à l'événement d'être déclenché de l'autre côté du réseau. AddEventHandler attache uniquement une fonction. Un gestionnaire pour un événement réseau a besoin des deux, ou RegisterNetEvent avec la fonction comme deuxième argument.

Comment envoyer un événement à chaque joueur ?

Sur le serveur, TriggerClientEvent('my_script:client:thing', -1, data). Le -1 signifie tous les clients connectés.

Pourquoi source est-il mauvais dans mon événement serveur ?

source est une globale qui contient le joueur qui a déclenché l'événement actuel. Si vous appelez Wait et un autre événement s'exécute entre-temps, cela change. Copiez-le dans une locale d'abord : local src = source.

Des scripts sans ce problème

Advanced BoostingDu boosting de véhicules piloté par tablette : contrats de classe D à S+, crews et file d’attente en direct.Voir le script →Drug Dealer AppLa vente de rue en app lb-phone : zones, acheteurs PNJ, niveaux et alertes police.Voir le script →Shop CreatorCréez un magasin en moins d’une minute — propriétaires, employés, coffres et braquages inclus.Voir le script →

À lire aussi