FiveM Events: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent

Wie FiveM Client- und Server-Events funktionieren: RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent mit -1, die Quellvariable und Event-Benennung.

Die meisten Skript-Fehler zwischen dem Spiel und dem Server kommen auf Events hinaus: Ein Handler, der nie läuft, ein Event, das an niemanden gesendet wird, oder ein source, der auf den falschen Spieler zeigt.

Dieser Artikel erklärt, wie Netzwerk-Events funktionieren, wie man sie in beide Richtungen registriert und auslöst, und wie man sie benennt, damit zwei Skripte nie kollidieren.

Die zwei Seiten

Eine FiveM-Ressource kann Code auf dem Client (eine Kopie pro Spieler) und auf dem Server (eine Kopie) haben. Sie kommunizieren durch Events:

Von Zu Funktion
Client Server TriggerServerEvent
Server Ein Client TriggerClientEvent(name, playerId, ...)
Server Alle Clients TriggerClientEvent(name, -1, ...)
Gleiche Seite Gleiche Seite TriggerEvent

Argumente werden serialisiert, daher kannst du Zahlen, Strings, Booleans und Tabellen senden. Du kannst Funktionen nicht senden. Entity-Handles sind lokal für jede Maschine, also sende stattdessen eine Netzwerk-ID (NetworkGetNetworkIdFromEntity).

Registriere einen Handler

Ein Handler ist eine Funktion, die ausgelöst wird, wenn das Event ankommt. Für ein Event, das aus dem Netzwerk kommt, musst du es auch als erlaubt markieren:

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

Das ist die Kurzform: RegisterNetEvent mit der Funktion als zweites Argument registriert das Event und hängt den Handler an. Die lange Form macht das gleiche in zwei Schritten:

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

Wenn du nur AddEventHandler verwendest, funktioniert der Handler für lokale TriggerEvent-Aufrufe, aber ein TriggerServerEvent oder TriggerClientEvent von der anderen Seite wird ignoriert. Das ist der häufigste Grund für ein Event, das nichts macht.

Client zu Server

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)

Auf dem Server hält das Global source die ID des Spielers, der das Event ausgelöst hat. Der Client sendet es nicht, daher kann es nicht von einem Client gefälscht werden, der seine Argumente ändert.

Server zu 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)

Das zweite Argument von TriggerClientEvent ist immer das Ziel. Wenn du es vergisst, wird dein erstes Daten-Argument als Spieler-ID gelesen und das Event geht nirgendwohin.

Speichere source, bevor du Wait verwendest

source ist kein Wert, den du besitzt, es ist ein Global, das die Runtime für das laufende Event setzt. Wenn dein Handler Wait aufruft, oder etwas aufruft, das ergibt, wie MySQL.query.await, kann ein anderes Event inzwischen laufen und source ändert sich.

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)

Mache local src = source die erste Zeile jeden Server-Handlers und verwende src danach. Das Gleiche gilt in einem Rückruf, den du an eine andere Funktion übergibst.

Event-Benennung

Events sind global für den ganzen Server: Jede Ressource kann jedes Event-Name auslösen. Wenn zwei Skripte beide buy oder server:buy registrieren, laufen beide Handler, und ein Spieler kann sie nach Name auslösen.

Präfixiere jedes Event mit der Ressource und der Seite:

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

Verwende nicht die Namen der Events anderer Skripte erneut, und benenne dein Event nicht etwas Generisches wie giveMoney. Ein lesbarer Name macht auch offensichtlich, in der Konsole, welches Skript was gesendet hat.

Vertraue dem Client nie

Alles, das durch TriggerServerEvent kommt, kann von einem Cheater mit beliebigen Argumenten jederzeit ausgelöst werden. Überprüfe auf dem Server:

  • ob der Spieler es tun darf (Job, Entfernung, Cooldown),
  • dass die Mengen und Artikelnamen gültig sind,
  • dass du den Preis oder die Belohnung nie aus den Argumenten nimmst.
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)

Die komplette Liste ist in securing server events. Für ein Ergebnis, das zum Aufrufer zurück kommen muss, ist ein Rückruf sauberer als zwei Events: siehe server callbacks on ESX, QBCore and ox_lib.

Große oder häufige Events

Jedes Event ist eine Netzwerk-Nachricht. Trigger nicht jeden Frame einen, und sende keine sehr große Tabelle an jeden Spieler. Wenn die Server-Konsole über zuverlässige Events druckt, die überfluten, sendest du zu viel, zu schnell: siehe reliable network event overflow.

Checkliste

Symptom Lösung
Handler läuft nie von der anderen Seite Verwende RegisterNetEvent, nicht nur AddEventHandler
TriggerClientEvent erreicht niemanden Pass eine Spieler-ID oder -1 als zweites Argument
source ist nil oder der falsche Spieler local src = source als erste Zeile, vor jedem Wait
Zwei Skripte reagieren auf das gleiche Event Präfixiere Namen mit der Ressource: my_script:server:buy
Client kann sich selbst Gegenstände oder Geld geben Validiere auf dem Server und schau Preise dort nach
Event-Spam in der Konsole oder Verzögerung Sende weniger, seltener: siehe zuverlässiger Netzwerk-Event-Überlauf

Kurze Antworten

Was ist der Unterschied zwischen RegisterNetEvent und AddEventHandler?

RegisterNetEvent ermöglicht es, dass das Event vom anderen Ende des Netzwerks ausgelöst wird. AddEventHandler hängt nur eine Funktion an. Ein Handler für ein Netzwerk-Event braucht beides, oder RegisterNetEvent mit der Funktion als zweites Argument.

Wie sende ich ein Event an jeden Spieler?

Auf dem Server, TriggerClientEvent('my_script:client:thing', -1, data). Die -1 bedeutet alle verbundenen Clients.

Warum ist source falsch in meinem Server-Event?

source ist ein Global, das den Spieler hält, der das aktuelle Event ausgelöst hat. Wenn du Wait aufrufst und ein anderes Event läuft inzwischen, ändert es sich. Kopiere es in ein Local zuerst: local src = source.

Scripts ohne dieses Problem

Advanced BoostingFahrzeug-Boosting per Tablet: Aufträge von Klasse D bis S+, Crews und eine Live-Warteschlange.Script ansehen →Drug Dealer AppStraßenverkauf als lb-phone-App: Zonen, NPC-Käufer, Level und Polizeialarme.Script ansehen →Shop CreatorBau einen Shop in unter einer Minute — Besitzer, Angestellte, Tresore und Überfälle inklusive.Script ansehen →

Weiterlesen