Tere kÔigile.
Sel kevadel sattusin projekti, kus noored Ôpivad kÀivitama Dota 2 serverit 2014. aasta versioonis ja seega ka mÀngima selle peal. Olen selle mÀngu suur fÀnn ja ei saanud mööda minna ainulaadsest vÔimalusest sukelduda oma lapsepÔlve.
Sukeldusin vĂ€ga sĂŒgavale ja nii juhtus, et kirjutasin Discordi boti, mis vastutab praktiliselt kogu funktsionaalsuse eest, mida vana mĂ€ngu versioon ei toeta, nimelt matchmaking.
KÔikide botiga seotud uuenduste eel koondati lobby kÀsitsi. Kogusime 10 reaktsiooni sÔnumile ja kogusime serveri kÀsitsi vÔi korraldasime kohalikku lobby.

Minu programmeerija loomus ei pidanud vastu nii suurtele kĂ€sitööde hulkadele ja öö jooksul viskasin kokku lihtsama botiversiooni, mis automaatselt tĂ”stis serveri ĂŒles, kui 10 inimest oli koos.
Otsustasin kirjutada nodejs'i, sest ma ei armasta pythonit vÀga ning tunnen end selles keskkonnas mugavamalt.
See on minu esimene kogemus Discordi boti kirjutamisel, kuid osutus, et kÔik on vÀga lihtne. Ametlik npm moodul discord.js pakub mugavat liidest sÔnumitega töötamiseks, reaktsioonide kogumiseks jne.
MĂ€rkus: kĂ”ik koodi nĂ€ited on "aktuaalsed", mis tĂ€hendab, et need on lĂ€binud mitu öist ĂŒmberkirjutamise ringi.
Matchmakingu aluseks on "jÀrjekord", kuhu paigutatakse mÀngijad, kes soovivad mÀngida, ja eemaldatakse, kui nad enam ei soovi vÔi on mÀngu leidnud.
Nii nÀeb vÀlja "mÀngija" entiteet. Alguses oli see lihtsalt Discordi kasutaja ID, kuid plaanis on kÀivitaja/mÀngu otsimine saidilt, aga sellest kÔigest hiljem.
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);
}
}
Ja siin on jĂ€rjekorra liides. Siin kasutatakse "mĂ€ngijate" asemel abstraktsiooni nimega "grupp". ĂksikmĂ€ngija jaoks koosneb grupp temast endast, ning grupi mĂ€ngijate jaoks vastavalt kĂ”igist grupi mĂ€ngijatest.
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
}
Otsustasin kasutada sĂŒndmusi konteksti vahetamiseks. See sobib juhtumitega - sĂŒndmuse "mĂ€ng leitud 10 inimesele" korral saab saata isiklikele sĂ”numitele mĂ€ngijatele vajaliku sĂ”numi ning teostada peamist Ă€riloogikat - kĂ€ivitada ĂŒlesanne valmisoleku kontrollimiseks, valmistada lobby avamiseks ja nii edasi.
IOC jaoks kasutan InversifyJS-i. Mul on selle teegiga meeldiv kogemus. Kiire ja lihtne!
Meie serveris on mitu jĂ€rjekorda - lisandusid reĆŸiimid 1x1, tavaline/reitinguline ja paar kohandatud. SeetĂ”ttu on olemas singleton RoomService, mis asub kasutaja ja mĂ€ngu otsingu vahel.
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))) {
// kui jÀrjekorras on see partei, siis me lisame partei uuesti
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))) {
// kui jÀrjekorras on see partei, siis me eemaldame partei
q.remove(event.party)
}
});
}
);
}
(KoodinÀide protsesside visualiseerimiseks)
Siin initsialiseerin jĂ€rjekorra iga realiseeritud mĂ€ngureĆŸiimi jaoks ja kuulan "gruppide" muudatusi, et kohandada jĂ€rjekordi ja vĂ€ltida mĂ”ningaid konflikte.
Nii, rĂ”hutan, ma lisasin kooditĂŒkke, mis ei seotud teemaga, nĂŒĂŒd aga liigume muthkemiseks.
Vaadakem juhtumit:
1) Kasutaja soovib mÀngida.
2) Otsingu alustamiseks kasutab ta Gateway=Discord, st reageerib sÔnumile:

