MAATRIX GAMES / Блог / FiveM: система штрафов ГИБДД для RP-сервера

FiveM: система штрафов ГИБДД для RP-сервера

MAATRIX GAMES

Игрок за рулём проезжает на красный, полицейский открывает MDT, выписывает штраф на конкретную статью — и деньги списываются с банковского счёта нарушителя, а не появляются из воздуха командой /give. Если у вас на сервере штрафы всё ещё выдаются через /fine <id> <сумма> в чат, вы теряете и атмосферу, и контроль над экономикой. Разберём, как собрать рабочую систему: MDT для оформления, штрафстоянку для эвакуированных тачек и баланс сумм, который не обвалит и не обесценит вашу игровую валюту.

Зачем на RP-сервере нужна нормальная система штрафов

Штрафы — это не декорация для полицейской РП-линии, а один из немногих легальных способов изымать деньги из экономики сервера. Без постоянного оттока валюты донат и фарм быстро разгоняют инфляцию: через пару недель штраф в 500$ превращается в чаевые, которые никто не замечает.

Плюс есть чисто ролевая сторона: если штраф выписывается командой в консоль, это ломает immersion — игрок видит системное сообщение вместо диалога с офицером. MDT (Mobile Data Terminal) решает обе задачи разом: полицейский оформляет протокол через игровой интерфейс на планшете/ноутбуке в машине, штраф автоматически списывается с banking-счёта, а в базе остаётся история нарушений конкретного персонажа — что удобно для судов, амнистий и разборов жалоб.

Полезно заранее продумать, как эта система стыкуется с общей экономикой RP-сервера и с системой работ — штрафы и зарплаты полицейских должны быть в одной весовой категории, иначе either коп нищий, либо штраф — не наказание, а мелочь.

MDT: связующее звено между полицией и базой данных

MDT на FiveM — это NUI-интерфейс (HTML/CSS/JS поверх игры), который открывается по команде или клавише и общается с сервером через events и exports. В большинстве RP-проектов MDT — самостоятельный ресурс, завязанный на фреймворк:

  • для ESX — типовые связки вроде esx_mdt/esx_policejob c таблицами в базе через oxmysql;
  • для QBCoreqb-mdt в паре с qb-policejob, где штрафы, ордера и досье персонажей идут через один интерфейс.

Логика везде похожа: MDT дергает серверный event, event проверяет права (job.name == 'police' и нужный job.grade), пишет запись в таблицу mdt_fines (или как она названа в конкретном ресурсе) и вызывает списание денег у игрока-нарушителя. Если у вас ESX, минимальный конфиг подключения обычно лежит в config.lua ресурса и требует указать имя таблицы банковского счёта — на новых сборках ESX Legacy это bank_accounts, на старых версиях может быть просто поле bank в users. Перед установкой сверьтесь, какая версия ESX стоит у вас — иначе списание денег просто не сработает и штраф "выпишется", а счёт не тронется.

Отдельно стоит продумать права доступа: стажёр (grade 0) не должен иметь возможность аннулировать чужие штрафы, а вот выписывать — да. Это настраивается в конфиге job'а полиции, обычно массивом grade → набор permissions.

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

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

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

Выписка штрафа: от клика в MDT до записи в базу

Разберём упрощённо, как выглядит серверная часть на ESX-подобном фреймворке — с чем реально придётся работать, если вы кастомизируете готовый MDT под свой сервер.

Таблица в базе (MySQL/oxmysql):

CREATE TABLE IF NOT EXISTS `mdt_fines` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `citizenid` VARCHAR(50) NOT NULL,
  `officer_id` VARCHAR(50) NOT NULL,
  `charge` VARCHAR(255) NOT NULL,
  `amount` INT(11) NOT NULL,
  `date` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

Серверный обработчик события (пример на ESX, oxmysql):

RegisterNetEvent('mdt:issueFine')
AddEventHandler('mdt:issueFine', function(targetId, charge, amount)
    local src = source
    local xPlayer = ESX.GetPlayerFromId(src)
    local xTarget = ESX.GetPlayerFromId(targetId)

    if xPlayer.job.name ~= 'police' then return end
    if not xTarget then return end

    xTarget.removeAccountMoney('bank', amount)

    MySQL.insert('INSERT INTO mdt_fines (citizenid, officer_id, charge, amount) VALUES (?, ?, ?, ?)', {
        xTarget.identifier, xPlayer.identifier, charge, amount
    })

    TriggerClientEvent('esx:showNotification', targetId, ('Штраф: %s (~r~%s$~s~)'):format(charge, amount))
end)

Ключевые моменты, о которые обычно спотыкаются:

  • Проверка баланса перед списанием. Если у игрока на счету меньше суммы штрафа, removeAccountMoney в части фреймворков просто уйдёт в минус, в других — не выполнится вовсе. Проверяйте баланс заранее и, если денег не хватает, переводите часть суммы в долг (система "штраф в рассрочку" — актуально, если на сервере есть банковские кредиты) или конвертируйте в реальный срок на штрафстоянке/аресте.
  • Rate limit на выписку. Без него нечистый на руку "коп" может накрутить десятки штрафов за секунды через дублирующий клик или спам события. Добавьте os.time()-проверку минимального интервала между вызовами от одного source.
  • Логирование в Discord. Полезно продублировать каждый штраф в лог-канал через webhook — это снимает половину споров "а мне выписали незаконно" в тикетах. Если у вас уже настроена Discord-интеграция сервера, добавить туда ещё один embed для штрафов — вопрос десяти строк кода.

Баланс: сколько должен стоить штраф в вашей экономике

Это самая частая ошибка новых RP-проектов: суммы штрафов копируют с чужого сервера или из реальной жизни, не глядя на собственную зарплатную сетку. Если водитель такси зарабатывает 300$ за смену, а штраф за превышение скорости — 1500$, никто просто не будет ездить по правилам, потому что бояться нечего — штраф всё равно не заплатить, и он спишется в минус или будет проигнорирован через impound.

Рабочий подход — considerить штрафы не в абсолютных цифрах, а в долях от средней часовой ставки на сервере. Если средний легальный заработок — условно 800$/час, диапазон штрафов стоит держать в пределах 5-40% от этой суммы за рядовые нарушения и выше — за тяжкие (оружие, сбитый пешеход, побег от полиции).

Пример конфига статей (config.lua MDT/policejob):

Config.Charges = {
    { code = 'ПДД-1', name = 'Превышение скорости',      fine = 150,  jail = 0  },
    { code = 'ПДД-4', name = 'Проезд на красный',         fine = 250,  jail = 0  },
    { code = 'УК-12',  name = 'Незаконное оружие',         fine = 1500, jail = 5  },
    { code = 'УК-19',  name = 'Побег от полиции',          fine = 2000, jail = 8  },
    { code = 'УК-27',  name = 'Наезд со смертельным исходом', fine = 5000, jail = 15 },
}

Таблица для ориентира — как раскладывать статьи по группам тяжести (конкретные суммы подгоняйте под свою экономику, это не готовые цифры для копирования):

КатегорияДоля от почасовой ставкиПример
Мелкие ПДД5-15%превышение скорости, парковка
Средние нарушения20-40%вождение без прав, ДТП с виновностью
Тяжкие статьи100-300%+оружие, сопротивление, побег
Особо тяжкие500%+ и арестубийство, крупный грабёж

Отдельно проверьте, не дублируются ли штрафы с системой фракций и банд — если у вас есть нелегальные фракции, для которых штрафы ГИБДД вообще не должны применяться (например, персонаж без легального имени), это стоит явно исключить в проверке job'а нарушителя.

Штрафстоянка: куда уезжает тачка нарушителя

