Система питания и ресторанов FiveM: скриптинг кухонь
Запускайте игровые рестораны в FiveM. Рецепты крафта, интерфейсы кассиров, доставки и лучшие скрипты еды, чтобы оживить гастрономическую сцену вашего города.
Agency Scripts
Основатель и ведущий разработчик Agency Scripts
Почему системы ресторанов улучшают ролевую игру
Системы еды и ресторанов , одни из самых социальных функций, которые можно добавить на FiveM ролевой сервер. В отличие от одиночных занятий, таких как добыча или рыбалка, рестораны создают органичные точки сбора, где игроки готовят, обслуживают и едят вместе. Хорошо продуманная ресторанная система даёт гражданским игрокам значимую работу без криминала, мотивирует персонажей регулярно посещать локации и вводит экономический слив через покупку ингредиентов. Рестораны также создают цепочки поставок в сочетании с фермерскими или охотничьими скриптами: один игрок выращивает помидоры, другой доставляет их, третий готовит из них бургер. Этот взаимосвязанный игровой цикл отличает живой сервер от того, где игроки просто повторяют один и тот же маршрут. Сами продукты могут давать временные бонусы к здоровью, выносливости или регенерации брони, делая рестораны актуальными для каждого игрока на сервере независимо от его работы или фракции.
Конфигурация ресторана и дизайн меню
Начните с определения ваших ресторанных локаций, их меню и ингредиентов, необходимых для каждого блюда. Конфигурация на основе данных позволяет владельцам серверов добавлять новые рестораны без изменения основной логики. Каждый ресторан должен иметь уникальную идентичность через своё меню, интерьерные предметы и униформу NPC-персонала. Структурируйте конфигурацию так, чтобы каждый пункт меню содержал список необходимых ингредиентов, время приготовления, цену продажи и любые баффы, которые он даёт потребителю. Такой подход означает, что балансировка вашей пищевой экономики сводится к настройке значений конфигурации, а не переписыванию кода:
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',
},
}
Параметр zones таблица разделяет ресторан на функциональные зоны. Зона кассы , это место, где клиенты делают заказы и получают еду. Кухонная зона ограничена для сотрудников, которые используют её для приготовления блюд. Зона хранения , место для запасов ингредиентов и их управления. Такое пространственное разделение обеспечивает реалистичный рабочий процесс, где повар не может напрямую обслуживать клиентов с плиты, создавая естественное взаимодействие между фронт-офисом и кухней.
Механика готовки и процесс крафта
Процесс приготовления должен быть интерактивным, но не утомительным. Когда игрок начинает готовить блюдо, проверьте, что все необходимые ингредиенты есть на складе ресторана, вычтите их и запустите прогресс-бар или мини-игру. Простые прогресс-бары подходят для базовых блюд, но добавление мини-игр с проверкой навыков для премиальных блюд создаёт ощущение мастерства и предотвращает AFK-эксплойты. Анимация готовки должна использовать соответствующие GTA natives, такие как барбекю или кухонная стойка, чтобы визуально показывать, что делает игрок, окружающим:
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
Механика проверки навыков добавляет элемент риска и награды в кулинарию. Неудачные попытки должны тратить часть ингредиентов, а не все, чтобы новые игроки не были слишком наказаны, но при этом стимулировать улучшение навыков. Вы можете масштабировать сложность в зависимости от ценности блюда, делая дешёвые блюда простыми, а дорогие фирменные требующими внимания. Это создаёт естественную прогрессию, где новые сотрудники начинают с простых блюд и постепенно переходят к премиальному меню.
Цепочка поставок ингредиентов
Ресторанам нужен постоянный поток ингредиентов, и способ их получения определяет основную часть игрового цикла. Существуют три распространённых подхода: покупка у оптовых NPC-продавцов, получение с ферм игроков или охота, и гибридная модель. Оптовый подход проще в реализации и гарантирует наличие, но лишён взаимодействия с игроками. Получение от других игроков создаёт богатые экономические связи, но рискует простоями ресторана при оффлайне поставщиков. Гибридная модель работает лучше всего: базовые ингредиенты , булочки, масло, специи , берутся у NPC, а премиум-ингредиенты , свежее мясо, органические овощи, особые соусы , добываются игроками. Это обеспечивает работу ресторанов на базовом уровне и вознаграждает полную цепочку поставок при её полном соединении. Реализуйте систему хранения для каждого ресторана, которая отслеживает количество ингредиентов и позволяет менеджерам делать оптовые заказы, списывающие средства с банковского счёта ресторана:
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)
Процесс заказа и обслуживания клиента
Опыт клиента должен быть бесшовным, независимо от того, заказывает ли он у NPC-кассира или у игрока-сотрудника. Когда клиент подходит к зоне кассы, отображайте меню UI с доступными товарами, их ценами и описаниями активных баффов. Для ресторанов с игроками-персоналом заказ должен поступать на кухонный дисплей, чтобы повара видели входящие заказы в реальном времени. Реализуйте систему очереди заказов, которая отслеживает каждый заказ от размещения до приготовления и доставки. Это создаёт симуляцию фастфуда, где кухня загружается в часы пик, добавляя аутентичное давление на роль повара. Показывайте статус заказа клиентам с простыми индикаторами: заказан, готовится, готов к выдаче. Когда еда готова, уведомляйте клиента и позволяйте ему забрать её на кассе. Для премиального опыта разрешите официантам-игрокам доставлять еду напрямую к сидящим за столами клиентам, зарабатывая чаевые в зависимости от скорости обслуживания.
Бонусы от еды и эффекты потребления
Пищевые предметы должны давать значимые игровые преимущества, выходящие за рамки простого утоления голода. Реализуйте систему бонусов, где разные блюда дают временные изменения характеристик, побуждая игроков есть стратегически перед активностями. Приём пищи, повышающей выносливость перед погоней пешком, блюдо с регенерацией брони перед ограблением или еда с восстановлением здоровья после боя делают продукты действительно ценными в экономике сервера. Отслеживайте активные бонусы для каждого игрока и отображайте их в HUD, чтобы игроки знали, какие эффекты у них сейчас и когда они истекают:
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
Балансируйте баффы от еды так, чтобы они улучшали игровой процесс, но не становились обязательными. Баффы должны давать заметное преимущество, но не быть настолько сильными, чтобы игроки чувствовали необходимость есть перед каждым действием. Держите длительность умеренной, от двух до пяти минут, чтобы экономика баффов оставалась активной. Рассмотрите возможность добавления убывающей отдачи, если одна и та же еда употребляется несколько раз за короткий период, чтобы предотвратить эксплойты с накоплением баффов.
Управление сотрудниками и расчёт заработной платы
Система ресторанов требует правильной иерархии с разными ролями и правами. Определите минимум три уровня: работник, супервайзер и менеджер. Работники готовят блюда и обслуживают клиентов. Супервайзеры делают всё, что работники, плюс управляют складом ингредиентов и утверждают заказы. Менеджеры имеют полный контроль, включая найм и увольнение сотрудников, установку цен и доступ к банковскому счету ресторана. Свяжите зарплату с доходами ресторана, чтобы сотрудники получали базовую ставку плюс комиссию с приготовленных или обслуженных ими позиций. Это стимулирует активное участие, а не простое присутствие. Отслеживайте индивидуальную статистику сотрудников: количество приготовленных блюд, обслуженных клиентов и сгенерированный доход, чтобы создавать таблицы лидеров и обзоры эффективности, добавляющие глубину работе.
Оптимизация производительности
Системы ресторанов включают несколько зон взаимодействия, NPC-персонажей, объекты-пропы и отслеживание заказов в реальном времени, что может нагрузить клиент при плохой оптимизации. Используйте взаимодействия через target system вместо маркеров расстояния, чтобы исключить вычисления координат каждый кадр. Отображайте интерьерные пропы ресторана только когда игроки внутри здания, используя IsPlayerInScope проверки. Группируйте ваши NUI-сообщения для отображения очереди заказов, а не отправляйте отдельные обновления при каждом изменении заказа. На стороне сервера кэшируйте инвентари ресторанов в памяти и записывайте в базу данных по таймеру, а не после каждого изменения ингредиента. При одновременной работе нескольких ресторанов распределяйте циклы синхронизации с базой, чтобы избежать пиков записи. Удаляйте все блики, объекты и NPC при остановке ресурса, чтобы предотвратить накопление «осиротевших» сущностей при перезапусках ресурса. Тестируйте систему под нагрузкой с несколькими ресторанами, несколькими поварами и постоянным потоком заказов, чтобы выявить узкие места до того, как их найдут ваши игроки.