É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é :
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 :
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
-- 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)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
-- 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)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.
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é :
'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.
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 →