MCSC - 基于 Python 的 Minecraft 服务器端插件式管理系统

notebook

MCSC 全程 MinecraftServerController,为一款基于 Python 开发的服务端服务端管理系统,它的核心特色是支持 Python 开发的插件,并且可以热加载服务器插件,能优化并丰富玩家在原版(vanilla)服务端的游戏体验。

目前正处于开发阶段,功能规划如下:

功能划分

  1. MCSC 是一款基于 Popen 的信息交互系统,通过与 Minecraft 真实后端的信息输入输出流交互来实现本系统与 Minecraft 的交互功能。MCSC 后端以及 Minecraft 客户端均为通过输入文字对本系统进行交互操作。

  2. 控制权限:限定不同玩家通过客户端对本系统的不同权限,确保系统安全性。通过服务器端控制台的所有操作为最高权限。在默认配置中,owner 以及 admin 权限可以对服务器进行敏感操作管理;helper 可以进行部分敏感操作;user 以上可以执行插件;vistor 玩家没有任何权限。

  3. 在服务器端进入后台时,将展示交互式后台,采取类似 Linux 下的 auto-suggestion 的方法为后端提供指令补全及建议。MC 客户端为正常的信息输入,不能补全。

  4. 通过统一 API 接口的方式,为不同 Minecraft 的服务端提供兼容,例如 waterfall / forge / bukkit / CatServer / Velocity 等众多第三方服务端。

  5. 拓展插件形式,包括但不仅限于单 Python 文件插件 / 处于文件夹的多文件插件 / 通过本系统打包的 .mcsc 多文件打包插件

插件事件

插件事件是插件与控制台和服务器交互的重要方式。

当服务器触发某一事件时,MCSC 会以处触发的参数传递至注册该事件的监听器,然后调用该监听器的回调函数。事件监听器默认以最高优先级运行,可以在插件中自定义事件监听的优先级

生命周期

一些 MCSC 的事件可以被划分成三个生命周期

  • 插件生命周期:插件被加载 -> 插件被卸载
  • 服务端生命周期:服务端启动 -> 服务端启动完成 -> 服务端被终止 -> 服务端完成终止
  • MCSC 生命周期: MCSC 启动 -> MCSC 终止

插件被加载

插件被加载事件将在插件加载后被触发一次,若你需要注册一些事件监听器/命令/帮助消息,以及初始化参数,那么你应该将它们置于这个函数里。

def on_load(server: PluginServerInterface, prev_module):
...

由于它是插件生命周期中的第一个事件,因此只能使用默认事件侦听器注册该事件,因此 on_load 函数是插件的入口点。因此,不应该在 on_load 函数中分发自定义事件,否则它将无效。MCSC 的事件侦听器存储此时尚未初始化。

  • Event ID: mcsc.plugin_loaded
  • Callback: PluginServerInterface, prev_module
  • Default function: on_load

插件被卸载

当 MCSC 卸载插件实例时,将派发该事件。这可能是由插件重载或卸载引起的。除此之外,在 MCSC 关闭过程中,该事件也将被派发,因此这非常适合用于执行清理操作。

  • Event ID: mcsc.plugin_unloaded
  • Callback: PluginServerInterface
  • Default function: on_unload

标准信息流

服务器输出新一行文本,或者从控制台中输入文本时,触发此事件。 MCSC 将把文本解析为 Info 对象。在这种情况下,插件可以响应信息。

$ e.g:
def on_info(server: PluginServerInterface, info: Info):
if info.is_player:
server.reply(message)
  • Event ID: mcsc.general_info
  • Callback: PluginServerInterface, Info
  • Default function: on_info

用户信息

用户信息 事件与 标准信息 事件非常相似,但仅在用户发送信息时——更准确地说,info.is_userTrue 时——才会触发。如果你需要一种简单的方式来处理用户输入,则可以使用此事件。以下是一个示例用法:

def on_user_info(server: PluginServerInterface, info: Info):
if info.content == 'Restart the server!':
server.reply(info, 'Roger that. Server restarting...')
server.restart()
  • Event ID: mcsc.user_info
  • Callback: PluginServerInterface, Info
  • Default function: on_user_info

玩家离开

一名玩家离开了游戏。插件可以清理与玩家相关的对象。

  • Event ID: mcsc.player_left
  • Callback: PluginServerInterface, player_name
  • Default function: on_player_left

自定义事件

除了 MCSC 本身,插件还可以分发自己的事件。你需要做的就是调用带有事件和一些参数的 server.dispatch_event API。

自定义事件是在插件之间传递消息的好办法。这也是一种不错的间接让插件响应其他插件请求的方式。

MCSC 指令树

本系统默认提供了参数拆分、解析处理,方便判断参数执行条件,如果 Mojang 的 Brigadier

工作流程

