كتابة التوفيق بين دوتا 2014

مرحبا.

صادفت هذا الربيع مشروعًا تعلم فيه الرجال كيفية تشغيل إصدار خادم Dota 2 2014 وبالتالي اللعب عليه. أنا معجب كبير بهذه اللعبة، ولا أستطيع تفويت هذه الفرصة الفريدة للانغماس في طفولتي.

لقد تعمقت كثيرًا، وقد حدث أنني كتبت برنامج Discord bot المسؤول عن جميع الوظائف غير المدعومة في الإصدار القديم من اللعبة تقريبًا، وهي التوفيق بين اللاعبين.
قبل كل الابتكارات المتعلقة بالروبوت، تم إنشاء الردهة يدويًا. قمنا بجمع 10 ردود فعل على رسالة وقمنا بتجميع خادم يدويًا أو استضافة ردهة محلية.

كتابة التوفيق بين دوتا 2014

لم تكن طبيعتي كمبرمج قادرة على تحمل الكثير من العمل اليدوي، وبين عشية وضحاها قمت برسم أبسط نسخة من الروبوت، والتي تعمل تلقائيًا على تشغيل الخادم عندما يكون هناك 10 أشخاص.

قررت على الفور أن أكتب باستخدام Nodejs، لأنني لا أحب Python حقًا، وأشعر براحة أكبر في هذه البيئة.

هذه هي تجربتي الأولى في كتابة روبوت لـ 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. لدي تجربة ممتعة في العمل مع هذه المكتبة. بسرعة وسهولة!

لدينا العديد من قوائم الانتظار على الخادم الخاص بنا - لقد أضفنا 1×1، وعادي/مصنف، واثنين من الأوضاع المخصصة. لذلك، توجد خدمة 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، أي يضع رد فعل على الرسالة:

كتابة التوفيق بين دوتا 2014

3) تذهب هذه البوابة إلى RoomService وتقول "يريد مستخدم من Discord الدخول إلى قائمة الانتظار، الوضع: لعبة غير مصنفة."

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 إرسال فحص الاستعداد إلى جميع المستخدمين.

كتابة التوفيق بين دوتا 2014

8) إذا رفض شخص ما اللعبة أو لم يقبلها خلال 3 دقائق، فلن نعيده إلى قائمة الانتظار. نعيد الجميع إلى قائمة الانتظار وننتظر حتى يكون هناك 10 أشخاص مرة أخرى. إذا قبل جميع اللاعبين اللعبة، يبدأ الجزء المثير للاهتمام.

تكوين خادم مخصص

يتم استضافة ألعابنا على VDS مع Windows Server 2012. ومن هذا يمكننا استخلاص عدة استنتاجات:

  1. لا يوجد عامل إرساء عليه مما أصابني في قلبي
  2. نحن ننقذ على الإيجار

وتتمثل المهمة في تشغيل عملية على VDS من VPS على Linux. لقد كتبت خادمًا بسيطًا في Flask. نعم، أنا لا أحب بايثون، ولكن ماذا يمكنك أن تفعل، إن كتابة هذا الخادم عليها أسرع وأسهل.

ينفذ 3 وظائف:

  1. بدء تشغيل الخادم بالتكوين - اختيار الخريطة وعدد اللاعبين لبدء اللعبة ومجموعة المكونات الإضافية. لن أكتب عن المكونات الإضافية الآن - فهذه قصة مختلفة مع لتر من القهوة في الليل ممزوجًا بالدموع والشعر الممزق.
  2. إيقاف/إعادة تشغيل الخادم في حالة عدم نجاح الاتصالات، وهو الأمر الذي لا يمكننا التعامل معه إلا يدويًا.

كل شيء بسيط هنا، وأمثلة التعليمات البرمجية ليست مناسبة حتى. نص 100 سطر

لذلك، عندما اجتمع 10 أشخاص وقبلوا اللعبة، تم إطلاق الخادم وكان الجميع متحمسًا للعب، وتم إرسال رابط الاتصال باللعبة في رسائل خاصة.

كتابة التوفيق بين دوتا 2014

من خلال النقر على الرابط، يتصل اللاعب بخادم اللعبة، وهذا كل شيء. بعد حوالي 25 دقيقة، يتم مسح "الغرفة" الافتراضية مع اللاعبين.

أعتذر مقدمًا عن حرج المقال، فأنا لم أكتب هنا منذ فترة طويلة، وهناك الكثير من التعليمات البرمجية لتسليط الضوء على الأقسام المهمة. الشعرية باختصار.

إذا رأيت اهتمامًا بالموضوع، فسيكون هناك جزء ثانٍ - سيحتوي على عذابي مع المكونات الإضافية لـ srcds (خادم المصدر المخصص)، وربما نظام التصنيف وموقع mini-dotabuff الذي يحتوي على إحصائيات اللعبة.

بعض الروابط:

  1. موقعنا الإلكتروني (الإحصائيات، المتصدرين، الصفحة المقصودة الصغيرة وتنزيل العميل)
  2. خادم الخلاف

المصدر: www.habr.com

إضافة تعليق