-
Notifications
You must be signed in to change notification settings - Fork 2
注解模块
@Command("test")
public void test(String text) {
//TODO
}
这就完成了命令的主要部分
如果你需要获得命令的发送者那么你可以这样做
@Command("test")
public void test(@Sender CommandSender sender,String text) {
//TODO
}
为什么需要@Sender这个注解? -- 因为如果没有这个注解会产生歧义,AnnotationCommand系统会认为这个sender是用户需要输入的部分,但实际上不是。
在第一个例子中,方法返回了void,那么在默认情况下,只要方法不抛出异常,那么命令的执行就不会被认为失败
如果你希望这条命令执行失败,那么你可以返回boolean以告诉AnnotationCommand系统执行失败了
@Command("test")
public boolean test(String text) {
return false;
}
但有时候仅仅靠boolean提示失败显然不够明确,这时你可以返回CommandResult以明确的提示执行者你的命令执行的结果
@Command("test")
public CommandResult test(String text) {
return new CommandResult(false,"some reason");
}
先看看下面这个命令
@Command("test")
public void test(String text) {
//TODO
}
可以看到,这个命令的形参是比较单一的,这时我希望多加一些形参以执行更酷的指令,但又不希望添加更多的根命令,那么你只需要把你需要的往里加就好了
@Command("test")
public void test(String text,int index) {
//TODO
}
假如用户输入了 "/test abc" 那么就会执行第一个命令,假如用户输入了"/test abc 123" 那么就会执行第二个命令
不同形参带来了更酷的指令执行方式,但是同时也带来了问题 先看以下两个命令
@Command("test")
public void test(String text) {
//TODO
}
@Command("test")
public void test(int index) {
//TODO
}
由于两个命令的形参不同,所以很显然这当用户输入文本或者数值时,会分别调用这两个方法。但...真的是这样么?
我们知道,在实际操作中,用户输入的数据永远是String,所以即使用户输入了数值,那么也只是以String方式表达的数值,这时就有可能造成预期的事情无法发生,例如用户输入了数值但调用了第一个方法。
我们的解决方法是优先级,在优先级的控制下,String永远是最后一个用于解析的类
即我们会先看看这个文本是不是int,是不是float,是不是boolean ... 在最后我们才会看他会不会是String,而这永远会是正确的。
先看看这两个命令
@Command("test")
public void test(String text) {
//TODO
}
@Command("test")
public void test(@Sender CommandSender sender,String text) {
//TODO
}
可以明确的看出,第二个命令多了一个Sender,但当用户使用了这个命令 "/test abc",他会调用哪个方法呢?
答案是两个都有可能被调用,第一个方法不需要Sender,他只需要一个String,abc正好满足他的要求。第二个方法需要Sender,但只要用户调用命令,那么这个值永远会是被满足的,因为调用命令的家伙必须是CommandSender。所以结果是无法预期的。
所以在这种情况下,AnnotationCommand规定,假设这两个方法存在,那么一定会调用没有Sender的那个。
现在我们终于做好了命令,我们希望他能够被用户使用,那么我们该怎么做呢?
AnnotationCommand内置了一个AnnotationCommandBuilder,我们使用它来完成命令的解析
获得AnnotationCommandBuilder并注册
AnnotationCommand
.getBuilder(CommandManager)
.addCommandHandler(commandHandler)
.addCommandHandler(otherHandler)
.register();
其中addCommandHandler就是那些拥有命令方法的类
可以看到,获得Builder必须要有给一个CommandManager,这是因为AnnotationCommand需要检查这个命令被注册了没有,以为其添加更多参数,避免覆盖。
需要注意的是,如果一个命令已被注册且不是被AnnotationCommandBuilder注册的,那么就会抛出异常
在之前我们只是看到了Command的其中一个属性
他还有另外两个属性
String desc() default "";
String helpMessage() default "";
desc --- 解释这个命令干什么用的
helpMessage --- 当命令出错时的提示信息
先看以下命令
@Command("test")
public void test(String text) {
//TODO
}
我们希望这个命令只能被某些有权利的人执行,那么我们就需要这样做
@Command("test")
@Permission("command.test")
public void test(String text) {
//TODO
}
请注意,Permission的属性是array即可以要求多个权限
@Command("test")
@Permission({"command.test","isOp"})
public void test(String text) {
//TODO
}
请看以下命令
@Command("test")
public void test(@Sender CommandSender sender) {
//TODO
}
我们希望这个命令只能由Player发出,那么我们可以将CommandSender修改为Player
@Command("test")
public void test(@Sender Player sender) {
//TODO
}
这样在命令执行过程中,AnnotationCommand将为你自动检测。
但...这样似乎还不够酷,我们希望一个命令可以同时由两个及以上的类执行,例如可以被Player和Console执行,但其他的就不行,Sender一样能满足你的要求
你只需要按下面所做即可
@Command("test")
public void test(@Sender({Player.class,Console.class}) CommandSender sender) {
//TODO
}
需要注意的是,形参需要是他们的共同父类,否则将不可避免的会出现问题。
先看以下命令
@Command("test")
public void test(String text) {
if(text == "build")
//TODO
else if(text == "delete")
//TODO
}
从这个方法可以看出,当text是某个特定的文本时就会做什么,但是这样太繁琐了,尤其当条件非常多时,代码将变得异常冗长和难看。
AnnotationCommand为你带来了新的解决方式,使用这个注解,以上的方法将变成
@Command("test")
public void test(@Required("build") String a) {
//TODO
}
@Command("test")
public void test(@Required("delete") String a) {
//TODO
}
AnnotationCommand是如何解析那些字符串并将他们准确转为你需要的类的?就是依靠Argument。
当你在注册时,ArgumentCommandBuilder就会生成一个默认的ArgumentManager以供使用
如果你需要自定义ArgumentManager,可以调用
ArgumentCommandBuilder.setArgumentManager(ArgumentManager)
方法。
你用以处理命令的方法其实就已经指定了用以处理的Argument,AnnotationCommand会用你的形参的类的Class在ArgumentManager中寻找对应的Argument。如果没有找到,那么就会报异常。
你当然可以通过ArgumentManager设置某个Class默认的Argument,但有时也许只有一个地方需要,而其他地方只需要用到默认的Argument,这时就可以使用我们的@ArgumentHandler
@Command("command")
public void command2(@ArgumentHandler("test") String a){
}
通过这个注解,你可以告诉AnnotationCommand,使用名字来寻找Argument而非Class
我们有如下命令
@Command("sendMessage")
public void test(Player player,String message){
player.sendMessage(message);
}
假如你直接拿到AnnotatioCommand中注册,你会发现注册失败,并告诉你,AnnotationCommand并不认识Player这个玩意。
这是因为AnnotationCommand中默认的ArgumentManager并不认识Player。
你需要在AnnotationCommandBuilder时就为其设置ArgumentManager,并往ArgumentManager里添加你用以解析Player的Argument。
像这样
SimpleArgumentManager argumentManager = new SimpleArgumentManager();
argumentManager.appendArgument(new SimpleArgument(Player.class,"player") {
@Override
public Optional parse(String arg) {
return Optional.ofNullable(Server.getPlayer(arg));
}
@Override
public Completer getCompleter() {
//Completer是一个复杂的系统,需要另开一页来讲解
});
AnnotationCommand
.getBuilder(commandManager)
.setArgumentManager(argumentManager)
.addCommandHandler(handler)
.register();
这样在注册命令时,AnnotationCommand就会正确识别你的类了。
对于Player来说,一个参数就可以明确对象,使用Argument是最好的选择,但是很多时候,一个类是需要很多参数才能明确的,例如Location
Location中有这样一个构造方法
public class Location{
public Location(World world,int x,int y,int z)
}
很明显,一个Argument是没法搞出一个Location的,除非你希望玩家输入蹩脚的/teleport world,x,y,z
而我们更希望玩家输入/teleport world x y z
但在代码层面相较于
@Command("teleport")
public void command1(@CommandSender Player player,World world,int x,int y,int z){
Location location = new Location(world,x,y,z)
//teleport
}
显然我们更希望的是
@Command("teleport")
public void command1(@CommandSender Player player,Location location){
//teleport
}
现在我们可以在Location的构造方法前添加@Generator 如下
public class Location{
@Generator
public Location(World world,int x,int y,int z)
}
Annotation就会根据构造方法的形参自动构建对应的模型,然后将其传输给你的方法
但请注意,一个类只能有一个Generator
更要注意的是,这件事只会在Annotation找不到对应的Argument时执行,也就是说,如果Location被设定了一个Argument,那么Generator就会无效。
你会注意到Location中有一个World,假设World已在ArgumentManager中注册,那么这个类会被正确的识别,但如果没有,你可以继续为World添加Generator
public class World{
@Generator
public World(String worldName)
}
明确的说,Generator就是Argument的补充,对于一个不认识的类,AnnotationCommand就会去看看未知类的构造方法,如果有Generator,那么就会根据构造方法的形参利用已有的Argument生成相应的模型自动适配,例如Location可以被分解成WorldArgument和3个IntArgument,但这个过程并不是自动生成Argument,请注意这一点。