0.引言
进行过1.13游戏的玩家都会知道,Mojang从这一版本开始为命令引入了一套自动的Tab补全,举个例子:
当然,在1.13之前的版本,也可以通过手动按下Tab来完成这样的补全
既然用过的人都说好,那么如何让自己的插件中的命令也能支持这样的自动补全呢?
所幸,Bukkit和Spigot为我们引入了相关的支持。
1.新事物
而这个提供了这一功能的接口名为 TabCompleter 。
顾名思义,这个接口就是用来进行Tab自动补全的,官方将实现了这个接口的类定义为“一个可以提供命令补全建议的类”,我在这里简称自动补全器
这个接口中只有一个方法,名为onTabComplete。
- public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args);
这个方法可以如此介绍的:
在用户输入命令时被调用,请求返回可能的命令补全列表。
参数 sender 命令的来源。如果玩家在命令方块中进行补全,将使用的参数是操作的玩家而不是被操作的命令方块。
参数 command 会被执行的命令
参数 alias 所使用的命令别名
参数 args 传递给命令的参数数组,包括尚未补全的参数。
返回 可能的命令补全列表,或返回null以使用默认的自动补全功能(玩家名称补全器)。
例如在上图中,我们所看到的“minecraft:heart_of_the_sea”以及“minecraft:hearvy_weighted_pressure_plate”就是命令补全列表,“@p”、“hea”组成的数组就是参数数组。也就是说,借助这个接口,我们可以为自己的命令提供完全自定义的Tab补全了。
2.如何使用
先用一张图表示相关类的继承关系。
一个普通的插件,都会有一个继承(extend)自JavaPlugin的类,这里称其为插件主类。
- public class TestPlugin extends JavaPlugin {
- public void onEnable() {
- //插件启动
- }
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- //处理命令
- }
- }
- public class TestCommand implements CommandExecutor {
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- //处理命令
- }
- }
由上文可知,只要一个类实现了TabCompleter,便可以提供自动补全功能,因此:
使用插件主类直接进行命令操作的,只需要覆写(Override)onTabComplete方法即可。
- public class TestPlugin extends JavaPlugin {
- public void onEnable() {
- //插件启动
- }
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- //处理命令
- }
- @Override
- public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
- //处理命令补全
- }
- }
- public class TestCommand implements TabExecutor {
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- //处理命令
- }
- @Override
- public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
- //处理命令补全
- }
- }
最后,请不要忘记在setExecutor(设置命令执行器)的时候,也同时setTabCompleter(设置自动补全器)。
3.示例
在这个示例中,我们为“sub”这个命令进行了子命令补全。
- import org.bukkit.command.Command;
- import org.bukkit.command.CommandSender;
- import org.bukkit.plugin.java.JavaPlugin;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
- import java.util.stream.Collectors;
- public class TestPlugin extends JavaPlugin {
- private static final String COMMAND_NAME = "sub";
- private String[] subCommands = {"test", "sample", "sam"};//子命令
- public void onEnable() {
- //注册命令对应的执行器
- getCommand(COMMAND_NAME).setExecutor(this);
- //注册命令对应的自动补全器
- //如果注册了执行器,且自动补全器和执行器在同一个类中,Bukkit会自动尝试将执行器实例转换为命令补全器实例
- //因此这里可以注册也可以不注册,若注册则不再尝试类型转换
- getCommand(COMMAND_NAME).setTabCompleter(this);
- }
- @Override
- public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
- //将第二个参数发给命令执行者
- sender.sendMessage(args.length > 0 ? args[0] : "nothing");
- return true;
- }
- @Override
- public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
- //如果不是能够补全的长度,则返回空列表
- if (args.length > 1) return new ArrayList<>();
- //如果此时仅输入了命令"sub",则直接返回所有的子命令
- if (args.length == 0) return Arrays.asList(subCommands);
- //筛选所有可能的补全列表,并返回
- return Arrays.stream(subCommands).filter(s -> s.startsWith(args[0])).collect(Collectors.toList());
- }
- }
4.进阶
如果需要补全的是一个物品甚至更多的匹配选项该怎么办?
显然,不断的流式操作本质上是遍历,难以满足每输入一个字母都要进行补全的操作。
这时,就可以请上一个有名的数据结构——Trie了,这个数据结构又称为字典树或者前缀树。
这个数据结构正好能够解决这一需求,只需要在插件启动的时候生成这样一棵“树”,每次进行补全查询的时候只需要极少的时间(时间复杂度可以从O(n)优化到O(logn))就能获得想要的结果。
教程适用于Spigot/Bukkit服务端,教程内的所有代码均可以自由使用。
希望这个教程能起到抛砖引玉的作用,欢迎提出更好的实践方案,感谢您的耐心阅读。