Пишемо матцхмакинг за Дота 2014

Поздрав свима.

Овог пролећа сам наишао на пројекат у којем су момци научили како да покрећу Дота 2 сервер верзију 2014 и, сходно томе, играју на њему. Велики сам обожаватељ ове игре и нисам могао да пропустим ову јединствену прилику да уроним у своје детињство.

Заронио сам веома дубоко, и десило се да сам написао Дисцорд бот који је одговоран за скоро све функционалности које нису подржане у старој верзији игре, а то је проводаџисање.
Пре свих иновација са ботом, лоби је креиран ручно. Прикупили смо 10 реакција на поруку и ручно саставили сервер или угостили локални лоби.

Пишемо матцхмакинг за Дота 2014

Моја природа програмера није могла да издржи толики ручни рад и преко ноћи сам скицирао најједноставнију верзију бота, који је аутоматски подизао сервер када је било 10 људи.

Одмах сам одлучио да пишем у нодејс-у, јер не волим баш Пајтон и осећам се пријатније у овом окружењу.

Ово је моје прво искуство писања бота за Дисцорд, али се испоставило да је врло једноставно. Званични нпм модул дисцорд.јс пружа згодан интерфејс за рад са порукама, прикупљање реакција итд.

Одрицање од одговорности: Сви примери кода су „актуелни“, што значи да су прошли кроз неколико итерација преписивања ноћу.

Основа упаривања је „ред“ у који се стављају играчи који желе да играју и уклањају их када то не желе или нађу игру.

Овако изгледа суштина „играча“. У почетку је то био само кориснички ИД у Дисцорд-у, али постоје планови за покретање/претрагу игара са сајта, али пре свега.

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 људи“, можете послати потребну поруку играчима у приватним порукама, и спровести основну пословну логику - покренути задатак да проверите спремност, припремите предворје за лансирање и тако даље.

За МОК користим ИнверсифиЈС. Имам пријатно искуство рада са овом библиотеком. Брзо и лако!

Имамо неколико редова на нашем серверу - додали смо 1к1, нормални/оцењени и неколико прилагођених режима. Стога постоји једнострука РоомСервице која лежи између корисника и претраге игре.

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) Да би започео претрагу, користи Гатеваи=Дисцорд, односно поставља реакцију на поруку:

Пишемо матцхмакинг за Дота 2014

3) Овај гатеваи иде на РоомСервице и каже „Корисник из дискорда жели да уђе у ред, режим: игра без оцене.“

4) РоомСервице прихвата захтев гатеваи-а и гура корисника (тачније, корисничку групу) у жељени ред.

5) Ред проверава сваки пут када има довољно играча за игру. Ако је могуће, емитујте догађај:

private onRoomFound(players: Party[]) {
    this.emit("room-found", {
      players,
    });
  }

6) РоомСервице очигледно радо слуша сваки ред у нестрпљењу ишчекивања овог догађаја. Добијамо листу играча као улаз, формирамо виртуелну „собу“ од њих и, наравно, издајемо догађај:

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) Тако смо дошли до „највишег” ауторитета - класе Бот. Уопштено говорећи, он се бави везом између капија (не могу да разумем колико смешно изгледа на руском) и пословне логике проводаџисања. Бот чује догађај и наређује ДисцордГатеваи-у да пошаље проверу спремности свим корисницима.

Пишемо матцхмакинг за Дота 2014

8) Ако неко одбије или не прихвати игру у року од 3 минута, онда га НЕ враћамо у ред. Све остале враћамо у ред и чекамо да опет буде 10 људи. Ако су сви играчи прихватили игру, онда почиње занимљив део.

Конфигурација наменског сервера

Наше игре се налазе на ВДС-у са Виндовс сервером 2012. Из овога можемо извући неколико закључака:

  1. На њему нема докера, који ме је погодио у срце
  2. Штедимо на кирију

Задатак је покренути процес на ВДС-у са ВПС-а на Линук-у. Написао сам једноставан сервер у Фласку. Да, не волим Питхон, али шта можете да урадите? Брже је и лакше написати овај сервер на њему.

Обавља 3 функције:

  1. Покретање сервера са конфигурацијом - избор мапе, број играча за покретање игре и сет додатака. Нећу сада писати о додацима - то је друга прича са литрама кафе ноћу помешаном са сузама и поцепаном косом.
  2. Заустављање/поновно покретање сервера у случају неуспешних конекција, што можемо да обрадимо само ручно.

Овде је све једноставно, примери кода нису ни прикладни. Скрипта од 100 линија

Дакле, када се 10 људи окупило и прихватило игру, сервер је покренут и сви су били жељни да играју, у приватним порукама је послат линк за повезивање са игром.

Пишемо матцхмакинг за Дота 2014

Кликом на везу, играч се повезује са сервером игре и то је то. После ~25 минута виртуелна „соба“ са играчима је очишћена.

Унапред се извињавам на неспретности чланка, дуго нисам овде писао, а превише је кода да би се истакли важни делови. Резанци, укратко.

Ако видим интересовање за тему, биће и други део - он ће садржати моју муку са додацима за срцдс (изворни наменски сервер), и, вероватно, систем оцењивања и мини-дотабуфф, сајт са статистиком игара.

Неки линкови:

  1. Наша веб локација (статистика, ранг листа, мала одредишна страница и преузимање клијента)
  2. Дисцорд сервер

Извор: ввв.хабр.цом

Додај коментар