Musim semi ini saya menemukan sebuah proyek di mana orang-orang belajar cara menjalankan server Dota 2 versi 2014 dan, karenanya, memainkannya. Saya adalah penggemar berat game ini, dan saya tidak bisa melewatkan kesempatan unik untuk terjun ke masa kecil saya.
Saya terjun sangat dalam, dan kebetulan saya menulis bot Discord, yang bertanggung jawab untuk hampir semua fungsi yang tidak didukung di versi lama game, yaitu perjodohan.
Sebelum semua inovasi dengan bot, lobi dibuat secara manual. Kami mengumpulkan 10 tanggapan untuk sebuah pesan dan secara manual menyusun server, atau menjadi tuan rumah di lobi lokal.
Sifat saya sebagai programmer tidak tahan dengan pekerjaan manual, dan dalam semalam saya membuat sketsa versi bot yang paling sederhana, yang secara otomatis membuka server ketika 10 orang direkrut.
Saya memutuskan untuk langsung menulis di nodejs, karena saya tidak terlalu suka python, dan saya merasa lebih nyaman di lingkungan ini.
Ini adalah pengalaman pertama saya menulis bot untuk Discord, tetapi ternyata sangat sederhana. Modul npm resmi discord.js menyediakan antarmuka yang nyaman untuk bekerja dengan pesan, mengumpulkan reaksi, dll.
Penafian: Semua contoh kode "up-to-date", yang berarti mereka telah melalui beberapa iterasi penulisan ulang dalam semalam.
Inti dari perjodohan adalah "antrian" di mana pemain yang ingin bermain ditempatkan dan dikeluarkan ketika mereka tidak ingin atau menemukan permainan.
Seperti inilah inti dari "pemain" itu. Awalnya, itu hanya id pengguna di Discord, tetapi rencananya termasuk peluncur / pencarian game dari situs, tetapi hal pertama yang pertama.
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);
}
}
Dan inilah antarmuka antriannya. Di sini, alih-alih "pemain", abstraksi dalam bentuk "grup" digunakan. Untuk pemain tunggal, grup terdiri dari dirinya sendiri, dan untuk pemain dalam grup, masing-masing, dari semua pemain dalam grup.
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
}
Saya memutuskan untuk menggunakan acara untuk bertukar konteks. Cocok untuk kasus - untuk acara "menemukan permainan untuk 10 orang", Anda dapat mengirim pesan yang diinginkan kepada para pemain dalam pesan pribadi, dan menjalankan logika bisnis utama - meluncurkan tugas untuk memeriksa kesiapan, menyiapkan lobi untuk peluncuran, dan sebagainya.
Untuk IOC, saya menggunakan InversifyJS. Saya memiliki pengalaman yang menyenangkan dengan perpustakaan ini. Cepat dan mudah!
Kami memiliki beberapa antrian di server - kami telah menambahkan mode 1x1, normal / peringkat, dan beberapa yang khusus. Oleh karena itu, ada RoomService tunggal yang terletak di antara pengguna dan pencarian game.
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)
}
});
}
);
}
(Kode mie untuk mewakili seperti apa proses itu terlihat)
Di sini saya menginisialisasi antrian untuk setiap mode permainan yang diimplementasikan, dan juga mendengarkan perubahan dalam "grup" untuk memperbaiki antrian dan menghindari beberapa konflik.
Jadi, saya hebat, saya memasukkan potongan kode yang tidak ada hubungannya dengan topik, dan sekarang mari langsung beralih ke pembuatan master.
Pertimbangkan sebuah kasus:
1) Pengguna ingin bermain.
2) Untuk memulai pencarian, dia menggunakan Gateway = Discord, yaitu, memberikan reaksi terhadap pesan:
3) Gateway ini menuju ke RoomService, dan berkata "Pengguna dari perselisihan ingin masuk ke antrian, mode: permainan tanpa peringkat."
4) RoomService menerima permintaan gateway, dan mendorongnya ke antrian pengguna yang diinginkan (lebih tepatnya, grup pengguna).
5) Antrian memeriksa setiap perubahan untuk melihat apakah ada cukup pemain untuk bermain. Jika memungkinkan - keluarkan acara:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService, tentu saja, dengan senang hati mendengarkan setiap antrian untuk mengantisipasi acara ini. Di pintu masuk kami menerima daftar pemain, membentuk "ruang" virtual dari mereka, dan, tentu saja, mengeluarkan acara:
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) Jadi kita sampai pada instance "tertinggi" - kelas Bot . Secara umum, dia berurusan dengan hubungan antara gateway (betapa konyolnya tampilannya dalam bahasa Rusia, saya tidak bisa) dan logika bisnis perjodohan. Bot menguping acara tersebut dan memerintahkan DiscordGateway untuk mengirimkan pemeriksaan kesiapan ke semua pengguna.
8) Jika seseorang menolak atau tidak menerima permainan dalam 3 menit, maka kami TIDAK mengembalikan mereka ke antrian. Kami mengembalikan sisanya ke antrian dan menunggu 10 orang direkrut lagi. Jika semua pemain telah menerima permainan, maka bagian yang menyenangkan dimulai.
Konfigurasi server khusus
Game kami di-host di VDS dengan server Windows 2012. Beberapa kesimpulan dapat diambil dari ini:
- Tidak ada buruh pelabuhan di atasnya, yang menyentuh hati saya
- Kami menghemat sewa
Tugasnya adalah memulai proses di VDS dengan VPS di Linux. Menulis server sederhana di Flask. Ya, saya tidak suka python, tapi apa yang bisa saya lakukan - menulis server ini di atasnya lebih cepat dan lebih mudah.
Ini memiliki 3 fungsi:
- Peluncuran server dengan konfigurasi - pemilihan peta, jumlah pemain untuk memulai permainan, dan satu set plugin. Saya tidak akan menulis tentang plugin sekarang - ini adalah cerita terpisah dengan liter kopi di malam hari, bercampur dengan air mata dan rambut sobek.
- Menghentikan / memulai ulang server jika terjadi koneksi yang tidak berhasil, yang hanya dapat kami tangani secara manual.
Semuanya sederhana di sini, contoh kode bahkan tidak pantas. Script untuk 100 baris
Jadi, ketika 10 orang berkumpul dan menerima permainan, server sedang berjalan dan semua orang ingin bermain, tautan untuk terhubung ke permainan datang dalam pesan pribadi.
Dengan mengklik tautan tersebut, pemain terhubung ke server game, dan kemudian itu saja. Setelah ~ 25 menit, "ruang" virtual dengan para pemain akan dihapus.
Saya mohon maaf sebelumnya atas kejanggalan artikel, saya sudah lama tidak menulis di sini, dan ada terlalu banyak kode untuk menyoroti bagian penting. Mie, singkatnya.
Jika saya melihat minat pada topik, maka akan ada bagian kedua - itu akan berisi siksaan saya dengan plugin untuk srcds (Source dedicated server), dan, mungkin, sistem peringkat dan mini-dotabuff, sebuah situs dengan statistik permainan.
Beberapa tautan: