Wróć do bloga
Tutorial8 min czytania

System gastronomii i restauracji FiveM: Skryptowanie kuchni

Prowadź restauracje w FiveM. Przepisy rzemieślnicze, interfejsy kasy, zlecenia dostaw i najlepsze skrypty gastronomiczne, które ożywią scenę kulinarną Twojego miasta.

Agency Scripts

Założyciel i główny deweloper w Agency Scripts

Dlaczego systemy restauracji wzbogacają Roleplay

Systemy jedzenia i restauracji należą do najbardziej angażujących społecznie funkcji, jakie możesz dodać do serwera roleplay FiveM. W przeciwieństwie do aktywności solo, takich jak górnictwo czy wędkarstwo, restauracje tworzą naturalne miejsca spotkań, gdzie gracze wspólnie gotują, serwują i jedzą. Dobrze zaprojektowany system restauracji daje graczom cywilnym wartościowe zajęcie niezwiązane z przestępczością, daje powód do regularnego odwiedzania lokacji i wprowadza odpływ gotówki z gospodarki poprzez zakup składników. Restauracje tworzą również łańcuchy dostaw w połączeniu ze skryptami rolnictwa lub polowania: jeden gracz uprawia pomidory, drugi je dostarcza, a trzeci przygotowuje z nich burgera. Ta połączona pętla rozgrywki odróżnia tętniący życiem serwer od takiego, na którym gracze w kółko grindują tę samą trasę. Same przedmioty spożywcze mogą zapewniać tymczasowe buffy do zdrowia, staminy lub regeneracji pancerza, dzięki czemu restauracje są przydatne dla każdego gracza na serwerze, niezależnie od jego pracy czy frakcji.

Konfiguracja restauracji i projektowanie menu

Zacznij od zdefiniowania lokalizacji restauracji, ich menu oraz składników wymaganych dla każdego dania. Konfiguracja oparta na danych pozwala właścicielom serwerów dodawać nowe restauracje bez modyfikowania logiki bazowej. Każda restauracja powinna mieć unikalny charakter dzięki pozycjom w menu, rekwizytom wnętrza i uniformom personelu NPC. Ustrukturyzuj konfigurację tak, aby każda pozycja menu zawierała wymagane składniki, czas craftingu, cenę sprzedaży i wszelkie efekty buffów przyznawane konsumentowi. Dzięki temu balansowanie ekonomii jedzenia sprowadza się do zmiany wartości w konfiguracji zamiast przepisywania kodu:

Config.Restaurants = {
    ['burgershot'] = {
        label = 'Burger Shot',
        blip = { sprite = 106, color = 1, scale = 0.8 },
        zones = {
            counter = vector3(-1196.42, -894.04, 13.98),
            kitchen = vector3(-1199.71, -897.12, 13.98),
            storage = vector3(-1202.84, -899.35, 13.98),
        },
        menu = {
            {
                item = 'bleeder_burger',
                label = 'Bleeder Burger',
                price = 25,
                ingredients = {
                    { item = 'raw_patty', amount = 2 },
                    { item = 'burger_bun', amount = 1 },
                    { item = 'lettuce', amount = 1 },
                    { item = 'cheese_slice', amount = 2 },
                },
                craftTime = 8000,
                buffs = {
                    hunger = 45,
                    thirst = 5,
                    armor_regen = { amount = 2, duration = 120 }
                }
            },
            {
                item = 'heartstopper',
                label = 'Heart Stopper',
                price = 40,
                ingredients = {
                    { item = 'raw_patty', amount = 3 },
                    { item = 'burger_bun', amount = 1 },
                    { item = 'bacon', amount = 4 },
                    { item = 'cheese_slice', amount = 3 },
                    { item = 'onion_ring', amount = 2 },
                },
                craftTime = 12000,
                buffs = {
                    hunger = 80,
                    thirst = -10,
                    stamina_boost = { amount = 15, duration = 180 }
                }
            },
        },
        requiredJob = 'burgershot',
    },
}

