Scriem matchmaking pentru Dota 2014

Buna ziua.

În această primăvară am dat peste un proiect în care băieții au învățat să ruleze serverul Dota 2 versiunea 2014 și, în consecință, să se joace cu el. Sunt un mare fan al acestui joc și nu puteam rata această ocazie unică de a mă scufunda în copilăria mea.

M-am scufundat foarte adânc și s-a întâmplat să scriu un bot Discord care este responsabil pentru aproape toată funcționalitatea care nu este acceptată în versiunea veche a jocului, și anume matchmaking.
Înainte de toate inovațiile cu botul, lobby-ul a fost creat manual. Am colectat 10 reacții la un mesaj și am asamblat manual un server sau am găzduit un lobby local.

Scriem matchmaking pentru Dota 2014

Natura mea de programator nu a rezistat atât de multă muncă manuală și peste noapte am schițat cea mai simplă versiune a botului, care a ridicat automat serverul când erau 10 persoane.

Am decis imediat să scriu în nodejs, pentru că nu prea îmi place Python și mă simt mai confortabil în acest mediu.

Aceasta este prima mea experiență când scriu un bot pentru Discord, dar s-a dovedit a fi foarte simplu. Modulul oficial npm discord.js oferă o interfață convenabilă pentru lucrul cu mesaje, colectarea reacțiilor etc.

Disclaimer: Toate exemplele de cod sunt „actuale”, ceea ce înseamnă că au trecut prin mai multe iterații de rescriere noaptea.

Baza matchmaking-ului este o „coadă” în care jucătorii care doresc să joace sunt plasați și eliminați atunci când nu doresc sau nu găsesc un joc.

Așa arată esența unui „jucător”. Inițial a fost doar un ID de utilizator în Discord, dar există planuri de lansare/căutare de jocuri de pe site, dar mai întâi.

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);
  }
}

Și aici este interfața de coadă. Aici, în loc de „jucători”, este folosită o abstractizare sub forma unui „grup”. Pentru un singur jucător, grupul este format din el însuși, iar pentru jucătorii dintr-un grup, respectiv, din toți jucătorii din grup.

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
}

Am decis să folosesc evenimente pentru a face schimb de context. A fost potrivit pentru cazuri - la evenimentul „a fost găsit un joc pentru 10 persoane”, puteți trimite mesajul necesar jucătorilor în mesaje private și puteți efectua logica de bază de afaceri - lansați o sarcină pentru a verifica pregătirea, pregătiți lobby-ul pentru lansare și așa mai departe.

Pentru IOC folosesc InversifyJS. Am o experiență plăcută de lucru cu această bibliotecă. Rapid și ușor!

Avem mai multe cozi pe serverul nostru - am adăugat 1x1, normal/evaluat și câteva moduri personalizate. Prin urmare, există un Singleton RoomService care se află între utilizator și căutarea jocului.

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)
          }
        });
      }
    );
  }

(Codați tăiței pentru a vă oferi o idee despre cum arată procesele în general)

Aici inițializez coada pentru fiecare dintre modurile de joc implementate și, de asemenea, ascult schimbările în „grupuri” pentru a ajusta cozile și pentru a evita unele conflicte.

Așa că, bravo, am inserat bucăți de cod care nu au nicio legătură cu subiectul, iar acum să trecem direct la matchmaking.

Să luăm în considerare cazul:

1) Utilizatorul dorește să joace.

2) Pentru a începe căutarea, folosește Gateway=Discord, adică pune o reacție la mesaj:

Scriem matchmaking pentru Dota 2014

3) Acest gateway merge la RoomService și spune „Un utilizator din discord vrea să intre în coadă, modul: joc neevaluat.”

4) RoomService acceptă cererea gateway-ului și împinge utilizatorul (mai precis, grupul de utilizatori) în coada dorită.

5) Coada se verifică de fiecare dată când există destui jucători pentru a juca. Dacă este posibil, emite un eveniment:

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

6) RoomService ascultă cu bucurie fiecare coadă în așteptarea nerăbdătoare a acestui eveniment. Primim o listă de jucători ca intrare, formăm o „camera” virtuală de la ei și, desigur, emitem un eveniment:

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) Așa că am ajuns la „cea mai înaltă” autoritate - clasa Bot. În general, el se ocupă de legătura dintre gateway-uri (nu înțeleg cât de amuzant arată în rusă) și logica de afaceri a matchmaking-ului. Botul aude evenimentul și ordonă DiscordGateway să trimită o verificare a pregătirii tuturor utilizatorilor.

Scriem matchmaking pentru Dota 2014

8) Dacă cineva respinge sau nu acceptă jocul în 3 minute, atunci NU îl readucem la coadă. Îi întoarcem pe toți ceilalți la coadă și așteptăm până când sunt din nou 10 persoane. Dacă toți jucătorii au acceptat jocul, atunci începe partea interesantă.

Configurare server dedicat

Jocurile noastre sunt găzduite pe VDS cu Windows Server 2012. Din aceasta putem trage câteva concluzii:

  1. Nu există niciun docker pe el, ceea ce m-a lovit în inimă
  2. Economisim la chirie

Sarcina este să rulați un proces pe VDS de la un VPS pe Linux. Am scris un server simplu în Flask. Da, nu-mi place Python, dar ce poți face? Este mai rapid și mai ușor să scrii acest server pe el.

Îndeplinește 3 funcții:

  1. Pornirea unui server cu o configurație - selectarea unei hărți, a numărului de jucători pentru a începe jocul și a unui set de pluginuri. Nu voi scrie acum despre pluginuri - asta este o poveste diferită, cu litri de cafea noaptea amestecați cu lacrimi și păr rupt.
  2. Oprirea/repornirea serverului în cazul conexiunilor nereușite, pe care le putem gestiona doar manual.

Totul este simplu aici, exemplele de cod nici măcar nu sunt potrivite. Script de 100 de linii

Așadar, când 10 persoane s-au adunat și au acceptat jocul, serverul a fost lansat și toată lumea era dornici să se joace, un link pentru a se conecta la joc a fost trimis în mesaje private.

Scriem matchmaking pentru Dota 2014

Făcând clic pe link, jucătorul se conectează la serverul de joc și asta este tot. După ~25 de minute, „camera” virtuală cu jucători este golită.

Îmi cer scuze anticipat pentru incomoditatea articolului, nu am mai scris aici de mult timp și există prea mult cod pentru a evidenția secțiuni importante. Taitei, pe scurt.

Dacă văd interes pentru subiect, va fi o a doua parte - va conține chinul meu cu pluginuri pentru srcds (Source dedicated server), și, probabil, un sistem de rating și mini-dotabuff, un site cu statistici de joc.

Cateva link-uri:

  1. Site-ul nostru web (statistici, clasament, pagină mică de destinație și descărcare client)
  2. Serverul Discord

Sursa: www.habr.com

Adauga un comentariu