Kumusta lahat.
Ngayong tagsibol, napadpad ako sa isang proyekto kung saan natutunan ng ilang lalaki kung paano tumakbo at maglaro sa isang 2014 Dota 2 server. Ako ay isang malaking tagahanga ng laro, at hindi ko napigilan ang natatanging pagkakataong ito na sariwain ang aking pagkabata.
Nag-delved ako ng malalim, at lumabas na nagsulat ako ng Discord bot na responsable para sa halos lahat ng functionality na hindi sinusuportahan sa lumang bersyon ng laro, lalo na ang matchmaking.
Bago ang lahat ng pagbabagong nauugnay sa bot, manu-manong ginawa ang mga lobby. Mangongolekta kami ng 10 reaksyon sa isang mensahe at manu-manong bumuo ng server o magho-host ng lokal na lobby.

Hindi kinaya ng aking pagiging programmer ang ganoong dami ng manu-manong trabaho, at sa magdamag ay pinagsama-sama ko ang pinakasimpleng bersyon ng bot na awtomatikong magsisimula sa server kapag umabot na sa 10 tao.
Nagpasya akong magsulat kaagad sa mga nodej dahil hindi ko talaga gusto ang Python, ngunit mas komportable ako sa kapaligirang ito.
Ito ang aking unang pagkakataon na magsulat ng isang Discord bot, ngunit ito ay naging talagang simple. Ang opisyal na discord.js npm module ay nagbibigay ng isang maginhawang interface para sa pagtatrabaho sa mga mensahe, pagkolekta ng mga reaksyon, at higit pa.
Disclaimer: Ang lahat ng mga halimbawa ng code ay "kasalukuyan", ibig sabihin, dumaan ang mga ito sa ilang mga pag-ulit ng muling pagsusulat sa gabi.
Ang batayan ng matchmaking ay isang "pila" kung saan ang mga manlalarong gustong maglaro ay inilalagay at inaalis kapag ayaw na nilang maglaro o maghanap ng laro.
Ito ang hitsura ng isang entity na "manlalaro." Sa una, ito ay isang Discord user ID lamang, ngunit ang mga plano ay tumatawag para sa isang launcher/laro na paghahanap mula sa website, ngunit una sa lahat.
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);
}
}
Narito ang queue interface. Sa halip na "mga manlalaro," ginagamit nito ang abstraction ng "grupo." Para sa isang solong manlalaro, ang grupo ay binubuo ng manlalaro na iyon, at para sa mga manlalaro sa isang grupo, ito ay binubuo ng lahat ng mga manlalaro sa grupo.
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
}
Nagpasya akong gumamit ng mga kaganapan upang makipagpalitan ng konteksto. Nababagay ito sa mga kaso ng paggamit—ang kaganapang "nahanap na ang laro ng 10-player" ay maaaring gamitin upang ipadala ang kinakailangang mensahe sa mga manlalaro sa pamamagitan ng mga pribadong mensahe at isagawa ang pangunahing lohika ng negosyo—magpatakbo ng isang gawain upang suriin ang kahandaan, ihanda ang lobby para sa paglulunsad, at iba pa.
Para sa IOC, gumagamit ako ng InversifyJS. Nagkaroon ako ng magandang karanasan sa library na ito. Ito ay mabilis at madali!
Marami kaming pila sa aming server—nagdagdag kami ng 1v1, regular/rank, at ilang custom na mode. Iyon ang dahilan kung bakit mayroon kaming singleton RoomService na nasa pagitan ng user at ng proseso ng matchmaking.
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 noodles para mabigyan ka ng ideya kung ano ang hitsura ng mga proseso)
Dito ko sinisimulan ang pila para sa bawat isa sa ipinatupad na mga mode ng laro, at nakikinig din para sa mga pagbabago sa "grupo" upang ayusin ang mga pila at maiwasan ang ilang mga salungatan.
Okay, well done, nag-paste ako ng ilang piraso ng code na hindi nauugnay sa paksa, at ngayon ay lumipat tayo sa mismong matchmaking.
Isaalang-alang natin ang isang kaso:
1) Nais maglaro ang gumagamit.
2) Upang simulan ang paghahanap, gumagamit ito ng Gateway=Discord, ibig sabihin, nagtatakda ito ng reaksyon sa mensahe:

3) Pupunta ang gateway na ito sa RoomService at nagsasabing "Gustong sumali ng isang user mula sa Discord sa queue, mode: unranked game."
4) Tinatanggap ng RoomService ang kahilingan sa gateway at itinutulak ang user (o mas tiyak, ang pangkat ng user) sa naaangkop na pila.
5) Sinusuri ng pila ang bawat oras na nagbabago ito kung may sapat na mga manlalaro na makalaro. Kung maaari, naglalabas ito ng isang kaganapan:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService, malinaw naman, sabik na nakikinig sa bawat pila sa pag-asam ng kaganapang ito. Nakatanggap kami ng isang listahan ng mga manlalaro sa pasukan, bumubuo ng isang virtual na "kuwarto" mula sa kanila, at, siyempre, naglalabas ng kaganapan:
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) Kaya naabot natin ang "pinakamataas" na awtoridad - ang klase botKaraniwan, pinangangasiwaan nito ang komunikasyon sa pagitan ng mga gateway (hindi ko man lang maintindihan kung gaano ito katawa sa wikang Ruso) at ang lohika ng negosyo sa paggawa ng mga posporo. Ang bot ay nag-eavesdrop sa isang kaganapan at nagtuturo sa DiscordGateway na magpadala ng isang pagsusuri sa kahandaan sa lahat ng mga gumagamit.

8) Kung may tumanggi o hindi tumanggap ng laro sa loob ng 3 minuto, HINDI namin sila ibabalik sa pila. Ang iba ay ibinalik sa pila at naghihintay kami hanggang sa muling umabot sa 10 tao ang pila. Kung tatanggapin ng lahat ng manlalaro ang laro, magsisimula ang masayang bahagi.
Nakatuon sa pagsasaayos ng server
Ang aming mga laro ay naka-host sa VDS c Windows server 2012. Maraming konklusyon ang maaaring makuha mula rito:
- Walang docker dito, na tumatak sa aking puso
- Nagtitipid kami sa upa
Ang gawain ay magpatakbo ng isang proseso sa isang VDS mula sa isang Linux VPS. Sumulat ako ng isang simpleng server sa Flask. Hindi ko gusto ang Python, ngunit mas mabilis at mas madaling isulat ang server na ito sa Python.
Gumaganap ito ng 3 function:
- Paglulunsad ng server na may configuration—pagpili ng mapa, ang bilang ng mga manlalaro na magsisimula ng laro, at isang hanay ng mga plugin. Hindi na ako pupunta sa mga plugin ngayon—iyan ay isang buong iba pang kuwento, na kinasasangkutan ng mga galon ng kape sa gabi, luha, at gutay-gutay na buhok.
- Ihihinto/i-restart ang server kung sakaling magkaroon ng mga hindi matagumpay na koneksyon, na maaari lang naming hawakan nang manu-mano.
Napakasimple nito, halos walang kaugnayan ang mga halimbawa ng code. Ang script ay 100 linya ang haba.
Kaya, kapag 10 tao ang nagsama-sama at tinanggap ang laro, ang server ay inilunsad at ang lahat ay sabik na maglaro, isang link upang sumali sa laro ay dumating sa isang pribadong mensahe.

Ang pag-click sa link ay nag-uugnay sa player sa server ng laro, at pagkatapos ay maayos ang lahat. Pagkatapos ng humigit-kumulang 25 minuto, ang virtual na "kuwarto" na naglalaman ng mga manlalaro ay na-clear.
Humihingi ako ng paumanhin nang maaga para sa awkwardness ng artikulong ito. Matagal na akong hindi nagsusulat dito, at napakaraming code para i-highlight ang mahahalagang seksyon. Talaga, ito ay isang grupo ng mga bagay na walang kapararakan.
Kung makakita ako ng interes sa paksa, magkakaroon ng pangalawang bahagi - isasama nito ang aking mga pakikibaka sa mga plugin para sa srcds (Source dedicated server), at, malamang, isang rating system at isang mini-dotabuff, isang site na may mga istatistika ng laro.
Ilang link:
Pinagmulan: www.habr.com
