В 2018 году я познакомился с RAGE MP и, недолго думая, взялся за очередной GTA-проект.
Реализовав классический TDM режим, вспомнил про фичу реализованную для SVO Clan в 2018 году - тогда ребята активно участвовали в ClanWars турнирах и было не всегда удобно мониторить статистику матчей вне дома.
Я реализовал простенький PHP-скрипт, который запускался по cron заданию, запрашивал информацию о сервере из мастер-листа SA:MP, генерировал изображение и публиковал в обложку группы ВК - это была крутая фича в те времена.

Из этого воспоминания вытекло несколько мыслей и немного додумав я определился со списком ключевых фич:
- Удобная возможность конфигурировать матч
- Возможность отслеживать процесс матча
- Сбор статистики игроков и кланов
- Матчмейкинг
- Турниры
Стек выбрал тот, на котором работал в тот момент: Vue.js, Node.js, PHP, Redis, MySQL, дополнительно вооружился Electron для desktop клиента.
Определившись с ключевыми фичами, я решил стартовать разработку.
Игровой сервер
В TDM версии проекта, конфигурация матча хранилась в json файле, рядом с бинарником сервера - она читалась при запуске и настраивала игровой процесс.
Распределение по командам было на совести самих игроков.
С матчмейкинг-системой это не стыковалось, поэтому потребовался ряд доработок. В новой версии нужна была интеграция с бэкенд-сервисом, который отдаст авторизационный ключ, конфигурацию, список карт, список оружия, игроков распределенных по командам, и так далее.
После запуска игрового сервера выполнялся запрос к бэкенду, запрашивая детальную конфигурацию. Далее он сообщал бэку, что успешно запустился, а бэкенд-сервис передавал клиентам новый статус для отрисовки кнопки подключения к серверу.
Если команды не собирались, по истечении КД сервер посылал запрос об отмене матча и выключался в штатном режиме.
При запуске, окончании раунда, убийстве игрока и прочих системных действиях выполнялись HTTP-запросы к бэкенд-сервису.
По окончании матча игровой сервер отправлял на бэкенд запрос с результатом матча и затем выключался.
Что забавно: при подключении к игровому серверу достаточно было указать в RAGE MP клиенте никнейм, который игрок вводил при регистрации на сайте. Если в лобби такой игрок был, сервер пропускал его в игру.
В последних версиях игровой режим был переработан и больше похож на классический Counter Strike режим, с закупом оружия, выдачей валюты за фраги и так далее.
Бэкенд
бэкенд был реализован в виде монолитного API-сервиса на Laravel с разделением на отдельные точки входа и RBAC моделью для пользователей состоящей из 5 ролей:
- Администратор
- Модератор
- Тестировщик
- Редактор карт
- Пользователь
Игровые серверы
Серверы передавали информацию в ручки server/v1/*, например старт/завершение раунда, вход/выход игрока и т.д., используя персонализированные ключи для авторизации.
Клиент
Пользователи аутентифицировались по паре почта + пароль, получая JWT токен, в дальнейшем передавая его в ручки client/v1/* для авторизации, а доступ к функционалу определялся RBAC моделью. Дополнительно, существовала ABAC модель в рамках кланов - была возможность создать роль и назначить ей определенные скоупы (привилегии), например: кикнуть или пригласить игрока в клан.
Редактор карт
Редактор авторизовывался по персональному ключу и обращался к ручкам editor/v1/*, а при подключении игрока к редактору, проверялись роли игрока на наличие "редактор карт".
Менеджер серверов
Маленький сервис на nodejs, стоявший на той же VPS машинке, где запускались игровые серверы.
Выполнял несколько задач:
- Обновлял бинарники игрового сервера и игрового режима по запросу
- Запрашивал у основного бэкенда перечень лобби готовых к запуску
- Клонировал игровой сервер создавая конфигурацию лобби
- Запускал новые инстансы игрового сервера используя pm2
Клиент
Клиент перерабатывался несколько раз и это была настоящая проблема проекта. Первая версия была приложением на Electron + Vue.js. К сожалению скриншотов первой версии я не нашел, поэтому показываю одну из последних Electron версий.



Клиент покрывал следующие задачи:
- Аутентификация/регистрация игрока
- Просмотр статистики игрока
- Создание и управление кланом
- Управление игроками клана, ранговая система
- Просмотр статистики клана и игроков
- Просмотр истории матчей клана и игрока
- Создание матчей, просмотр матчей
Отдельно выделю создание матчей - конструктором мог воспользоваться любой игрок. Количество одновременно-запущенных матчей ограничивалось системно, что позволяло контролировать ресурсы и ограничивать флуд от вредных пользователей.






На выбор было два варианта подбора игроков, Classic и Mix - первый позволял подключатся только участникам задействованных кланов, второй позволял подключиться кому угодно.
В блоке о игровом режиме я упоминал события сервера, которые отправлялись на бэкенд. Так вот, все события прилетевшие на бэкенд стримились клиентам в приложение посредством websocket, таким образом было реализовано реалтайм обновление статистики матча, статистики игроков и текстовая трансляция матча. Вся эта информация сохранялась и к ней можно было вернуться спустя время.






Финальная версия клиента переехала в браузер, с поддержкой PWA - сайт можно было установить как приложение на рабочий стол. Так я избавился от дополнительной технологии в виде Electron, сократив себе объем поддерживаемого кода, вроде апдейтера приложения и ряда сопутствующих Electron-фич.
Панель администратора
Админка была реализована частью бэкенд-сервиса используя Sleeping Owl над Laravel. В первую очередь использовалась для управления контентом, разбора жалоб от игроков, управления картами, паками карт, оружием и т.д.
Выдать бан, назначить роль игроку и прочее, описывать не буду - стандартные фичи.
Из действительно интересного было управления картами, с возможностью посмотреть карту на 2d схеме, посмотреть версии и по необходимости откатить до нужной.

Редактор карт
Первая версия редактора была реализована как игровой сервер, использующийся локально. Создание и управление картами было на основе команд в чате, например:
- /arena [name] - создать карту
- /larena [name] - загрузить карту
- /spawn [attack/defence] - установить точку спауна команды
- etc. Карты хранились в файлах формата json, прямо в директории сервера.
Вторая версия была запущена как отдельный сервер, доступный из интернета. Редактор обзавелся аутентификацией по учетной записи для пользователей с ролью "редактор карт", полноценным интерфейсом и управлением при помощи горячих клавиш.
Обновленный редактор упростил использование функций из предыдущей версии и добавил ряд новых, реализуя следующие возможности:
- Создать/отредактировать/удалить карту
- Изменить название, описание
- Разметить границы
- Разметить спауны команд атаки и защиты
- Расставить камеры, которые проигрываются на старте раунда
- Сделать скриншот-превью с одной из камер
- Выбрать погоду и время
Интересный факт - редактор карт до сих пор используется на благо Epsilon Play.
Финальная архитектура проекта
Вместо множества слов, прилагаю упрощенную схемку архитектуры проекта

Итог
Проект был закрыт в сентябре 2023, потому что мой перфекционизм сыграл злую шутку: лучшее - враг хорошего.
Я несколько раз переписывал клиент, сначала он был на Electron + Vue.js, затем на Nuxt, затем Nuxt + PWA, несколько раз менял его дизайн и часть ключевых фич.
Игровой TDM режим так же бесконечно дорабатывался, а финальным аккордом стала идея запустить игровой режим в формате Counter Strike а затем еще и перевезти на alt:V, все это тянулось довольно долго из-за непостоянной занятости проектом и желанием все сделать идеально, а в итоге проект закрылся.
Закончился ли мой путь в GTA? Нет - сейчас по мере возможностей пилю фичи для Epsilon Play, который недавно перевез с alt:V на FiveM, но это отдельная история.
P.S.
Полноценный матчмейкинг и систему турниров я так и не реализовал, а жаль, это интересные фичи.
Помимо Team DeathMatch режима, я планировал и другие игровые режимы, например Capture The Flag. И даже была мысль реализовать sdk для сторонних разработчиков с возможностью интеграции игровых режимов от коммьюнити в проект.
Статья написана для обывателя, с минимумом технической информации - если вам интересны детали реализации, напишите мне - поделюсь.