[上一章](第三章-如何编写监听器.md) [下一章](第五章-如何使用配置文件.md)
# 第一部分 第四章 如何编写命令
参与编写者: MagicLu550
#### 建议学习时间: 30分钟
##### 学习要点: 学习自己创建一个简易的命令,了解指令和指令映射,了解SimpleCommand的使用

其实创建一个简易的命令很简单，创建的过程也不必多讲，先前我在
[第二章](第二章-插件要素.md)已经提到了如何创建它，大家可以参考

nukkit的原生命令也是很多基于Command创建的.很多项目是需求指令
以使得用户和您的项目操作，也就是作为一个 **接口(Interface)** ,
接下来在基于[第二章](第二章-插件要素.md)的命令注册后，我们介绍
以下nukkit的命令。
nukkit的命令的父类是Command，它有很多的构造方法
```
    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
```
159 private boolean registerAlias(Command command, boolean isAlias, String fallbackPrefix, String label) {
160      this.knownCommands.put(fallbackPrefix + ":" + label, command);

```
--


存储指令的容器是实现CommandMap接口的，即SimpleCommandMap,我们可以通过
```
        this.getServer().getCommandMap();
```
得到CommandMap.

Command,CommandMap有很多方法

1. Command
    1. 根构造方法的参数为
        ```
        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里的代码
            它的参数我们在[第二章](第二章-插件要素.md)提到了
            
        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
```
@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;
    }
``` 
这段代码我们可以知道方法的参数有规范要求的
    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 void onHelloCommand(Object object, CommandSender sender, String label, String[] args){
        //这里写指令处理代码
    }
}
```
最终通过registerSimpleCommand注册即可.事实上是对Command的封装

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

```yaml
permissions: #这个标签我们只写一次就ok了
  plugin.hello:
    description: 你好
    default: op #权限组，后期会讲到
commands: #这个标签我们只写一次就ok了
  hello:
    description: 你好
    usage: "/hello"
    permission: plugin.hello
```
这里我们发现了default，这个的选项有以下几种

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

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

参考文献:
- [Nukkit插件从0开始](https://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html)

[上一章](第三章-如何编写监听器.md) [下一章](第五章-如何使用配置文件.md)
