今年春天,我遇到了一个项目,他们在其中学习了如何运行2014版的Dota 2服务器,并据此进行操作。我是这个游戏的忠实拥护者,我不能错过进入童年时代的难得机会。
我投入了很深的精力,碰巧我写了一个Discord机器人,它负责游戏的旧版本中不支持的几乎所有功能,即配对。
在使用机器人进行所有创新之前,大厅是手动创建的。我们收集了对一条消息的10条回复,并手动组装了服务器,或托管了一个本地游说机构。
我作为程序员的天性不能忍受太多的手动工作,一夜之间,我绘制了最简单的bot版本,该机器人在招募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模式,正常/评级和几个自定义模式。因此,在用户和游戏搜索之间存在一个Singleton 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 Server 2012的VDS上。由此可以得出一些结论:
- 上面没有码头工人,这让我很震惊
- 我们节省租金
任务是在Linux上的VPS和VPS上启动该过程。在Flask中编写了一个简单的服务器。是的,我不喜欢python,但是该怎么办-在此服务器上编写此服务器更快,更轻松。
它具有3个功能:
- 服务器启动并进行配置-选择地图,启动游戏的玩家人数以及一组插件。我现在不再写插件了-这是一个单独的故事,晚上有几升咖啡,夹杂着眼泪和头发。
- 在连接失败的情况下停止/重新启动服务器,我们只能手动处理。
这里的一切都很简单,代码示例甚至不合适。100行的脚本
因此,当10个人聚在一起接受游戏时,服务器正在运行,并且每个人都渴望玩游戏,因此连接到游戏的链接会包含私人消息。
通过单击链接,播放器将连接到游戏服务器,仅此而已。大约25分钟后,将清除与玩家的虚拟“房间”。
对于本文的尴尬之处,我事先表示歉意,我已经在这里写了很长时间了,并且没有太多代码可以突出重点部分。简而言之,面条。
如果我对该主题感兴趣,那么将有第二部分-它将包含我对srcds(源专用服务器)插件的煎熬,以及大概的评分系统和mini-dotabuff(具有游戏统计信息的网站)。
几个链接: