События FiveM: RegisterNetEvent, TriggerServerEvent, TriggerClientEvent

Как работают события клиента и сервера FiveM: RegisterNetEvent, AddEventHandler, TriggerServerEvent, TriggerClientEvent с -1, переменная source и именование событий.

Большинство ошибок скрипта между игрой и сервером сводятся к событиям: обработчику, который никогда не запускается, событию, отправленному никому, или source, который указывает на неправильного игрока.

Эта статья объясняет, как работают сетевые события, как регистрировать и запускать их в обоих направлениях, и как их называть, чтобы два скрипта никогда не столкнулись.

Две стороны

Ресурс FiveM может иметь код на клиенте (одна копия за игрока) и на сервере (одна копия). Они общаются через события:

От Кому Функция
Клиент Сервер TriggerServerEvent
Сервер Один клиент TriggerClientEvent(name, playerId, ...)
Сервер Все клиенты TriggerClientEvent(name, -1, ...)
Одна сторона Одна сторона TriggerEvent

Аргументы сериализованы, поэтому вы можете отправить числа, строки, логические значения и таблицы. Вы не можете отправить функции. Дескрипторы сущностей локальны для каждой машины, поэтому отправьте сетевой id (NetworkGetNetworkIdFromEntity) вместо этого.

Регистрация обработчика

Обработчик — это функция, которая запускается при поступлении события. Для события, поступающего из сети, вы также должны пометить его как разрешенный:

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

Это краткая форма: RegisterNetEvent с функцией как второй аргумент регистрирует событие и присоединяет обработчик. Длинная форма делает то же самое в два шага:

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

Если вы только используете AddEventHandler, обработчик работает для локальных вызовов TriggerEvent, но TriggerServerEvent или TriggerClientEvent с другой стороны игнорируются. Это наиболее распространенная причина события, которое ничего не делает.

Клиент на сервер

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)

На сервере глобальное значение source содержит id игрока, который запустил событие. Клиент не отправляет его, поэтому он не может быть подделан клиентом, изменяющим его аргументы.

Сервер на клиент

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)

Второй аргумент TriggerClientEvent всегда цель. Если вы его забудете, ваш первый аргумент данных читается как идентификатор игрока и событие никуда не уходит.

Сохраните source перед Wait

source — это не значение, которым вы владеете, это глобальное значение, которое среда выполнения устанавливает для работающего события. Если ваш обработчик вызывает Wait или вызывает что-то, что дает выход, такое как MySQL.query.await, другое событие может запуститься тем временем и source изменится.

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)

Сделайте local src = source первой строкой каждого обработчика сервера и используйте src после этого. То же применяется внутри обратного вызова, который вы передаете другой функции.

Имена событий

События глобальны для всего сервера: любой ресурс может запустить любое имя события. Если два скрипта оба регистрируют buy или server:buy, оба обработчика запускаются, и игрок может запустить их по имени.

Предыдите каждое событие префиксом ресурса и стороны:

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

Не переиспользуйте имена событий других скриптов и не называйте ваше событие чем-то общим, таким как giveMoney. Читаемое имя также делает очевидным в консоли, какой скрипт что отправил.

Никогда не доверяйте клиенту

Все, что приходит через TriggerServerEvent, может быть запущено читером с любыми аргументами в любое время. Проверьте на сервере:

  • что игроку разрешено это делать (работа, расстояние, кулдаун),
  • что количества и имена предметов действительны,
  • что вы никогда не берете цену или награду из аргументов.
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)

Полный список находится в securing server events. Для результата, который должен вернуться к вызывающему, обратный вызов чище чем два события: смотрите server callbacks on ESX, QBCore and ox_lib.

Большие или частые события

Каждое событие — это сетевое сообщение. Не запускайте его каждый кадр и не отправляйте очень большую таблицу каждому игроку. Если консоль сервера печатает о надежных событиях переполнения, вы отправляете слишком много, слишком быстро: смотрите reliable network event overflow.

Контрольный список

Симптом Исправление
Обработчик никогда не запускается с другой стороны Используйте RegisterNetEvent, а не только AddEventHandler
TriggerClientEvent не достигает никого Передайте идентификатор игрока или -1 как второй аргумент
source равна nil или неправильному игроку local src = source как первая строка, перед любым Wait
Два скрипта реагируют на то же событие Предыдите имена префиксом ресурса: my_script:server:buy
Клиент может дать себе предметы или деньги Валидируйте на сервере и смотрите цены там
Спам событий в консоли или лаг Отправляйте меньше, реже: смотрите reliable network event overflow

Короткие ответы

Какова разница между RegisterNetEvent и AddEventHandler?

RegisterNetEvent позволяет событию запускаться с другой стороны сети. AddEventHandler только присоединяет функцию. Обработчик сетевого события нуждается в обоих, или RegisterNetEvent с функцией как второй аргумент.

Как я отправляю событие каждому игроку?

На сервере TriggerClientEvent('my_script:client:thing', -1, data). -1 означает всех подключенных клиентов.

Почему source неправильно в моем событии сервера?

source — это глобальное значение, которое содержит игрока, который запустил текущее событие. Если вы вызываете Wait и тем временем запускается другое событие, оно меняется. Скопируйте его в локальное первым: local src = source.

Скрипты без этой проблемы

Advanced BoostingУгон машин на заказ через планшет: контракты от класса D до S+, команды и живая очередь.Смотреть скрипт →Drug Dealer AppУличная торговля в виде приложения lb-phone: зоны, NPC-покупатели, уровни и вызовы полиции.Смотреть скрипт →Shop CreatorМагазин меньше чем за минуту — владельцы, сотрудники, сейфы и ограбления в комплекте.Смотреть скрипт →

Читайте также