FiveM playerConnecting i deferrals: defer, update, done wyjaśnione

Jak AddEventHandler('playerConnecting') działa z deferal: defer, Wait(0), update, done(reason), karty adaptacyjne, i dlaczego gracze zawieszają się, gdy zapomnisz wezwać done.

Chcesz uruchomić coś, zanim gracz będzie dozwolony: sprawdzenie białej listy, listę banonów, wyszukiwanie w bazie danych, kartę powitalną. Miejsce na to to zdarzenie playerConnecting, a narzędziem są deferrals. Jeśli użyte źle, gracze siedzą na ekranie łączenia na zawsze, więc szczegóły mają znaczenie.

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

Jakie są argumenty

  • name: nazwa gracza.
  • setKickReason(reason): funkcja, która odrzuca połączenie wiadomością. Istnieje dla prostych, natychmiastowych odrzuceń.
  • deferrals: obiekt z funkcjami, które pozwalają ci wstrzymać połączenie i rozmawiać z graczem podczas pracy.

Wewnątrz handlera source jest identyfikatorem połączenia gracza.

Cztery wezwania deferral

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() mówi FiveM, aby trzymał gracza, aż się zdecydujesz. Bez tego gracz jest wpuszczany, gdy tylko twój handler się zwróci, nawet jeśli zacząłeś zadanie asynchroniczne.
  2. Wait(0) jest potrzebne tuż po defer(). Wezwania do update, done lub presentCard w tym samym takcie co defer mogą nie działać.
  3. deferrals.update(message) zastępuje tekst, który gracz widzi na ekranie łączenia. Użyj go, aby pokazać postęp.
  4. deferrals.done(reason) kończy czekanie. Brak argumentu oznacza, że gracz jest akceptowany. Ciąg znaków oznacza, że są odrzuceni i widzą ten ciąg.

Wskazówka: skopiuj source do zmiennej lokalnej na samym początek. Po Wait lub żądaniu HTTP, globalny source może wskazywać na innego gracza.

Odrzucenie gracza

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(...) kończy handler w tym samym czasie, co uniemożliwia ci wywołanie done dwa razy.

Sprawdzenia asynchroniczne

Odroczenia są przeznaczone do pracy, która zajmuje czas: zapytanie bazy danych lub wywołanie HTTP. Utrzymuj połączenie odroczone i wezwij done w callbacku.

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 istnieje w obecnych kompilacjach serwera. Jeśli twój artefakt jest stary, zamiast tego pętluj po GetPlayerIdentifiers, jak powyżej. Aby uzyskać składnię zapytania, zobacz przewodnik oxmysql. Pełny przykład z rolami Discord znajduje się w Discord whitelist dla FiveM.

Typowy błąd: zapomnienie done

Gracze utkwieci na ekranie łączenia prawie zawsze oznaczają, że jedna ścieżka kodu nigdy nie osiąga 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)

Jeśli row to nil, gracz jest trzymany na zawsze. Rozwiązaniem jest 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)

Trzy inne sposoby, aby się zaciąć:

  • Błąd skryptu wewnątrz handlera przed done. Handler się zatrzymuje, a gracz czeka. Przeczytaj konsolę. Zobacz czytanie błędu skryptu.
  • Wywołanie HTTP, które nigdy nie odpowiada. Dodaj limit czasu, na przykład śledząc czas startu i kończąc wiadomością, jeśli nic nie wróciło.
  • Dwukrotne wezwanie done lub wezwanie update po done. Użyj return, aby opuścić handler po każdym zakończeniu.

Kilka handlerów naraz

Wiele zasobów słucha playerConnecting, a każdy, który wezwie defer, dodaje do czekania. Gracz jest wpuszczony tylko wtedy, gdy wszystkie się skończą. Powolny lub uszkodzony handler w jednym skrypcie trzyma wszystkich w dół, więc najpierw testuj tylko ten zasób, gdy połączenia się zawieszają.

Karty adaptacyjne

deferrals.presentCard pokazuje formularz lub ekran zasad z Adaptive Card, które jest układem JSON. Gracz może nacisnąć przycisk, a twój callback otrzymuje wynik.

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)

Utrzymuj karty w prostocie: tekst, obraz i jeden lub dwa przyciski. Callback nadal musi się zakończyć w done.

Lista kontrolna

Symptom Naprawa
Gracz utkwiłem na łączeniu Jedna ścieżka nigdy nie wezwie deferrals.done; dodaj brakujący else
update lub done nie ma efektu Wezwij Wait(0) tuż po deferrals.defer()
Zły gracz dotkniętym Najpierw zapisz local src = source
Gracz wpuszczony przed końcem sprawdzenia Zapomnisz deferrals.defer()
Odrzucenie z wiadomością deferrals.done('reason')
Zaakceptuj gracza deferrals.done() bez argumentu
Zawieszenie tylko z wieloma skryptami Handler playerConnecting innego zasobu jest powolny lub uszkodzony

Szybkie odpowiedzi

Dlaczego potrzebuję Wait(0) po deferrals.defer()?

Odroczenie jest aktywne dopiero po tym, jak silnik przesunął się o jeden takt. Wywołanie deferrals.update lub deferrals.done w tym samym takcie może być zignorowane, dlatego najpierw ustąp raz za pomocą Wait(0).

Jak odrzucić gracza?

Wywołaj deferrals.done('twój powód') z ciągiem znaków. Gracz widzi tekst i nie może wejść. Wywołanie deferrals.done() bez argumentu pozwala mu na połączenie.

Co się dzieje, jeśli nigdy nie wezwę deferrals.done?

Gracz zostaje na ekranie łączenia, dopóki połączenie nie przekroczy limitu czasu. Każda ścieżka kodu w twoim handlerze musi się zakończyć w deferrals.done.

Skrypty bez tego problemu

Tebex TemplateMotyw premium dla twojego sklepu Tebex, bez kodu — edytowany w całości z panelu Tebex.Zobacz skrypt →Mic PhoneSkładany telefon, który rozkłada się w tablet i działa też na prawdziwym telefonie gracza.Zobacz skrypt →

Czytaj dalej