At skrive matchmaking til Dota 2014

Hej alle sammen.

I foråret stødte jeg på et projekt, hvor fyrene lærte at køre Dota 2-serverversionen 2014 og dermed spille på den. Jeg er en stor fan af dette spil, og jeg kunne ikke gå glip af denne unikke mulighed for at fordybe mig i min barndom.

Jeg dykkede meget dybt, og det skete så, at jeg skrev en Discord-bot, der er ansvarlig for næsten al den funktionalitet, som ikke er understøttet i den gamle version af spillet, nemlig matchmaking.
Før alle innovationerne med botten blev lobbyen oprettet manuelt. Vi indsamlede 10 reaktioner på en besked og sammensatte manuelt en server eller var vært for en lokal lobby.

At skrive matchmaking til Dota 2014

Min natur som programmør kunne ikke holde til så meget manuelt arbejde, og fra den ene dag til den anden skitserede jeg den enkleste version af botten, som automatisk hævede serveren, når der var 10 personer.

Jeg besluttede mig straks for at skrive i nodejs, fordi jeg ikke rigtig kan lide Python, og jeg føler mig mere tilpas i dette miljø.

Dette er min første oplevelse med at skrive en bot til Discord, men det viste sig at være meget enkelt. Det officielle npm-modul discord.js giver en praktisk grænseflade til at arbejde med beskeder, indsamle reaktioner osv.

Ansvarsfraskrivelse: Alle kodeeksempler er "aktuelle", hvilket betyder, at de har gennemgået flere gentagelser af omskrivning om natten.

Grundlaget for matchmaking er en "kø", hvor spillere, der vil spille, placeres og fjernes, når de ikke vil eller finder et spil.

Sådan ser essensen af ​​en "spiller" ud. Oprindeligt var det kun et bruger-id i Discord, men der er planer om at starte/søge efter spil fra siden, men først og fremmest.

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

Og her er kø-grænsefladen. Her bruges i stedet for "spillere" en abstraktion i form af en "gruppe". For en enkelt spiller består gruppen af ​​henholdsvis ham selv og for spillere i en gruppe af alle spillerne i gruppen.

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
}

Jeg besluttede at bruge begivenheder til at udveksle kontekst. Det var velegnet til sager - efter begivenheden "der blev fundet et spil til 10 personer", kan du sende den nødvendige besked til spillerne i private beskeder og udføre den grundlæggende forretningslogik - starte en opgave for at kontrollere beredskab, forberede lobbyen til lancering og så videre.

Til IOC bruger jeg InversifyJS. Jeg har en behagelig oplevelse med at arbejde med dette bibliotek. Hurtigt og nemt!

Vi har flere køer på vores server - vi har tilføjet 1x1, normal/rated, og et par brugerdefinerede tilstande. Derfor er der en singleton RoomService, der ligger mellem brugeren og spilsøgningen.

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

(Kod nudler for at give en idé om, hvordan processerne nogenlunde ser ud)

Her initialiserer jeg køen for hver af de implementerede spiltilstande, og lytter også efter ændringer i "grupper" for at justere køerne og undgå nogle konflikter.

Så godt gået, jeg indsatte stykker kode, som ikke har noget med emnet at gøre, og lad os nu gå direkte videre til matchmaking.

Lad os overveje sagen:

1) Brugeren ønsker at spille.

2) For at starte søgningen bruger han Gateway=Discord, det vil sige sætter en reaktion på beskeden:

At skrive matchmaking til Dota 2014

3) Denne gateway går til RoomService og siger "En bruger fra discord ønsker at komme ind i køen, tilstand: uklassificeret spil."

4) RoomService accepterer gatewayens anmodning og skubber brugeren (mere præcist brugergruppen) ind i den ønskede kø.

5) Køen tjekker hver gang der er nok spillere til at spille. Hvis det er muligt, udsend en begivenhed:

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

6) RoomService lytter naturligvis med glæde til hver eneste kø i spændt forventning om denne begivenhed. Vi modtager en liste over spillere som input, danner et virtuelt "rum" fra dem og udsender selvfølgelig en begivenhed:

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) Så vi kom til den "højeste" autoritet - klassen Bot. Generelt beskæftiger han sig med forbindelsen mellem gateways (jeg kan ikke forstå, hvor sjovt det ser ud på russisk) og forretningslogikken i matchmaking. Botten overhører begivenheden og beordrer DiscordGateway til at sende en beredskabskontrol til alle brugere.

At skrive matchmaking til Dota 2014

8) Hvis nogen afviser eller ikke accepterer spillet inden for 3 minutter, så returnerer vi dem IKKE til køen. Vi bringer alle andre tilbage i køen og venter til der er 10 personer igen. Hvis alle spillere har accepteret spillet, begynder den interessante del.

Dedikeret server konfiguration

Vores spil er hostet på VDS med Windows server 2012. Ud fra dette kan vi drage flere konklusioner:

  1. Der er ingen havnemand på det, hvilket ramte mig i hjertet
  2. Vi sparer på huslejen

Opgaven er at køre en proces på VDS fra en VPS på Linux. Jeg skrev en simpel server i Flask. Ja, jeg kan ikke lide Python, men hvad kan du gøre? Det er hurtigere og nemmere at skrive denne server på den.

Den udfører 3 funktioner:

  1. Start af en server med en konfiguration - valg af et kort, antallet af spillere til at starte spillet og et sæt plugins. Jeg vil ikke skrive om plugins nu - det er en anden historie med litervis af kaffe om natten blandet med tårer og revet hår.
  2. Stop/genstart af serveren i tilfælde af mislykkede forbindelser, hvilket vi kun kan håndtere manuelt.

Alt er enkelt her, kodeeksempler er ikke engang passende. 100 linjers script

Så da 10 personer mødtes og accepterede spillet, blev serveren lanceret og alle var ivrige efter at spille, et link til at oprette forbindelse til spillet blev sendt i private beskeder.

At skrive matchmaking til Dota 2014

Ved at klikke på linket opretter spilleren forbindelse til spilserveren, og så er det det. Efter ~25 minutter er det virtuelle "rum" med spillere ryddet.

Jeg undskylder på forhånd for artiklens akavethed, jeg har ikke skrevet her i lang tid, og der er for meget kode til at fremhæve vigtige afsnit. Nudler, kort sagt.

Hvis jeg ser interesse for emnet, vil der være en anden del - den vil indeholde min pine med plugins til srcd'er (Source dedikeret server), og sandsynligvis et ratingsystem og mini-dotabuff, et websted med spilstatistik.

Nogle links:

  1. Vores hjemmeside (statistik, leaderboard, lille landingsside og klientdownload)
  2. Discord server

Kilde: www.habr.com

Tilføj en kommentar