Ten zones tabela dzieli restaurację na strefy funkcjonalne. Strefa lady to miejsce, w którym klienci składają zamówienia i odbierają jedzenie. Strefa kuchni jest dostępna tylko dla pracowników, którzy używają jej do przygotowywania dań. Strefa magazynu to miejsce, w którym składowane i zarządzane są składniki. Ten podział przestrzenny wymusza realistyczny workflow, w którym kucharz nie może obsługiwać klientów bezpośrednio od kuchenki, tworząc naturalną współpracę między obsługą sali a personelem kuchni.

Mechanika gotowania i przepływ craftingu

Gotowanie powinno być interaktywne, ale nie monotonne. Gdy gracz zaczyna przygotowywać danie, sprawdź, czy w magazynie restauracji znajdują się wszystkie wymagane składniki, odejmij je i uruchom pasek postępu lub sekwencję minigry. Zwykłe paski postępu sprawdzają się przy prostych potrawach, ale dodanie minigier skill-check dla dań premium daje poczucie wprawy i zapobiega exploitom AFK. Animacja gotowania powinna wykorzystywać odpowiednie natywne funkcje GTA, takie jak scenariusze grilla czy blatu kuchennego, aby wizualnie pokazać pobliskim graczom, co robi postać:

function StartCooking(restaurantId, menuIndex)
    local restaurant = Config.Restaurants[restaurantId]
    local recipe = restaurant.menu[menuIndex]

    -- Check ingredients in storage
    local hasAll = lib.callback.await('restaurant:checkIngredients', false,
        restaurantId, recipe.ingredients)

    if not hasAll then
        lib.notify({ description = 'Missing ingredients!', type = 'error' })
        return
    end

    -- Lock player into cooking animation
    local playerPed = PlayerPedId()
    local animDict = 'mini@repair'
    lib.requestAnimDict(animDict)
    TaskPlayAnim(playerPed, animDict, 'fixing_a_player', 8.0, -8.0,
        -1, 1, 0, false, false, false)

    -- Cooking progress with skill check
    local success = lib.skillCheck(
        {'easy', 'easy', 'medium'},
        {'w', 'a', 's', 'd'}
    )

    ClearPedTasks(playerPed)

    if success then
        TriggerServerEvent('restaurant:finishCooking', restaurantId, menuIndex)
        lib.notify({ description = 'Cooked: ' .. recipe.label, type = 'success' })
    else
        -- Failed cooking wastes some ingredients
        TriggerServerEvent('restaurant:failedCooking', restaurantId, menuIndex)
        lib.notify({ description = 'Burnt the food! Some ingredients wasted.',
            type = 'error' })
    end
end

Mechanika skill checku dodaje do gotowania element ryzyka i nagrody. Nieudane próby powinny marnować tylko część składników, a nie wszystkie, dzięki czemu nowi gracze nie są surowo karani, a jednocześnie mają motywację do doskonalenia umiejętności. Możesz skalować poziom trudności na podstawie wartości dania, sprawiając, że tanie pozycje są łatwe do przygotowania, a drogie dania popisowe wymagają prawdziwej uwagi. Tworzy to naturalną progresję, w której nowi pracownicy zaczynają od prostych dań i stopniowo dochodzą do menu premium.

Łańcuch dostaw składników

Restauracje potrzebują stałego dopływu składników, a sposób ich pozyskiwania przez graczy definiuje istotną część pętli rozgrywki. Istnieją trzy powszechne podejścia: zakup u hurtowych sprzedawców NPC, pozyskiwanie z farm prowadzonych przez graczy lub polowań oraz model hybrydowy. Podejście hurtowe jest prostsze do wdrożenia i gwarantuje dostępność, ale brakuje w nim interakcji między graczami. Pozyskiwanie od innych graczy tworzy bogate relacje ekonomiczne, ale niesie ryzyko przestojów restauracji, gdy dostawcy są offline. Model hybrydowy sprawdza się najlepiej: podstawowe składniki, takie jak bułki, olej i przyprawy, pochodzą od NPC, natomiast składniki premium, takie jak świeże mięso, warzywa ekologiczne i specjalne sosy, muszą być pozyskiwane z aktywności graczy. Zapewnia to restauracjom ciągłość działania na podstawowym poziomie, nagradzając jednocześnie pełny łańcuch dostaw, gdy wszystkie elementy ze sobą współgrają. Wdróż system magazynowy dla każdej restauracji, który śledzi ilości składników i pozwala menedżerom składać zamówienia hurtowe potrącane z konta bankowego restauracji:

