[Python, Node.JS, Серверное администрирование, TypeScript, Игры и игровые приставки] Пишем матчмейкинг для Доты 2014 года
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет.
Этой весной я наткнулся на проект, в котором ребята научились запускать Dota 2 сервер версии 2014 года и, соответственно, играть на нем. Я большой фанат этой игры, и не смог пройти мимо уникальной возможности окунуться в свое детство.
Окунулся я очень глубоко, и так вышло что я написал Discord бота, который отвечает практически за весь функционал, который не поддерживается в старой версии игры, а именно матчмейкинг.
До всех нововведений с ботом лобби создавалось вручную. Собирали 10 реакций на сообщение и вручную собирали сервер, либо хостили локальное лобби.
Моя натура программиста не выдержала такое количество ручной работы, и за ночь я набросал самую простую версию бота, которая автоматически поднимала сервер, когда набиралось 10 человек
Писать сходу решил на nodejs, потому что не очень люблю питон, ну и комфортнее себя чувствую в этой среде.
Это мой первый опыт написания бота для Discord, но оказалось все очень даже просто. Официальный npm модуль discord.js предоставляет удобный интерфейс для работы с сообщениями, сбором реакций и т.д.
Дисклеймер: все примеры кода являются «актуальными», то есть прошли несколько итераций переписывания по ночам.
Основа матчмейкинга — это «очередь», в которую помещаются игроки, которые хотят играть, и убираются, когда расхотели или нашли игру.
Так выглядит сущность «игрока». Изначально это был просто id пользователя в Discord, но в планах лаунчер/поиск игры с сайта, но обо всем по порядку.
export enum Realm {
DISCORD,
EXTERNAL,
}
export default class QueuePlayer {
constructor(public readonly realm: Realm, public readonly id: string) {}
public is(qp: QueuePlayer): boolean {
return this.realm === qp.realm && this.id === qp.id;
}
static Discord(id: string) {
return new QueuePlayer(Realm.DISCORD, id);
}
static External(id: string) {
return new QueuePlayer(Realm.EXTERNAL, id);
}
}
А вот интерфейс очереди. Тут вместо «игроков» используется абстракция в виде «группы». Для одиночного игрока группа состоит из него самого, а для игроков в группе, соответственно, из всех игроков группы.
export default interface IQueue extends EventEmitter {
inQueue: QueuePlayer[]
put(uid: Party): boolean;
remove(uid: Party): boolean;
removeAll(ids: Party[]): void;
mode: MatchmakingMode
roomSize: number;
clear(): void
}
Решил использовать события для обмена контекстом. Подходило под кейсы — по событию «найдена игра для 10 человек» можно и отправить в личные сообщения игрокам нужное сообщение, и выполнить основную бизнес логику — запустить таск для проверки готовности, подготовить лобби к запуску и так далее.
Для IOC я использую InversifyJS. Имею приятный опыт работы с этой библиотекой. Быстро и просто!
Очередей у нас на сервере несколько — добавились режими 1х1, обычный/рейтинговый, и пара кастомок. Поэтому есть singleton RoomService, который лежит между пользователем и поиском игры.
constructor(
@inject(GameServers) private gameServers: GameServers,
@inject(MatchStatsService) private stats: MatchStatsService,
@inject(PartyService) private partyService: PartyService
) {
super();
this.initQueue(MatchmakingMode.RANKED);
this.initQueue(MatchmakingMode.UNRANKED);
this.initQueue(MatchmakingMode.SOLOMID);
this.initQueue(MatchmakingMode.DIRETIDE);
this.initQueue(MatchmakingMode.GREEVILING);
this.partyService.addListener(
"party-update",
(event: PartyUpdatedEvent) => {
this.queues.forEach((q) => {
if (has(q.queue, (t) => t.is(event.party))) {
// if queue has this party, we re-add party
this.leaveQueue(event.qp, q.mode)
this.enterQueue(event.qp, q.mode)
}
});
}
);
this.partyService.addListener(
"party-removed",
(event: PartyUpdatedEvent) => {
this.queues.forEach((q) => {
if (has(q.queue, (t) => t.is(event.party))) {
// if queue has this party, we re-add party
q.remove(event.party)
}
});
}
);
}
(Лапша кода для представления, как примерно выглядят процессы)
Здесь я инициализирую очередь под каждый из реализованных режимов игры, а так же слушаю изменения «групп», чтобы подкорректировать очереди и избежать некоторых конфликтов.
Так, я молодец, я вставил куски кода, которые никак не относятся к топику, а теперь перейдем уже непосредственно к мачтмейкингу.
Рассмотрим кейс:
1) Пользователь хочет поиграть
2) Для того, чтобы начать поиск, он использует Gateway=Discord, то есть ставит реакцию на сообщение
3) Этот гейтвей идет в RoomService, и говорит «Пользователь из дискорда хочет войти в очередь, режим: нерейтинговая игра»
4) RoomService принимает просьбу гейтвея, и пихает в нужную очередь пользователя(точнее, группу пользователя)
5) Очередь при каждом изменении проверяет, хватает ли игроков для игры. Если можно — эмиттим событие
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService, очевидно, с радостью слушает каждую очередь в трепетном ожидании этого события. На вход мы получаем список игроков, формируем из них виртуальную «комнату», и, конечно же, эмиттим событие
queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log(
`Room found mode: [${mode}]. Time to get free room for these guys`
);
const room = this.getFreeRoom(mode);
room.fill(event.players);
this.onRoomFormed(room);
});
7) Вот мы и добрались до «высшей» инстанции — класса Bot. В целом он занимается связью между гейтвеями(как это смешно на русском выглядит я не могу) и бизнес логикой матчмейкинга. Бот подслушивает событие, и приказывает DiscordGateway отослать всем пользователям проверку на готовность.
8) Если кто-то отклонил или не принял игру за 3 минуты, то мы НЕ возвращаем их в очередь. Всех остальных возвращаем в очередь и ждем, когда снова наберется 10 человек. Если все игроки приняли игру, то начинается интересная часть.
Конфигурация выделенного сервера
У нас игры хостятся на VDS c Windows server 2012. Из этого можно сделать несколько выводов:
1) На него нет докера, что ударило меня в самое сердце
2) Мы экономим на аренде
Стоит задача: с VPS на линуксе запускать процесс на VDS. Написал простой сервер на Flask. Да, не люблю питон, но что поделать — на нем написать этот сервер быстрее и проще.
Он выполняет 3 функции:
1) Запуск сервера с конфигурацией — выбор карты, количества игроков для старта игры, и набор плагинов. Про плагины сейчас не буду писать — это отдельная история с литрами кофе по ночам вперемешку со слезами и вырванными волосами.
2) Остановка/перезапуск сервера в случае неудачных подключений, которые мы можем обработать только вручную
Тут все просто, примеры кода даже неуместны. Скрипт на 100 строчек
Итак, когда 10 человек собрались вместе и приняли игру, запущен сервер и все жаждут играть, в личные сообщения приходит ссылка на подключение к игре.
По нажатию ссылки игрока коннектит к игровому серверу, и дальше уже само все. Через ~25 минут виртуальная «комната» с игроками очищается.
Заранее извиняюсь за нескладность статьи, давно не писал сюда, да и кода слишком много, чтобы выделить важные участки. Лапша, короче.
Если увижу интерес к теме, то будет вторая часть — в ней будут мои мучения с плагинами для srcds(Source dedicated server), и, наверное, система рейтинга и мини-dotabuff, сайт со статистикой игр.
Немного ссылок:
1) Наш сайт(статистика, таблица лидеров, небольшой лендос и скачивание клиента)
2) Discord сервер
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, PHP, Программирование, Проектирование и рефакторинг] Модернизация старого PHP-приложения (перевод)
- [Информационная безопасность, Разработка веб-сайтов, JavaScript] Как npm обеспечивает безопасность
- [Системное администрирование, Серверное администрирование, DevOps, Kubernetes] CRI-O как замена Docker в качестве исполняемой среды для Kubernetes: настройка на CentOS 8
- [Разработка на Raspberry Pi, 3D-принтеры, Умный дом, DIY или Сделай сам] Умный дом как хобби
- [IT-инфраструктура, Серверное администрирование, Big Data, Визуализация данных, DevOps] ELK SIEM Open Distro: Прогулка по open Distro (перевод)
- [Анализ и проектирование систем, Управление разработкой, Управление проектами] Как справиться с декомпозицией задач и не перестараться
- [Информационная безопасность, Реверс-инжиниринг, Программирование микроконтроллеров, Производство и разработка электроники] Реверс embedded: трассировка кода через SPI-flash
- [Законодательство в IT, Искусственный интеллект] Юридические эксперименты в ИТ. Как кастомизировать закон под себя
- [Разработка мобильных приложений, Разработка под Android, Тестирование мобильных приложений] Автотесты на Android. Картина целиком
- [Веб-дизайн, Интерфейсы, Прототипирование, Графический дизайн, Дизайн] Какие вопросы UX/UI-дизайнер должен задать клиенту на старте, чтобы не вносить кучу правок?
Теги для поиска: #_python, #_node.js, #_servernoe_administrirovanie (Серверное администрирование), #_typescript, #_igry_i_igrovye_pristavki (Игры и игровые приставки), #_typescript, #_nodejs, #_bot (бот), #_discord, #_dota_2, #_matchmejking (матчмейкинг), #_rejting (рейтинг), #_python, #_node.js, #_servernoe_administrirovanie (
Серверное администрирование
), #_typescript, #_igry_i_igrovye_pristavki (
Игры и игровые приставки
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:49
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет. Этой весной я наткнулся на проект, в котором ребята научились запускать Dota 2 сервер версии 2014 года и, соответственно, играть на нем. Я большой фанат этой игры, и не смог пройти мимо уникальной возможности окунуться в свое детство. Окунулся я очень глубоко, и так вышло что я написал Discord бота, который отвечает практически за весь функционал, который не поддерживается в старой версии игры, а именно матчмейкинг. До всех нововведений с ботом лобби создавалось вручную. Собирали 10 реакций на сообщение и вручную собирали сервер, либо хостили локальное лобби. Моя натура программиста не выдержала такое количество ручной работы, и за ночь я набросал самую простую версию бота, которая автоматически поднимала сервер, когда набиралось 10 человек Писать сходу решил на nodejs, потому что не очень люблю питон, ну и комфортнее себя чувствую в этой среде. Это мой первый опыт написания бота для Discord, но оказалось все очень даже просто. Официальный npm модуль discord.js предоставляет удобный интерфейс для работы с сообщениями, сбором реакций и т.д. Дисклеймер: все примеры кода являются «актуальными», то есть прошли несколько итераций переписывания по ночам. Основа матчмейкинга — это «очередь», в которую помещаются игроки, которые хотят играть, и убираются, когда расхотели или нашли игру. Так выглядит сущность «игрока». Изначально это был просто id пользователя в Discord, но в планах лаунчер/поиск игры с сайта, но обо всем по порядку. export enum Realm {
DISCORD, EXTERNAL, } export default class QueuePlayer { constructor(public readonly realm: Realm, public readonly id: string) {} public is(qp: QueuePlayer): boolean { return this.realm === qp.realm && this.id === qp.id; } static Discord(id: string) { return new QueuePlayer(Realm.DISCORD, id); } static External(id: string) { return new QueuePlayer(Realm.EXTERNAL, id); } } А вот интерфейс очереди. Тут вместо «игроков» используется абстракция в виде «группы». Для одиночного игрока группа состоит из него самого, а для игроков в группе, соответственно, из всех игроков группы. export default interface IQueue extends EventEmitter {
inQueue: QueuePlayer[] put(uid: Party): boolean; remove(uid: Party): boolean; removeAll(ids: Party[]): void; mode: MatchmakingMode roomSize: number; clear(): void } Решил использовать события для обмена контекстом. Подходило под кейсы — по событию «найдена игра для 10 человек» можно и отправить в личные сообщения игрокам нужное сообщение, и выполнить основную бизнес логику — запустить таск для проверки готовности, подготовить лобби к запуску и так далее. Для IOC я использую InversifyJS. Имею приятный опыт работы с этой библиотекой. Быстро и просто! Очередей у нас на сервере несколько — добавились режими 1х1, обычный/рейтинговый, и пара кастомок. Поэтому есть singleton RoomService, который лежит между пользователем и поиском игры. constructor(
@inject(GameServers) private gameServers: GameServers, @inject(MatchStatsService) private stats: MatchStatsService, @inject(PartyService) private partyService: PartyService ) { super(); this.initQueue(MatchmakingMode.RANKED); this.initQueue(MatchmakingMode.UNRANKED); this.initQueue(MatchmakingMode.SOLOMID); this.initQueue(MatchmakingMode.DIRETIDE); this.initQueue(MatchmakingMode.GREEVILING); this.partyService.addListener( "party-update", (event: PartyUpdatedEvent) => { this.queues.forEach((q) => { if (has(q.queue, (t) => t.is(event.party))) { // if queue has this party, we re-add party this.leaveQueue(event.qp, q.mode) this.enterQueue(event.qp, q.mode) } }); } ); this.partyService.addListener( "party-removed", (event: PartyUpdatedEvent) => { this.queues.forEach((q) => { if (has(q.queue, (t) => t.is(event.party))) { // if queue has this party, we re-add party q.remove(event.party) } }); } ); } (Лапша кода для представления, как примерно выглядят процессы) Здесь я инициализирую очередь под каждый из реализованных режимов игры, а так же слушаю изменения «групп», чтобы подкорректировать очереди и избежать некоторых конфликтов. Так, я молодец, я вставил куски кода, которые никак не относятся к топику, а теперь перейдем уже непосредственно к мачтмейкингу. Рассмотрим кейс: 1) Пользователь хочет поиграть 2) Для того, чтобы начать поиск, он использует Gateway=Discord, то есть ставит реакцию на сообщение 3) Этот гейтвей идет в RoomService, и говорит «Пользователь из дискорда хочет войти в очередь, режим: нерейтинговая игра» 4) RoomService принимает просьбу гейтвея, и пихает в нужную очередь пользователя(точнее, группу пользователя) 5) Очередь при каждом изменении проверяет, хватает ли игроков для игры. Если можно — эмиттим событие private onRoomFound(players: Party[]) {
this.emit("room-found", { players, }); } 6) RoomService, очевидно, с радостью слушает каждую очередь в трепетном ожидании этого события. На вход мы получаем список игроков, формируем из них виртуальную «комнату», и, конечно же, эмиттим событие queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log( `Room found mode: [${mode}]. Time to get free room for these guys` ); const room = this.getFreeRoom(mode); room.fill(event.players); this.onRoomFormed(room); }); 7) Вот мы и добрались до «высшей» инстанции — класса Bot. В целом он занимается связью между гейтвеями(как это смешно на русском выглядит я не могу) и бизнес логикой матчмейкинга. Бот подслушивает событие, и приказывает DiscordGateway отослать всем пользователям проверку на готовность. 8) Если кто-то отклонил или не принял игру за 3 минуты, то мы НЕ возвращаем их в очередь. Всех остальных возвращаем в очередь и ждем, когда снова наберется 10 человек. Если все игроки приняли игру, то начинается интересная часть. Конфигурация выделенного сервера У нас игры хостятся на VDS c Windows server 2012. Из этого можно сделать несколько выводов: 1) На него нет докера, что ударило меня в самое сердце 2) Мы экономим на аренде Стоит задача: с VPS на линуксе запускать процесс на VDS. Написал простой сервер на Flask. Да, не люблю питон, но что поделать — на нем написать этот сервер быстрее и проще. Он выполняет 3 функции: 1) Запуск сервера с конфигурацией — выбор карты, количества игроков для старта игры, и набор плагинов. Про плагины сейчас не буду писать — это отдельная история с литрами кофе по ночам вперемешку со слезами и вырванными волосами. 2) Остановка/перезапуск сервера в случае неудачных подключений, которые мы можем обработать только вручную Тут все просто, примеры кода даже неуместны. Скрипт на 100 строчек Итак, когда 10 человек собрались вместе и приняли игру, запущен сервер и все жаждут играть, в личные сообщения приходит ссылка на подключение к игре. По нажатию ссылки игрока коннектит к игровому серверу, и дальше уже само все. Через ~25 минут виртуальная «комната» с игроками очищается. Заранее извиняюсь за нескладность статьи, давно не писал сюда, да и кода слишком много, чтобы выделить важные участки. Лапша, короче. Если увижу интерес к теме, то будет вторая часть — в ней будут мои мучения с плагинами для srcds(Source dedicated server), и, наверное, система рейтинга и мини-dotabuff, сайт со статистикой игр. Немного ссылок: 1) Наш сайт(статистика, таблица лидеров, небольшой лендос и скачивание клиента) 2) Discord сервер =========== Источник: habr.com =========== Похожие новости:
Серверное администрирование ), #_typescript, #_igry_i_igrovye_pristavki ( Игры и игровые приставки ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:49
Часовой пояс: UTC + 5