Здравейте на всички.
Тази пролет попаднах на проект, в който момчетата се научиха как да стартират сървърната версия на Dota 2 2014 и съответно да играят на нея. Аз съм голям фен на тази игра и не можех да пропусна тази уникална възможност да се потопя в детството си.
Гмурнах се много дълбоко и се случи така, че написах бот на Discord, който отговаря за почти цялата функционалност, която не се поддържа в старата версия на играта, а именно намирането на мачове.
Преди всички нововъведения с бота, лобито се създаваше ръчно. Събрахме 10 реакции на съобщение и ръчно сглобихме сървър или хоствахме локално лоби.
Природата ми на програмист не можеше да издържи толкова много ръчна работа и за една нощ начертах най-простата версия на бота, който автоматично вдигаше сървъра, когато имаше 10 души.
Веднага реших да пиша в nodejs, защото не харесвам много Python и се чувствам по-комфортно в тази среда.
Това е първият ми опит в писането на бот за Discord, но се оказа много просто. Официалният npm модул discord.js предоставя удобен интерфейс за работа със съобщения, събиране на реакции и др.
Отказ от отговорност: Всички примери на кодове са „актуални“, което означава, че са преминали през няколко повторения на пренаписване през нощта.
Основата на сватовството е „опашка“, в която играчите, които искат да играят, се поставят и премахват, когато не искат или не намерят игра.
Ето как изглежда същността на „играча“. Първоначално това беше просто потребителско име в 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. Имам приятно изживяване от работата с тази библиотека. Бързо и лесно!
Имаме няколко опашки на нашия сървър - добавихме 1x1, нормален/оценен и няколко персонализирани режима. Следователно има единичен 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 и казва „Потребител от discord иска да влезе в опашката, режим: неоценена игра.“
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 с Windows server 2012. От това можем да направим няколко заключения:
- На него няма докер, което ме удари в сърцето
- Спестяваме от наем
Задачата е да стартирате процес на VDS от VPS на Linux. Написах прост сървър във Flask. Да, не харесвам Python, но какво можете да направите? По-бързо и по-лесно е да напишете този сървър на него.
Изпълнява 3 функции:
- Стартиране на сървър с конфигурация - избор на карта, брой играчи за стартиране на играта и набор от плъгини. Сега няма да пиша за плъгини - това е друга история с литрите кафе през нощта, примесени със сълзи и накъсани коси.
- Спиране/рестартиране на сървъра при неуспешни връзки, които можем да обработим само ръчно.
Тук всичко е просто, примерите за код дори не са подходящи. 100 редов скрипт
И така, когато 10 души се събраха и приеха играта, сървърът беше стартиран и всички бяха нетърпеливи да играят, линк за връзка с играта беше изпратен в лични съобщения.
Щраквайки върху връзката, играчът се свързва със сървъра на играта и това е всичко. След ~25 минути виртуалната „стая“ с играчи се изчиства.
Предварително се извинявам за неудобството на статията, не съм писал тук от дълго време и има твърде много код за подчертаване на важни секции. Юфка, накратко.
Ако видя интерес към темата, ще има втора част - тя ще съдържа моите терзания с плъгини за srcds (Source посветен сървър), и, вероятно, система за рейтинг и mini-dotabuff, сайт със статистика на играта.
Някои връзки:
Източник: www.habr.com