Minecraft(我的世界)中文论坛

 找回密码
 注册(register)
查看: 2205|回复: 5

[插件开发教程] [基础插件教程][Bukkit]让插件命令支持Tab自动补全

[复制链接]
gooding300 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
10563
钻石
性别
保密
注册时间
2012-4-13
查看详细资料
发表于 2018-8-29 20:52:09 | 显示全部楼层 |阅读模式

您尚未登录,立即登录享受更好的浏览体验!

您需要 登录 才可以下载或查看,没有帐号?注册(register)

x
本帖最后由 gooding300 于 2018-9-2 13:37 编辑

0.引言
进行过1.13游戏的玩家都会知道,Mojang从这一版本开始为命令引入了一套自动的Tab补全,举个例子:

                               
登录/注册后可看大图

当然,在1.13之前的版本,也可以通过手动按下Tab来完成这样的补全

                               
登录/注册后可看大图

既然用过的人都说好,那么如何让自己的插件中的命令也能支持这样的自动补全呢?
所幸,Bukkit和Spigot为我们引入了相关的支持。

1.新事物
而这个提供了这一功能的接口名为 TabCompleter
顾名思义,这个接口就是用来进行Tab自动补全的,官方将实现了这个接口的类定义为“一个可以提供命令补全建议的类”,我在这里简称自动补全器
这个接口中只有一个方法,名为onTabComplete
  1. 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的类,这里称其为插件主类
  1. public class TestPlugin extends JavaPlugin {
  2.     public void onEnable() {
  3.         //插件启动
  4.     }

  5.     @Override
  6.     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
  7.         //处理命令
  8.     }
  9. }
复制代码
抑或将命令的执行部分从插件主类中分离出去,单独实现(implement)CommandExecutor,这里称其为命令类
  1. public class TestCommand implements CommandExecutor {
  2.     @Override
  3.     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
  4.         //处理命令
  5.     }
  6. }
复制代码

由上文可知,只要一个类实现了TabCompleter,便可以提供自动补全功能,因此:
使用插件主类直接进行命令操作的,只需要覆写(Override)onTabComplete方法即可。
  1. public class TestPlugin extends JavaPlugin {
  2.     public void onEnable() {
  3.         //插件启动
  4.     }

  5.     @Override
  6.     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
  7.         //处理命令
  8.     }

  9.     @Override
  10.     public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
  11.         //处理命令补全
  12.     }
  13. }
复制代码
使用独立命令类的,请将实现CommandExecutor改为实现TabExecutor(无需修改执行相关的代码),增加onTabComplete方法即可。
  1. public class TestCommand implements TabExecutor {
  2.     @Override
  3.     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
  4.         //处理命令
  5.     }

  6.     @Override
  7.     public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
  8.         //处理命令补全
  9.     }
  10. }
复制代码

最后,请不要忘记在setExecutor(设置命令执行器)的时候,也同时setTabCompleter(设置自动补全器)。

3.示例
在这个示例中,我们为“sub”这个命令进行了子命令补全
  1. import org.bukkit.command.Command;
  2. import org.bukkit.command.CommandSender;
  3. import org.bukkit.plugin.java.JavaPlugin;

  4. import java.util.ArrayList;
  5. import java.util.Arrays;
  6. import java.util.List;
  7. import java.util.stream.Collectors;

  8. public class TestPlugin extends JavaPlugin {
  9.     private static final String COMMAND_NAME = "sub";
  10.     private String[] subCommands = {"test", "sample", "sam"};//子命令

  11.     public void onEnable() {
  12.         //注册命令对应的执行器
  13.         getCommand(COMMAND_NAME).setExecutor(this);
  14.         //注册命令对应的自动补全器
  15.         //如果注册了执行器,且自动补全器和执行器在同一个类中,Bukkit会自动尝试将执行器实例转换为命令补全器实例
  16.         //因此这里可以注册也可以不注册,若注册则不再尝试类型转换
  17.         getCommand(COMMAND_NAME).setTabCompleter(this);
  18.     }

  19.     @Override
  20.     public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
  21.         //将第二个参数发给命令执行者
  22.         sender.sendMessage(args.length > 0 ? args[0] : "nothing");
  23.         return true;
  24.     }

  25.     @Override
  26.     public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
  27.         //如果不是能够补全的长度,则返回空列表
  28.         if (args.length > 1) return new ArrayList<>();

  29.         //如果此时仅输入了命令"sub",则直接返回所有的子命令
  30.         if (args.length == 0) return Arrays.asList(subCommands);

  31.         //筛选所有可能的补全列表,并返回
  32.         return Arrays.stream(subCommands).filter(s -> s.startsWith(args[0])).collect(Collectors.toList());
  33.     }
  34. }
复制代码

4.进阶
如果需要补全的是一个物品甚至更多的匹配选项该怎么办?
显然,不断的流式操作本质上是遍历,难以满足每输入一个字母都要进行补全的操作。
这时,就可以请上一个有名的数据结构——Trie了,这个数据结构又称为字典树或者前缀树
这个数据结构正好能够解决这一需求,只需要在插件启动的时候生成这样一棵“树”,每次进行补全查询的时候只需要极少的时间(时间复杂度可以从O(n)优化到O(logn))就能获得想要的结果。

