碎碎念...

关于命令系统的构思,我采用了一些Web框架的路由设计。
他们是像这样的:

Rapidoid-web-router

注: 这个例子用到了Rapidoid Web框架

所以我在Phoenix中也引入了这种设计。
可以说,这部分是最让我引以为豪的地方了。

这篇文章属于Phoenix Framework 入门教程的一部分。

注册一个命令执行器

咱建议,在插件被启用的时候就马上让Phoenix给我们的模块注册监听器。所以,在我们项目主类的onEnable()方法下,写上这么一行:

Phoenix.getCommandManager().registerCommand(this,new MyCommandExecutor());

如果一切顺利的话,接下来, 你的IDE会开始报错。(没报错的同学也没事,接着看往下操作。)
我们根据IDE的提示,创建一个叫做MyCommandExecutor的类,它就是我们的命令执行器
在这里,我建议所有的命令执行器都统一丢在一个.command包下,方便区分。
根据Phoenix的要求,这个类需要实现Command接口(Interface)。
...
下面是一个来自PhoenixHelloWorld的例子:

Command-router-example

让我们来看看。
Phoenix的命令执行器主要由注解组成。
要想编写一个命令执行器,首先你需要让你的类实现Command接口,并且在类上写一个@PhoenixCommand注解。
接下来则是许多带有@CommandRouter注解的方法,以及两个带有@Override注解的方法,它们的返回类型都是CommandResult
那么,Phoenix的命令执行器到底是怎样运作的呢?

@PhoenixCommand注解

这个注解向Phoenix简单介绍了我们的命令执行器。
目前它有如下几个参数:

名称 数据类型 必填 描述
label String 命令的名称。
description String 命令的描述,通常用来显示在/help菜单中。
permission String 命令的权限。执行这个命令时必须满足的权限,你可以留空来实现自己更详细的权限判断。
alias String[] 命令的别名,命令发送者可以通过alias执行这个命令。
在命令发送者调用某个命令之后,Phoenix就会在自己的内存中寻找合适的命令执行器。 它会检测玩家输入的命令是否符合 `label` 或者 `alias` 中的内容, ~~然后判断权限是否满足(还未实现)~~。

当一切都满足之后,Phoenix才会进一步寻找这个类中的 @CommandRouter 注解。

@CommandRouter注解

这个注解出现在命令执行器的 方法(Methods) 上,正如它的名字所示的那样,它代表着一个命令 路由(Router)

名称 数据类型 必填 描述
args String 命令的参数。
permission String 执行该参数需要的权限。
sender Sender 命令的触发者类型。
last boolean 是否为最后一个路由。

args,参数

args 表示的是命令的参数。参数是什么意思呢?
它指的是命令发送者执行的一串字符中,从第一个空格开始的内容。
举个例子, 某个玩家输入了/hello world !
那么hello就是**@PhoenixCommand** 中的 label 或者 alias,而world!则是参数。
考虑到参数的多样性,我把它简单地分为 命名参数(Named Parameters)变量参数(Variable Parameters) 两类。

我们用上面的代码作为例子,来学习一下如何使用这两种参数。

命名参数

所谓命名参数,指的是不可变的、规定好的参数。
在上面的例子中,help(...)就是一个命名参数。
命令发送者只有输入/demo help,才能调用这个命令,而输入/demo h e l p或者/demo help 123等内容,都是无法调用的。
所以,命名参数就是你规定命令发送者要输入的内容。

变量参数

我们经常需要使用命令向服务器传达某个信息,使用命名参数固然无法满足要求,所以我们需要变量
在上面的例子中,echo(...) 方法就是一个典型的例子。
玩家输入/demo echo test, Phoenix 就会把test识别为<msg>中的内容,并保存到CommandContent中,并且就像Map一样容易调用。

args中,我们使用一对尖括号<>来指定一个变量的名字。Phoenix会将命令发送者发送命令的适当部分转换为这个变量名,让你在CommandContent中读取它。

