Dota 2014 のマッチメイキングを作成する

こんにちは。

この春、私は彼らが Dota 2 サーバー バージョン 2014 の実行方法を学び、それに従ってプレイするプロジェクトに出会いました。 私はこのゲームの大ファンなので、子供時代に浸れるこのユニークな機会を逃すわけにはいきませんでした。

私は非常に深く研究し、たまたま古いバージョンのゲームでサポートされていないほぼすべての機能、つまりマッチメイキングを担当する Discord ボットを作成することができました。
ボットによるあらゆる革新が行われる前は、ロビーは手動で作成されていました。 メッセージに対する 10 件の反応を収集し、手動でサーバーを構築するか、ローカル ロビーをホストしました。

Dota 2014 のマッチメイキングを作成する

プログラマーとしての私の性質は、あまりにも多くの手作業に耐えることができず、一晩かけて、10 人がいるときに自動的にサーバーを立ち上げる最も単純なバージョンのボットをスケッチしました。

私は Python があまり好きではないので、nodejs で書くことにすぐに決めました。そして、この環境の方が快適だと感じたからです。

Discord のボットを作成するのはこれが初めての経験でしたが、非常に簡単であることがわかりました。 公式の npm モジュール discord.js は、メッセージの操作、反応の収集などに便利なインターフェイスを提供します。

免責事項: すべてのコード例は「最新」です。つまり、夜間に何度か書き換えが繰り返されています。

マッチメイキングの基本は、プレイしたいプレイヤーが入れられ、プレイしたくない場合やゲームが見つからない場合に削除される「キュー」です。

これが「プレイヤー」の本質です。 当初はDiscordのユーザーIDのみでしたが、サイトからゲームを起動・検索する予定ですが、まずはまずは。

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 を使用します。つまり、メッセージに反応します。

Dota 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 に準備状況チェックをすべてのユーザーに送信するよう命令します。

Dota 2014 のマッチメイキングを作成する

8) 誰かが 3 分以内にゲームを拒否または受け入れなかった場合、その人をキューに戻すことはありません。 他の全員を列に戻し、再び 10 人になるまで待ちます。 すべてのプレイヤーがゲームに同意した場合、興味深い部分が始まります。

専用サーバー構成

私たちのゲームは Windows Server 2012 の VDS でホストされています。これから、いくつかの結論を導き出すことができます。

  1. 港湾労働者がいないことに私は心を打たれました
  2. 家賃を節約します

タスクは、Linux 上の VPS から VDS 上のプロセスを実行することです。 Flaskで簡単なサーバーを書きました。 はい、私は Python は好きではありませんが、何ができますか? このサーバーを Python 上で作成する方が速くて簡単です。

次の 3 つの機能を実行します。

  1. 構成を使用してサーバーを起動します。マップ、ゲームを開始するプレーヤーの数、およびプラグインのセットを選択します。 プラグインについては今は書きません。夜に何リットルものコーヒーに涙と破れた髪が混ざった場合は別の話になります。
  2. 接続が失敗した場合のサーバーの停止/再起動。これは手動でのみ処理できます。

ここではすべてが単純であり、コード例は適切ではありません。 100行のスクリプト

10 人が集まってゲームを承認すると、サーバーが起動し、全員がプレイしたいと熱望し、ゲームに接続するためのリンクがプライベート メッセージで送信されました。

Dota 2014 のマッチメイキングを作成する

リンクをクリックすると、プレーヤーはゲーム サーバーに接続するだけです。 約 25 分後、プレイヤーがいる仮想「ルーム」はクリアされます。

記事がぎこちないことをあらかじめお詫びしておきます。長い間ここに書いていなかったので、コードが多すぎて重要なセクションを強調できませんでした。 要するに麺類。

このトピックに興味があれば、第 XNUMX 部が作成されるでしょう。そこには、srcds (ソース専用サーバー) 用のプラグインと、おそらく評価システムとミニ dotabuff、ゲーム統計のサイトに関する私の苦痛が含まれます。

いくつかのリンク:

  1. 当社の Web サイト (統計、リーダーボード、小さなランディング ページ、クライアント ダウンロード)
  2. Discordサーバー

出所: habr.com

コメントを追加します