RegisterNetEvent('restaurant:orderSupplies', function(restaurantId, order)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)

    -- Verify player is manager rank
    if Player.PlayerData.job.name ~= restaurantId then return end
    if Player.PlayerData.job.grade.level < 3 then
        TriggerClientEvent('QBCore:Notify', src,
            'Only managers can order supplies', 'error')
        return
    end

    -- Calculate total cost
    local totalCost = 0
    for _, item in ipairs(order) do
        local wholesalePrice = Config.WholesalePrices[item.name]
        if wholesalePrice then
            totalCost = totalCost + (wholesalePrice * item.amount)
        end
    end

    -- Deduct from restaurant bank account
    local balance = exports['qb-management']:GetAccount(restaurantId)
    if balance < totalCost then
        TriggerClientEvent('QBCore:Notify', src,
            'Insufficient restaurant funds', 'error')
        return
    end

    exports['qb-management']:RemoveMoney(restaurantId, totalCost)

    -- Add items to restaurant storage
    for _, item in ipairs(order) do
        AddToRestaurantStorage(restaurantId, item.name, item.amount)
    end

    TriggerClientEvent('QBCore:Notify', src,
        string.format('Order placed! $%d deducted', totalCost), 'success')
end)

Proces zamawiania i obsługi klienta

Obsługa klienta powinna być płynna niezależnie od tego, czy zamawia on u kasjera NPC, czy u pracownika będącego graczem. Gdy klient podchodzi do strefy lady, wyświetl menu UI pokazujące dostępne pozycje wraz z ich cenami i opisami aktywnych buffów. W przypadku restauracji prowadzonych przez graczy zamówienie powinno trafić na ekran kuchenny, aby kucharze widzieli nowe zamówienia w czasie rzeczywistym. Wdróż system kolejki zamówień śledzący każde zamówienie od złożenia, przez przygotowanie, aż po wydanie. Tworzy to symulację lokalu fast food, w którym w godzinach szczytu kuchnia ma pełne ręce roboty, co dodaje autentycznej presji do pracy kucharza. Pokazuj klientom status zamówienia za pomocą prostych wskaźników: złożone, w przygotowaniu, gotowe do odbioru. Gdy jedzenie jest gotowe, powiadom klienta i pozwól mu odebrać je przy ladzie. Aby zapewnić wrażenia na najwyższym poziomie, pozwól kelnerom dostarczać jedzenie bezpośrednio do stolików, dzięki czemu mogą zdobywać napiwki zależne od szybkości obsługi.

Buffy z jedzenia i efekty konsumpcji

Przedmioty spożywcze powinny zapewniać wymierne korzyści w rozgrywce, wykraczające poza zwykłe zaspokajanie głodu. Wdróż system buffów, w którym różne potrawy dają tymczasowe modyfikacje statystyk, zachęcające graczy do strategicznego jedzenia przed różnymi aktywnościami. Posiłek zwiększający staminę przed pościgiem pieszym, danie regenerujące pancerz przed napadem lub posiłek przywracający zdrowie w czasie po walce sprawiają, że jedzenie staje się naprawdę wartościowe w gospodarce serwera. Śledź aktywne buffy każdego gracza i wyświetlaj je w HUD, aby gracze wiedzieli, jakie efekty aktualnie posiadają i kiedy one wygasają:

local activeBuffs = {}

