Vi skriver matchmaking for Dota 2014

Hei alle sammen.

I vår kom jeg over et prosjekt der gutta lærte å kjøre Dota 2-serverversjonen 2014 og følgelig spille på den. Jeg er en stor fan av dette spillet, og jeg kunne ikke gå glipp av denne unike muligheten til å fordype meg i barndommen min.

Jeg dukket veldig dypt, og det hendte at jeg skrev en Discord-bot som er ansvarlig for nesten all funksjonalitet som ikke støttes i den gamle versjonen av spillet, nemlig matchmaking.
Før alle innovasjonene med boten ble lobbyen opprettet manuelt. Vi samlet inn 10 reaksjoner på en melding og satt sammen en server manuelt, eller var vert for en lokal lobby.

Vi skriver matchmaking for Dota 2014

Min natur som programmerer tålte ikke så mye manuelt arbeid, og over natten skisserte jeg den enkleste versjonen av boten, som automatisk hevet serveren når det var 10 personer.

Jeg bestemte meg umiddelbart for å skrive i nodejs, fordi jeg egentlig ikke liker Python, og jeg føler meg mer komfortabel i dette miljøet.

Dette er min første erfaring med å skrive en bot for Discord, men det viste seg å være veldig enkelt. Den offisielle npm-modulen discord.js gir et praktisk grensesnitt for å jobbe med meldinger, samle inn reaksjoner osv.

Ansvarsfraskrivelse: Alle kodeeksempler er "aktuelle", noe som betyr at de har gått gjennom flere gjentakelser av omskrivning om natten.

Grunnlaget for matchmaking er en "kø" der spillere som ønsker å spille blir plassert i og fjernet når de ikke vil eller finner et spill.

Dette er hvordan essensen av en "spiller" ser ut. Opprinnelig var det bare en bruker-ID i Discord, men det er planer om å lansere/søke etter spill fra siden, men først.

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øgrensesnittet. Her, i stedet for "spillere", brukes en abstraksjon i form av en "gruppe". For en enkeltspiller består gruppen av seg selv, og for spillere i en gruppe, henholdsvis av 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 bestemte meg for å bruke hendelser til å utveksle kontekst. Det var egnet for tilfeller - ved arrangementet "det ble funnet et spill for 10 personer", kan du sende den nødvendige meldingen til spillerne i private meldinger, og utføre den grunnleggende forretningslogikken - starte en oppgave for å sjekke beredskap, forberede lobbyen for lansering og så videre.

For IOC bruker jeg InversifyJS. Jeg har en hyggelig opplevelse å jobbe med dette biblioteket. Raskt og enkelt!

Vi har flere køer på serveren vår - vi har lagt til 1x1, normal/rated, og et par tilpassede moduser. Derfor er det en singleton RoomService som ligger mellom brukeren og spillsøket.

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

(Kode nudler for å gi en ide om hvordan prosessene grovt sett ser ut)

Her initialiserer jeg køen for hver av de implementerte spillmodusene, og lytter også etter endringer i "grupper" for å justere køene og unngå noen konflikter.

Så godt gjort, jeg la inn kodebiter som ikke har noe med emnet å gjøre, og la oss nå gå direkte til matchmaking.

La oss vurdere saken:

1) Brukeren ønsker å spille.

2) For å starte søket bruker han Gateway=Discord, det vil si setter en reaksjon på meldingen:

Vi skriver matchmaking for Dota 2014

3) Denne gatewayen går til RoomService og sier "En bruker fra discord ønsker å gå inn i køen, modus: uklassifisert spill."

4) RoomService aksepterer gatewayens forespørsel og skyver brukeren (nærmere bestemt brukergruppen) inn i ønsket kø.

5) Køen sjekker hver gang det er nok spillere til å spille. Send ut en hendelse hvis mulig:

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

6) RoomService lytter åpenbart gladelig til hver eneste kø i spent påvente av denne begivenheten. Vi mottar en liste over spillere som input, danner et virtuelt "rom" fra dem, og utsteder selvfølgelig en begivenhet:

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øyeste" autoriteten - klassen Bot. Generelt tar han for seg forbindelsen mellom gateways (jeg kan ikke forstå hvor morsomt det ser ut på russisk) og forretningslogikken til matchmaking. Boten overhører hendelsen og beordrer DiscordGateway til å sende en beredskapssjekk til alle brukere.

Vi skriver matchmaking for Dota 2014

8) Hvis noen avviser eller ikke aksepterer spillet innen 3 minutter, så returnerer vi dem IKKE til køen. Vi returnerer alle andre i køen og venter til det er 10 personer igjen. Hvis alle spillere har akseptert spillet, begynner den interessante delen.

Dedikert serverkonfigurasjon

Spillene våre er vert på VDS med Windows server 2012. Fra dette kan vi trekke flere konklusjoner:

  1. Det er ingen havnearbeider på den, som traff meg i hjertet
  2. Vi sparer på husleien

Oppgaven er å kjøre en prosess på VDS fra en VPS på Linux. Jeg skrev en enkel server i Flask. Ja, jeg liker ikke Python, men hva kan du gjøre? Det er raskere og enklere å skrive denne serveren på den.

Den utfører 3 funksjoner:

  1. Starte en server med en konfigurasjon - velg et kart, antall spillere som skal starte spillet, og et sett med plugins. Jeg vil ikke skrive om plugins nå - det er en annen historie med litervis av kaffe om natten blandet med tårer og revet hår.
  2. Stoppe/restarte serveren ved mislykkede tilkoblinger, noe vi kun kan håndtere manuelt.

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

Så da 10 personer kom sammen og godtok spillet, ble serveren lansert og alle var ivrige etter å spille, en lenke for å koble til spillet ble sendt i private meldinger.

Vi skriver matchmaking for Dota 2014

Ved å klikke på lenken kobler spilleren seg til spillserveren, og så er det det. Etter ~25 minutter er det virtuelle "rommet" med spillere tømt.

Jeg beklager på forhånd for det vanskelige med artikkelen, jeg har ikke skrevet her på lenge, og det er for mye kode til å fremheve viktige deler. Nudler, kort sagt.

Hvis jeg ser interesse for emnet, vil det være en andre del - den vil inneholde min plage med plugins for srcds (Source dedicated server), og sannsynligvis et rangeringssystem og mini-dotabuff, et nettsted med spillstatistikk.

Noen linker:

  1. Vår nettside (statistikk, ledertavle, liten landingsside og klientnedlasting)
  2. Discord server

Kilde: www.habr.com

Legg til en kommentar