FiveM playerConnecting и deferrals: defer, update, done объяснено
Как работает AddEventHandler('playerConnecting') с deferrals: defer, Wait(0), update, done(reason), адаптивные карточки и почему игроки зависают, когда вы забываете вызвать done.
Вы хотите выполнить что-то перед тем, как игрок будет допущен: проверку белого списка, список забанов, поиск в базе данных, приветственную карточку. Место для этого — событие playerConnecting, а инструмент — deferrals. Неправильное использование приводит к тому, что игроки сидят на экране подключения вечно, поэтому детали имеют значение.
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
-- runs when a player starts connecting
end)Что означают аргументы
name: имя игрока.setKickReason(reason): функция, которая отклоняет соединение с сообщением. Она существует для простых, немедленных отклонений.deferrals: объект с функциями, которые позволяют вам приостановить соединение и поговорить с игроком во время работы.
Внутри обработчика source — это id подключения игрока.
Четыре вызова отсрочки
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local src = source -- save it now
deferrals.defer() -- 1. pause the connection
Wait(0) -- 2. let the engine register it
deferrals.update('Checking your account...') -- 3. show a message
-- do your checks here
deferrals.done() -- 4. let the player in
end)deferrals.defer()сообщает FiveM удерживать игрока до тех пор, пока вы не решите. Без этого игрок допускается, как только ваш обработчик возвращается, даже если вы запустили асинхронную задачу.Wait(0)необходимо вызвать сразу же послеdefer(). Вызовыupdate,doneилиpresentCardв одном и том же тике, что иdefer, могут не работать.deferrals.update(message)заменяет текст, который видит игрок на экране подключения. Используйте его для показа прогресса.deferrals.done(reason)завершает ожидание. Без аргумента означает, что игрок принят. Строка означает, что они отклонены и видят эту строку.
Совет: скопируйте
sourceв локальную переменную в самом начале. ПослеWaitили HTTP запроса глобальныйsourceможет указывать на другого игрока.
Отклонение игрока
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local src = source
deferrals.defer()
Wait(0)
local license
for _, id in ipairs(GetPlayerIdentifiers(src)) do
if id:sub(1, 8) == 'license:' then license = id break end
end
if not license then
return deferrals.done('No Rockstar license found. Restart the game and try again.')
end
deferrals.done()
end)return deferrals.done(...) завершает обработчик в то же время, что препятствует двойному вызову done.
Асинхронные проверки
Отсрочки предназначены для работы, которая требует времени: запрос базы данных или вызов HTTP. Держите соединение отложенным и вызывайте done в обратном вызове.
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
local src = source
deferrals.defer()
Wait(0)
deferrals.update('Looking you up...')
local license = GetPlayerIdentifierByType(src, 'license')
MySQL.scalar('SELECT 1 FROM whitelist WHERE license = ?', { license }, function(found)
if found then
deferrals.done()
else
deferrals.done('You are not whitelisted.')
end
end)
end)GetPlayerIdentifierByType существует в текущих сборках сервера. Если ваш артефакт старый, переберите GetPlayerIdentifiers вместо этого, как выше. Смотрите руководство oxmysql для синтаксиса запроса. Полный пример с ролями Discord находится в Discord белый список для FiveM.
Типичная ошибка: забыть done
Игроки, застрявшие на экране подключения, почти всегда означают, что один путь кода никогда не достигает deferrals.done.
-- Bad: nothing happens when the query returns no row
MySQL.single('SELECT * FROM bans WHERE license = ?', { license }, function(row)
if row then
deferrals.done('You are banned.')
end
end)Если row равен nil, игрок удерживается вечно. Исправление — это else:
MySQL.single('SELECT * FROM bans WHERE license = ?', { license }, function(row)
if row then
deferrals.done('You are banned.')
else
deferrals.done()
end
end)Три других способа застрять:
- Ошибка скрипта внутри обработчика перед
done. Обработчик останавливается и игрок ждет. Прочитайте консоль. Смотрите чтение ошибки скрипта. - Вызов HTTP, который никогда не отвечает. Добавьте тайм-аут, например отслеживая время начала и завершая сообщением, если ничего не пришло.
- Вызов
doneдважды или вызовupdateпослеdone. Используйтеreturnчтобы покинуть обработчик после каждого завершения.
Несколько обработчиков одновременно
Множество ресурсов слушают playerConnecting, и каждый, который вызывает defer, добавляет к ожиданию. Игрок допускается только после того, как все закончат. Медленный или сломанный обработчик в одном скрипте держит всех в напряжении, поэтому при зависании соединений сначала проверьте только этот ресурс.
Адаптивные карточки
deferrals.presentCard показывает форму или экран правил из адаптивной карточки, которая представляет собой макет JSON. Игрок может нажать кнопку, и ваш обратный вызов получает результат.
local card = {
type = 'AdaptiveCard',
version = '1.3',
body = {
{ type = 'TextBlock', text = 'Server rules', weight = 'Bolder', size = 'Large' },
{ type = 'TextBlock', text = 'Be respectful and no cheating.', wrap = true },
},
actions = {
{ type = 'Action.Submit', title = 'I agree', data = { accepted = true } },
},
}
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
deferrals.defer()
Wait(0)
deferrals.presentCard(json.encode(card), function(data)
if data and data.accepted then
deferrals.done()
else
deferrals.done('You must accept the rules.')
end
end)
end)Делайте карточки простыми: текст, изображение и одну или две кнопки. Обратный вызов все еще должен заканчиваться на done.
Контрольный список
| Симптом | Исправление |
|---|---|
| Игрок застрял на подключении | Один путь никогда не вызывает deferrals.done; добавьте отсутствующий else |
update или done не имеет эффекта |
Вызовите Wait(0) сразу после deferrals.defer() |
| Неправильный игрок затронут | Сохраните local src = source первым |
| Игрок пустить раньше, чем проверка закончится | Вы забыли deferrals.defer() |
| Отклонить с сообщением | deferrals.done('reason') |
| Принять игрока | deferrals.done() без аргумента |
| Зависание только со многими скриптами | Обработчик playerConnecting другого ресурса медленный или сломан |
Короткие ответы
Почему мне нужен Wait(0) после deferrals.defer()?
Отсрочка становится активной только после того, как движок перейдет на следующий тик. Вызов deferrals.update или deferrals.done в одном и том же тике может быть проигнорирован, поэтому сначала выдержите один раз с Wait(0).
Как мне отклонить игрока?
Вызовите deferrals.done('your reason') со строкой. Игрок видит текст и не может войти. Вызов deferrals.done() без аргумента позволяет ему подключиться.
Что происходит, если я никогда не вызываю deferrals.done?
Игрок остается на экране подключения до истечения времени подключения. Каждый путь кода в вашем обработчике должен завершиться deferrals.done.

