Eventi FiveM: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent
Come funzionano gli eventi client e server di FiveM: RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent con -1, la variabile source e la denominazione degli eventi.
La maggior parte dei bug degli script tra il gioco e il server si riducono agli eventi: un handler che non viene mai eseguito, un evento inviato a nessuno, o un source che punta al giocatore sbagliato.
Questo articolo spiega come funzionano gli eventi di rete, come registrarli e attivarli in entrambe le direzioni, e come nominarli in modo che due script non collidano mai.
I due lati
Una risorsa FiveM può avere codice su client (una copia per giocatore) e sul server (una copia). Comunicano attraverso eventi:
| Da | A | Funzione |
|---|---|---|
| Client | Server | TriggerServerEvent |
| Server | Un client | TriggerClientEvent(name, playerId, ...) |
| Server | Tutti i client | TriggerClientEvent(name, -1, ...) |
| Stesso lato | Stesso lato | TriggerEvent |
Gli argomenti sono serializzati, quindi puoi inviare numeri, stringhe, booleani e tabelle. Non puoi inviare funzioni. Gli handle delle entità sono locali per ogni macchina, quindi invia invece un id di rete (NetworkGetNetworkIdFromEntity).
Registrazione di un handler
Un handler è una funzione che viene eseguita quando l'evento arriva. Per un evento che proviene dalla rete devi anche marcarlo come consentito:
RegisterNetEvent('my_script:client:notify', function(message)
print(message)
end)Questo è il modulo breve: RegisterNetEvent con la funzione come secondo argomento registra l'evento e allega il handler. Il modulo lungo fa lo stesso in due passaggi:
RegisterNetEvent('my_script:client:notify')
AddEventHandler('my_script:client:notify', function(message)
print(message)
end)Se usi solo AddEventHandler, il handler funziona per le chiamate TriggerEvent locali, ma un TriggerServerEvent o TriggerClientEvent dall'altro lato viene ignorato. Questa è la ragione più comune per un evento che non fa nulla.
Client a server
-- 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)Sul server, il globale source contiene l'id del giocatore che ha attivato l'evento. Il client non lo invia, quindi non può essere falsificato da un client che cambia i suoi argomenti.
Server a 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)Il secondo argomento di TriggerClientEvent è sempre il bersaglio. Se lo dimentichi, il tuo primo argomento di dati viene letto come id del giocatore e l'evento non va da nessuna parte.
Salva source prima di Wait
source non è un valore che possiedi, è un globale che il runtime imposta per l'evento che sta girando. Se il tuo handler chiama Wait, o chiama qualcosa che cede come MySQL.query.await, un altro evento potrebbe essere eseguito nel frattempo e source cambia.
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)Fai local src = source la prima riga di ogni handler del server e usa src dopo. Lo stesso vale dentro un callback che passi a un'altra funzione.
Denominazione degli eventi
Gli eventi sono globali a tutto il server: qualsiasi risorsa può attivare qualsiasi nome di evento. Se due script registrano entrambi buy o server:buy, entrambi gli handler vengono eseguiti, e un giocatore può attivarli per nome.
Anteponi ogni evento con la risorsa e il lato:
'my_script:server:buy'
'my_script:client:updateJob'Non riutilizzare i nomi degli eventi di altri script, e non denominare il tuo evento qualcosa di generico come giveMoney. Un nome leggibile rende anche ovvio nella console quale script ha inviato cosa.
Non fidarti mai del client
Qualsiasi cosa arrivi attraverso TriggerServerEvent può essere attivata da un cheater con qualsiasi argomento, in qualsiasi momento. Controlla sul server:
- che il giocatore sia autorizzato a farlo (lavoro, distanza, cooldown),
- che gli importi e i nomi degli oggetti siano validi,
- che tu non prenda mai il prezzo o la ricompensa dagli argomenti.
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)L'elenco completo è in protezione degli eventi del server. Per un risultato che deve tornare al chiamante, un callback è più pulito di due eventi: vedi callback del server su ESX, QBCore e ox_lib.
Eventi grandi o frequenti
Ogni evento è un messaggio di rete. Non attivarne uno ad ogni frame, e non inviare una tabella molto grande a ogni giocatore. Se la console del server stampa informazioni sugli eventi affidabili che traboccano, stai inviando troppo, troppo velocemente: vedi overflow di evento di rete affidabile.
Checklist
| Sintomo | Soluzione |
|---|---|
| L'handler non viene mai eseguito dall'altro lato | Usa RegisterNetEvent, non solo AddEventHandler |
TriggerClientEvent non raggiunge nessuno |
Passa un id del giocatore, o -1, come secondo argomento |
source è nil o il giocatore sbagliato |
local src = source come prima riga, prima di qualsiasi Wait |
| Due script reagiscono allo stesso evento | Anteponi i nomi con la risorsa: my_script:server:buy |
| Il client può darsi oggetti o soldi | Convalida sul server e cerca lì i prezzi |
| Spam di eventi nella console o lag | Invia meno, meno spesso: vedi overflow di evento di rete affidabile |
Risposte rapide
Qual è la differenza tra RegisterNetEvent e AddEventHandler?
RegisterNetEvent permette all'evento di essere attivato dall'altro lato della rete. AddEventHandler allega solo una funzione. Un handler per un evento di rete ha bisogno di entrambi, o RegisterNetEvent con la funzione come secondo argomento.
Come invio un evento a ogni giocatore?
Sul server, TriggerClientEvent('my_script:client:thing', -1, data). -1 significa tutti i client connessi.
Perché source è sbagliato nel mio evento server?
source è un globale che contiene il giocatore che ha attivato l'evento corrente. Se chiami Wait e un altro evento funziona nel frattempo, cambia. Copialo in una variabile locale prima: local src = source.
Script senza questo problema
Advanced BoostingBoosting di veicoli dal tablet: contratti dalla classe D alla S+, crew e coda in tempo reale.Vedi script →
Drug Dealer AppSpaccio di strada come app per lb-phone: zone, acquirenti NPC, livelli e allerte alla polizia.Vedi script →
Shop CreatorCrea un negozio in meno di un minuto — proprietari, dipendenti, casseforti e rapine inclusi.Vedi script →