很多插件都提供了使用自定义命令的方法。既然它们能做到,我们当然也能做到。
命令处理分两部分构成:命令注册和命令处理。
命令注册这一步不使用 Java,而要在 plugin.yml
中完成。
plugin.yml
中注册命令的要求如下:
- 写在
commands
键中,一个命令占用一个键 - 这个键指向的是另一个「字典」(对象)
- 必须包含
usage
和description
,aliases
和permissions
与permission-message
是可选的, 由于原版的权限很少,permission
一般不用。
一个合法的命令如下:
name: HelloWorld
main: rarityeg.helloworld.HelloWorld
version: 1.0
api-version: 1.16
# YAML 中的注释,一行有效
# 以上是插件基本信息,以下是命令注册
commands:
superkill: # 命令的基本名,请不要使用奇怪的字符!
aliases: # 别名,方便玩家使用
- "skill"
- "sk"
usage: "/superkill <player-name>" # 用法
description: "Kill some player." # 描述
permission: "minecraft.command.kill" # 需要的权限,通常不用
permission-message: "Only killer can do that!" # 无权限返回的消息,通常不用
这里要说明的一点是,plugin.yml
中只允许英文,所以现在大家请忍耐一下,稍后我们可以自定义命令无效时返回的消息。
这样命令就注册好啦,没错,这一步不需要编写代码,Bukkit 会为我们完成这项工作。
命令注册好后,我们就需要在命令被输入时进行处理。
一个命令被玩家使用时通常是这样:
/命令名称 参数 参数 参数 BlahBlah
参数之间是以空格分开的。
命令处理和事件处理比较类似,不同的是我们需要一个命令处理器。
要让 Bukkit 认识到这是一个命令处理器,我们需要实现它提供的接口(签订协议),这个接口就是 org.bukkit.command.CommandExecutor
。
这个接口中有一个必须实现的方法,它叫 onCommand
,它的签名如下:
public boolean onCommand(CommandSender sender, Command command, String label, String args)
接受四个值,返回一个布尔(逻辑)值:
CommandSender sender
:命令的发送者Command command
:命令对象String label
:玩家实际使用的名称(别名),即使用别名时,上面的命令对象是一样的,而这里的label
是不一样的String[] args
:参数,以数组形式传递
?> 什么是数组?
数组(Array)是 Java 用来组织多个同类对象时的方法之一。在类名后面加上 []
即表示一个「装着」该类的数组。
你可以把数组想象成一些对象整齐地排在内存的某处,我们可以把它们作为一个整体传递,也可以取其中的一个。
那我们要怎么编写这个函数呢?下面我还是以「输入 /superkill <玩家名>
时杀死玩家」为例,演示命令处理函数的写法。
首先创建一个新的类代表命令处理器,再按照接口 CommandExecutor
的规范,写好签名:
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import javax.annotation.ParametersAreNonnullByDefault;
public class Commander implements CommandExecutor {
@Override
@ParametersAreNonnullByDefault
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
return false;
}
}
这里 sender
、command
、label
和 args
都是函数内部名称,也就是说,只是由我们来起一个名字方便表示,所以换成其它别的名字也没有问题(只需要同时修改函数内的相应部分)。
@ParametersAreNonnullByDefault
是一个注解,看名字就知道,它表示各个参数都不是 null
,那我们就可以放心地使用这四个参数了。
顺便说一句,这个注解是 Java 提供的。那还有哪些东西(接口、类、注解)是 Java 提供的呢?
java
包下的所有内容,例如java.util
,java.io
等javax
包下的所有内容,例如javax.swing
,javax.awt
等sun
包下的所有内容,例如sun.reflect
等
差不多就是这些。使用它们时我们无须像导入 spigot-1.16.5.jar
那样操作。
回到正题上来。
命令被触发时,我们的命令处理函数将被调用。而我们要实现的功能很简单:获取第一个参数,并杀死对应的玩家。
首先我们应该检查参数是否正确,由于我们只需要第一个参数,因此 args
的长度大于等于 1,也就是不等于 0 就可以了。
至于查找玩家,Bukkit 提供了一个方法,该方法是 org.bukkit.Bukkit
的一个静态(static
)方法(类方法),签名如下:
@Nullable
Player getPlayer(@NotNull String name);
那么我们就可以实现这个功能了:
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import javax.annotation.ParametersAreNonnullByDefault;
public class Commander implements CommandExecutor {
@Override
@ParametersAreNonnullByDefault
public boolean onCommand(CommandSender commandSender, Command command, String label, String[] args) {
if (args.length == 0) {
// 参数数量太少,拒绝处理
return false;
}
Player player = Bukkit.getPlayer(args[0]);
// args[0] 是参数的第一项,args[1] 是参数的第二项,以此类推
if (player == null) {
// 如果这个玩家不存在,getPlayer 将返回 null,如果调用 null 的方法,将引发 NullPointerException 异常
return true;
}
player.setHealth(0); // setHealth 设置玩家血量,0 为死亡
return true;
}
}
根据上面的注释,你应该可以独自看懂。
现在只剩一个问题了:为什么要返回 true
和 false
?
这是命令处理标志位,返回 true
代表「命令语法正确」,false
代表「命令语法错误」。
如果返回 false
,Bukkit 会向该命令的使用者发送 usage
中的内容提醒使用者使用正确的语法,仅此而已。
?> 为什么多余参数不能删掉?
这里我们没有用到 sender
和 label
,那为什么不能把它们删掉呢?
别忘了,Java 是用方法签名识别一个方法的,如果我们改变了参数列表,也就是改变了方法签名,Bukkit 要求 Java 调用这个方法时,Java 就会找不到并且报错的!所以,即使麻烦一点,也要写上。
这样命令处理器就编写好了。
和事件处理器一样,我们需要注册命令处理器。在插件主类 onEnable
中写上:
if (Bukkit.getPluginCommand("superkill") != null) {
Bukkit.getPluginCommand("superkill").setExecutor(new Commander());
}
应该很简单。至于为什么要多一个 null
判定,是因为 Bukkit 并不能保证一定能获得 superkill
这个命令,万一它不存在呢?
你可能会说,我都写到 plugin.yml
里了,怂啥?
可是,Java 不是专门为插件开发设计的,它不知道什么是 plugin.yml
,不保证能够满足 Bukkit 的要求。所以虽然麻烦,但还是要这样写。
好啦,这样,在游戏里面就可以使用 superkill
这个命令啦!
这里我们只是大致地讲了一下命令处理器的基本知识,实际上已经足够常规使用。在后面的章节中,我们会使用更高级的 TabExecutor
完成命令提示补全功能。