سلام بر همه
بهار امسال با پروژه ای روبرو شدم که در آن بچه ها یاد گرفتند که چگونه سرور Dota 2 نسخه 2014 را اجرا کنند و بر این اساس روی آن بازی کنند. من از طرفداران پر و پا قرص این بازی هستم و نمی توانستم این فرصت منحصر به فرد را برای غوطه ور شدن در کودکی ام از دست بدهم.
من خیلی عمیق فرو رفتم و این اتفاق افتاد که یک ربات Discord نوشتم که تقریباً مسئول تمام عملکردهایی است که در نسخه قدیمی بازی پشتیبانی نمی شود ، یعنی خواستگاری.
قبل از همه نوآوری ها با ربات، لابی به صورت دستی ایجاد شد. ما 10 واکنش به یک پیام را جمع آوری کردیم و به صورت دستی یک سرور مونتاژ کردیم یا یک لابی محلی را میزبانی کردیم.
طبیعت من به عنوان یک برنامه نویس نمی توانست این همه کار دستی را تحمل کند، و یک شبه ساده ترین نسخه ربات را ترسیم کردم، که به طور خودکار سرور را با 10 نفر بالا می برد.
بلافاصله تصمیم گرفتم در nodejs بنویسم، زیرا پایتون را خیلی دوست ندارم و در این محیط احساس راحتی بیشتری می کنم.
این اولین تجربه من برای نوشتن ربات برای Discord است، اما بسیار ساده بود. ماژول رسمی npm discord.js یک رابط مناسب برای کار با پیام ها، جمع آوری واکنش ها و غیره فراهم می کند.
سلب مسئولیت: همه نمونههای کد «جاری» هستند، به این معنی که چندین بار در شب بازنویسی شدهاند.
اساس خواستگاری یک «صف» است که در آن بازیکنانی که میخواهند بازی کنند، در آن قرار میگیرند و وقتی نمیخواهند یا بازی را پیدا نمیکنند، حذف میشوند.
این همان چیزی است که جوهر یک "بازیکن" به نظر می رسد. در ابتدا این فقط یک شناسه کاربری در Discord بود، اما برنامههایی برای راهاندازی/جستجوی بازیها از سایت وجود دارد، اما اول از همه.
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);
}
}
و اینجا رابط صف است. در اینجا به جای "بازیکنان" از انتزاع به شکل "گروه" استفاده شده است. برای یک بازیکن، گروه از خودش و برای بازیکنان یک گروه به ترتیب از همه بازیکنان گروه تشکیل می شود.
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
}
تصمیم گرفتم از رویدادها برای تبادل زمینه استفاده کنم. برای موارد مناسب بود - پس از رویداد "یک بازی برای 10 نفر پیدا شد"، می توانید پیام های لازم را در پیام های خصوصی برای بازیکنان ارسال کنید و منطق اصلی تجارت را انجام دهید - کار را برای بررسی آمادگی راه اندازی کنید، لابی را آماده کنید. برای راه اندازی و غیره.
برای IOC من از InversifyJS استفاده می کنم. من تجربه خوشایندی از کار با این کتابخانه دارم. سریع و آسان!
ما چندین صف در سرور خود داریم - ما 1x1، نرمال/رتبه بندی شده و چند حالت سفارشی اضافه کرده ایم. بنابراین، یک RoomService تک تنی وجود دارد که بین کاربر و جستجوی بازی قرار دارد.
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)
}
});
}
);
}
(نودل ها را کدنویسی کنید تا ایده ای در مورد اینکه فرآیندها تقریباً چگونه به نظر می رسند)
در اینجا من صف را برای هر یک از حالت های بازی اجرا شده مقداردهی اولیه می کنم و همچنین به تغییرات در "گروه ها" گوش می دهم تا صف ها را تنظیم کنم و از برخی درگیری ها جلوگیری کنم.
بنابراین، آفرین، من کدهایی را وارد کردم که ربطی به موضوع ندارند، و اکنون مستقیماً به سراغ همسریابی می رویم.
بیایید مورد را در نظر بگیریم:
1) کاربر می خواهد بازی کند.
2) برای شروع جستجو از Gateway=Discord استفاده می کند، یعنی به پیام واکنش نشان می دهد:
3) این دروازه به RoomService می رود و می گوید "یک کاربر از اختلاف می خواهد وارد صف شود، حالت: بازی بدون رتبه."
4) RoomService درخواست دروازه را می پذیرد و کاربر (به طور دقیق تر، گروه کاربری) را به صف مورد نظر می برد.
5) هر بار که تعداد بازیکنان کافی برای بازی وجود دارد، صف چک می کند. در صورت امکان، یک رویداد منتشر کنید:
private onRoomFound(players: Party[]) {
this.emit("room-found", {
players,
});
}
6) RoomService بدیهی است که با خوشحالی به هر صف در انتظار این رویداد گوش می دهد. ما فهرستی از بازیکنان را به عنوان ورودی دریافت میکنیم، یک «اتاق» مجازی از آنها تشکیل میدهیم و البته رویدادی را صادر میکنیم:
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) بنابراین به "بالاترین" مرجع - کلاس رسیدیم ربات. به طور کلی، او با ارتباط بین دروازهها (نمیتوانم بفهمم در روسی چقدر خندهدار به نظر میرسد) و منطق تجاری خواستگاری میپردازد. ربات رویداد را می شنود و به DiscordGateway دستور می دهد تا یک چک آمادگی برای همه کاربران ارسال کند.
8) اگر کسی در عرض 3 دقیقه بازی را رد کرد یا قبول نکرد، او را به صف برنمیگردانیم. بقیه را به صف برمی گردانیم و منتظر می مانیم تا دوباره 10 نفر باشند. اگر همه بازیکنان بازی را پذیرفته باشند، قسمت جالب شروع می شود.
پیکربندی سرور اختصاصی
بازیهای ما بر روی VDS با سرور ویندوز 2012 میزبانی میشوند. از این میتوان چندین نتیجه گرفت:
- هیچ داکری روی آن نیست که به قلبم برخورد کرد
- در اجاره پس انداز می کنیم
وظیفه اجرای یک فرآیند روی VDS از VPS در لینوکس است. من یک سرور ساده در Flask نوشتم. بله، من پایتون را دوست ندارم، اما چه کاری می توانید انجام دهید؟ نوشتن این سرور روی آن سریع تر و آسان تر است.
این 3 عملکرد را انجام می دهد:
- راه اندازی سرور با پیکربندی - انتخاب نقشه، تعداد بازیکنان برای شروع بازی و مجموعه ای از افزونه ها. من اکنون در مورد پلاگین ها نمی نویسم - این یک داستان متفاوت است با لیتر قهوه در شب که با اشک و موهای پاره شده مخلوط شده است.
- توقف/راهاندازی مجدد سرور در صورت اتصال ناموفق، که فقط به صورت دستی میتوانیم آن را مدیریت کنیم.
همه چیز در اینجا ساده است، نمونه های کد حتی مناسب نیستند. اسکریپت 100 خطی
بنابراین وقتی 10 نفر دور هم جمع شدند و بازی را پذیرفتند، سرور راه اندازی شد و همه مشتاق بازی بودند، لینک اتصال به بازی در پیام خصوصی ارسال شد.
با کلیک بر روی لینک، بازیکن به سرور بازی متصل می شود و تمام. پس از 25 دقیقه، "اتاق" مجازی با بازیکنان پاک می شود.
پیشاپیش بابت بیمعنا بودن مقاله عذرخواهی میکنم، مدت زیادی است که اینجا ننوشتهام و کد زیادی برای برجسته کردن بخشهای مهم وجود دارد. به طور خلاصه رشته فرنگی.
اگر علاقه ای به موضوع ببینم، قسمت دوم وجود خواهد داشت - شامل عذاب من با افزونه های srcds (سرور اختصاصی منبع) و احتمالاً یک سیستم رتبه بندی و mini-dotabuff، یک سایت با آمار بازی است.
چند لینک:
منبع: www.habr.com