Üdvözlet mindenkinek.
Idén tavasszal találkoztam egy projekttel, amelyben a srácok megtanulták, hogyan kell futtatni a Dota 2 szerver 2014-es verzióját, és ennek megfelelően játszani is rajta. Nagy rajongója vagyok ennek a játéknak, és nem hagyhattam ki ezt az egyedülálló lehetőséget, hogy elmerüljek a gyerekkoromban.
Nagyon mélyen belemerültem, és úgy esett, hogy írtam egy Discord botot, amely szinte minden olyan funkcióért felelős, amelyet a játék régi verziója nem támogat, nevezetesen a párkeresésért.
A bottal kapcsolatos újítások előtt az előcsarnokot manuálisan hozták létre. Összegyűjtöttünk 10 reakciót egy üzenetre, és manuálisan összeállítottunk egy szervert, vagy helyi lobbit hoztunk létre.
Programozói természetem nem bírt ki ennyi kézi munkát, és egyik napról a másikra felvázoltam a bot legegyszerűbb változatát, ami 10 fő esetén automatikusan megemelte a szervert.
Azonnal úgy döntöttem, hogy nodejs-ben írok, mert nem igazán szeretem a Python-t, és jobban érzem magam ebben a környezetben.
Ez az első tapasztalatom, hogy botot írok a Discordhoz, de nagyon egyszerűnek bizonyult. A hivatalos discord.js npm modul kényelmes felületet biztosít az üzenetekkel való munkavégzéshez, a reakciók összegyűjtéséhez stb.
Felelősség kizárása: Minden kódpélda „aktuális”, ami azt jelenti, hogy több iteráción estek át éjszakai újraíráson.
A matchmaking alapja egy „sor”, amelybe a játszani akaró játékosokat behelyezik és eltávolítják, amikor nem akarnak vagy nem találnak játékot.
Így néz ki a „játékos” lényege. Kezdetben ez csak egy felhasználói azonosító volt a Discordban, de tervben van játékok elindítása/keresése az oldalról, de először is.
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);
}
}
És itt van a sor felület. Itt a „játékosok” helyett egy „csoport” formájú absztrakciót használnak. Egyetlen játékos esetén a csoport saját magából, a csoport játékosainál pedig a csoport összes játékosából áll.
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
}
Úgy döntöttem, hogy az eseményeket felhasználom a kontextus cseréjére. Alkalmas volt az esetekre - a "talált egy 10 fős játékot" eseményen privát üzenetben elküldheti a játékosoknak a szükséges üzenetet, és végrehajthatja az alapvető üzleti logikát - készenléti feladatot indíthat, előkészítheti a lobbyt. indításhoz és így tovább.
IOC-hoz InversifyJS-t használok. Kellemes tapasztalataim vannak ezzel a könyvtárral. Gyorsan és egyszerűen!
Szerverünkön több sor is található – hozzáadtunk 1x1, normál/besorolt módot és néhány egyéni módot. Ezért van egy egyetlen RoomService, amely a felhasználó és a játékkereső között helyezkedik el.
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)
}
});
}
);
}
(Kódolja a tésztát, hogy képet adjon arról, hogy nagyjából hogyan is néznek ki a folyamatok)
Itt inicializálom a sort az egyes megvalósított játékmódokhoz, és figyelem a „csoportok” változásait is, hogy módosítsam a sorokat és elkerülhessem az ütközéseket.
Szóval, jól sikerült, beszúrtam olyan kódrészleteket, amelyeknek semmi közük a témához, és most térjünk át közvetlenül a párkeresésre.
Nézzük az esetet:
1) A felhasználó játszani akar.
2) A keresés elindításához a Gateway=Discord parancsot használja, azaz reagál az üzenetre:
3) Ez az átjáró a RoomService-hez megy, és azt mondja: „A discordból származó felhasználó be akar lépni a sorba, mód: besorolatlan játék.”
4) A RoomService elfogadja az átjáró kérését, és a felhasználót (pontosabban a felhasználói csoportot) a kívánt sorba tolja.
5) A sor minden alkalommal ellenőrzi, hogy van elég játékos. Ha lehetséges, küldjön eseményt:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) A RoomService nyilvánvalóan boldogan hallgat minden sorban állást, izgatottan várja ezt az eseményt. Bemenetként megkapjuk a játékosok listáját, belőlük egy virtuális „szobát” alakítunk ki, és természetesen eseményt adunk ki:
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) Így eljutottunk a „legmagasabb” tekintélyhez - az osztályhoz Bot. Általában az átjárók (nem értem, milyen viccesen néz ki oroszul) és a párkeresés üzleti logikája közötti kapcsolattal foglalkozik. A bot kihallgatja az eseményt, és megparancsolja a DiscordGateway-nek, hogy küldjön készenléti ellenőrzést minden felhasználónak.
8) Ha valaki 3 percen belül elutasítja vagy nem fogadja el a játékot, akkor NEM tesszük vissza a sorba. A többieket visszaállítjuk a sorba, és megvárjuk, amíg ismét 10 ember lesz. Ha minden játékos elfogadta a játékot, akkor kezdődik az érdekes rész.
Dedikált szerver konfiguráció
Játékaink Windows Server 2012 rendszerű VDS-en vannak tárolva. Ebből több következtetést is levonhatunk:
- Nincs rajta dokkoló, ami szíven ütött
- A bérleti díjon spórolunk
A feladat egy folyamat futtatása VDS-en egy VPS-ről Linuxon. Flaskban írtam egy egyszerű szervert. Igen, nem szeretem a Pythont, de mit tehetsz? Gyorsabb és egyszerűbb ráírni ezt a szervert.
3 funkciót lát el:
- Szerver indítása konfigurációval – térkép kiválasztása, a játék indításához szükséges játékosok száma és a beépülő modulok készlete. A bővítményekről most nem írok – az egy másik történet, amikor az éjszakai kávék literei könnyekkel és tépett hajjal keverednek.
- A szerver leállítása/újraindítása sikertelen csatlakozások esetén, amit csak manuálisan tudunk kezelni.
Itt minden egyszerű, a kódpéldák nem is megfelelőek. 100 soros szkript
Így amikor 10 ember összegyűlt és elfogadta a játékot, elindult a szerver és mindenki szívesen játszott, privát üzenetben elküldték a linket a játékhoz való csatlakozáshoz.
A linkre kattintva a játékos csatlakozik a játékszerverhez, és ennyi. ~25 perc elteltével a virtuális „terem” a játékosokkal kiürül.
Előre is elnézést kérek a cikk kínos voltáért, régóta nem írtam ide, és túl sok a kód a fontos részek kiemeléséhez. Tészta, röviden.
Ha látok érdeklődést a téma iránt, lesz egy második rész is – benne lesz az srcds-hez (Forrás dedikált szerver) való pluginokkal való gyötrelem, és valószínűleg egy minősítő rendszer és egy mini-dotabuff, egy játékstatisztikájú oldal.
Néhány link:
Forrás: will.com