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.
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.
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.
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:
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:
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.
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:
Non hai ningún docker, que me golpeou no corazón
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:
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.
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.
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.