3) See vĂ€rav lĂ€heb RoomService'i ja ĂŒtleb: "Discordi kasutaja soovib jĂ€rjekorda astuda, reĆŸiim: mitte-reitinguline mĂ€ng."
4) RoomService vÔtab vÀrava taotluse vastu ja suunab kasutaja (tÀpsemalt, kasutaja grupi) vajalikku jÀrjekorda.
5) Iga jĂ€rjekorra muudatuse korral kontrollitakse, kas mĂ€ngimiseks on piisavalt mĂ€ngijaid. Kui on vĂ”imalik - vĂ€ljastame sĂŒndmuse:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService kuulab ilmselt igat jĂ€rjekorda pĂ”nevil ootusĂ€revuses. Saame sisendiks mĂ€ngijate nimekirja, loome neist virtuaalse "toa" ja muidugi saadame sĂŒndmuse:
queue.addListener("room-found", (event: RoomFoundEvent) => {
console.log(
`Toa leidmise reĆŸiim: [${mode}]. Aeg saada tasuta tuba nende jaoks`
);
const room = this.getFreeRoom(mode);
room.fill(event.players);
this.onRoomFormed(room);
});
7) NĂŒĂŒd oleme jĂ”udnud "kĂ”rgema" instantsi â klassini Bot. Ăldiselt tegeleb ta vĂ€ravate (kui naljakalt see vene keeles kĂ”lab) ja mĂ€ngude sobitamise Ă€riloogika vahelise ĂŒhenduse loomisega. Bot kuulab sĂŒndmust ja kĂ€sib DiscordGateway'l saata kĂ”igile kasutajatele valmisoleku kontrolli.

8) Kui keegi lĂŒkkab mĂ€ngu tagasi vĂ”i ei nĂ”ustu sellega kolme minuti jooksul, siis me EI tagasta neid jĂ€rjekorda. KĂ”ik teised tagastatakse jĂ€rjekorda ja ootame, kuni meid on taas 10 inimest. Kui kĂ”ik mĂ€ngijad on mĂ€nguga nĂ”ustunud, algab huvitav osa.
PĂŒhendatud serveri konfiguratsioon
Meie mÀngud on hostitud VDS-iga, millel on Windows server 2012. Sellest vÔib teha mitu jÀreldust:
- Seal ei ole dockerit, mis lĂ”i mu sĂŒdamesse tĂ”sise kahjustuse
- Me sÀÀstame rendilt
EesmĂ€rk on: kĂ€ivitada protsess VPS-l Linuxis VDS-is. Kirjutasin lihtsa serveri Flask'is. Jah, ma ei armasta Pythonit, aga mis sa teed â selle serveri kirjutamine on kiire ja lihtne.
See tÀidab 3 funktsiooni:
- Serveri kĂ€ivitamine konfiguratsiooniga â kaardi valik, mĂ€ngijate arvu mÀÀramine mĂ€ngu alustamiseks ja pluginamoodulite valimine. Pluginatest ei rÀÀgi ma hetkel â see on eraldi lugu, kus on litreid kohvi öösel, segatuna pisarate ja vĂ€ljatĂ”mmatud juustega.
- Serveri peatamine/taaskĂ€ivitamine juhul, kui me saame ĂŒhenduse ebaĂ”nnestumise kĂ€sitleda ainult kĂ€sitsi.
Siin on kÔik lihtne, koodinÀidised ei ole asjakohased. Skript 100 rida
Nii, kui 10 inimest on koos ja on nĂ”ustunud mĂ€nguga, on server kĂ€ivitatud ja kĂ”ik tahtsid mĂ€ngida, saadetakse neile isiklikesse sĂ”numitesse link mĂ€ngusse ĂŒhendamiseks.

Linki vajutamisel ĂŒhendab mĂ€ngija mĂ€nguserveriga ja edasi lĂ€heb kĂ”ik iseenesest. Umbes 25 minuti pĂ€rast tĂŒhjendatakse virtuaalne "tuba" mĂ€ngijatega.
Vabandan ette, et artikkel on kohmakas, pole ammu siia kirjutanud ja koodi on liiga palju, et eristada olulisi osi. Lihtsalt nuud.
Kui nĂ€en teemas huvi, siis tuleb teine osa â seal on mu piinad srcds (Source dedicated server) pluginatega, ja tĂ”enĂ€oliselt ka reitingusĂŒsteem ja mini-dotabuff, mĂ€ngude statistika leht.
MÔned lingid:
Allikas: habr.com
