第四节 如何编写命令

上一节 下一节

第一章 第四节 如何编写命令

参与编写者: MagicLu550

建议学习时间: 30分钟

学习要点: 学习自己创建一个简易的命令,了解指令和指令映射,了解SimpleCommand的使用

其实创建一个简易的命令很简单,我们可以在PluginBase中重写onCommand方法,进行equals识别,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PluginMain extends PluginBase{
//... 其他
@Override
// args指后面的那一串参数,直接取出即可,注意先检查length,label其实就是指令名称,也可以用command的信息,这个是个人习惯
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if("hello".equals(command.getName())){ // 识别指令名称
this.getLogger().info("hello!");
return true;
}
return false;
}
}

nukkit的原生命令也是很多基于Command创建的.很多项目是需求指令
以使得用户和您的项目操作,也就是作为一个 接口(Interface) ,接下来我们介绍以下nukkit的命令。

nukkit的命令的父类是Command,它有很多的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public Command(String name) {
this(name, "", null, new String[0]);
}

public Command(String name, String description) {
this(name, description, null, new String[0]);
}

public Command(String name, String description, String usageMessage) {
this(name, description, usageMessage, new String[0]);
}

public Command(String name, String description, String usageMessage, String[] aliases) {
this.commandData = new CommandData();
this.name = name.toLowerCase(); // Uppercase letters crash the client?!?
this.nextLabel = name;
this.label = name;
this.description = description;
this.usageMessage = usageMessage == null ? "/" + name : usageMessage;
this.aliases = aliases;
this.activeAliases = aliases;
this.timing = Timings.getCommandTiming(this);
this.commandParameters.put("default", new CommandParameter[]{new CommandParameter("args", CommandParamType.RAWTEXT, true)});
}

我们可以知道,nukkit的命令都是toLowerCase的,即小写,也就是说,命令不区分大小写。
对于命令如何存储,服务端如何识别一个命令,我们从源码中找到以下资料:

命令的区分标识是fallBackPrefix+:+label,默认为指令的名称,一般fallBackPrefix都写""

这一块知识缺点将会有其他人补充[1]

代码依据 - SimpleCommandMap.java