Штрафстоянка (impound) логически продолжает систему штрафов: если машину эвакуировали за нарушение (неправильная парковка, брошенное авто в розыске, конфискация при аресте), игрок должен либо оплатить штраф за эвакуацию и хранение, либо потерять машину.

Механика обычно такая:

  1. Полицейский или скрипт-триггер (например, при аресте владельца рядом с машиной) помечает vehicle-запись в базе как state = 2 (impounded) — это тот же принцип, что используется при обычном гараже.
  2. На точке штрафстоянки висит NPC/зона, которая через oxmysql-запрос ищет все машины игрока со статусом impound и показывает список с суммой к оплате.
  3. Сумма обычно считается как base_fee + (hours_since_impound * hourly_rate) — чем дольше не забираешь тачку, тем дороже. Это дополнительный денежный сток и стимул не забивать на нарушения.
exports.oxmysql:execute('SELECT * FROM owned_vehicles WHERE owner = ? AND state = 2', {citizenid}, function(vehicles)
    -- отрисовка списка в NUI штрафстоянки с расчётом суммы
end)

Частая недоработка — забыть развязать штрафстоянку с гаражной системой. Если игрок забирает машину со штрафстоянки, но в скрипте гаража она всё ещё числится "на споуне" или "в другом гараже", получите дюп машины или зависшую запись в базе. Тестируйте связку эвакуация → штрафстоянка → возврат в гараж на тестовом персонаже перед тем, как выкатывать на прод.

Частые грабли при настройке штрафов и MDT

  • Несовпадение identifier'ов. Часть MDT-ресурсов ждёт citizenid (QBCore), другая — identifier (ESX, обычно steam-hex или license). Если ставите MDT не "из коробки" под свой фреймворк, а адаптируете чужой — первым делом проверьте, какое поле используется для связки игрока с записью в базе, иначе штрафы будут привязываться не к тем персонажам.
  • NUI не открывается у части полицейских. Обычно это права доступа (ACE permissions или проверка job.grade в клиентском коде) либо конфликт биндов клавиш с другим ресурсом — смотрите f8-консоль на клиенте на предмет ошибок NUI.
  • Штраф списывается, а событие в Discord не летит. Обычно webhook просто не проверяет код ответа — Discord может тихо ронять запросы при превышении rate limit (429). Добавьте проверку статуса ответа и повторную отправку с задержкой.
  • Дублирование штрафов при лаге. Если клиент не получил подтверждение и жмёт кнопку повторно, а сервер не проверяет debounce — нарушителю могут выписать один и тот же штраф дважды. Решается серверным флагом "заявка в обработке" на время выполнения запроса.
  • MySQL таймауты при большом онлайне. На нагруженных серверах INSERT в mdt_fines может попадать в очередь, если используете синхронный MySQL.Sync вместо асинхронного MySQL.insert/MySQL.Async.execute. Всегда используйте асинхронные методы oxmysql — синхронные вызовы блокируют серверный поток и на пике онлайна ощутимо аукаются лагами по всему серверу.

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

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

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

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

Какой MDT лучше ставить — платный или бесплатный?

Для старта хватает open-source решений под ESX/QBCore — они закрывают базовый функционал (профили, штрафы, ордера). Платные версии обычно дают более гибкий UI и готовую интеграцию со штрафстоянкой и судебной системой, но переносить кастомные статьи и права всё равно придётся руками под свой конфиг.

Можно ли выписывать штрафы без ареста игрока?

Да, это стандартный сценарий — MDT-штраф не требует физического задержания, если нарушение мелкое (ПДД). Арест актуален для тяжких статей, где предусмотрен tюремный срок в конфиге.

Что делать, если у игрока нет денег на счету?

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

Нужно ли синхронизировать суммы штрафов между несколькими RP-серверами одного проекта (например, RU и EU-инстансами)?

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

Как связать штрафы с судебной RP-системой (адвокаты, обжалование)?

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