Ahoj všichni
Letos na jaře jsem narazil na projekt, ve kterém se kluci naučili provozovat server Dota 2 verze 2014 a podle toho na něm hrát. Jsem velkým fanouškem této hry a nemohl jsem si nechat ujít tuto jedinečnou příležitost ponořit se do svého dětství.
Ponořil jsem se velmi hluboko, a tak se stalo, že jsem napsal Discord bota, který je zodpovědný za téměř všechny funkce, které nejsou podporovány ve staré verzi hry, konkrétně matchmaking.
Před všemi inovacemi s botem byla lobby vytvořena ručně. Shromáždili jsme 10 reakcí na zprávu a ručně jsme sestavili server nebo hostili místní lobby.
Moje povaha programátora nevydržela tolik ruční práce a přes noc jsem načrtl nejjednodušší verzi bota, který automaticky zvedl server, když bylo 10 lidí.
Okamžitě jsem se rozhodl psát v nodejs, protože nemám moc rád Python a v tomto prostředí se cítím pohodlněji.
Toto je moje první zkušenost s psaním bota pro Discord, ale ukázalo se, že je to velmi jednoduché. Oficiální npm modul discord.js poskytuje pohodlné rozhraní pro práci se zprávami, sběr reakcí atp.
Upozornění: Všechny příklady kódu jsou „aktuální“, což znamená, že prošly několika iteracemi přepisování v noci.
Základem matchmakingu je „fronta“, ve které jsou hráči, kteří chtějí hrát, umístěni a odstraněni, když nechtějí nebo najdou hru.
Tak vypadá podstata „hráče“. Zpočátku to bylo jen ID uživatele v Discordu, ale existují plány na spuštění/vyhledání her z webu, ale nejdřív.
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);
}
}
A zde je rozhraní fronty. Zde se místo „hráčů“ používá abstrakce ve formě „skupiny“. Pro jednoho hráče se skupina skládá z něj samotného a pro hráče ve skupině ze všech hráčů ve skupině.
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
}
Rozhodl jsem se využít události k výměně kontextu. Bylo to vhodné pro případy - při události „našla se hra pro 10 lidí“, můžete hráčům poslat potřebnou zprávu v soukromých zprávách a provést základní obchodní logiku - spustit úlohu pro kontrolu připravenosti, připravit lobby pro spuštění a tak dále.
Pro IOC používám InversifyJS. Mám příjemnou zkušenost s touto knihovnou. Rychlé a snadné!
Na našem serveru máme několik front – přidali jsme 1x1, normální/hodnocený a několik vlastních režimů. Proto existuje singleton RoomService, který leží mezi uživatelem a hledáním hry.
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)
}
});
}
);
}
(Napište nudle, abyste měli představu, jak procesy zhruba vypadají)
Zde inicializujem frontu pro každý z implementovaných herních režimů a také poslouchám změny ve „skupinách“, abych upravil fronty a vyhnul se některým konfliktům.
Takže výborně, vložil jsem kousky kódu, které nemají s tématem nic společného, a nyní přejděme přímo k matchmakingu.
Podívejme se na případ:
1) Uživatel chce hrát.
2) Pro spuštění vyhledávání použije Gateway=Discord, to znamená, že na zprávu reaguje:
3) Tato brána přejde do RoomService a řekne „Uživatel z discordu chce vstoupit do fronty, režim: neohodnocená hra.“
4) RoomService přijme požadavek brány a přesune uživatele (přesněji skupinu uživatelů) do požadované fronty.
5) Fronta kontroluje pokaždé, když je dostatek hráčů ke hře. Pokud je to možné, vygenerujte událost:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService zjevně s radostí poslouchá každou frontu v napjatém očekávání této události. Jako vstup obdržíme seznam hráčů, vytvoříme z nich virtuální „místnost“ a samozřejmě vydáme událost:
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) Dostali jsme se tedy k „nejvyšší“ autoritě – třídě Bot. Obecně se zabývá spojením mezi bránami (nechápu, jak vtipně to vypadá v ruštině) a obchodní logikou matchmakingu. Bot zaslechne událost a nařídí DiscordGateway, aby všem uživatelům odeslala kontrolu připravenosti.
8) Pokud někdo hru odmítne nebo nepřijme do 3 minut, NEVRÁTÍME ho do fronty. Všechny ostatní vracíme do fronty a čekáme, až bude opět 10 lidí. Pokud všichni hráči hru přijali, začíná zajímavá část.
Konfigurace vyhrazeného serveru
Naše hry jsou hostovány na VDS s Windows serverem 2012. Z toho můžeme vyvodit několik závěrů:
- Není na něm žádný docker, což mě zasáhlo u srdce
- Šetříme na nájemném
Úkolem je spustit proces na VDS z VPS na Linuxu. Napsal jsem jednoduchý server ve Flasku. Ano, nemám rád Python, ale co se dá dělat? Je rychlejší a jednodušší napsat tento server na něj.
Plní 3 funkce:
- Spuštění serveru s konfigurací - výběr mapy, počtu hráčů pro spuštění hry a sady pluginů. Nebudu teď psát o pluginech - to je jiný příběh s litry kávy v noci smíchanými se slzami a vytrhanými vlasy.
- Zastavení/restart serveru v případě neúspěšného připojení, které můžeme řešit pouze ručně.
Všechno je zde jednoduché, příklady kódu nejsou ani vhodné. 100 řádkový skript
Když se tedy sešlo 10 lidí a hru přijali, server byl spuštěn a všichni se těšili na hraní, v soukromých zprávách byl zaslán odkaz na připojení ke hře.
Kliknutím na odkaz se hráč připojí k hernímu serveru a je to. Po ~25 minutách se virtuální „místnost“ s hráči vyčistí.
Předem se omlouvám za nešikovnost článku, dlouho jsem sem nepsal a je tam příliš mnoho kódu na zvýraznění důležitých sekcí. Nudle, zkrátka.
Pokud uvidím zájem o téma, bude druhý díl - bude obsahovat moje muka s pluginy pro srcds (Source dedikovaný server) a pravděpodobně i systém hodnocení a mini-dotabuff, web se statistikami her.
Nějaké odkazy:
Zdroj: www.habr.com