Kirjoittaa matchmaking for Dota 2014

Hei kaikki

Tänä keväänä törmäsin projektiin, jossa kaverit oppivat ajamaan Dota 2 -palvelinversiota 2014 ja vastaavasti pelaamaan sillä. Olen tämän pelin suuri fani, enkä voinut jättää väliin tätä ainutlaatuista tilaisuutta uppoutua lapsuuteeni.

Suostuin hyvin syvälle, ja niin tapahtui, että kirjoitin Discord-botin, joka vastaa lähes kaikista toiminnoista, joita pelin vanha versio ei tue, nimittäin matchmakingista.
Ennen kaikkia botin innovaatioita aula luotiin manuaalisesti. Keräsimme 10 vastausta viestiin ja kokosimme manuaalisesti palvelimen tai isännöimme paikallista aulaa.

Kirjoittaa matchmaking for Dota 2014

Ohjelmoijaluontoni ei kestänyt niin paljoa manuaalista työtä, ja yhdessä yössä luonnostelin botin yksinkertaisimman version, joka nosti automaattisesti palvelimen kun paikalla oli 10 henkilöä.

Päätin heti kirjoittaa nodejsissa, koska en oikeastaan ​​pidä Pythonista ja tunnen oloni mukavammaksi tässä ympäristössä.

Tämä on ensimmäinen kokemukseni kirjoittaa botti Discordille, mutta se osoittautui hyvin yksinkertaiseksi. Virallinen npm-moduuli discord.js tarjoaa kätevän käyttöliittymän viestien käsittelyyn, reaktioiden keräämiseen jne.

Vastuuvapauslauseke: Kaikki koodiesimerkit ovat "nykyisiä", mikä tarkoittaa, että ne ovat käyneet läpi useita toistokertoja yöllä.

Matchmakingin perusta on "jono", johon pelaajat, jotka haluavat pelata, asetetaan ja poistetaan, kun he eivät halua tai löydä peliä.

Tältä näyttää "pelaajan" olemus. Aluksi se oli vain Discordin käyttäjätunnus, mutta suunnitelmia on käynnistää / etsiä pelejä sivustolta, mutta ensin asiat ensin.

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

Ja tässä on jonokäyttöliittymä. Tässä "pelaajien" sijaan käytetään abstraktia "ryhmän" muodossa. Yksittäiselle pelaajalle ryhmä koostuu hänestä ja ryhmän pelaajille vastaavasti kaikista ryhmän pelaajista.

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
}

Päätin käyttää tapahtumia kontekstin vaihtamiseen. Se sopi tapauksiin - tapahtumassa "löytyi peli 10 hengelle", voit lähettää tarvittavan viestin pelaajille yksityisviesteissä ja suorittaa perusliikelogiikkaa - käynnistää tehtävän valmiuden tarkistamiseksi, aulan valmistelemiseksi käynnistämistä varten ja niin edelleen.

IOC:lle käytän InversifyJS:ää. Minulla on miellyttävä kokemus työskennellä tämän kirjaston kanssa. Nopeaa ja helppoa!

Palvelimellamme on useita jonoja - olemme lisänneet 1x1, normaali/rated ja pari mukautettua tilaa. Siksi käyttäjän ja pelihaun välissä on yksittäinen RoomService.

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

(Koodaa nuudelit saadaksesi käsityksen siitä, miltä prosessit suunnilleen näyttävät)

Täällä alustan jonon jokaiselle toteutetulle pelimoodille ja kuuntelen myös muutoksia "ryhmissä" jonojen säätämiseksi ja joidenkin ristiriitojen välttämiseksi.

Joten, hyvin tehty, lisäsin koodinpätkiä, joilla ei ole mitään tekemistä aiheen kanssa, ja nyt siirrytään suoraan matchmakingiin.

Mietitäänpä tapausta:

1) Käyttäjä haluaa pelata.

2) Haun aloittamiseksi hän käyttää Gateway=Discord, eli laittaa reaktion viestiin:

Kirjoittaa matchmaking for Dota 2014

