MAATRIX GAMES / Блог / FiveM: гараж и система штрафстоянки

FiveM: гараж и система штрафстоянки

MAATRIX GAMES

Игрок купил тачку, залил стволы бампером о столб, вышел из игры — а при заходе машина стоит целая и с полным баком, как будто ничего не было. Или того хуже: припарковался криво у входа в магазин, машину эвакуировали, а забрать её неоткуда, потому что штрафстоянки на сервере просто нет. Обе ситуации убивают ощущение RP-мира быстрее, чем баг с текстурами. В этой статье разберём, как поднять связку гараж + штрафстоянка с реальным сохранением состояния транспорта и логикой эвакуации, которая работает, а не просто красиво выглядит в демо-видео мода.

Почему стандартного спавна тачки недостаточно

В большинстве стартовых фреймворков (голый ESX или QBCore без доп. ресурсов) машина просто спавнится заново при команде /car или через NPC-дилера — без памяти о повреждениях, топливе и апгрейдах. Для RP это плохо по трём причинам.

Во-первых, пропадает цена ошибки: разбил тачку — через пять минут выехал на такой же целой. Во-вторых, ломается экономика вокруг автосервисов и топлива — если статьи про систему топлива и автосервисов не привязаны к реальному состоянию машины, они бессмысленны. В-третьих, нет наказания за неправильную парковку — игроки бросают тачки где попало, сервер захламляется entity, FPS в людных зонах проседает.

Рабочая система гаража решает всё это: машина хранится в базе с полем состояния (fuel, engine health, body health, mods), выезжает из гаража ровно в том виде, в каком её туда поставили, и пропадает из мира, когда не используется — это разгружает клиента и синхронизацию.

Выбор ресурса для гаража

На 2026 год для ESX и QBCore есть готовые ресурсы, которые закрывают 90% задачи без написания своего скрипта с нуля:

  • qb-garages (для QBCore) — гараж по классам (машина/мото/лодка/самолёт), поддержка нескольких гаражей на карте, депо для эвакуированных.
  • esx_advancedgarage или cd_garage (для ESX) — похожая логика, отдельные точки гаража с привязкой к job/gang для RP-фракций.
  • wasabi_carlock или встроенный lock-скрипт фреймворка — нужен рядом, чтобы машина закрывалась и не угонялась пока стоит на улице.

Если у вас кастомный фреймворк или сильно переделанный ESX, проще форкнуть один из этих ресурсов и подогнать под свои таблицы БД, чем писать с нуля — там уже решены edge-кейсы вроде дублирования плейта или спавна машины внутри объекта.

Установка стандартная: скачиваете ресурс, кладёте в resources/[qb]/qb-garages (или [esx]/esx_advancedgarage), прописываете в server.cfg:

ensure qb-garages

и проверяете зависимости — обычно это oxmysql (или mysql-async для старых сборок), qb-core/es_extended, иногда PolyZone или ox_lib для UI зон.

Поднять сервер FiveM (GTA V) за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Сохранение состояния транспорта в базе

Ключевая таблица — player_vehicles (в QBCore) или owned_vehicles (в ESX). Минимальная структура для гаража с состоянием:

CREATE TABLE IF NOT EXISTS `player_vehicles` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `citizenid` VARCHAR(50) NOT NULL,
  `vehicle` VARCHAR(50) NOT NULL,
  `hash` VARCHAR(50) NOT NULL,
  `mods` LONGTEXT,
  `plate` VARCHAR(15) NOT NULL,
  `fakeplate` VARCHAR(15) DEFAULT NULL,
  `garage` VARCHAR(50) DEFAULT 'pillboxgarage',
  `fuel` INT DEFAULT 100,
  `engine` FLOAT DEFAULT 1000,
  `body` FLOAT DEFAULT 1000,
  `state` INT DEFAULT 1,
  `depotprice` INT DEFAULT 0,
  `drivingdistance` INT DEFAULT NULL,
  `status` LONGTEXT DEFAULT NULL
);

Поле state — это как раз статус машины: 0 (в гараже/despawned), 1 (снаружи, заспавнена), 2 (на штрафстоянке). Поля fuel, engine, body сохраняются при каждом заезде в гараж (событие vehicleSaved или аналог) и подтягиваются обратно при выезде через SetVehicleFuelLevel, SetVehicleEngineHealth, SetVehicleBodyHealth.

Частая грабля: если сохранение вешать только на клик "поставить в гараж", а не на периодический автосейв, при крашах клиента или дисконнекте без выхода из машины состояние теряется и откатывается к последнему ручному сохранению. Добавьте таймер автосейва раз в 2-5 минут для активных машин игроков — это недорого по нагрузке на БД (один UPDATE по plate), зато спасает от жалоб "машина скинулась в прошлое состояние".

Логика эвакуации: где и когда забирают машину

Эвакуация должна быть событийной, а не по таймеру "просто так убрать лишние entity". Рабочие триггеры:

  1. Неправильная парковка — зона (через PolyZone или ox_lib points) у входов в важные локации (банк, магазины, спавн-точки), где машина, оставленная дольше N минут без игрока рядом, помечается на эвакуацию.
  2. Штраф от полиции/госслужбы — job-скрипт для полицейских RP-фракций с командой /impound [plate], переводящей машину в state = 2 и записывающей depotprice.
  3. Долг по штрафам ГИБДД — если у игрока накопились неоплаченные штрафы, машина при следующей встрече с патрулём или на чекпоинте эвакуируется автоматически. Логика штрафов и их накопления разобрана отдельно в статье про систему штрафов ГИБДД для RP-сервера — штрафстоянка логично работает как продолжение этой системы, а не отдельный модуль.