1
2
3
159 private boolean registerAlias(Command command, boolean isAlias, String fallbackPrefix, String label) {
160 this.knownCommands.put(fallbackPrefix + ":" + label, command);

存储指令的容器是实现CommandMap接口的,即SimpleCommandMap,我们可以通过

1
this.getServer().getCommandMap();

得到CommandMap.

Command,CommandMap有很多方法

  1. Command

    1. 根构造方法的参数为

      1
      2
      3
      4
      5
      6
      7
      8
      9
      String name, String description, String usageMessage, String[] aliases

      第一个name是指令名称,最终会转换为全小写

      第二个description是指令介绍,用于给玩家查看使用的

      第三个usageMessage就是当玩家对命令使用错误,返回的信息

      第四个aliases就是指令的别名,指令可以有多个别名

      当然,也有简化的构造方法,可以根据你的需求任意调用,这里不多阐述,其他的都是为默认值

    2. Command的主要属性
      如同command的构造方法一样,command我们需要了解的属性基本也就是这四个。其他属性将会
      在nukkit原理解析的时候讲解

    3. Command的比较常用的方法

      1. boolean execute(CommandSender commandSender, String label, String[] strings)

        这个方法是需要开发者自行实现的方法,当指令被触发,就会执行execute里的代码
        它的参数我们在第二章提到了

      2. String getName()

        这个可以获取指令的名称

      其余方法我们将会在后期附件讲解到,如果有想要知悉的其他方法,我们会另外在这里做补充,
      或者您认为常用的,也可以pull request添加进去

  2. CommandMap

    1. boolean register(String fallbackPrefix, Command command)
      可以注册指令,fallbackPrefix是前缀,用于服务端存储命令对象的标识
      nukkit的本地命令的fallbackPrefix为nukkit
      command则为你的自定义命令对象
    2. void registerAll(String fallbackPrefix, List<? extends Command> commands)
      这个可以一次性注册多个指令
    3. boolean dispatch(CommandSender sender, String cmdLine)
      这个调用一个命令,cmdLine就是日常所输入的命令
    4. void registerSimpleCommands(Object object)
      这个是调用简单指令,通过注解实现的指令对象,我们后面将会演示如何使用它。

nukkit官方后来推出一系列简化操作,如SimpleCommand,SimpleConfig等,我们这里解释以下SimpleCommand

SimpleCommand运用了注解,同样通过 反射 实现的,我们可以看到官方的源码来探讨它的使用

SimpleCommandMap.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
public void registerSimpleCommands(Object object) {
for (Method method : object.getClass().getDeclaredMethods()) {
cn.nukkit.command.simple.Command def = method.getAnnotation(cn.nukkit.command.simple.Command.class);
if (def != null) {
SimpleCommand sc = new SimpleCommand(object, method, def.name(), def.description(), def.usageMessage(), def.aliases());

Arguments args = method.getAnnotation(Arguments.class);
if (args != null) {
sc.setMaxArgs(args.max());
sc.setMinArgs(args.min());
}

CommandPermission perm = method.getAnnotation(CommandPermission.class);
if (perm != null) {
sc.setPermission(perm.value());
}

if (method.isAnnotationPresent(ForbidConsole.class)) {
sc.setForbidConsole(true);
}

CommandParameters commandParameters = method.getAnnotation(CommandParameters.class);
if (commandParameters != null) {
Map<String, CommandParameter[]> map = Arrays.stream(commandParameters.parameters())
.collect(Collectors.toMap(Parameters::name, parameters -> Arrays.stream(parameters.parameters())
.map(parameter -> new CommandParameter(parameter.name(), parameter.type(), parameter.optional()))
.distinct()
.toArray(CommandParameter[]::new)));

sc.commandParameters.putAll(map);
}

this.register(def.name(), sc);
}
}
}
```
很显然,简易命令必须要有@Command注解在方法上,方法上标记一些内容,当然,最终只是把一个类拆解,分为多个命令
对象注册(SimpleCommand),最终也继承自Command。SimpleCommand提供了对于参数最大和最小的限制。

SimpleCommand.java

@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
if (this.forbidConsole && sender instanceof ConsoleCommandSender) {
this.sendInGameMessage(sender);
return false;
} else if (!this.testPermission(sender)) {
return false;
} else if (this.maxArgs != 0 && args.length > this.maxArgs) {
this.sendUsageMessage(sender);
return false;
} else if (this.minArgs != 0 && args.length < this.minArgs) {
this.sendUsageMessage(sender);
return false;
}

    boolean success = false;

    try {
        //这里执行我们的命令
        success = (Boolean) this.method.invoke(this.object, sender, commandLabel, args);
    } catch (Exception exception) {
        Server.getInstance().getLogger().logException(exception);
    }

    if (!success) {
        this.sendUsageMessage(sender);
    }

    return success;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
这段代码我们可以知道方法的参数有规范要求的
Object object,CommandSender sender,String label,String[] args
object就是我们的命令对象了,通过registerSimpleCommand注册进去的命令对象
其他显而易见,不再多讲,具体如何使用,其实很简单
```java
package net.noyark.www;


import cn.nukkit.command.CommandSender;
import cn.nukkit.command.simple.Arguments;
import cn.nukkit.command.simple.Command;

public class MySimpleCommand {

@Command(name = "hello",description = "233",usageMessage = "/hello")
@Arguments(max = 10,min = 0)
public boolean onHelloCommand(CommandSender sender, String label, String[] args){
//这里写指令处理代码
return true;//为布尔类型,否则会发生空指针异常,在先前源码可以分析其返回类型
}
}

最终通过registerSimpleCommand注册即可.事实上是对Command的封装

command的用户组(这里参考自snake1999的文章)

1
2
3
4
5
6
7
8
9
permissions: #这个标签我们只写一次就ok了
plugin.hello:
description: 你好
default: op #权限组,后期会讲到
commands: #这个标签我们只写一次就ok了
hello:
description: 你好
usage: "/hello"
permission: plugin.hello

这里我们发现了default,这个的选项有以下几种

  • op,代表服务器管理员,在ops.txt中规定。
  • notop,代表除服务器管理员外的所有玩家。
  • true,代表所有玩家。
  • false,代表空集。如果某个命令对应这个权限,那就没有人能够使用这个命令(控制台除外)。

大家可以根据这些选项来控制指令的使用范围了

参考文献:

上一节 下一节