FiveM playerConnecting и deferrals: defer, update, done объяснено

Как работает AddEventHandler('playerConnecting') с deferrals: defer, Wait(0), update, done(reason), адаптивные карточки и почему игроки зависают, когда вы забываете вызвать done.

Вы хотите выполнить что-то перед тем, как игрок будет допущен: проверку белого списка, список забанов, поиск в базе данных, приветственную карточку. Место для этого — событие playerConnecting, а инструмент — deferrals. Неправильное использование приводит к тому, что игроки сидят на экране подключения вечно, поэтому детали имеют значение.

lua
AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    -- runs when a player starts connecting
end)

Что означают аргументы

  • name: имя игрока.
  • setKickReason(reason): функция, которая отклоняет соединение с сообщением. Она существует для простых, немедленных отклонений.
  • deferrals: объект с функциями, которые позволяют вам приостановить соединение и поговорить с игроком во время работы.

Внутри обработчика source — это id подключения игрока.

Четыре вызова отсрочки

lua
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)
  1. deferrals.defer() сообщает FiveM удерживать игрока до тех пор, пока вы не решите. Без этого игрок допускается, как только ваш обработчик возвращается, даже если вы запустили асинхронную задачу.
  2. Wait(0) необходимо вызвать сразу же после defer(). Вызовы update, done или presentCard в одном и том же тике, что и defer, могут не работать.
  3. deferrals.update(message) заменяет текст, который видит игрок на экране подключения. Используйте его для показа прогресса.
  4. deferrals.done(reason) завершает ожидание. Без аргумента означает, что игрок принят. Строка означает, что они отклонены и видят эту строку.

Совет: скопируйте source в локальную переменную в самом начале. После Wait или HTTP запроса глобальный source может указывать на другого игрока.

Отклонение игрока

lua
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 в обратном вызове.

lua
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.

lua
-- 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:

lua
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. Игрок может нажать кнопку, и ваш обратный вызов получает результат.

lua
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.

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

Tebex TemplateПремиальная тема для вашего магазина Tebex без кода, полностью настраиваемая из панели Tebex.Смотреть скрипт →Mic PhoneСкладной телефон, который раскладывается в планшет и работает и на настоящем телефоне игрока.Смотреть скрипт →

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