function ApplyFoodBuff(buffType, amount, duration)
    -- Remove existing buff of same type
    if activeBuffs[buffType] then
        activeBuffs[buffType].active = false
    end

    activeBuffs[buffType] = {
        amount = amount,
        endTime = GetGameTimer() + (duration * 1000),
        active = true
    }

    -- Send buff data to HUD
    SendNUIMessage({
        action = 'addBuff',
        buffType = buffType,
        amount = amount,
        duration = duration
    })

    -- Create buff application thread
    CreateThread(function()
        local buff = activeBuffs[buffType]
        while buff.active and GetGameTimer() < buff.endTime do
            if buffType == 'armor_regen' then
                local current = GetPedArmour(PlayerPedId())
                if current < 100 then
                    SetPedArmour(PlayerPedId(), math.min(100, current + amount))
                end
            elseif buffType == 'stamina_boost' then
                RestorePlayerStamina(PlayerId(), amount * 0.1)
            elseif buffType == 'health_regen' then
                local current = GetEntityHealth(PlayerPedId())
                local max = GetEntityMaxHealth(PlayerPedId())
                if current < max then
                    SetEntityHealth(PlayerPedId(), math.min(max, current + amount))
                end
            end
            Wait(1000)
        end

        activeBuffs[buffType] = nil
        SendNUIMessage({ action = 'removeBuff', buffType = buffType })
    end)
end

Starannie zbalansuj buffy z jedzenia, aby wzbogacały rozgrywkę, nie stając się obowiązkowe. Buffy powinny dawać zauważalną przewagę, ale nie na tyle dużą, by gracze czuli się zmuszeni do jedzenia przed każdą aktywnością. Utrzymuj umiarkowany czas trwania, od dwóch do pięciu minut, aby ekonomia buffów pozostała aktywna. Rozważ dodanie malejących korzyści (diminishing returns), jeśli to samo jedzenie jest spożywane wielokrotnie w krótkim czasie, aby zapobiec exploitom ze stackowaniem buffów.

Zarządzanie pracownikami i lista płac

System restauracji wymaga odpowiedniej hierarchii z różnymi rolami i uprawnieniami. Zdefiniuj co najmniej trzy poziomy: pracownik, kierownik i menedżer. Pracownicy mogą gotować posiłki i obsługiwać klientów. Kierownicy mogą robić wszystko to, co pracownicy, a dodatkowo zarządzać magazynem składników i zatwierdzać zamówienia. Menedżerowie mają pełną kontrolę, w tym zatrudnianie i zwalnianie pracowników, ustalanie cen oraz dostęp do konta bankowego restauracji. Powiąż wypłaty z przychodami restauracji, aby pracownicy otrzymywali pensję podstawową powiększoną o prowizję od przygotowanych lub podanych osobiście dań. Zachęca to do aktywnego udziału zamiast nabijania godzin na AFK. Śledź statystyki poszczególnych pracowników, takie jak przygotowane potrawy, obsłużeni klienci i wygenerowany przychód, aby tworzyć rankingi i oceny okresowe, które dodają głębi pracy.

Optymalizacja wydajności

Systemy restauracji obejmują wiele stref interakcji, pedów NPC, obiektów prop oraz śledzenie zamówień w czasie rzeczywistym, co może obciążać wydajność klienta, jeśli nie zostanie starannie zoptymalizowane. Używaj interakcji przez target system zamiast sprawdzania odległości opartego na markerach, aby wyeliminować obliczenia współrzędnych w każdej klatce. Renderuj propy wnętrza restauracji tylko wtedy, gdy gracze znajdują się wewnątrz budynku, używając IsPlayerInScope sprawdzenia. Grupuj komunikaty NUI dla wyświetlania kolejki zamówień, zamiast wysyłać pojedyncze aktualizacje przy każdej zmianie zamówienia. Po stronie serwera buforuj stany magazynowe restauracji w pamięci i zapisuj je do bazy danych w określonych odstępach czasu, zamiast po każdej zmianie składnika. Gdy jednocześnie działa wiele restauracji, rozłóż w czasie ich cykle synchronizacji z bazą danych, aby uniknąć nagłych skoków zapisu. Usuwaj wszystkie blipy, propy i encje NPC po zatrzymaniu resource, aby zapobiec powstawaniu osieroconych encji, które kumulują się przy kolejnych restartach resource. Przetestuj swój system pod obciążeniem, przy wielu działających restauracjach, kilku kucharzach pracujących jednocześnie i stałym napływie zamówień od klientów, aby zidentyfikować wąskie gardła, zanim twoi gracze zrobią to za ciebie.

Gotowy, aby zacząć?

Kup skrypty w naszym sklepie lub dołącz do Discorda, aby uzyskać wsparcie, aktualizacje i sprawdzić nadchodzące nowości.