为 Dota 2014 撰写对接会

您好!

今年春天,我遇到了一个项目,其中的成员学习了如何运行 Dota 2 服务器版本 2014,并相应地在其上进行游戏。 我是这款游戏的忠实粉丝,我不能错过这个沉浸在童年时光的独特机会。

我研究得很深,碰巧我写了一个 Discord 机器人,它负责几乎所有旧版本游戏不支持的功能,即匹配。
在机器人进行所有创新之前,大厅是手动创建的。 我们收集了 10 个对一条消息的反应,并手动组装了一个服务器,或托管了一个本地大厅。

为 Dota 2014 撰写对接会

我作为程序员的本性无法承受如此多的手动工作,连夜我勾画出了最简单的机器人版本,当有 10 个人时,它会自动提升服务器。

我立即决定用nodejs来写,因为我不太喜欢Python,而且我在这个环境中感觉更舒服。

这是我第一次为 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. 上面没有docker,戳中了我的心
  2. 我们节省房租

任务是从 Linux 上的 VPS 在 VDS 上运行进程。 我在 Flask 中写了一个简单的服务器。 是的,我不喜欢Python,但是你能做什么呢?用它来编写这个服务器更快更容易。

它执行 3 个功能:

  1. 使用配置启动服务器 - 选择地图、启动游戏的玩家数量以及一组插件。 我现在不会写插件 - 这是一个不同的故事,晚上喝几升咖啡,混合着眼泪和撕裂的头发。
  2. 连接不成功时停止/重新启动服务器,我们只能手动处理。

这里一切都很简单,代码示例甚至都不合适。 100行脚本

于是,当10人齐聚并接受游戏时,服务器启动了,大家都跃跃欲试,私信中就发送了连接游戏的链接。

为 Dota 2014 撰写对接会

通过点击链接,玩家连接到游戏服务器,然后就这样了。 大约 25 分钟后,玩家所在的虚拟“房间”就被清理干净了。

对于文章的尴尬,我提前表示歉意,我已经很长时间没有在这里写文章了,并且代码太多,无法突出显示重要部分。 简而言之,面条。

如果我发现对该主题感兴趣,将会有第二部分 - 它将包含我对 srcds(源专用服务器)插件的折磨,并且可能还有一个评级系统和 mini-dotabuff(一个包含游戏统计数据的网站)。

一些链接:

  1. 我们的网站(统计、排行榜、小型登陆页面和客户端下载)
  2. 不和谐服务器

来源: habr.com

添加评论