Pišemo iskanje ujemalcev za Doto 2014

Pozdravljeni vsi.

To pomlad sem naletel na projekt, v katerem so se fantje naučili zagnati različico strežnika Dota 2 2014 in s tem igrati na njej. Sem velik oboževalec te igre in nisem mogel izpustiti te edinstvene priložnosti, da se potopim v svoje otroštvo.

Poglobil sem se zelo globoko in tako se je zgodilo, da sem napisal bota Discord, ki je odgovoren za skoraj vse funkcije, ki niso podprte v stari različici igre, in sicer za povezovanje.
Pred vsemi novostmi z botom je bil lobby ustvarjen ročno. Zbrali smo 10 odzivov na sporočilo in ročno sestavili strežnik ali gostili lokalni lobby.

Pišemo iskanje ujemalcev za Doto 2014

Moja programerska narava ni zdržala toliko ročnega dela in čez noč sem skiciral najpreprostejšo verzijo bota, ki je samodejno dvignil strežnik, ko je bilo 10 ljudi.

Takoj sem se odločil, da bom pisal v nodejsu, ker mi Python ni ravno všeč in se v tem okolju počutim bolj udobno.

To je moja prva izkušnja s pisanjem bota za Discord, vendar se je izkazalo za zelo preprosto. Uradni modul npm discord.js ponuja priročen vmesnik za delo s sporočili, zbiranje odzivov itd.

Zavrnitev odgovornosti: vsi primeri kode so »trenutni«, kar pomeni, da so šli skozi več iteracij prepisovanja ponoči.

Osnova povezovanja je "čakalna vrsta", v katero so igralci, ki želijo igrati, uvrščeni in odstranjeni, ko ne želijo ali najdejo igre.

Tako je videti bistvo "igralca". Sprva je bil to samo ID uporabnika v Discordu, vendar obstajajo načrti za zagon/iskanje iger s spletnega mesta, a najprej.

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

In tukaj je vmesnik čakalne vrste. Tukaj je namesto "igralcev" uporabljena abstrakcija v obliki "skupine". Pri posameznem igralcu skupino sestavlja on sam, pri igralcih v skupini pa vsi igralci v skupini.

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
}

Odločil sem se uporabiti dogodke za izmenjavo konteksta. Primerno je bilo za primere - ob dogodku "najdena je bila igra za 10 ljudi", lahko pošljete potrebno sporočilo igralcem v zasebnih sporočilih in izvedete osnovno poslovno logiko - zaženete nalogo za preverjanje pripravljenosti, pripravite lobby za zagon itd.

Za IOC uporabljam InversifyJS. S to knjižnico imam prijetne izkušnje. Hitro in enostavno!

Na našem strežniku imamo več čakalnih vrst - dodali smo 1x1, običajni/ocenjeni in nekaj načinov po meri. Zato obstaja singleton RoomService, ki leži med uporabnikom in iskanjem 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 dobite predstavo o tem, kako približno izgledajo procesi)

Tukaj inicializiram čakalno vrsto za vsakega od implementiranih načinov igre in tudi poslušam spremembe v "skupinah", da prilagodim čakalne vrste in se izognem nekaterim konfliktom.

Torej, dobro opravljeno, vstavil sem dele kode, ki nimajo nobene zveze s to temo, zdaj pa preidimo neposredno na povezovanje.

Poglejmo primer:

1) Uporabnik želi igrati.

2) Za začetek iskanja uporabi Gateway=Discord, to pomeni, da reagira na sporočilo:

Pišemo iskanje ujemalcev za Doto 2014

3) Ta prehod gre v RoomService in pravi "Uporabnik iz discorda želi vstopiti v čakalno vrsto, način: igra brez ocene."

4) RoomService sprejme zahtevo prehoda in uporabnika (natančneje uporabniško skupino) potisne v želeno čakalno vrsto.

5) Čakalna vrsta se preveri vsakič, ko je dovolj igralcev za igro. Če je mogoče, oddaj dogodek:

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

6) RoomService očitno veselo prisluhne vsaki čakalni vrsti v nestrpnem pričakovanju tega dogodka. Kot vhod prejmemo seznam igralcev, iz njih oblikujemo virtualno "sobo" in seveda izdamo dogodek:

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 prišli do "najvišje" avtoritete - razreda Bot. Na splošno se ukvarja s povezavo med prehodi (ne morem razumeti, kako smešno je videti v ruščini) in poslovno logiko povezovanja. Bot presliši dogodek in naroči DiscordGatewayu, naj pošlje preverjanje pripravljenosti vsem uporabnikom.

Pišemo iskanje ujemalcev za Doto 2014

8) Če nekdo zavrne ali ne sprejme igre v 3 minutah, ga NE vrnemo v čakalno vrsto. Vse ostale vrnemo v vrsto in počakamo, da je spet 10 ljudi. Če so vsi igralci sprejeli igro, se začne zanimiv del.

Konfiguracija namenskega strežnika

Naše igre gostujejo na VDS s strežnikom Windows Server 2012. Iz tega lahko potegnemo več sklepov:

  1. Na njem ni dokerja, kar me je zadelo v srce
  2. Prihranimo pri najemnini

Naloga je zagnati proces na VDS iz VPS v Linuxu. V Flasku sem napisal preprost strežnik. Da, ne maram Pythona, ampak kaj lahko storite? Hitreje in lažje je pisati ta strežnik na njem.

Izvaja 3 funkcije:

  1. Zagon strežnika s konfiguracijo - izbira zemljevida, števila igralcev za začetek igre in nabora vtičnikov. Ne bom zdaj pisal o vtičnikih - to je druga zgodba z litri kave ponoči, pomešano s solzami in raztrganimi lasmi.
  2. Zaustavitev/ponovni zagon strežnika v primeru neuspešnih povezav, kar lahko rešimo le ročno.

Tukaj je vse preprosto, primeri kode niso niti primerni. 100-vrstični scenarij

Torej, ko se je 10 ljudi zbralo in sprejelo igro, se je strežnik zagnal in so bili vsi nestrpni za igro, je bila v zasebnih sporočilih poslana povezava za povezavo z igro.

Pišemo iskanje ujemalcev za Doto 2014

S klikom na povezavo se igralec poveže z igralnim strežnikom in to je to. Po približno 25 minutah je virtualna "soba" z igralci počiščena.

Vnaprej se opravičujem za nerodnost članka, že dolgo nisem pisal tukaj in je preveč kode za poudarjanje pomembnih delov. Rezanci, skratka.

Če vidim zanimanje za temo, bo drugi del - vseboval bo moje muke z vtičniki za srcds (izvorni namenski strežnik) in verjetno sistem ocenjevanja in mini-dotabuff, spletno mesto s statistiko iger.

Nekaj ​​povezav:

  1. Naše spletno mesto (statistika, lestvica najboljših, majhna ciljna stran in prenos odjemalca)
  2. Strežnik Discord

Vir: www.habr.com

Dodaj komentar