Nesta primavera me deparei com um projeto no qual os caras aprenderam como rodar o servidor Dota 2 versão 2014 e, consequentemente, jogar nele. Sou um grande fã deste jogo e não poderia deixar passar esta oportunidade única de mergulhar na minha infância.
Mergulhei muito fundo e aconteceu que escrevi um bot Discord que é responsável por quase todas as funcionalidades que não são suportadas na versão antiga do jogo, nomeadamente matchmaking.
Antes de todas as inovações com o bot, o lobby era criado manualmente. Coletamos 10 reações a uma mensagem e montamos manualmente um servidor ou hospedamos um lobby local.
Minha natureza de programador não aguentava tanto trabalho manual, e da noite para o dia esbocei a versão mais simples do bot, que acionava automaticamente o servidor quando havia 10 pessoas.
Decidi imediatamente escrever em nodejs, porque não gosto muito de Python e me sinto mais confortável neste ambiente.
Esta é minha primeira experiência escrevendo um bot para Discord, mas acabou sendo muito simples. O módulo npm oficial discord.js fornece uma interface conveniente para trabalhar com mensagens, coletar reações, etc.
Isenção de responsabilidade: todos os exemplos de código são “atuais”, o que significa que passaram por várias iterações de reescrita durante a noite.
A base do matchmaking é uma “fila” na qual os jogadores que querem jogar são colocados e removidos quando não querem ou não encontram um jogo.
É assim que se parece a essência de um “jogador”. Inicialmente era apenas um ID de usuário no Discord, mas há planos para lançar/pesquisar jogos no site, mas primeiro o mais importante.
E aqui está a interface da fila. Aqui, em vez de “jogadores”, é usada uma abstração na forma de “grupo”. Para um único jogador, o grupo é composto por ele mesmo, e para os jogadores de um grupo, respectivamente, por todos os jogadores do grupo.
Decidi usar eventos para trocar contexto. Foi adequado para os casos - no evento “foi encontrado um jogo para 10 pessoas”, você pode enviar a mensagem necessária aos jogadores em mensagens privadas, e realizar a lógica básica de negócios - lançar uma tarefa para verificar a prontidão, preparar o lobby para lançamento e assim por diante.
Para IOC eu uso InversifyJS. Tenho uma experiência agradável trabalhando com esta biblioteca. Rápido e fácil!
Temos várias filas em nosso servidor - adicionamos 1x1, normal/classificado e alguns modos personalizados. Portanto, existe um RoomService singleton que fica entre o usuário e a busca do jogo.
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 macarrão para dar uma ideia de como são os processos)
Aqui inicializo a fila para cada um dos modos de jogo implementados, e também ouço mudanças nos “grupos” para ajustar as filas e evitar alguns conflitos.
Então, muito bem, inseri trechos de código que não têm nada a ver com o assunto e agora vamos direto para o matchmaking.
Vamos considerar o caso:
1) O usuário quer jogar.
2) Para iniciar a busca ele utiliza Gateway=Discord, ou seja, coloca uma reação à mensagem:
3) Este gateway vai para RoomService e diz “Um usuário do discord deseja entrar na fila, modo: jogo sem classificação”.
4) RoomService aceita a solicitação do gateway e empurra o usuário (mais precisamente, o grupo de usuários) para a fila desejada.
5) A fila verifica sempre que há jogadores suficientes para jogar. Se possível, emita um evento:
6) O RoomService obviamente está ouvindo com alegria todas as filas, aguardando ansiosamente esse evento. Recebemos uma lista de jogadores como entrada, formamos uma “sala” virtual deles e, claro, emitimos um 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ão chegamos à autoridade “mais alta” - a classe Bot. Em geral, ele trata da conexão entre gateways (não consigo entender o quão engraçado isso parece em russo) e da lógica de negócios do matchmaking. O bot ouve o evento e ordena que o DiscordGateway envie uma verificação de prontidão a todos os usuários.
8) Se alguém rejeitar ou não aceitar o jogo em 3 minutos, NÃO o devolveremos à fila. Devolvemos todos os demais à fila e esperamos até que haja 10 pessoas novamente. Se todos os jogadores aceitarem o jogo, começa a parte interessante.
Configuração de servidor dedicado
Nossos jogos são hospedados em VDS com Windows server 2012. Disto podemos tirar várias conclusões:
Não há janela de encaixe nele, o que me atingiu no coração
Economizamos no aluguel
A tarefa é executar um processo no VDS a partir de um VPS no Linux. Eu escrevi um servidor simples em Flask. Sim, não gosto de Python, mas o que posso fazer? É mais rápido e fácil escrever este servidor nele.
Desempenha 3 funções:
Iniciando um servidor com uma configuração - selecionando um mapa, o número de jogadores para iniciar o jogo e um conjunto de plugins. Não vou escrever sobre plugins agora - a história é diferente com litros de café à noite misturados com lágrimas e cabelos rasgados.
Parar/reiniciar o servidor em caso de conexões malsucedidas, que só podemos tratar manualmente.
Tudo é simples aqui, exemplos de código nem são apropriados. roteiro de 100 linhas
Assim, quando 10 pessoas se reuniram e aceitaram o jogo, o servidor foi lançado e todos ficaram ansiosos para jogar, um link para se conectar ao jogo foi enviado em mensagens privadas.
Ao clicar no link, o jogador se conecta ao servidor do jogo e pronto. Após cerca de 25 minutos, a “sala” virtual com jogadores é esvaziada.
Peço desculpas antecipadamente pela estranheza do artigo, não escrevo aqui há muito tempo e há muito código para destacar seções importantes. Macarrão, em suma.
Se eu perceber interesse no tema, haverá uma segunda parte - ela conterá meu tormento com plugins para srcds (servidor dedicado de origem), e, provavelmente, um sistema de classificação e mini-dotabuff, um site com estatísticas de jogos.