Pišemo pronalaženje partnera za Dotu 2014

Pozdrav.

Ovog sam proljeća naišao na projekt u kojem su dečki naučili kako pokrenuti Dota 2 poslužiteljsku verziju 2014. i, sukladno tome, igrati na njemu. Veliki sam obožavatelj ove igre i nisam mogao propustiti ovu jedinstvenu priliku da uronim u svoje djetinjstvo.

Zaronio sam vrlo duboko i dogodilo se da sam napisao Discord bota koji je odgovoran za gotovo sve funkcije koje nisu podržane u staroj verziji igre, naime matchmaking.
Prije svih inovacija s botom, predvorje je kreirano ručno. Prikupili smo 10 reakcija na poruku i ručno sastavili poslužitelj ili ugostili lokalni lobby.

Pišemo pronalaženje partnera za Dotu 2014

Moja programerska priroda nije mogla izdržati toliki ručni rad i preko noći sam skicirao najjednostavniju verziju bota koji je automatski dizao server kad je bilo 10 ljudi.

Odmah sam odlučio pisati u nodejima, jer ne volim baš Python, a u ovakvom okruženju osjećam se ugodnije.

Ovo je moje prvo iskustvo pisanja bota za Discord, ali pokazalo se da je vrlo jednostavno. Službeni npm modul discord.js pruža prikladno sučelje za rad s porukama, prikupljanje reakcija itd.

Odricanje od odgovornosti: svi primjeri koda su "trenutni", što znači da su prošli kroz nekoliko iteracija prepisivanja noću.

Osnova povezivanja je "red" u koji se stavljaju igrači koji žele igrati i uklanjaju kada ne žele ili pronađu igru.

Ovako izgleda suština “igrača”. U početku je to bio samo korisnički ID u Discordu, ali postoje planovi za pokretanje/pretragu igara sa stranice, ali prvo na redu.

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

I ovdje je sučelje čekanja. Ovdje se umjesto "igrača" koristi apstrakcija u obliku "grupe". Za jednog igrača grupu čini on sam, a za igrače u grupi, odnosno svi igrači u grupi.

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
}

Odlučio sam koristiti događaje za razmjenu konteksta. Bilo je prikladno za slučajeve - nakon događaja "pronađena je igra za 10 ljudi", možete poslati potrebnu poruku igračima u privatnim porukama i provesti osnovnu poslovnu logiku - pokrenuti zadatak za provjeru spremnosti, pripremiti predvorje za lansiranje i tako dalje.

Za IOC koristim InversifyJS. Imam ugodno iskustvo rada s ovom knjižnicom. Brzo i jednostavno!

Imamo nekoliko redova čekanja na našem poslužitelju - dodali smo 1x1, normalno/ocijenjeno i nekoliko prilagođenih načina. Stoga postoji singleton RoomService koji se nalazi između korisnika i pretraživanja igre.

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

(Kodirajte rezance da biste dobili ideju o tome kako procesi otprilike izgledaju)

Ovdje inicijaliziram red čekanja za svaki od implementiranih načina igre, a također osluškujem promjene u "grupama" kako bih prilagodio redove i izbjegao neke sukobe.

Dakle, dobro obavljeno, ubacio sam dijelove koda koji nemaju nikakve veze s temom, a sada prijeđimo izravno na spajanje.

Razmotrimo slučaj:

1) Korisnik želi igrati.

2) Da bi pokrenuo pretragu, koristi Gateway=Discord, odnosno postavlja reakciju na poruku:

Pišemo pronalaženje partnera za Dotu 2014

3) Ovaj pristupnik ide na RoomService i kaže "Korisnik iz discorda želi ući u red čekanja, način: igra bez ocjene."

4) RoomService prihvaća zahtjev gatewaya i gura korisnika (točnije grupu korisnika) u željeni red čekanja.

5) Red čekanja provjerava svaki put kada ima dovoljno igrača za igru. Ako je moguće, emitirajte događaj:

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

6) RoomService očito radosno osluškuje svaki red u napetom iščekivanju ovog događaja. Kao ulaz primamo popis igrača, od njih formiramo virtualnu "sobu" i, naravno, izdajemo događaj:

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) Tako smo došli do “najvišeg” autoriteta - razreda Bot. Općenito, bavi se vezom između pristupnika (ne mogu shvatiti kako to smiješno izgleda na ruskom) i poslovne logike spajanja partnera. Bot čuje događaj i naređuje DiscordGatewayu da pošalje provjeru spremnosti svim korisnicima.

Pišemo pronalaženje partnera za Dotu 2014

8) Ako netko odbije ili ne prihvati igru ​​unutar 3 minute, NE VRAĆAMO ga u red čekanja. Sve ostale vraćamo u red i čekamo da opet bude 10 ljudi. Ako su svi igrači prihvatili igru, tada počinje zanimljiv dio.

Konfiguracija namjenskog poslužitelja

Naše se igre nalaze na VDS-u s Windows poslužiteljem 2012. Iz ovoga možemo izvući nekoliko zaključaka:

  1. Na njemu nema dokera, što me pogodilo u srce
  2. Štedimo na najmu

Zadatak je pokrenuti proces na VDS-u iz VPS-a na Linuxu. Napisao sam jednostavan poslužitelj u Flasku. Da, ne volim Python, ali što mogu? Brže je i lakše pisati ovaj poslužitelj na njemu.

Obavlja 3 funkcije:

  1. Pokretanje poslužitelja s konfiguracijom - odabirom karte, broja igrača za pokretanje igre i skupa dodataka. Neću sad pisati o dodacima - to je druga priča s litrama kave noću pomiješanim sa suzama i počupanom kosom.
  2. Zaustavljanje/ponovno pokretanje poslužitelja u slučaju neuspješnih veza, što možemo riješiti samo ručno.

Ovdje je sve jednostavno, primjeri koda nisu ni prikladni. skripta od 100 redaka

Dakle, kada se 10 ljudi okupilo i prihvatilo igru, server je pokrenut i svi su bili nestrpljivi da igraju, link za spajanje na igru ​​je poslan u privatnim porukama.

Pišemo pronalaženje partnera za Dotu 2014

Klikom na poveznicu igrač se spaja na server igre i to je to. Nakon ~25 minuta, virtualna "soba" s igračima je očišćena.

Unaprijed se ispričavam zbog nespretnosti članka, dugo nisam ovdje pisao, a previše je koda za isticanje važnih dijelova. Rezanci, ukratko.

Ako vidim interes za temu, bit će drugi dio - sadržavat će moje muke s dodacima za srcds (Source namjenski poslužitelj), i, vjerojatno, sustav ocjenjivanja i mini-dotabuff, mjesto sa statistikom igre.

Neki linkovi:

  1. Naša web stranica (statistika, ploča s najboljim rezultatima, mala odredišna stranica i preuzimanje klijenta)
  2. Discord poslužitelj

Izvor: www.habr.com

Dodajte komentar