MCSC 维护了一个 dict 用于储存注册的指令。该 dict 的值均为指令树根节点列表,而值对应的键则是根节点的字面值。有了它,MCSC 可以快速地找到可能可以接收到来指令的指令树。

每次处理用户信息时,MCSC都会尝试将用户输入解析为指令。它将用户输入的第一个分段作为键来查询指令树存储字典。如果指令存在,则调用 info.cancel_send_to_server()阻止将信息发送到服务器的标准输入流 ,然后使用对应的指令树来处理该指令。

如果解析指令时发生错误,且插件未将错误设置为已处理,则 MCSC 会将翻译后的指令错误消息发送到指令源。

构建指令树

假设插件 example.py 包含三种指令:

  • !!example_plg list
  • !!example_plg remove <id>
  • !!example_plg add <player> <message>

要实现这些指令,我们可以构建如下所示的指令树:

Literal('!!email')
├─ Literal('list')
├─ Literal('remove')
│ └─ Integer('id')
└─ Literal('add')
└─ Text('player')
└─ GreedyText('message')

因此,当执行 !!example_plg remove 1 时,将发生以下过程:

  1. 于节点 Literal('!!email') 解析指令 !!email remove 1
    1. 字面量节点 Literal('!!email') 获取了 !!email remove 1 的第一个元素,它是 !!email ——与字面量节点匹配。
    2. 现在余下的指令是 remove 1
    3. 于是,它搜索其字面量子节点,找到与下一个指令元素 remove 匹配的子节点 Literal('remove')
    4. 这样,它让该子节点处理其余指令。
  2. 于节点 Literal('remove') 解析指令 remove 1
    1. 字面量节点 Literal('remove') 获取了 remove 1 的第一个元素,它是 remove ——与字面量节点匹配。
    2. 现在余下的指令是 1
    3. 然后它搜索其字面量子节点,但未找到与下一个指令元素 1 匹配的任何字面量子节点。
    4. 因此,它让它的非字面量子节点 Integer(‘id’) 处理剩余指令。
  3. 于节点 Integer('id') 解析指令 1
    1. 整数节点 Integer('id') 获得了 1 的第一个元素,这是一个合法的整数。
    2. 它使用键 id 将值 1 存储到上下文 dict 中。
    3. 然后,它发现指令解析已经完成,因此它以指令源和上下文 dict 作为参数来调用回调函数。
  4. 至此,指令解析完成。

匹配文字节点,解析剩余指令,将解析后的值存储在上下文字典中,这就是指令系统的工作方式。

上下文

上下文(Context)储存着当前指令解析过程中的信息,是一个继承自 dict 的类

指令解析过程中解析得到的值将会使用 dict 的方法,储存在上下文中。这意味着你可以使用 context['arg_name'] 来访问这些值

指令节点

  • TODO

参数节点

  • TODO

API

当你需要从 MCSC 中导入些东西时,除了直接从 MCSC 的内部实现中导入外,你还可以从 MCServerController.api 中进行导入。

MCServerController.api 是供插件开发者导入的包。如果你仅从 api 包进行导入目标类,就可以保证插件导入目标类的导入路径与目标类的实际路径解耦。如果以后 MCSC 重构了目标类,亦或是移动了目标类的位置,那么仅从 api 包中导入目标类的插件就能丝毫不受影响。

WatchDog

看门狗是一个监视着任务执行者 (task executor) 线程运行的守护线程。

所有 MCSC 相关的事件,都会在任务执行者线程中作为任务,按顺序依次派发及执行。当一个任务在任务执行者线程中运行了超过 10 秒,看门狗将会认为任务执行者被阻塞了,并将为 MCSC 重新创建一个任务执行者线程,因此设计不良的插件不会永远地阻塞 MCSC 的运行逻辑。

如果你想要在你的插件中运行一些耗时的工作,推荐创建一个线程并使其在新线程中执行。@new_thread 装饰器提供了一种简单的方法来做到这一点。

命令行接口

MCSC 提供了一些实用的命令行接口(CLI)工具。如果你在启动 MCSC 的时候在启动指令末尾追加一些参数。使用方法:在启动 MCSC 的命令末尾追加一些参数。

以下命令来显示 CLI 的帮助信息:

python -m mcdreforged -h

CLI 命令的格式为:

mcdreforged [global args] <sub_command> [sub_command args]

全局参数

  • -q, --quiet: 禁用 CLI 信息输出

子命令

start

python -m mcdreforged start [-h]

python -m mcservercontroller 一样,启动 MCSC

init

python -m mcdreforged init [-h]

准备 MCDR 的工作环境

在当前工作目录下生成默认配置、权限文件,及常用的文件夹,包含:

  • logs/
  • configs/
  • plugins/
  • server/
  • config.yml
  • permission.yml

More args to be added

Wait and see.

Author: DioPong

Permalink: https://blog.2to.fun/post/notebook/MinecraftServerController/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。