Pisanje matchmakinga za Dota 2014

Pozdrav svima.

Ovog proljeća sam naišao na projekat u kojem su momci naučili kako pokrenuti Dota 2 server verziju 2014 i, shodno tome, igrati na njemu. Veliki sam obožavatelj ove igre i nisam mogao propustiti ovu jedinstvenu priliku da uronim u svoje djetinjstvo.

Zaronio sam jako duboko, i dogodilo se da sam napisao Discord bota koji je odgovoran za gotovo sve funkcionalnosti koje nisu podržane u staroj verziji igre, odnosno matchmaking.
Prije svih inovacija s botom, lobi je kreiran ručno. Prikupili smo 10 reakcija na poruku i ručno sastavili server ili ugostili lokalni lobi.

Pisanje matchmakinga za Dota 2014

Moja priroda programera nije mogla da izdrži toliki ručni rad i preko noći sam skicirao najjednostavniju verziju bota, koji je automatski podigao server kada je bilo 10 ljudi.

Odmah sam odlučio da pišem u nodejs-u, jer ne volim baš Python, a osjećam se ugodnije u ovom okruženju.

Ovo je moje prvo iskustvo pisanja bota za Discord, ali se pokazalo vrlo jednostavnim. Zvanični npm modul discord.js pruža zgodan interfejs za rad sa porukama, prikupljanje reakcija itd.

Odricanje od odgovornosti: Svi primjeri koda su "trenutni", što znači da su prošli kroz nekoliko iteracija prepisivanja noću.

Osnova matchmakinga je „red“ u koji se igrači koji žele igrati stavljaju i uklanjaju kada to ne žele ili pronađu igru.

Ovako izgleda suština "igrača". U početku je to bio samo korisnički ID u Discordu, ali postoje planovi za pokretanje/pretraživanje igara sa stranice, ali prvo.

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 evo i interfejsa za red čekanja. Ovdje se umjesto "igrača" koristi apstrakcija u obliku "grupe". Za jednog igrača, grupu čini on, a za igrače u grupi, respektivno, svi igrači u grupi.

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
}

Odlučio sam koristiti događaje za razmjenu konteksta. Bilo je pogodno za slučajeve - na događaju "nađena je igra za 10 ljudi", možete poslati potrebnu poruku igračima u privatnim porukama, te provesti osnovnu poslovnu logiku - pokrenuti zadatak da provjerite spremnost, pripremite predvorje za lansiranje i tako dalje.

Za IOC koristim InversifyJS. Imam ugodno iskustvo rada sa ovom bibliotekom. Brzo i lako!

Imamo nekoliko redova na našem serveru - dodali smo 1x1, normalni/ocenjeni i nekoliko prilagođenih modova. Stoga postoji jednostruki RoomService koji se nalazi između korisnika i pretraživanja igre.

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

(Kodirajte rezance da biste dobili predstavu o tome kako procesi otprilike izgledaju)

Ovdje inicijaliziram red za svaki od implementiranih modova igre, a također osluškujem promjene u “grupama” kako bih prilagodio redove i izbjegao neke konflikte.

Dakle, bravo, ubacio sam delove koda koji nemaju veze sa temom, a sada idemo direktno na matchmaking.

Hajde da razmotrimo slučaj:

1) Korisnik želi da igra.

2) Da bi započeo pretragu, koristi Gateway=Discord, odnosno stavlja reakciju na poruku:

Pisanje matchmakinga za Dota 2014

3) Ovaj gateway ide na RoomService i kaže “Korisnik iz diskorda želi ući u red, način: igra bez ocjene.”

4) RoomService prihvaća zahtjev gateway-a i gura korisnika (tačnije, korisničku grupu) u željeni red čekanja.

5) Red provjerava svaki put kada ima dovoljno igrača za igru. Ako je moguće, emitujte događaj:

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

6) RoomService očito rado sluša svaki red u tjeskobnom iščekivanju ovog događaja. Dobijamo listu igrača kao ulaz, formiramo virtuelnu „sobu“ od njih i, naravno, izdajemo događaj:

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) Tako smo došli do „najvišeg” autoriteta – klase Bot. Općenito, on se bavi vezom između gateway-a (ne mogu razumjeti koliko smiješno izgleda na ruskom) i poslovne logike provodadžisanja. Bot čuje događaj i naređuje DiscordGatewayu da pošalje provjeru spremnosti svim korisnicima.

Pisanje matchmakinga za Dota 2014

8) Ako neko odbije ili ne prihvati igru ​​u roku od 3 minuta, onda ga NE vraćamo u red. Sve ostale vraćamo u red i čekamo da ih opet bude 10. Ako su svi igrači prihvatili igru, tada počinje zanimljivi dio.

Konfiguracija namjenskog servera

Naše igre se nalaze na VDS-u sa Windows serverom 2012. Iz ovoga možemo izvući nekoliko zaključaka:

  1. Na njemu nema dokera koji me je pogodio u srce
  2. Štedimo na renti

Zadatak je pokrenuti proces na VDS-u sa VPS-a na Linuxu. Napisao sam jednostavan server u Flasku. Da, ne volim Python, ali šta možete učiniti? Brže je i lakše napisati ovaj server na njemu.

Obavlja 3 funkcije:

  1. Pokretanje servera sa konfiguracijom - odabir mape, broj igrača za pokretanje igre i set dodataka. Neću sad pisati o dodacima - to je druga priča sa litrama kafe noću pomiješane sa suzama i počupanom kosom.
  2. Zaustavljanje/ponovno pokretanje servera u slučaju neuspešnih konekcija, što možemo da obradimo samo ručno.

Ovdje je sve jednostavno, primjeri koda nisu ni prikladni. Skripta od 100 linija

Dakle, kada se 10 ljudi okupilo i prihvatilo igru, server je pokrenut i svi su bili željni igre, u privatnim porukama je poslat link za povezivanje na igru.

Pisanje matchmakinga za Dota 2014

Klikom na link, igrač se povezuje na server igre i to je to. Nakon ~25 minuta, virtuelna "soba" sa igračima je očišćena.

Unaprijed se izvinjavam na nespretnosti članka, dugo nisam ovdje pisao, a previše je koda za isticanje važnih dijelova. Rezanci, ukratko.

Ako vidim interesovanje za temu, biće i drugi deo - sadržaće moju muku sa dodacima za srcds (izvorni namenski server), i, verovatno, sistem ocenjivanja i mini-dotabuff, sajt sa statistikom igre.

Neki linkovi:

  1. Naša web stranica (statistika, rang lista, mala odredišna stranica i preuzimanje klijenta)
  2. Discord server

izvor: www.habr.com

Dodajte komentar