Redacción de emparejamiento para Dota 2014

Hola a todos.

Esta primavera me encontré con un proyecto en el que los muchachos aprendieron a ejecutar el servidor Dota 2 versión 2014 y, en consecuencia, a jugar en él. Soy un gran admirador de este juego y no podía dejar pasar esta oportunidad única de sumergirme en mi infancia.

Me sumergí muy profundamente y resultó que escribí un bot de Discord que es responsable de casi todas las funciones que no son compatibles con la versión anterior del juego, es decir, el emparejamiento.
Antes de todas las innovaciones con el bot, el lobby se creaba manualmente. Recopilamos 10 reacciones a un mensaje y montamos manualmente un servidor o organizamos un lobby local.

Redacción de emparejamiento para Dota 2014

Mi naturaleza como programador no podía soportar tanto trabajo manual, y de la noche a la mañana esbocé la versión más simple del bot, que levantaba automáticamente el servidor cuando había 10 personas.

Inmediatamente decidí escribir en nodejs porque no me gusta mucho Python y me siento más cómodo en este entorno.

Esta es mi primera experiencia escribiendo un bot para Discord, pero resultó ser muy simple. El módulo oficial de npm discord.js proporciona una interfaz conveniente para trabajar con mensajes, recopilar reacciones, etc.

Descargo de responsabilidad: todos los ejemplos de código son "actuales", lo que significa que han pasado por varias iteraciones de reescritura por la noche.

La base del emparejamiento es una “cola” en la que los jugadores que quieren jugar son colocados y eliminados cuando no quieren o no encuentran un juego.

Así es la esencia de un “jugador”. Inicialmente era solo una identificación de usuario en Discord, pero hay planes para iniciar/buscar juegos desde el sitio, pero lo primero es lo primero.

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

Y aquí está la interfaz de la cola. Aquí, en lugar de "jugadores", se utiliza una abstracción en forma de "grupo". Para un solo jugador, el grupo está formado por él mismo y, para los jugadores de un grupo, respectivamente, por todos los jugadores del 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í usar eventos para intercambiar contexto. Era adecuado para casos: cuando ocurre "se encontró un juego para 10 personas", puede enviar el mensaje necesario a los jugadores en mensajes privados y llevar a cabo la lógica comercial básica: iniciar una tarea para verificar la preparación, preparar el lobby. para el lanzamiento, etc.

Para el COI utilizo InversifyJS. Tengo una experiencia agradable trabajando con esta biblioteca. ¡Rapido y facil!

Tenemos varias colas en nuestro servidor: hemos agregado modos 1x1, normal/clasificado y un par de modos personalizados. Por lo tanto, existe un RoomService único que se encuentra entre el usuario y la búsqueda del juego.

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

(Codifique los fideos para dar una idea de cómo se ven los procesos aproximadamente)

Aquí inicializo la cola para cada uno de los modos de juego implementados y también escucho los cambios en los "grupos" para ajustar las colas y evitar algunos conflictos.

Bien hecho, inserté fragmentos de código que no tienen nada que ver con el tema y ahora pasemos directamente al emparejamiento.

Consideremos el caso:

1) El usuario quiere jugar.

2) Para iniciar la búsqueda utiliza Gateway=Discord, es decir, pone una reacción al mensaje:

Redacción de emparejamiento para Dota 2014

3) Esta puerta de enlace va a RoomService y dice "Un usuario de Discord quiere ingresar a la cola, modo: juego sin clasificar".

4) RoomService acepta la solicitud de la puerta de enlace y empuja al usuario (más precisamente, al grupo de usuarios) a la cola deseada.

5) La cola comprueba cada vez que hay suficientes jugadores para jugar. Si es posible, emita un evento:

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

6) RoomService obviamente escucha felizmente cada cola con ansiosa anticipación de este evento. Recibimos una lista de jugadores como entrada, formamos una "sala" virtual a partir de ellos y, por supuesto, organizamos 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) Entonces llegamos a la autoridad "más alta": la clase. Bot. En general, se ocupa de la conexión entre las puertas de enlace (no entiendo lo gracioso que parece en ruso) y la lógica empresarial del emparejamiento. El robot escucha el evento y ordena a DiscordGateway que envíe una verificación de preparación a todos los usuarios.

Redacción de emparejamiento para Dota 2014

8) Si alguien rechaza o no acepta el juego dentro de los 3 minutos, NO lo devolveremos a la cola. Devolvemos a todos los demás a la cola y esperamos hasta que vuelvan a ser 10 personas. Si todos los jugadores han aceptado el juego, entonces comienza la parte interesante.

Configuración del servidor dedicado

Nuestros juegos están alojados en VDS con Windows server 2012. De esto podemos sacar varias conclusiones:

  1. No hay ninguna ventana acoplable, lo cual me golpeó en el corazón.
  2. Ahorramos en alquiler

La tarea consiste en ejecutar un proceso en VDS desde un VPS en Linux. Escribí un servidor simple en Flask. Sí, no me gusta Python, pero ¿qué puedes hacer? Es más rápido y fácil escribir este servidor en él.

Realiza 3 funciones:

  1. Iniciar un servidor con una configuración: seleccionar un mapa, la cantidad de jugadores para iniciar el juego y un conjunto de complementos. No escribiré sobre complementos ahora; esa es una historia diferente con litros de café por la noche mezclados con lágrimas y cabello rasgado.
  2. Detener/reiniciar el servidor en caso de conexiones fallidas, que sólo podemos manejar manualmente.

Aquí todo es sencillo, los ejemplos de código ni siquiera son apropiados. guión de 100 líneas

Entonces, cuando 10 personas se reunieron y aceptaron el juego, se lanzó el servidor y todos estaban ansiosos por jugar, se envió un enlace para conectarse al juego en mensajes privados.

Redacción de emparejamiento para Dota 2014

Al hacer clic en el enlace, el jugador se conecta al servidor del juego y listo. Después de aproximadamente 25 minutos, se limpia la “sala” virtual con los jugadores.

Pido disculpas de antemano por la incomodidad del artículo, no he escrito aquí durante mucho tiempo y hay demasiado código para resaltar secciones importantes. Fideos, en definitiva.

Si veo interés en el tema, habrá una segunda parte: contendrá mi tormento con complementos para srcds (servidor dedicado de origen) y, probablemente, un sistema de clasificación y mini-dotabuff, un sitio con estadísticas de juegos.

Algunos enlaces:

  1. Nuestro sitio web (estadísticas, tabla de clasificación, pequeña página de inicio y descarga del cliente)
  2. Servidor de discordia

Fuente: habr.com

Añadir un comentario