命令树
厌倦了手动拆分参数、解析命令?厌烦了复杂的判断条件?快来尝试 MCDR 的命令构建系统吧!
MCDR 内置了一个命令树构建系统,供插件构建其命令。它如同一个 Mojang 的 brigadier 的精简版
工作流程
MCDR 维护了一个 dict 用于储存注册的命令。该 dict 的值均为命令树根节点列表,而值对应的键则是根节点的字面值。有了它,MCDR 可以快速地找到可能可以接收到来命令的命令树
每次处理用户信息时,MCDR都会尝试将用户输入解析为命令。它将用户输入的第一个分段作为键来查询命令树存储字典。如果命令存在,则调用 cancel_send_to_server() 来阻止将信息发送到服务器的标准输入流 ,然后使用对应的命令树来处理该命令
如果解析命令时发生错误,且插件未将错误设置为已处理,则 MCDR 会将翻译后的命令错误消息发送到命令源
先瞅一眼...
让我们来看看命令树的实际含义。例如,假设某插件包含3种命令:
!!email list!!email remove <email_id>!!email send <player> <message>
要实现这些命令,我们可以构建如下所示的命令树:
Literal('!!email')
├─ Literal('list')
├─ Literal('remove')
│ └─ Integer('email_id')
└─ Literal('send')
└─ Text('player')
└─ GreedyText('message')
当执行 !!email remove 21 命令时,以下过程将会发生:
于节点
Literal('!!email')解析命令!!email remove 21字面量节点
Literal('!!email')获取了!!email remove 21的第一个元素,它是!!email——与字面量节点匹配现在余下的命令是
remove 21于是,它搜索其字面量子节点,找到与下一个命令元素
remove匹配的子节点Literal('remove')这样,它让该子节点处理其余命令
于节点
Literal('remove')解析命令remove 21字面量节点
Literal('remove')获取了remove 21的第一个元素,它是remove——与字面量节点匹配现在余下的命令是
21然后它搜索其字面量子节点,但未找到与下一个命令元素
21匹配的任何字面量子节点因此,它让它的非字面量子节点
Integer('email_id')处理剩余命令
于节点
Integer('email_id')解析命令21整数节点
Integer('email_id')获得了21的第一个元素,这是一个合法的整数它使用键
email_id将值21存储到上下文 dict 中然后,它发现命令解析已经完成,因此它以命令源和上下文 dict 作为参数来调用回调函数
至此,命令解析完成
以上是命令构建系统逻辑部分的快速概述,主要是为了帮助你建立对命令树和命令构建系统的感性理解
匹配文字节点,解析剩余命令,将解析后的值存储在上下文字典中,这就是命令系统的工作方式
构建命令树的方法
如果你熟悉 Mojang 在 Minecraft 中使用的命令树类库 brigadier,或者你需要使用 MCDR 命令树的完整特性,阅读相关的 类参考 以了解如何创建命令节点、增添子节点,以及设置节点属性
如果你是刚接触基于树的命令构建系统的新手,不知道如何整命令树的这一套东西,你可以试试 简易命令构建器 这个工具,来进行简单的命令树构建
除了阅读本文档外,学习使用 MCDR 命令构建系统的另一种好办法是引用和模仿现有代码。你可以在 mcdreforged.plugin.builtin.mcdr.mcdreforged_plugin.MCDReforgedPlugin 类的 __register_commands 方法下找到 !!MCDR 命令的构建代码
上下文
上下文(Context)储存着当前命令解析过程中的信息,是一个继承自 dict 的类
命令解析过程中解析得到的值将会使用 dict 的方法,储存在上下文中。这意味着你可以使用 context['arg_name'] 来访问这些值
简易命令构建器
在 v2.6.0 版本加入.
对命令树一头雾水?厌烦了基于树的命令构造方式?快来试试这个不含树的命令构建器,体验清晰简单的命令构建流程吧
声明&定义,你要做的就这些
用法
可以使用以下代码构造 先瞅一眼... 部分中的命令树:
from mcdreforged.api.command import SimpleCommandBuilder, Integer, Text, GreedyText
def on_load(server: PluginServerInterface, prev_module):
builder = SimpleCommandBuilder()
# declare your commands
builder.command('!!email list', list_email)
builder.command('!!email remove <email_id>', remove_email)
builder.command('!!email send <player> <message>', send_email)
# define your command nodes
builder.arg('email_id', Integer)
builder.arg('player', Text)
builder.arg('message', GreedyText)
# done, now register the commands to the server
builder.register(server)
其中 list_email、remove_email 和 send_email 为对应命令的回调函数
就这么简单!
参见
类 SimpleCommandBuilder 的参考
自定义
MCDR 支持自定义参数节点。它也许能节省一些你为构建命令而重复工作的时间
要创建自定义参数节点,你需要声明一个继承自 AbstractNode 的类,然后实现 parse 的方法逻辑
自定义异常提供了一种使用 on_error 方法处理异常的精确方法。如果你想在参数节点无法解析文本时引发自定义异常,则需要使自定义异常继承自 CommandSyntaxError
这是一个自定义参数节点 PointArgument 的简单示例。它接受连续 3 个 float 类型的参数输入作为坐标,并将它们作为点储存到列表中。如果它获得非浮点输入,则抛出 IllegalPoint 异常。如果命令在读取完三个浮点数之前结束,则抛出 IncompletePoint 异常
class IllegalPoint(CommandSyntaxError):
def __init__(self, char_read: int):
super().__init__('Invalid Point', char_read)
class IncompletePoint(CommandSyntaxError):
def __init__(self, char_read: int):
super().__init__('Incomplete Point', char_read)
class PointArgument(ArgumentNode):
def parse(self, text: str) -> ParseResult:
total_read = 0
coords = []
for i in range(3):
total_read += len(text[total_read:]) - len(command_builder_utils.remove_divider_prefix(text[total_read:]))
value, read = command_builder_utils.get_float(text[total_read:])
if read == 0:
raise IncompletePoint(total_read)
total_read += read
if value is None:
raise IllegalPoint(total_read)
coords.append(value)
return ParseResult(coords, total_read)
对于它的用法,这是一个简单的示例,以及一个对应的输入/输出表:
def on_load(server, prev):
server.register_command(
Literal('!!mypoint').then(
PointArgument('pt').
runs(lambda src, ctx: src.reply('You have input a point ({}, {}, {})'.format(*ctx['pt'])))
)
)
输入值 |
输出值 |
|---|---|
!!mypoint 1 2 3 |
You have input a point (1.0, 2.0, 3.0) |
!!mypoint 1 2 |
Incomplete Point: !!mypoint 1 2<-- |
!!mypoint xxx |
Invalid Point: !!mypoint xxx<-- |
!!mypoint 1 2 x |
Invalid Point: !!mypoint 1 2 x<-- |