教程适用于Spigot/Bukkit服务端,教程内的所有代码均可以自由使用。
希望这个教程能起到抛砖引玉的作用,欢迎提出更好的实践方案,感谢您的耐心阅读



评分

参与人数 14人气 +31 金粒 +135 收起 理由
NGK3 + 2 神乎其技,不服不行!
Akron + 1 神乎其技,不服不行!
qnickx + 2 MCBBS有你更精彩~
SPGoding + 2 MCBBS有你更精彩~
1250305944 + 2 MCBBS有你更精彩~
602723113 + 2 可以可以
ustc_zzzz + 4 33333333333333333333
Lss233 + 2 + 20 MCBBS有你更精彩~
317620111 + 2 + 20 神乎其技,不服不行!
1582952890 + 3 + 25 MCBBS有你更精彩~
Auange_M夜幕 + 3 神乎其技,不服不行!
森林蝙蝠 + 2 + 40 神乎其技,不服不行!
a6809936 + 2 MCBBS有你更精彩~
luenlin + 2 + 30 教程深入且详细 阅读又舒适 建议加精(迫真.

查看全部评分

754503921 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
10553
钻石
性别
保密
注册时间
2015-2-24
查看详细资料
发表于 2018-9-2 02:52:09 | 显示全部楼层
本帖最后由 754503921 于 2018-9-2 03:02 编辑
而这个在1.13被引入,提供了这一功能的接口名为 TabCompleter 。

这玩意儿真的是 1.13 引入的吗
这个接口我记得1.7.10的bukkit就有了,只不过彼时的Minecraft需要手按tab键补全

不断的流式操作纵然有多核运算的加持

还有这句话什么意思 QAQ

我想学学新的,比如新版本的命令参数类型,指定某个参数只能为int或者玩家啥的
33dalao请务必讲一讲
tab补全请求我觉得可以丢给另一个线程处理了直接发一个包回去,嗯,请也讲一讲
回复

使用道具 举报

gooding300 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
10563
钻石
性别
保密
注册时间
2012-4-13
查看详细资料
 楼主| 发表于 2018-9-2 13:47:47 | 显示全部楼层
本帖最后由 gooding300 于 2018-9-2 14:06 编辑
754503921 发表于 2018-9-2 02:52
这玩意儿真的是 1.13 引入的吗
这个接口我记得1.7.10的bukkit就有了,只不过彼时的Minecraft需要手按tab键 ...

非常感谢您的建议,已针对您提出的问题再次查看了相关源码并进行了修正。

1.经过搜索,确实在1.7.10的Bukkit文档中找到了这个类,已修改相关内容。
2.之前对Stream API的并行处理有所误解,未考虑到并行处理不能保证顺序,不适用于补全操作,现已修正。
3.经过查看Bukkit相关源码,仅发现了玩家名字补全器,原版其他类型的补全器应该尚未加入。
4.我觉得应该没有必要多线程处理,在Trie中匹配的时间复杂度仅为O(logn),即使有一亿个待匹配项,只要限定最终的匹配项数量(例如只有一个字母的时候不匹配,否则网络也是个瓶颈),也可以在1ms内处理完毕。
回复

使用道具 举报

754503921 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
10553
钻石
性别
保密
注册时间
2015-2-24
查看详细资料
发表于 2018-9-6 14:00:46 | 显示全部楼层
之前对Stream API的并行处理有所误解,未考虑到并行处理不能保证顺序

为啥是并行处理,Arrays.stream 返回的应该不是一个并行流,并且要顺序的话 sorted() 就行

至于多线程处理那个,假如我想做个插件搜索引擎(我有病),让百度补全,能不能在取得结果后直接向客户端发送补全包

原版其他的补全器肯定是没有的,但是我们水桶开发者应该非常熟悉搞nms的操作了,你就研究下呗
回复

使用道具 举报

1204074991 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
1733
钻石
性别
保密
注册时间
2013-10-28
查看详细资料
发表于 2019-5-26 09:50:40 | 显示全部楼层
怎么补全指令呢?好像只能补全参数的样子。
回复

使用道具 举报

Tcaks_NB 当前离线
帖子
主题
精华
贡献
最后登录
1970-1-1
爱心
积分
108
钻石
性别
保密
注册时间
2019-6-27
查看详细资料
发表于 2019-6-30 23:20:02 | 显示全部楼层
后排表示支持。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册(register)

本版积分规则

Archiver|小黑屋|Mcbbs.net ( 京ICP备15023768号-1 ) | 京公网安备 11010502037624号 | 手机版

GMT+8, 2019-8-21 09:16 , Processed in 0.034185 second(s), Total 13, Slave 12 queries , Gzip On, MemCached On.

"Minecraft"以及"我的世界"为Mojang Synergies AB的商标 本站与Mojang以及微软公司没有从属关系

© 2010-2019 我的世界中文论坛 版权所有 本站原创图文内容版权属于原创作者,未经许可不得转载

快速回复 返回顶部 返回列表