Tej wiosny natknąłem się na projekt, w którym chłopaki nauczyli się uruchamiać serwer Dota 2 w wersji 2014 i odpowiednio na nim grać. Jestem wielkim fanem tej gry i nie mogłem przepuścić tej wyjątkowej okazji, aby zanurzyć się w dzieciństwie.
Zanurkowałem bardzo głęboko i tak się złożyło, że napisałem bota na Discordzie, który odpowiada za prawie całą funkcjonalność, która nie jest obsługiwana w starej wersji gry, czyli matchmaking.
Przed wszystkimi innowacjami związanymi z botem lobby było tworzone ręcznie. Zebraliśmy 10 reakcji na wiadomość i ręcznie zmontowaliśmy serwer lub hostowaliśmy lokalne lobby.
Moja natura jako programisty nie wytrzymała tak dużej pracy ręcznej i z dnia na dzień naszkicowałem najprostszą wersję bota, który automatycznie podnosił serwer, gdy było 10 osób.
Od razu zdecydowałem się na pisanie w nodejs, bo nie do końca przepadam za Pythonem, a w tym środowisku czuję się bardziej komfortowo.
To moje pierwsze doświadczenie z pisaniem bota dla Discorda, ale okazało się to bardzo proste. Oficjalny moduł npm discord.js zapewnia wygodny interfejs do pracy z wiadomościami, zbierania reakcji itp.
Zastrzeżenie: Wszystkie przykłady kodu są „aktualne”, co oznacza, że przeszły kilka iteracji przepisywania w nocy.
Podstawą dobierania graczy jest „kolejka”, w której gracze chcący zagrać są umieszczani i usuwani, gdy nie chcą lub nie znajdują gry.
Tak wygląda istota „gracza”. Początkowo był to tylko identyfikator użytkownika na Discordzie, ale w planach jest uruchamianie/wyszukiwanie gier na stronie, ale przede wszystkim.
A oto interfejs kolejki. Tutaj zamiast „graczy” użyto abstrakcji w postaci „grupy”. W przypadku pojedynczego gracza grupa składa się z niego samego, a w przypadku graczy w grupie odpowiednio ze wszystkich graczy w grupie.
Zdecydowałem się wykorzystać zdarzenia do wymiany kontekstu. Nadawało się to do przypadków - w przypadku zdarzenia „znaleziono grę na 10 osób” można wysłać graczom niezbędną wiadomość w wiadomościach prywatnych i przeprowadzić podstawową logikę biznesową - uruchomić zadanie sprawdzenia gotowości, przygotować lobby do startu i tak dalej.
Do IOC używam InversifyJS. Mam miłe doświadczenia ze współpracą z tą biblioteką. Szybko i łatwo!
Mamy kilka kolejek na naszym serwerze - dodaliśmy tryby 1x1, normalny/oceniony i kilka niestandardowych. Dlatego istnieje pojedyncza usługa RoomService, która leży pomiędzy użytkownikiem a wyszukiwarką gry.
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)
}
});
}
);
}
(Zakoduj makaron, aby dać wyobrażenie o tym, jak z grubsza wyglądają procesy)
Tutaj inicjuję kolejkę dla każdego z zaimplementowanych trybów gry, a także nasłuchuję zmian w „grupach”, aby dostosować kolejki i uniknąć pewnych konfliktów.
No to dobra robota, wstawiłem fragmenty kodu nie mające nic wspólnego z tematem, a teraz przejdźmy od razu do matchmakingu.
Rozważmy przypadek:
1) Użytkownik chce zagrać.
2) Aby rozpocząć wyszukiwanie, używa Gateway=Discord, czyli umieszcza reakcję na wiadomość:
3) Ta bramka przechodzi do RoomService i mówi: „Użytkownik z niezgody chce wejść do kolejki, tryb: gra bez oceny”.
4) RoomService przyjmuje żądanie bramki i wypycha użytkownika (a dokładniej grupę użytkowników) do żądanej kolejki.
5) Kolejka jest sprawdzana za każdym razem, gdy jest wystarczająca liczba graczy do gry. Jeśli to możliwe, wyemituj zdarzenie:
6) RoomService oczywiście z radością wysłuchuje każdej kolejki w niecierpliwym oczekiwaniu na to wydarzenie. Jako dane wejściowe otrzymujemy listę graczy, tworzymy od nich wirtualny „pokój” i oczywiście wydajemy wydarzenie:
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) I tak dotarliśmy do „najwyższego” autorytetu – klasy Bot. Ogólnie zajmuje się powiązaniem bramek (nie rozumiem, jak śmiesznie to wygląda po rosyjsku) i logiką biznesową kojarzeń. Bot podsłuchuje wydarzenie i nakazuje DiscordGateway wysłanie kontroli gotowości do wszystkich użytkowników.
8) Jeśli ktoś w ciągu 3 minut odrzuci lub nie zaakceptuje gry, wówczas NIE zwracamy go do kolejki. Wszystkich pozostałych zwracamy do kolejki i czekamy, aż znów będzie 10 osób. Jeśli wszyscy gracze zaakceptują grę, rozpoczyna się interesująca część.
Dedykowana konfiguracja serwera
Nasze gry hostowane są na VDS z systemem Windows Server 2012. Z tego możemy wyciągnąć kilka wniosków:
Nie ma na nim żadnego dokera, co mnie uderzyło w serce
Oszczędzamy na czynszu
Zadanie polega na uruchomieniu procesu na VDS z VPS na Linuksie. Napisałem prosty serwer w Flasku. Tak, nie lubię Pythona, ale co zrobić? Szybciej i łatwiej jest napisać na nim ten serwer.
Pełni 3 funkcje:
Uruchomienie serwera wraz z konfiguracją - wybranie mapy, liczby graczy, dla których rozpocznie się gra oraz zestawu wtyczek. O wtyczkach nie będę się już rozpisywać – to inna historia z litrami kawy wieczorem wymieszanej ze łzami i potarganymi włosami.
Zatrzymanie/restart serwera w przypadku nieudanych połączeń, z którymi możemy sobie poradzić jedynie ręcznie.
Tutaj wszystko jest proste, przykłady kodu nie są nawet odpowiednie. Skrypt 100-liniowy
Tak więc, gdy zebrało się 10 osób i zaakceptowało grę, serwer został uruchomiony i wszyscy byli chętni do gry, w prywatnych wiadomościach został wysłany link umożliwiający połączenie się z grą.
Klikając na link, gracz łączy się z serwerem gry i to wszystko. Po około 25 minutach wirtualny „pokój” z graczami zostaje oczyszczony.
Z góry przepraszam za niezręczność artykułu, dawno tu nie pisałem, a kodu jest za dużo, żeby wyróżnić ważne sekcje. W skrócie makaron.
Jeśli zobaczę zainteresowanie tematem, pojawi się druga część - będzie zawierała moją udrękę z wtyczkami do srcds (serwer dedykowany źródłowy), oraz prawdopodobnie system oceniania i mini-dotabuff, czyli stronę ze statystykami gier.