Scriem matchmaking pentru Dota din 2014

Salutare tuturor.

Primăvara aceasta am dat peste un proiect în care niște tineri au reușit să lanseze un server Dota 2 versiunea 2014 și, prin urmare, să joace pe el. Sunt un mare fan al acestui joc și nu am putut să trec cu vederea oportunitatea unică de a reveni în copilărie.

M-am implicat foarte mult și așa a ieșit că am scris un bot pentru Discord, care se ocupă practic de toate funcționalitățile neacceptate în versiunea veche a jocului, mai exact matchmaking.
Înainte de toate inovațiile cu botul, lobby-ul era creat manual. Strângeam 10 reacții la mesaj și construia manual serverul sau găzduiam un lobby local.

Scriem matchmaking pentru Dota din 2014

Natura mea de programator nu a suportat o astfel de cantitate de muncă manuală, așa că într-o singură noapte am conturat cea mai simplă versiune a botului, care ridica automat serverul atunci când se adunau 10 persoane.

Am decis să scriu rapid pe nodejs, deoarece nu mă prea plac pythonul și mă simt mai confortabil în acest mediu.

Aceasta este prima mea experiență de scriere a unui bot pentru Discord, dar s-a dovedit a fi foarte simplă. Modulul oficial npm discord.js oferă o interfață convenabilă pentru gestionarea mesajelor, colectarea reacțiilor etc.

Declinare de responsabilitate: toate exemplele de cod sunt „actuale”, adică au trecut prin mai multe iterații de rescriere pe timpul nopții.

Baza matchmaking-ului este „coada”, în care sunt plasați jucătorii care doresc să joace și sunt scoși când nu mai vor să joace sau când au găsit un joc.

Așa arată entitatea „jucător”. Inițial a fost doar id-ul utilizatorului în Discord, dar în plan sunt un launcher/căutare de joc de pe site, dar să le luăm pe rând.

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

Iată interfața cozii. Aici, în loc de „jucători”, se folosește o abstracție sub formă de „grup”. Pentru un jucător singular, grupul constă din el însuși, iar pentru jucătorii dintr-un grup, din toți jucătorii grupului.

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 schimba contextul. Se potrivește pentru cazuri – la evenimentul „joc găsit pentru 10 persoane” se poate trimite un mesaj direct jucătorilor și se poate executa logica principală de afaceri – să se lanseze o sarcină pentru a verifica pregătirea, să prepare lobby-ul pentru pornire și așa mai departe.

Pentru IOC folosesc InversifyJS. Am o experiență plăcută cu această bibliotecă. Rapid și simplu!

Avem mai multe cozi pe server – s-au adăugat moduri 1x1, obișnuit/ranked și câteva personalizate. Așadar, 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))) {
            // dacă coada are această echipă, re-adăugăm echipa
            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))) {
            // dacă coada are această echipă, re-adăugăm echipa
            q.remove(event.party)
          }
        });
      }
    );
  }

(Codul este o reprezentare, pentru a ilustra cum arată procesele)

Aici inițializez coada pentru fiecare din modurile de joc implementate, și totodată ascult schimbările „grupurilor”, pentru a ajusta cozile și a evita unele conflicte.

Astfel, sunt grozav, am inserat fragmente de cod care nu au nicio legătură cu subiectul, iar acum să trecem deja la matchmaking.

Să luăm în considerare cazul:

1) Utilizatorul vrea să joace.

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

Scriem matchmaking pentru Dota din 2014

3) Acest gateway merge la RoomService și spune „Utilizatorul din Discord vrea să intre în coadă, modul: joc neclasificat”.

4) RoomService acceptă cererea gateway-ului și adaugă utilizatorul în coada corespunzătoare (mai exact, grupul utilizatorului).

5) Cua fiecare modificare, coada verifică dacă sunt destui jucători pentru a juca. Dacă da – emisăm un eveniment:

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

6) RoomService, evident, ascultă cu plăcere fiecare coadă în așteptarea emoționantă a acestui eveniment. La intrare, primim o listă de jucători, formăm o „cameră” virtuală și, desigur, emitim evenimentul:

queue.addListener("room-found", (event: RoomFoundEvent) => {
      console.log(
        `Mod cameră găsit: [${mode}]. Timp pentru a obține o cameră gratuită pentru acești băieți`
      );
      const room = this.getFreeRoom(mode);
      room.fill(event.players);

      this.onRoomFormed(room);
    });

7) Am ajuns la „instanța superioară” — clasa Bot. În general, se ocupă de legătura între gateway-uri (cât de amuzant arată asta în rusă nu pot) și logica de afaceri a matchmaking-ului. Botul ascultă evenimentul și ordonă DiscordGateway-ului să trimită tuturor utilizatorilor o verificare a pregătirii.

Scriem matchmaking pentru Dota din 2014

8) Dacă cineva a refuzat sau nu a acceptat jocul în 3 minute, nu îi returnăm în coadă. Pe toți ceilalți îi returnăm în coadă și așteptăm să se adune din nou 10 persoane. Dacă toți jucătorii au acceptat jocul, atunci începe partea interesantă.

Configurarea serverului dedicat

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

  1. Nu există Docker, ceea ce m-a lovit direct în inimă
  2. Economisim la închiriere

Obiectivul este: să ruleze un proces pe VDS din VPS pe Linux. Am scris un server simplu pe Flask. Da, nu-mi place Python, dar ce pot face — să scriu acest server este mai repede și mai simplu.

El îndeplinește 3 funcții:

  1. Pornirea serverului cu configurația — alegerea hărții, numărul de jucători pentru a începe jocul și setul de pluginuri. Despre pluginuri nu voi scrie acum — este o poveste separată cu litri de cafea noaptea amestecate cu lacrimi și fire smulse.
  2. Oprirea/restartarea serverului în cazul conexiunilor ratate, pe care le putem gestiona doar manual.

Aici totul este simplu, exemplele de cod sunt chiar nepotrivite. Scriptul are 100 de linii

Așadar, când 10 persoane s-au adunat împreună și au acceptat jocul, serverul este pornit și toți sunt dornici să joace, în mesajele private vine un link pentru a se conecta la joc.

Scriem matchmaking pentru Dota din 2014

Când jucătorul dă clic pe link, acesta se conectează la serverul de joc și apoi totul decurge de la sine. După aproximativ 25 de minute, „camera” virtuală cu jucătorii se golește.

Îmi cer scuze pentru neclaritatea articolului, de mult nu am scris aici și există prea mult cod pentru a scoate în evidență părțile importante. Este o supă de tăiței, pe scurt.

Dacă voi observa interes pentru subiect, va exista o a doua parte – în care voi povesti despre dificultățile mele cu pluginurile pentru srcds (Server dedicat sursă) și, probabil, un sistem de rating și un mini-dotabuff, un site pentru statistici de jocuri.

Câteva linkuri:

  1. Site-ul nostru (statistici, tabelul liderilor, o mică pagină de aterrizare și descărcarea clientului)
  2. Server Discord

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster