Dota2014のマッチメイキングを書く

こんにちは。



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



私は非常に深く突入しましたが、たまたま、古いバージョンのゲームでサポートされていないほとんどすべての機能、つまりマッチメイキングを担当するDiscordボットを作成しました。

ボットによるすべての革新の前に、ロビーは手動で作成されました。メッセージに対する10の応答を収集し、サーバーを手動で組み立てるか、ローカルロビーをホストしました。







プログラマーとしての私の性格はそれほど手作業に耐えることができず、一晩で最も単純なバージョンのボットをスケッチしました。これは、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を使用します。つまり、メッセージに反応します







。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)それで、「最高の」インスタンスであるBotクラスに到達しました一般的に、彼はゲートウェイ(ロシア語でどれほどばかげているように見えるか、私にはできません)とマッチメイキングのビジネスロジックの間の接続を扱います。ボットはイベントを盗聴し、DiscordGatewayにすべてのユーザーに準備チェックを送信するように命令します。







8)誰かが3分以内にゲームを拒否または受け入れなかった場合、キューに戻さない。他の全員をキューに戻し、10人が再び採用されるのを待ちます。すべてのプレイヤーがゲームを受け入れた場合、楽しい部分が始まります。



専用サーバー構成



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



  1. 私の心を打つドッカーはありません
  2. 家賃を節約


タスクは、Linux上のVPSを使用してVDSでプロセスを開始することです。Flaskでシンプルなサーバーを作成しました。はい、私はpythonが好きではありませんが、何ができますか?このサーバーをその上に作成する方が速くて簡単です。



3つの機能があります。



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


ここではすべてが単純で、コード例も不適切です。100行のスクリプトしたがって



、10人が集まってゲームを受け入れると、サーバーが実行され、誰もがプレイしたいと思っているので、ゲームに接続するためのリンクがプライベートメッセージで表示されます。







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



記事がぎこちなくて、ここに長い間書いていません。重要なセクションを強調するにはコードが多すぎます。要するに、麺。



このトピックに興味がある場合は、2番目の部分があります。これには、srcds(ソース専用サーバー)のプラグインによる苦痛と、おそらく、評価システムとゲーム統計のあるサイトであるmini-dotabuffが含まれます。



いくつかのリンク:



  1. 私たちのサイト(統計、リーダーボード、小さなランドス、クライアントのダウンロード)
  2. 不和サーバー



All Articles