Estamos escribindo matchmaking para Dota 2014

Ola a todos.

Esta primavera atopeime cun proxecto no que os rapaces aprenderon a executar a versión do servidor Dota 2 2014 e, en consecuencia, xogar con el. Son un gran fan deste xogo e non podía deixar pasar esta oportunidade única de mergullarme na miña infancia.

Mermeime moi profundamente, e ocorreu que escribín un bot de Discord que é responsable de case todas as funcións que non se admiten na versión antiga do xogo, é dicir, o matchmaking.
Antes de todas as innovacións co bot, o lobby creouse manualmente. Recollemos 10 reaccións a unha mensaxe e montamos manualmente un servidor ou aloxamos un lobby local.

Estamos escribindo matchmaking para Dota 2014

A miña natureza como programador non podía soportar tanto traballo manual, e da noite para a mañá esbocei a versión máis sinxela do bot, que levantou automaticamente o servidor cando había 10 persoas.

Inmediatamente decidín escribir en nodejs, porque non me gusta moito Python e síntome máis cómodo neste ambiente.

Esta é a miña primeira experiencia escribindo un bot para Discord, pero resultou moi sinxelo. O módulo oficial npm discord.js ofrece unha interface conveniente para traballar con mensaxes, recoller reaccións, etc.

Descargo de responsabilidade: todos os exemplos de código son "actuales", o que significa que pasaron por varias iteracións de reescritura pola noite.

A base do matchmaking é unha "cola" na que os xogadores que queren xogar son colocados e eliminados cando non queren ou atopan un xogo.

Así se ve a esencia dun "xogador". Inicialmente era só un identificador de usuario en Discord, pero hai plans para lanzar/buscar xogos no sitio, pero o primeiro é primeiro.

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

E aquí está a interface da cola. Aquí, en lugar de "xogadores", úsase unha abstracción en forma de "grupo". Para un só xogador, o grupo está composto por si mesmo e para os xogadores dun grupo, respectivamente, por todos os xogadores do grupo.

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
}

Decidín usar eventos para intercambiar contexto. Era adecuado para casos: no evento "atopouse un xogo para 10 persoas", pode enviar a mensaxe necesaria aos xogadores en mensaxes privadas e levar a cabo a lóxica empresarial básica: lanzar unha tarefa para comprobar a preparación, preparar o vestíbulo. para o lanzamento, etc.

Para IOC uso InversifyJS. Teño unha experiencia agradable traballando con esta biblioteca. Rápido e sinxelo!

Temos varias colas no noso servidor: engadimos 1x1, normal/clasificado e un par de modos personalizados. Polo tanto, hai un Singleton RoomService que se atopa entre o usuario e a busca do xogo.

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

(Code os fideos para dar unha idea de como se ven os procesos aproximadamente)

Aquí inicializo a cola para cada un dos modos de xogo implementados, e tamén escoito os cambios nos "grupos" para axustar as filas e evitar algúns conflitos.

Entón, ben feito, introducín pezas de código que non teñen nada que ver co tema, e agora imos pasar directamente ao matchmaking.

Consideremos o caso:

1) O usuario quere xogar.

2) Para comezar a busca, usa Gateway=Discord, é dicir, pon unha reacción á mensaxe:

Estamos escribindo matchmaking para Dota 2014

3) Esta pasarela vai a RoomService e di "Un usuario de Discord quere entrar na cola, modo: xogo sen clasificación".

4) RoomService acepta a solicitude da pasarela e empurra o usuario (máis precisamente, o grupo de usuarios) á cola desexada.

5) A cola comproba cada vez que hai suficientes xogadores para xogar. Se é posible, emita un evento:

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

6) RoomService está obviamente escoitando felizmente todas as filas coa ansiosa anticipación deste evento. Recibimos unha lista de xogadores como entrada, formamos unha "sala" virtual con eles e, por suposto, emitimos 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) Entón chegamos á "máxima" autoridade: a clase bot. En xeral, trata a conexión entre as pasarelas (non podo entender o divertido que se ve en ruso) e a lóxica empresarial do matchmaking. O bot escoita o evento e ordena a DiscordGateway que envíe unha comprobación de preparación a todos os usuarios.

Estamos escribindo matchmaking para Dota 2014

8) Se alguén rexeita ou non acepta o xogo nun prazo de 3 minutos, NON o devolvemos á cola. Volvemos a todos os demais á cola e agardamos a que volvan haber 10 persoas. Se todos os xogadores aceptaron o xogo, comeza a parte interesante.

Configuración do servidor dedicado

Os nosos xogos están aloxados en VDS con Windows Server 2012. Diso podemos extraer varias conclusións:

  1. Non hai ningún docker, que me golpeou no corazón
  2. Aforramos en aluguer

A tarefa é executar un proceso en VDS desde un VPS en Linux. Escribín un servidor sinxelo en Flask. Si, non me gusta Python, pero que podes facer?É máis rápido e sinxelo escribir este servidor nel.

Realiza 3 funcións:

  1. Iniciar un servidor cunha configuración: seleccionar un mapa, o número de xogadores para iniciar o xogo e un conxunto de complementos. Non vou escribir sobre complementos agora: esa é unha historia diferente con litros de café pola noite mesturados con bágoas e cabelos rasgados.
  2. Deter/reiniciar o servidor en caso de conexións non exitosas, que só podemos xestionar manualmente.

Aquí todo é sinxelo, os exemplos de código nin sequera son apropiados. Script de 100 liñas

Entón, cando 10 persoas se xuntaron e aceptaron o xogo, lanzouse o servidor e todos estaban ansiosos por xogar, enviouse unha ligazón para conectarse ao xogo en mensaxes privadas.

Estamos escribindo matchmaking para Dota 2014

Ao facer clic na ligazón, o xogador conéctase ao servidor do xogo e xa está. Despois de ~25 minutos, borrarase a "sala" virtual con xogadores.

Pido desculpas de antemán pola torpeza do artigo, hai moito tempo que non escribo aquí e hai demasiado código para destacar seccións importantes. Fideos, en definitiva.

Se vexo interese no tema, haberá unha segunda parte: conterá o meu tormento con complementos para srcds (servidor dedicado de orixe) e, probablemente, un sistema de clasificación e mini-dotabuff, un sitio con estatísticas de xogo.

Algunhas ligazóns:

  1. O noso sitio web (estatísticas, táboa de clasificación, pequena páxina de destino e descarga do cliente)
  2. Servidor Discord

Fonte: www.habr.com

Engadir un comentario