Matchmaking a 2014-es Dota-hoz

Helló mindenkinek.

Idén tavasszal rátaláltam egy projektre, ahol a fiúk megtanulták, hogyan indítsanak Dota 2 szervert a 2014-es verzióban, és ennek megfelelően játszhassanak rajta. Nagy rajongója vagyok ennek a játéknak, és nem tudtam elmenni a különleges lehetőség mellett, hogy visszatérjek a gyermekkoromban.

Rendkívül mélyen merültem el a dologban, és végül írtam egy Discord botot, amely szinte az összes funkciót ellátja, amelyet a régi verzió nem támogat, például a matchmakinget.
A bot bevezetése előtt a lobbik manuálisan készültek. 10 reakciót gyűjtöttünk egy üzenetre, majd manuálisan összehoztuk a szervert, vagy helyi lobbikat hosztoltunk.

Matchmaking a 2014-es Dota-hoz

Programozói természetem nem bírta el a sok manuális munkát, és egy éjszaka alatt megírtam a legbasicabb verziót a botból, amely automatikusan elindítja a szervert, amikor 10 ember összegyűlik.

Node.js-ben kezdtem írni, mert nem túl kedvelem a Python-t, és jobban érzem magam ebben a környezetben.

Ez az első tapasztalatom a Discord bot írásában, de meglepően egyszerűnek bizonyult. Az official npm modul, a discord.js kényelmes interfészt biztosít az üzenetek kezelésére, a reakciók összegyűjtésére stb.

Felelősségkizárás: minden kódemelvény 'aktuális', tehát átment néhány éjszakai újraíráson.

A matchmaking alapja a 'sor', amelybe a játékosok kerülnek, akik játszani akarnak, és kikerülnek, amikor meggondolják magukat vagy találnak egy játékot.

Így néz ki a 'játékos' entitás. Eredetileg csak a Discord felhasználó azonosítója volt, de terveim között szerepel a launcher/játékkeresés a weboldalról, de erről később.

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);
  }
}

Íme a sor interfésze. Itt a 'játékosok' helyett egy 'csoport' absztrakciót használnak. Egyéni játékos számára a csoport önmagából áll, míg a csoportban lévő játékosok esetében a csoport összes játékosából.

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
}

Elhatároztam, hogy eseményeket használok a kontextus cseréjére. Ez illeszkedett az esetekhez — az "10 fős játék találva" esemény alapján küldhetünk kért üzenetet a játékosoknak közvetlen üzenetben, és végrehajthatjuk a fő üzleti logikát — elindítani egy feladatot a készenlét ellenőrzésére, előkészíteni a lobbyt a kezdéshez és így tovább.

Az IOC-hoz az InversifyJS-t használom. Fényes tapasztalataim vannak ezzel a könyvtárral. Gyors és egyszerű!

Több sor várakozik a szerverünkön — bejöttek az 1x1, normál/ranglistás módban, és néhány egyedi játék. Ezért van egy singleton RoomService, amely a felhasználó és a játékkeresés között helyezkedik el.

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))) {
            // ha a sorban van ez a párt, akkor újra hozzáadjuk a pártot
            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))) {
            // ha a sorban van ez a párt, akkor eltávolítjuk a pártot
            q.remove(event.party)
          }
        });
      }
    );
  }

(A kód példája annak bemutatására, hogy körülbelül hogy néznek ki a folyamatok)

Itt inicializálom a sort a játék által megvalósított minden egyes módhoz, és figyelem a "csoportok" változásait, hogy finomhangolhassam a sorokat és elkerüljem a bizonyos konfliktusokat.

Na szóval, ügyes vagyok, beillesztettem kóddarabokat, amelyek semmilyen módon nem kapcsolódnak a témához, most pedig térjünk rá a matchmakingre.

Nézzük meg az esetet:

1) A felhasználó játszani akar.

2) Ahhoz, hogy elkezdje a keresést, használja a Gateway=Discord szolgáltatást, azaz reagál az üzenetre:

