Píšeme matchmaking pre Dota 2014

Ahoj všetci

Túto jar som narazil na projekt, v ktorom sa chlapci naučili, ako spustiť server Dota 2 verzie 2014, a podľa toho na ňom hrať. Som veľkým fanúšikom tejto hry a nemohol som si nechať ujsť túto jedinečnú príležitosť ponoriť sa do detstva.

Ponoril som sa veľmi hlboko, a tak sa stalo, že som napísal Discord bota, ktorý je zodpovedný za takmer všetky funkcie, ktoré nie sú podporované v starej verzii hry, konkrétne matchmaking.
Pred všetkými inováciami s robotom bola lobby vytvorená ručne. Zozbierali sme 10 reakcií na správu a ručne sme zostavili server alebo sme hostili miestnu lobby.

Píšeme matchmaking pre Dota 2014

Moja povaha programátora nevydržala toľko ručnej práce a cez noc som načrtol najjednoduchšiu verziu bota, ktorý automaticky zdvihol server, keď tam bolo 10 ľudí.

Okamžite som sa rozhodol písať v nodejs, pretože nemám veľmi rád Python a v tomto prostredí sa cítim pohodlnejšie.

Toto je moja prvá skúsenosť s písaním robota pre Discord, ale ukázalo sa, že je to veľmi jednoduché. Oficiálny npm modul discord.js poskytuje pohodlné rozhranie pre prácu so správami, zbieranie reakcií atď.

Zrieknutie sa zodpovednosti: Všetky príklady kódu sú „aktuálne“, čo znamená, že prešli niekoľkými iteráciami prepisovania v noci.

Základom matchmakingu je „poradie“, v ktorom sú hráči, ktorí chcú hrať, umiestnení a odstránení, keď nechcú alebo nájdu hru.

Takto vyzerá podstata „hráča“. Spočiatku to bolo len ID používateľa v Discord, ale existujú plány na spustenie/vyhľadanie hier zo stránky, ale najprv.

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 tu je rozhranie frontu. Tu sa namiesto „hráčov“ používa abstrakcia vo forme „skupiny“. Pre jedného hráča sa skupina skladá z neho samého a pre hráčov v skupine zo všetkých hráčov v skupine.

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
}

Rozhodol som sa využiť udalosti na výmenu kontextu. Bolo to vhodné pre prípady - pri udalosti „našla sa hra pre 10 ľudí“, môžete hráčom poslať potrebnú správu v súkromných správach a vykonať základnú obchodnú logiku - spustiť úlohu na kontrolu pripravenosti, pripraviť lobby na spustenie a pod.

Pre IOC používam InversifyJS. Mám príjemné skúsenosti s prácou s touto knižnicou. Rýchle a jednoduché!

Na našom serveri máme niekoľko frontov – pridali sme 1x1, normálny/hodnotený a niekoľko vlastných režimov. Preto existuje singleton RoomService, ktorý leží medzi používateľom a vyhľadávaní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)
          }
        });
      }
    );
  }

(Napíšte rezance, aby ste získali predstavu o tom, ako procesy zhruba vyzerajú)

Tu inicializujem front pre každý z implementovaných herných režimov a tiež počúvam zmeny v „skupinách“, aby som upravil fronty a predišiel niektorým konfliktom.

Takže výborne, vložil som kúsky kódu, ktoré nemajú nič spoločné s témou, a teraz prejdime priamo k matchmakingu.

Zoberme si prípad:

1) Používateľ chce hrať.

2) Na spustenie vyhľadávania použije Gateway=Discord, to znamená, že zareaguje na správu:

Píšeme matchmaking pre Dota 2014

3) Táto brána prejde do RoomService a povie „Používateľ z discordu chce vstúpiť do frontu, režim: neohodnotená hra.“

4) RoomService prijme požiadavku brány a zatlačí používateľa (presnejšie skupinu používateľov) do požadovaného poradia.

5) Fronta kontroluje vždy, keď je dostatok hráčov na hranie. Ak je to možné, vygenerujte udalosť:

private onRoomFound(players: Party[]) {
    this.emit("room-found", {
      players,
    });
  }

6) RoomService zjavne s radosťou počúva každý rad v napätom očakávaní tejto udalosti. Ako vstup dostaneme zoznam hráčov, vytvoríme z nich virtuálnu „miestnosť“ a, samozrejme, vydáme udalosť:

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) Tak sme sa dostali k „najvyššej“ autorite – triede Bot. Vo všeobecnosti sa zaoberá prepojením medzi bránami (nechápem, ako vtipne to vyzerá v ruštine) a obchodnou logikou matchmakingu. Robot si vypočuje udalosť a nariadi DiscordGateway, aby poslala kontrolu pripravenosti všetkým používateľom.

Píšeme matchmaking pre Dota 2014

8) Ak niekto hru odmietne alebo neprijme do 3 minút, NEVRÁTAME ho do poradia. Všetkých ostatných vraciame do radu a čakáme, kým bude opäť 10 ľudí. Ak všetci hráči prijali hru, začína sa zaujímavá časť.

Konfigurácia dedikovaného servera

Naše hry sú hosťované na VDS s Windows serverom 2012. Z toho môžeme vyvodiť niekoľko záverov:

  1. Nie je na ňom žiadny doker, čo ma zasiahlo do srdca
  2. Šetríme na nájomnom

Úlohou je spustiť proces na VDS z VPS na Linuxe. Napísal som jednoduchý server vo Flasku. Áno, nemám rád Python, ale čo môžete robiť? Napísať tento server na ňom je rýchlejšie a jednoduchšie.

Vykonáva 3 funkcie:

  1. Spustenie servera s konfiguráciou - výber mapy, počtu hráčov na spustenie hry a sady pluginov. Nebudem teraz písať o pluginoch - to je iný príbeh s litrami kávy v noci zmiešanými so slzami a vytrhanými vlasmi.
  2. Zastavenie/reštart servera v prípade neúspešných spojení, ktoré môžeme riešiť iba manuálne.

Všetko je tu jednoduché, príklady kódu nie sú ani vhodné. 100 riadkový skript

Keď sa teda 10 ľudí zišlo a prijalo hru, server bol spustený a všetci sa chystali hrať, v súkromných správach bol odoslaný odkaz na pripojenie k hre.

Píšeme matchmaking pre Dota 2014

Kliknutím na odkaz sa hráč pripojí k hernému serveru a je to. Po ~25 minútach sa virtuálna „miestnosť“ s hráčmi vyčistí.

Vopred sa ospravedlňujem za nešikovnosť článku, dlho som sem nepísal a je tu príliš veľa kódu na zvýraznenie dôležitých častí. Skrátka rezance.

Ak uvidím záujem o tému, bude aj druhá časť - bude obsahovať moje trápenie s pluginmi pre srcds (Source dedikovaný server) a pravdepodobne aj systém hodnotenia a mini-dotabuff, stránku s hernými štatistikami.

Nejaké odkazy:

  1. Naša webová stránka (štatistiky, rebríček, malá vstupná stránka a stiahnutie klienta)
  2. Discord server

Zdroj: hab.com

Pridať komentár