参考 echo_2(....) ,变量参数的数量基本上没有限制,你设置好几个也没有问题。

你可能会考虑到,如果我想让玩家输入一句话,这里面也许会有很多单词或者空格,但又不能确定数量,怎么办呢?
很简单!在变量名之后加上三个点号...,就像echo_a(...)中的那样。Phoenix遇到这种标识符之后,就会把之后所有的内容全部转换为数组型String,你可以通过CommandContent中的getStrings(...)来读取参数。
值得注意的是,...是一个标识符,但它也是变量名的一部分,因此你在读取时可千万别忘了带上它。

sender,发送者类型

有些命令你并不想被玩家执行,有些命令只能由玩家执行。
你可以通过sender来限制发送者的类型。只有发送者与sender中的类型相同,命令才会调用。
sender的值默认为All(所有),可选的参数有:

  • Console(控制台)
  • RemoteConsole(远程控制台,如CRON)
  • Block(方块,如命令方块)
  • Player(玩家)

last,最终路由

Phoenix的命令执行器是一个路由系统。也就是说 — 在匹配到某个路由符合条件后,Phoenix会执行它,然后寻找下一个符合条件的@CommandRouter,直到它找完为止。
通过这一特性你可以写许多具有连贯性的命令。
如果你将last设置为true,Phoenix在调用这个方法之后就会停止,不再寻找下一个路由。

Command接口

这个接口除了告诉Phoenix,你的类是一个命令执行器之外,它还要求你实现两个方法,也就是加了@Override注解的两个:onRoot(...)onMisHandle(...)

onRoot

@CommandRouter所有的内容都需要参数。它不能匹配到没有参数的内容。
所以,如果命令发送者没有携带任何参数,它就会调用onRoot方法。在此时,CommandContent应当是空的,无论你提供什么样的参数,都不会有结果。

onMisHandled

当命令发送者的参数没有被任何一个路由匹配到时,就会它就会被执行。此时的CommandContent应当也是空的。你可以在这里写你自己的提示内容。如告诉命令发送者:参数错误,输入/demo help获取帮助。

CommandResult类

每个路由的返回值都是 CommandResult类,该类可以用于我们向服务端反馈命令的执行情况。
这个类主要包含下列几个字段:

  • affectedBlocks - 影响的方块数量,默认为 0
  • affectedEntities - 影响的实体数量,默认为 0
  • affectedItems - 影响的物品数量,默认为 0
  • queryResult - 匹配到的结果数量,默认为 0
  • reason - 执行失败原因,默认为 NONE

你可以使用Builder来构造一个CommandResult
假设我们正在编写一个命令,玩家执行之后将会以 消耗手上的1个物品 为代价,造成一次爆炸,
这个爆炸 破坏了10个方块,杀死了5只生物,那么我们可以这样写我们的CommandResult
CommandResult-builder-example

reason 则用来标识命令执行失败的原因:

  • NONE - 没有失败,默认值。
  • NOT_FOUND - 没有匹配到路由。
  • NO_PERMISSION - 权限不足。
  • INTERNAL_ERROR - 内部错误。
  • INVALID_ARGUMENT - 参数错误。

更多的时候,你的命令可能不会影响什么东西。这样写CommandResult实属繁琐,因此Phoenix API内置了 5 种常用的反馈结果:

  • CommandResult.success(); - 该命令执行成功。
  • CommandResult.notFound(); - 该命令没有匹配到路由。
  • CommandResult.internalError(); - 该命令在执行过程中遇到内部错误。
  • CommandResult.invalidArgument(); - 该命令的参数不正确。
  • CommandResult.permissionDenied(); - 该命令执行者没有权限执行这个命令。

至此,你已经学完了Phoenix Framework的命令部分。

如果本文章出现任何问题,请您在评论区或者通过其他方式向作者反馈,谢谢。