3) Tämä yhdyskäytävä menee RoomServiceen ja sanoo "Discord-käyttäjä haluaa mennä jonoon, tila: luokittelematon peli."

4) RoomService hyväksyy yhdyskäytävän pyynnön ja työntää käyttäjän (tarkemmin sanottuna käyttäjäryhmän) haluttuun jonoon.

5) Jono tarkistaa aina, kun pelaajia on tarpeeksi. Jos mahdollista, lähetä tapahtuma:

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

6) RoomService kuuntelee ilmeisesti iloisesti jokaista jonoa innokkaasti tätä tapahtumaa odotellessa. Saamme syötteeksi listan pelaajista, muodostamme heistä virtuaalisen "huoneen" ja tietysti järjestämme tapahtuman:

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) Joten pääsimme "korkeimpaan" auktoriteettiin - luokkaan Bot. Yleisesti ottaen hän käsittelee yhdyskäytävien (en ymmärrä kuinka hauskalta se venäjäksi näyttää) ja matchmakingin liikelogiikan välistä yhteyttä. Botti kuulee tapahtuman ja käskee DiscordGatewayn lähettämään valmiustarkastuksen kaikille käyttäjille.

Kirjoittaa matchmaking for Dota 2014

8) Jos joku hylkää tai ei hyväksy peliä 3 minuutin sisällä, emme palauta häntä jonoon. Palautamme kaikki muut jonoon ja odotamme, kunnes paikalla on taas 10 henkilöä. Jos kaikki pelaajat ovat hyväksyneet pelin, mielenkiintoinen osuus alkaa.

Oma palvelinkokoonpano

Pelimme isännöidään VDS:llä, jossa on Windows Server 2012. Tästä voimme tehdä useita johtopäätöksiä:

  1. Siinä ei ole telakkaa, joka osui sydämeeni
  2. Säästämme vuokrassa

Tehtävänä on suorittaa prosessi VDS:llä VPS:stä Linuxissa. Kirjoitin yksinkertaisen palvelimen Flaskissa. Kyllä, en pidä Pythonista, mutta mitä voit tehdä? Tämän palvelimen kirjoittaminen on nopeampaa ja helpompaa.

Se suorittaa 3 toimintoa:

  1. Palvelimen käynnistäminen määrityksellä - kartan valitseminen, pelin aloittavien pelaajien lukumäärä ja lisäosat. En kirjoita nyt laajennuksista - se on eri tarina, kun litraa kahvia öisin sekoitettuna kyyneliin ja repeytyneisiin hiuksiin.
  2. Palvelimen pysäyttäminen/uudelleenkäynnistys epäonnistuneiden yhteyksien sattuessa, jotka voimme käsitellä vain manuaalisesti.

Kaikki on täällä yksinkertaista, koodiesimerkit eivät ole edes sopivia. 100 rivin käsikirjoitus

Joten kun 10 ihmistä kokoontui ja hyväksyi pelin, palvelin käynnistettiin ja kaikki olivat innokkaita pelaamaan, linkki peliin liittymiseksi lähetettiin yksityisviesteinä.

Kirjoittaa matchmaking for Dota 2014

Napsauttamalla linkkiä pelaaja muodostaa yhteyden pelipalvelimeen ja siinä se. ~25 minuutin kuluttua virtuaalinen "huone" pelaajineen tyhjennetään.

Pahoittelen jo etukäteen artikkelin hankaluutta, en ole kirjoittanut tänne pitkään aikaan, ja koodia on liian paljon tärkeiden osien korostamiseen. Nuudelit, lyhyesti sanottuna.

Jos näen kiinnostusta aiheeseen, tulee toinen osa - se sisältää kärsimykseni srcds-laajennuksista (lähde omistettu palvelin), ja luultavasti luokitusjärjestelmän ja mini-dotabuffin, sivuston pelitilastoilla.

Muutama linkki:

  1. Verkkosivustomme (tilastot, tulostaulukko, pieni aloitussivu ja asiakaslataus)
  2. Discord-palvelin

Lähde: will.com

Lisää kommentti