Пример серверной функции эвакуации на QBCore (упрощённо, под адаптацию под вашу структуру):

RegisterNetEvent('garage:impoundVehicle', function(plate, reason)
    local src = source
    local result = MySQL.query.await('SELECT * FROM player_vehicles WHERE plate = ?', {plate})
    if result[1] then
        MySQL.update('UPDATE player_vehicles SET state = ?, garage = ?, depotprice = ? WHERE plate = ?',
            {2, 'impound', Config.ImpoundBasePrice, plate})
        TriggerClientEvent('QBCore:Notify', src, 'Транспорт эвакуирован на штрафстоянку: ' .. reason, 'error')
    end
end)

Не забудьте вызывать DeleteEntity/DeleteVehicle на клиенте у самой физической модели после записи в базу — иначе машина продолжит валяться в мире как призрак, доступный для взаимодействия, хотя формально уже "на штрафстоянке".

Штрафстоянка: точка выдачи и расчёт цены возврата

Штрафстоянка — это отдельный гараж с фильтром state = 2, куда игрок приходит и платит за возврат, прежде чем машина снова появится в мире. Логика цены обычно завязана на два фактора: базовая ставка за эвакуацию плюс дни простоя.

Config.ImpoundBasePrice = 500
Config.ImpoundDailyFee = 100

function CalculateImpoundPrice(impoundedAt)
    local daysHeld = math.floor((os.time() - impoundedAt) / 86400)
    return Config.ImpoundBasePrice + (daysHeld * Config.ImpoundDailyFee)
end

При выдаче деньги списываются через Player.Functions.RemoveMoney('cash', price) или банковский перевод, если у вас настроена банковская система с кредитами — логично дать игроку выбор оплаты наличкой или переводом с карты, это ощущается органичнее, чем жёсткая привязка к одному способу.

Таблица для понимания разных сценариев ценообразования, которые встречаются на реальных RP-серверах:

Сценарий эвакуацииБазовая ставкаДоп. платаКому уходит доход
Неправильная парковка (авто-система)300-500нетв бюджет сервера/казну города
Эвакуация полицией (нарушение)500-800+100/день простояв казну фракции полиции
Конфискация за долг по штрафам800-1500штраф не гасится автоматическив казну сервера

Цифры ориентировочные — баланс сильно зависит от экономики конкретного сервера (стартовый капитал, средний доход по джобам), тестируйте на своих значениях, не копируйте чужие один в один.

Гараж с несколькими точками и привязкой к локации

Если карта большая или сервер использует кастомные MLO-карты, логично сделать несколько гаражей вместо одного центрального — иначе игроки со всей карты тратят по 5-10 минут только на дорогу до машины. В qb-garages это настраивается через таблицу локаций в конфиге:

Config.Garages = {
    ["pillboxgarage"] = {
        label = "Pillbox Garage",
        vehicleTypes = { "car", "byke" },
        takeVehicle = { x = -212.51, y = -1330.68, z = 31.29 },
        spawnDefault = { x = -215.5, y = -1301.5, z = 31.4, h = 130.0 },
    },
    ["sandygarage"] = {
        label = "Sandy Shores Garage",
        vehicleTypes = { "car", "air" },
        takeVehicle = { x = 1731.0, y = 3672.0, z = 34.9 },
        spawnDefault = { x = 1735.0, y = 3680.0, z = 34.9, h = 45.0 },
    },
}

Машина, поставленная в один гараж, при желании может быть недоступна из другого (реалистичный вариант) — либо доступна из любого гаража того же типа (аркадный, но удобнее для игроков). Это решение стоит принять до релиза сервера: смена модели после того, как игроки привыкли, вызывает много недовольства.

Поднять сервер FiveM (GTA V) за пару минут

Готовый образ MAATRIX GAMES: NVMe, AMD EPYC, DDoS-защита, панель управления. Локации UK, US, RU. Оплата картой РФ, СБП или криптой.

Создать сервер

Частые вопросы

Машина пропадает из гаража после рестарта сервера — почему?

Обычно проблема в том, что состояние сохраняется только в памяти ресурса (переменная), а не в БД сразу. Проверьте, что событие сохранения реально делает UPDATE/INSERT в таблицу, а не просто обновляет локальный кэш скрипта.

Можно ли сделать штрафстоянку без job для полиции, если RP-фракций пока нет?

Да, эвакуация может идти полностью через авто-систему по зонам неправильной парковки — job полиции нужен только для ручной эвакуации патрулём, это отдельная опциональная ветка.

Как не дать игрокам эвакуировать чужие машины ради троллинга?

Ограничьте команду эвакуации по ACE-правам (add_ace group.police impound.vehicle allow) или по job в самом ресурсе — обычный игрок не должен иметь доступа к событию garage:impoundVehicle вообще, проверяйте это и на сервере, а не только скрывайте кнопку в UI.

Что делать, если машина застряла в состоянии "эвакуирована", но на штрафстоянке её нет?

Это рассинхрон БД и мира — обычно из-за краша скрипта между записью state = 2 и удалением сущности. Добавьте админ-команду для ручного сброса state по plate через консоль или chat-команду с проверкой прав.

Нужен ли отдельный ресурс для эвакуатора (анимация подъёма машины) или можно просто телепортировать в депо?

Для атмосферы приятно, но не обязательно на старте — можно запустить с мгновенным перемещением в state = 2 и добавить визуальный эвакуатор позже, когда основная логика обкатана и стабильна.