Matchmaking schrijven voor Dota 2014

Hallo allemaal

Dit voorjaar kwam ik een project tegen waarin de jongens leerden hoe ze de Dota 2-serverversie 2014 moesten gebruiken en er dienovereenkomstig op moesten spelen. Ik ben een grote fan van dit spel en ik kon deze unieke kans niet voorbij laten gaan om mezelf onder te dompelen in mijn kindertijd.

Ik dook heel diep, en zo gebeurde het dat ik een Discord-bot schreef die verantwoordelijk is voor bijna alle functionaliteit die niet wordt ondersteund in de oude versie van het spel, namelijk matchmaking.
Vóór alle innovaties met de bot werd de lobby handmatig gemaakt. We verzamelden 10 reacties op een bericht en stelden handmatig een server samen, of hostten een lokale lobby.

Matchmaking schrijven voor Dota 2014

Mijn aard als programmeur was niet bestand tegen zoveel handmatig werk, en van de ene op de andere dag schetste ik de eenvoudigste versie van de bot, die de server automatisch omhoog bracht als er tien mensen waren.

Ik besloot meteen in nodejs te schrijven, omdat ik Python niet echt leuk vind, en ik me prettiger voel in deze omgeving.

Dit is mijn eerste ervaring met het schrijven van een bot voor Discord, maar het bleek heel eenvoudig. De officiële npm-module discord.js biedt een handige interface voor het werken met berichten, het verzamelen van reacties, enz.

Disclaimer: alle codevoorbeelden zijn 'actueel', wat betekent dat ze 's nachts verschillende iteraties van herschrijven hebben ondergaan.

De basis van matchmaking is een ‘wachtrij’ waarin spelers die willen spelen worden geplaatst en verwijderd wanneer ze een spel niet willen of vinden.

Dit is hoe de essentie van een “speler” eruit ziet. Aanvankelijk was het gewoon een gebruikers-ID in Discord, maar er zijn plannen om games vanaf de site te starten/zoeken, maar eerst en vooral.

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

En hier is de wachtrij-interface. Hier wordt in plaats van ‘spelers’ een abstractie in de vorm van een ‘groep’ gebruikt. Voor een enkele speler bestaat de groep uit hemzelf, en voor spelers in een groep uit respectievelijk alle spelers in de groep.

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
}

Ik besloot evenementen te gebruiken om context uit te wisselen. Het was geschikt voor cases - bij het evenement "er werd een spel voor 10 personen gevonden", kun je het benodigde bericht in privéberichten naar de spelers sturen en de basisbedrijfslogica uitvoeren - een taak starten om de gereedheid te controleren, de lobby voorbereiden voor lancering, enzovoort.

Voor IOC gebruik ik InversifyJS. Ik heb een prettige ervaring met deze bibliotheek. Snel en gemakkelijk!

We hebben verschillende wachtrijen op onze server - we hebben 1x1, normaal/beoordeeld en een paar aangepaste modi toegevoegd. Daarom is er een singleton RoomService die tussen de gebruiker en het zoeken naar games ligt.

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

(Codeer noedels om een ​​idee te geven hoe de processen er ongeveer uitzien)

Hier initialiseer ik de wachtrij voor elk van de geïmplementeerde spelmodi, en luister ik ook naar veranderingen in “groepen” om de wachtrijen aan te passen en enkele conflicten te vermijden.

Dus, goed gedaan, ik heb stukjes code ingevoegd die niets met het onderwerp te maken hebben, en laten we nu direct verder gaan met matchmaking.

Laten we het geval eens bekijken:

1) De gebruiker wil spelen.

2) Om de zoekopdracht te starten, gebruikt hij Gateway=Discord, dat wil zeggen hij plaatst een reactie op het bericht:

Matchmaking schrijven voor Dota 2014

3) Deze gateway gaat naar RoomService en zegt: "Een gebruiker uit Discord wil in de wachtrij komen, modus: spel zonder classificatie."

4) RoomService accepteert het verzoek van de gateway en duwt de gebruiker (meer precies: de gebruikersgroep) in de gewenste wachtrij.

5) De wachtrij controleert elke keer dat er voldoende spelers zijn om te spelen. Zend indien mogelijk een gebeurtenis uit:

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

6) RoomService luistert uiteraard met plezier naar elke wachtrij in afwachting van deze gebeurtenis. We ontvangen een lijst met spelers als input, vormen van hen een virtuele ‘kamer’ en geven uiteraard een evenement uit:

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) Dus kwamen we bij de “hoogste” autoriteit: de klas Bot. Over het algemeen houdt hij zich bezig met het verband tussen gateways (ik begrijp niet hoe grappig het er in het Russisch uitziet) en de zakelijke logica van matchmaking. De bot hoort de gebeurtenis en geeft DiscordGateway opdracht om een ​​gereedheidscontrole naar alle gebruikers te sturen.

Matchmaking schrijven voor Dota 2014

8) Als iemand het spel binnen 3 minuten afwijst of niet accepteert, plaatsen we deze NIET terug in de wachtrij. We zetten alle anderen weer in de rij en wachten tot er weer 10 mensen zijn. Als alle spelers het spel hebben geaccepteerd, begint het interessante deel.

Toegewijde serverconfiguratie

Onze games worden gehost op VDS met Windows server 2012. Hieruit kunnen we verschillende conclusies trekken:

  1. Er staat geen havenarbeider op, wat mij in het hart raakte
  2. Wij besparen op de huur

De taak is om een ​​proces op VDS uit te voeren vanaf een VPS op Linux. Ik schreef een eenvoudige server in Flask. Ja, ik hou niet van Python, maar wat kun je doen? Het is sneller en gemakkelijker om deze server erop te schrijven.

Het vervult 3 functies:

  1. Een server starten met een configuratie - het selecteren van een kaart, het aantal spelers om het spel te starten en een set plug-ins. Ik zal nu niet over plug-ins schrijven - dat is een ander verhaal met liters koffie 's nachts vermengd met tranen en gescheurd haar.
  2. Het stoppen/herstarten van de server bij mislukte verbindingen, wat we alleen handmatig kunnen afhandelen.

Alles is hier eenvoudig, codevoorbeelden zijn niet eens geschikt. 100-regelig script

Dus toen 10 mensen bij elkaar kwamen en het spel accepteerden, werd de server gelanceerd en iedereen wilde graag spelen. Er werd een link om verbinding te maken met het spel in privéberichten verzonden.

Matchmaking schrijven voor Dota 2014

Door op de link te klikken, maakt de speler verbinding met de spelserver, en dat is alles. Na ~25 minuten wordt de virtuele “kamer” met spelers leeggemaakt.

Bij voorbaat mijn excuses voor de onhandigheid van het artikel. Ik heb hier al een hele tijd niet meer geschreven en er is te veel code om belangrijke secties te benadrukken. Noedels, kortom.

Als ik interesse in het onderwerp zie, zal er een tweede deel zijn - het zal mijn kwelling bevatten met plug-ins voor srcds (dedicated source-server), en waarschijnlijk een beoordelingssysteem en mini-dotabuff, een site met spelstatistieken.

Enkele koppelingen:

  1. Onze website (statistieken, klassement, kleine landingspagina en clientdownload)
  2. Discord-server

Bron: www.habr.com

Voeg een reactie