Scrittura di matchmaking per Dota 2014

Ciao.

Questa primavera mi sono imbattuto in un progetto in cui i ragazzi hanno imparato come eseguire il server Dota 2 versione 2014 e, di conseguenza, giocarci. Sono un grande fan di questo gioco e non potevo perdere questa opportunità unica di immergermi nella mia infanzia.

Mi sono immerso molto in profondità, ed è successo che ho scritto un bot Discord che è responsabile di quasi tutte le funzionalità che non erano supportate nella vecchia versione del gioco, vale a dire il matchmaking.
Prima di tutte le innovazioni con il bot, la lobby veniva creata manualmente. Abbiamo raccolto 10 reazioni a un messaggio e assemblato manualmente un server o ospitato una lobby locale.

Scrittura di matchmaking per Dota 2014

La mia natura di programmatore non poteva sopportare così tanto lavoro manuale e durante la notte ho abbozzato la versione più semplice del bot, che sollevava automaticamente il server quando c'erano 10 persone.

Ho deciso subito di scrivere in nodejs, perché non mi piace molto Python, e mi sento più a mio agio in questo ambiente.

Questa è la mia prima esperienza nella scrittura di un bot per Discord, ma si è rivelato molto semplice. Il modulo npm ufficiale discord.js fornisce una comoda interfaccia per lavorare con i messaggi, raccogliere reazioni, ecc.

Dichiarazione di non responsabilità: tutti gli esempi di codice sono "attuali", nel senso che hanno subito diverse iterazioni di riscrittura durante la notte.

La base del matchmaking è una "coda" in cui i giocatori che vogliono giocare vengono inseriti e rimossi quando non vogliono o non trovano una partita.

Ecco come appare l'essenza di un "giocatore". Inizialmente era solo un ID utente in Discord, ma ci sono piani per lanciare/cercare giochi dal sito, ma prima le cose.

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

Ed ecco l'interfaccia della coda. Qui, invece di "giocatori", viene utilizzata un'astrazione sotto forma di "gruppo". Per un singolo giocatore, il gruppo è composto da lui stesso, mentre per i giocatori di un gruppo, rispettivamente, da tutti i giocatori del gruppo.

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
}

Ho deciso di utilizzare gli eventi per scambiare contesto. Era adatto ai casi: all'evento "è stato trovato un gioco per 10 persone", puoi inviare il messaggio necessario ai giocatori in messaggi privati ​​ed eseguire la logica aziendale di base - avviare un'attività per verificare la disponibilità, preparare la lobby per il lancio e così via.

Per IOC utilizzo InversifyJS. Ho una piacevole esperienza lavorando con questa libreria. Veloce e facile!

Abbiamo diverse code sul nostro server: abbiamo aggiunto 1x1, normale/classificata e un paio di modalità personalizzate. Pertanto esiste un RoomService singleton che si trova tra l'utente e la ricerca del gioco.

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

(Codificare le tagliatelle per dare un'idea di come appaiono approssimativamente i processi)

Qui inizializzo la coda per ciascuna delle modalità di gioco implementate e ascolto anche i cambiamenti nei "gruppi" per regolare le code ed evitare alcuni conflitti.

Quindi, bravo, ho inserito dei pezzi di codice che non c'entrano nulla con l'argomento, e ora passiamo direttamente al matchmaking.

Consideriamo il caso:

1) L'utente vuole giocare.

2) Per avviare la ricerca usa Gateway=Discord, cioè mette una reazione al messaggio:

Scrittura di matchmaking per Dota 2014

3) Questo gateway va a RoomService e dice "Un utente di Discord vuole entrare nella coda, modalità: gioco senza classificazione".

4) RoomService accetta la richiesta del gateway e inserisce l'utente (più precisamente il gruppo di utenti) nella coda desiderata.

5) La coda viene controllata ogni volta che ci sono abbastanza giocatori per giocare. Se possibile, emetti un evento:

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

6) RoomService ovviamente ascolta felicemente ogni coda in trepida attesa di questo evento. Riceviamo un elenco di giocatori come input, formiamo da loro una "stanza" virtuale e, ovviamente, pubblichiamo un evento:

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) Quindi siamo arrivati ​​​​all'autorità "più alta": la classe Bot. In generale, si occupa della connessione tra i gateway (non riesco a capire quanto sia divertente in russo) e della logica aziendale del matchmaking. Il bot ascolta l'evento e ordina a DiscordGateway di inviare un controllo di disponibilità a tutti gli utenti.

Scrittura di matchmaking per Dota 2014

8) Se qualcuno rifiuta o non accetta il gioco entro 3 minuti, NON lo rimetteremo in coda. Riportiamo tutti gli altri in coda e aspettiamo che ci siano di nuovo 10 persone. Se tutti i giocatori hanno accettato il gioco, allora inizia la parte interessante.

Configurazione server dedicato

I nostri giochi sono ospitati su VDS con server Windows 2012. Da ciò possiamo trarre diverse conclusioni:

  1. Non c'è nessuna finestra mobile su di esso, il che mi ha colpito al cuore
  2. Risparmiamo sull'affitto

Il compito è eseguire un processo su VDS da un VPS su Linux. Ho scritto un semplice server in Flask. Sì, non mi piace Python, ma cosa puoi fare? È più semplice e veloce scrivere questo server su di esso.

Svolge 3 funzioni:

  1. Avvio di un server con una configurazione: selezione di una mappa, numero di giocatori per avviare il gioco e una serie di plug-in. Non scriverò di plugin adesso: questa è una storia diversa con litri di caffè di notte mescolati a lacrime e capelli strappati.
  2. Arresto/riavvio del server in caso di connessioni non riuscite, che possiamo gestire solo manualmente.

Qui tutto è semplice, gli esempi di codice non sono nemmeno appropriati. Scrittura di 100 righe

Quindi, quando 10 persone si sono riunite e hanno accettato il gioco, il server è stato avviato e tutti erano ansiosi di giocare, è stato inviato un collegamento per connettersi al gioco nei messaggi privati.

Scrittura di matchmaking per Dota 2014

Facendo clic sul collegamento, il giocatore si connette al server di gioco e il gioco è fatto. Dopo circa 25 minuti, la “stanza” virtuale con i giocatori viene svuotata.

Mi scuso in anticipo per l'imbarazzo dell'articolo, non scrivo qui da molto tempo e c'è troppo codice per evidenziare sezioni importanti. Tagliatelle, in breve.

Se vedo interesse per l'argomento, ci sarà una seconda parte: conterrà il mio tormento con i plugin per srcds (server dedicato alla fonte) e, probabilmente, un sistema di valutazione e un mini-dotabuff, un sito con statistiche di gioco.

Alcuni link:

  1. Il nostro sito web (statistiche, classifica, piccola landing page e download del client)
  2. Server Discordia

Fonte: habr.com

Aggiungi un commento