Matchmaking a 2014-es Dota-hoz

3) Ez a gateway eljut a RoomService-hez, és mondja: "A Discordból származó felhasználó szeretne belépni a sorba, mód: nem ranglista játék".

4) A RoomService elfogadja a gateway kérését, és a felhasználót ( pontosabban a felhasználó csoportját) a megfelelő sorba helyezi.

5) A sor minden változásnál ellenőrzi, hogy elegendő játékos van-e a játékhoz. Ha igen — kibocsátunk egy eseményt:

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

6) A RoomService, nyilvánvalóan, örömmel hallgat minden várakozási sort ezen esemény izgatott várakozásával. Beérkezik egy listája a játékosoknak, és ezekből létrehozzuk a virtuális "szobát", és természetesen kibocsátjuk az eseményt:

queue.addListener("room-found", (event: RoomFoundEvent) => {
      console.log(
        `Szoba megtalálva mód: [${mode}]. Ideje ingyenes szobát szerezni ezeknek az embereknek`
      );
      const room = this.getFreeRoom(mode);
      room.fill(event.players);

      this.onRoomFormed(room);
    });

7) Elérkeztünk a "legmagasabb" szintű példányhoz — a klasszhoz Bot. Általában a kapuek és a matchmaking üzleti logika közötti kapcsolattartással foglalkozik. A bot figyeli az eseményt, és utasítja a DiscordGateway-t, hogy küldjön minden felhasználónak készenléti ellenőrzést.

Matchmaking a 2014-es Dota-hoz

8) Ha valaki három perc alatt elutasítja vagy nem fogadja el a játékot, akkor NEM visszük vissza őt a sorba. Minden más visszatér a sorba, és várunk, amíg újra összegyűlik 10 fő. Ha minden játékos elfogadta a játékot, akkor kezdődik az izgalmas rész.

Dedicated server konfiguráció

A játékaink VDS-en, Windows Server 2012-n vannak hosztolva. Ebből néhány következtetést le lehet vonni:

  1. Nincs Docker, ami a szívembe döfött.
  2. Költséget takarítunk meg a bérlésen.

A feladat az, hogy VPS-en Linuxon folyamatot indítsunk a VDS-en. Írtam egy egyszerű szervert Flask-ban. Igen, nem szeretem a Python-t, de mit tehetek — ezen a nyelven gyorsabb és könnyebb megírni ezt a szervert.

Három funkciót lát el:

  1. A szerver indítása konfigurációval — a térkép, a játékhoz szükséges játékosok száma, és a pluginek kiválasztása. A pluginekről most nem írok — ez egy külön történet, rengeteg kávéval az éjszaka közepén, vegyítve könnyekkel és kihúzott hajakkal.
  2. A szerver leállítása/újraindítása sikertelen kapcsolatok esetén, amelyeket csak manuálisan tudunk kezelni.

Itt minden egyszerű, a kód példák nem is relevánsak. A script 100 sorból áll.

Tehát, amikor 10 ember összegyűlik és elfogadja a játékot, a szerver elindul, és mindenki játszani vágyik, a privát üzenetekben megérkezik a játékhoz való kapcsolódás linkje.

Matchmaking a 2014-es Dota-hoz

A linkre kattintva a játékos csatlakozik a játék szerverhez, és a többi már automatikusan történik. Kb. 25 perc múlva a virtuális "szoba" játékosokkal kiürül.

Előre is elnézést kérek a cikk zavarosságáért, rég írtam ide, és túl sok a kód, hogy kiemeljem a fontos részeket. Röviden, csak egy kis felfordulás.

Ha látom, hogy van érdeklődés a téma iránt, készítek egy második részt — abban az én szenvedéseim lesznek a srcds (Source dedicated server) bővítményekkel, és valószínűleg egy értékelési rendszer és egy mini-dotabuff, egy statisztikai oldal a játékokhoz.

Néhány link:

  1. A weboldalunk (statisztikák, ranglista, egy kis landing page és a kliens letöltése)
  2. Discord szerver

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster