Kirjutame matchmaking'i Dota 2014. aastaks

Tere kÔigile.

Sel kevadel leidsin projekti, kus seltskond Ôppis kÀivitama Dota 2 serverit 2014. aasta versioonis ning seega mÀngima sellel. Olen suur selle mÀngu fÀnn ja ei saanud mööda vaadata ainulaadsest vÔimalusest naasta oma lapsepÔlve.

Sukeldusin sĂŒgavale ja lĂ”puks kirjutasin Discordi boti, mis vastutab praktiliselt kogu funktsionaalsuse eest, mida ei toetata vanas mĂ€nguversioonis, nimelt matĆĄide leidmine.
Kuni kÔikide uuendusteni moodustati lobby kÀsitsi. Koguti 10 reaktsiooni sÔnumile ja koguti server kÀsitsi vÔi korraldati kohalik lobby.

Kirjutame matchmaking'i Dota 2014. aastaks

Minu programmeerija loomus ei pidanud vastu nii suurele hulgale kĂ€sitööle ja öö jooksul viskasin kokku kĂ”ige lihtsama boti versiooni, mis tĂ”stis serveri automaatselt ĂŒles, kui 10 inimest sai kokku.

Otsustasin kirjutada Node.js-is, sest ma ei armasta pythonit vÀga ning tunnen end selles keskkonnas mugavamalt.

See on minu esimene kogemus Discordi boti kirjutamisel, kuid osutus kÔik vÀga lihtsaks. Ametlik npm moodul discord.js pakub mugavat liidest sÔnumite, reaktsioonide kogumise jne jaoks.

Klausel: kĂ”ik koodi nĂ€ited on „aktuaalsed“, see tĂ€hendab, et need on lĂ€binud mitu öist ĂŒmberkirjutamist.

MatĆĄide leidmise aluseks on „jĂ€rjekord“, kuhu paigutatakse mĂ€ngijad, kes soovivad mĂ€ngida, ja kust nad eemaldatakse, kui nad enam ei soovi vĂ”i on mĂ€ngu leidnud.

Nii nĂ€eb vĂ€lja „mĂ€ngija“ entiteet. Alguses oli see lihtsalt Discordi kasutaja id, kuid plaanis on ka launcher/mĂ€nguotsing veebisaidilt, aga kĂ”igest jĂ€rgemööda.

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 siin on jĂ€rjekorra liides. Siin kasutatakse „mĂ€ngijate“ asemel abstraktsiooni „grupp“. Ühe tagamĂ€ngija jaoks koosneb grupp temast endast, grupimĂ€ngijate jaoks aga grupi kĂ”igist mĂ€ngijatest.

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
}

Otsustasin kasutada sĂŒndmusi konteksti edastamiseks. See sobis juhtumitega — sĂŒndmuse „mĂ€ng leitud 10 inimesele” korral saab saata mĂ€ngijatele vajaliku sĂ”numi ning tĂ€ita peamise Ă€riloogika — alustada ĂŒlesande tĂ€itmist valmisoleku kontrollimiseks, valmistada lobby kĂ€ivitamiseks jne.

IOC jaoks kasutan InversifyJS-i. Mul on selle teegiga meeldiv kogemus. Kiire ja lihtne!

Meil on serveris mitmeid jĂ€rjekordi — on lisandunud 1x1 reĆŸiimid, tavaline/reitingu ning paar kohandatud reĆŸiimi. SeetĂ”ttu on olemas singleton RoomService, mis asub kasutaja ja mĂ€nguotsimise vahel.

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 the queue has this party, we re-add the 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 the queue has this party, we re-add the party
            q.remove(event.party)
          }
        });
      }
    );
  }

(Koodi makaronid, et illustreerida, kuidas protsessid vÀlja nÀevad)

Siin ma initsialiseerin jĂ€rjekorra iga rakendatud mĂ€ngureĆŸiimi jaoks ning jĂ€lgin grupimuudatusi, et kohandada jĂ€rjekordi ja vĂ€ltida teatud konflikte.

Noh, ma olen tubli, lisasin koodikillud, mis ei kuulu teema juurde, ja nĂŒĂŒd liigume juba matĆĄide leidmiseni.

Vaatame juhtumit:

1) Kasutaja soovib mÀngida.

2) MÀnguotsingu alustamiseks kasutab ta Gateway=Discord, st annab reaktsiooni sÔnumile:

Kirjutame matchmaking'i Dota 2014. aastaks

3) See vĂ€rav lĂ€heb RoomService'i ja ĂŒtleb: „Discordi kasutaja soovib jĂ€rjekorda siseneda, reĆŸiim: mitte-reitingu mĂ€ng.”

4) RoomService vastu vÔtab vÀrava taotluse ja paneb kasutaja (tÀpsemalt kasutaja grupi) sobivasse jÀrjekorda.

5) Iga muudatuse korral kontrollib jĂ€rjekord, kas mĂ€ngijad mĂ€ngimiseks piisavad. Kui nii — emiteerime sĂŒndmuse:

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

6) RoomService, mĂ”istagi, kuulab igat jĂ€rjekorda selle sĂŒndmuse jĂ€rele, oodates suure elevusega. Me saame sisendina nimekirja mĂ€ngijatest, loome neist virtuaalse „toa” ja emiteerime loomulikult sĂŒndmuse:

queue.addListener("room-found", (event: RoomFoundEvent) => {
      console.log(
        `Tuba leiti reĆŸiimi: [${mode}]. Aeg on nende tĂŒĂŒpide jaoks tasuta tuba leida`
      );
      const room = this.getFreeRoom(mode);
      room.fill(event.players);

      this.onRoomFormed(room);
    });

7) NĂŒĂŒd oleme jĂ”udnud "kĂ”rgeimasse" astmesse — klassi Bot. Üldiselt tegeleb see vĂ€ravate (kuidas see eesti keeles kĂ”lab) ja matĆĄimĂ€ngu Ă€riloogika vahelise ĂŒhenduse loomisega. Bot kĂ”rvutab sĂŒndmust ja kĂ€seb DiscordGateway'il kĂ”igile kasutajatele valmisoleku kontrolli saata.

Kirjutame matchmaking'i Dota 2014. aastaks

8) Kui keegi on mĂ€ngu 3 minuti jooksul tagasi lĂŒkanud vĂ”i aktsepteerinud, siis EI naase me neid jĂ€rjekorda. KĂ”iki teisi naaseme jĂ€rjekorda ja ootame, kuni jĂ€lle 10 inimest kokku tuleb. Kui kĂ”ik mĂ€ngijad on mĂ€ngu kinnitanud, algab huvitav osa.

PĂŒhendatud serveri konfiguratsioon

Meie mÀngud on hostitud VDS-l, millel on Windows server 2012. Selle pÔhjal saab teha mitmeid jÀreldusi:

  1. Seal pole Dockerit, mis lĂ”i mu sĂŒdamesse viga
  2. Me sÀÀstame ĂŒĂŒri pealt

Ülesanne on: VPS-il Linuxis kĂ€ivitada protsess VDS-il. Kirjutasin lihtsa serveri Flaskis. Jah, ma ei armasta Pythoni, aga mis teha — selle serveri kirjutamine on kiirem ja lihtsam.

See tÀidab 3 funktsiooni:

  1. Serveri kĂ€ivitamine konfiguratsiooniga — kaardi valik, mĂ€ngijate arvu mÀÀramine mĂ€ngu alustamiseks ja pluginate komplekt. Plaginadest ei hakka hetkel rÀÀkima — see on eraldi lugu, mis vajab liitrit kohvi ja pisaraid, segatuna juuste vĂ€lja tĂ”mbamisega.
  2. Serveri peatamine/taaskĂ€ivitamine, kui meil on ebaĂ”nnestunud ĂŒhendusi, mida me saame kĂ€sitleda ainult kĂ€sitsi.

Siin on kÔik lihtne, koodinÀidete toomine ei ole asjakohane. Skript on 100 rida

Nii et kui 10 inimest on koos ja on mĂ€ngu aktsepteerinud, kĂ€ivitatakse server ja kĂ”ik ootavad mĂ€ngimist, saadetakse isiklikele sĂ”numitele link mĂ€nguĂŒhendusele.

Kirjutame matchmaking'i Dota 2014. aastaks

Lingile klikates ĂŒhendab mĂ€ngija end mĂ€nguserveriga ja edasine lĂ€heb juba ise. Umbes 25 minuti pĂ€rast tĂŒhjeneb virtuaalne "tuba" mĂ€ngijatega.

Vabandust, et artikkel on kohmakas, olen kaua siia kirjutanud ja koodi on liiga palju, et olulisi osi vÀlja tuua. Lihtsalt nuud.

Kui nĂ€en huvi teema vastu, siis tuleb teine osa — seal rÀÀgin oma kannatustest pluginate osas srcds (Source dedicated server) jaoks, ja suur tĂ”enĂ€osus on, et sĂŒsteemist, mis hindab ja mini-dotabuff, veebileht mĂ€ngustatistika jaoks.

MÔningad lingid:

  1. Meie veebileht (statistika, liidrite tabel, vÀike leht ja kliendi allalaadimine)
  2. Discord server

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster