十四、消息和命令补全器
在开始之前请确保你有net.md_5.bungee.*
包!!!
本章会讲解 Spigot项目 中net.md_5.bungee
开头的三个程序包,这三个程序包是关于聊天消息之类的,此外还有命令补全器。
上面三个程序包其实是 BungeeCord (简称BC)的一部分API(心疼没有汉化)。
标题中的“消息”是不准确的,因为 BC 是用于连接客户端与多台服务端之间的网络代理,玩家在多台服务器间跳来跳去,但他们实际觉得好像在多个世界之间跳,有利于减轻 CPU 的负荷。
标题中的“消息”又是准确的,因为 Spigot 包括了 BC 的聊天组件API。聊天 API 之外其他 BC 端的 API 是不可以使用的。
第一部分适用于所有BungeeCord
端和Spigot
端。
1.消息
1.1.基础
我想给某玩家发送一条消息,这很简单:
player.sendMessage("Hello World!");
但如果你在用 Spigot API,可以这样:
player.spigot().sendMessage(new TextComponent("Hello World!"));
TextComponent
就是文本组件,就是一条消息,它还包含了消息的文本、颜色、格式和事件。
1.2.颜色 & 格式
一条消息应该有如下几种格式:
格式 | 解释 |
---|---|
Bold | 粗体 |
Italic | 斜体 |
Underline | 下划线 |
Strikethrough | |
Obfuscate | 乱码 |
所以我们可以给一条消息增加一些格式:
TextComponent message = new TextCompoent();
message.setBold(true);//粗体
message.setItalic(true);//斜体
message.setUnderlined(true);//下划线
message.setObfuscated(true);//乱码
message.setStrikethrough(true);//删除线
message.setColor(ChatColor.DARK_RED);//设置颜色
player.spigot().sendMessage(message);//发送消息
但一条消息如果这么设置颜色和格式的话太臃肿了,所以我们可以使用 ComponentBuilder
类一行设置完一条消息的颜色和格式(就是建造模式):
player.spigot().sendMessage(new ComponentBuilder("Hello World!").
color(ChatColor.RED).
bold(true).
create());//一定要在最后调用create方法创建一个TextComponent
除此之外,我们还可以将一条消息进行分段,并且每一段的格式和颜色是不一样的:
player.spigot().sendMessage(new ComponentBuilder("Hello ").
color(ChatColor.RED).
bold(true).
append("World!").//使用append进行分段
italic(true).
create());
可以看到append
方法就是将这条消息进行了一次分段,但最后呈现给玩家的还是一条完整的消息。
1.3.消息の位置
一条消息可以发在不同的位置,但必定是以下四种位置之一:
位置 | 解释 |
---|---|
CHAT | 聊天位置 |
SYSTEM | 系统聊天位置? |
ACTION_BAR | 位于经验条上方一段小小的文字的位置 |
sendMessage
方法拥有第二种重载版本,可以将消息发送于指定的位置:
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, ...);
其中,第一个参数就是消息的位置。
还可以用setCursor
方法设置这段消息的光标(Cursor)位置。
1.4.消息国际化(i18n)
国际化尤为重要,一般说的i18n
是指internationalization
(国际化),取首尾字母 i 和 n 及中间18个字符称为i18n
,指软件无需大变化便可适应不同地区或语言的需要。
每一种语言都有相应的配置文件,切换语言时软件就会加载相应的配置文件。比如zh_CN.properties
就是中文下的配置文件,en_US.properties
就是英文下的配置文件。
我们一般写时间是这么写的:2023/1/7/18:24
。
但老外是这么写的:18:24/7/1/2023
。
所以i18n
不仅仅是翻译一段文字那么简单,还受上面的日期格式、文化等等因素影响,所以成了不少码农的噩梦。
但我们目的不是实现i18n
,Minecraft 早已完成了国际化。语言配置文件格式形如:
"<key>": "<value>"
尽管软件使用不同的语言编写,其配置文件的格式(如.xaml
和.properties
截然不同)或多或少会有点差异,但打死你都会在语言配置文件看见key
和value
。
其中key
是一个在任何配置文件都恒不变的标识符,软件通过调用标识符来获取value
的值,value
就是用不同国家语言翻译后的句子。比如一句“你好”在中文配置文件和英文配置文件分别是:
# 中文
"hello": "你好"
# 英文
"hello": "Hello"
插件也通过调用key
来实现国际化,可以点 这里 来查看英文配置文件。
TranslatableComponent
类就是专门用来国际化的文本组件,本质是将key
提供给客户端让客户端获取相应的value
。
TextComponent
和TranslatableComponent
全部从BaseComponent
类中派生出来,但new
一个BaseComponent
是屁用没有。
TranslatableComponent message = new TranslatableComponent("<key>");
参数填的就是key
,比如我选一个death.attack.magic
这个key
。它在中文的客户端下就是<某人>被魔法杀死了。
,英文的客户端下就是<sb.> was killed by magic.
。
这样做的好处就是服务器往往会有来自不同国家的玩家游玩,比方说服务器给美国人全部呈现中文(哦,歪果仁的噩梦),不用说也得一头雾水,这样的服务器往往只适合在国内发展,在国外人家认不得字,自然而然不想玩了。
而插件作为服务器一大特色,就算不要求自己搞一个i18n
,怎么说也要把原版发送给玩家的消息进行一次i18n
吧。这样好歹能认出一些消息,退一万步也有想玩的念头了。
实际 TranslatableComponent
可以看成 TextComponent
,只不过前者可以国际化罢了,并且可以用addWith
方法增加一个新文本组件或一段文字,最后拼接成一段新的文字。并且TranslatableComponent
也可以设置颜色、格式等等。
TranslatableComponent message = new TranslatableComponent("item.record.11.desc");
message.addWith(" かわいい");
message.addWith(new TextComponent(" 哈哈哈哈"));
message.setBold(true);
这样在中文下就是“11号唱片 かわいい 哈哈哈哈”,英文下就是“C418 - 11 かわいい 哈哈哈哈”。
(かわいい就是卡哇伊的意思啦~)
1.5.事件
文本事件分为ClickEvent
和HoverEvent
两种事件。
ClickEvent
触发必定会是以下五种动作之一:
动作 | 解释 |
---|---|
Action.OPEN_URL | 玩家点击后打开链接 |
Action.OPEN_FILE | 玩家点击后打开文件 |
Action.RUN_COMMAND | 玩家点击后执行指令 |
Action.SUGGEST_COMMAND | 玩家点击文字后在获取后面设置的value 并复制到玩家聊天框中 |
Action.CHANGE_PAGE | 玩家点击后更改书本的页数 |
HoverEvent
触发必定会是以下四种动作之一:
动作 | 解释 |
---|---|
Action.SHOW_TEXT | 玩家鼠标悬浮后显示一段文字 |
Action.SHOW_ACHIEVEMENT | 玩家鼠标悬浮后显示关于某种成就的介绍(在新版本中被弃用) |
Action.SHOW_ITEM | 玩家鼠标悬浮后显示关于某种物品的介绍 |
Action.SHOW_ENTITY | 玩家鼠标悬浮后显示关于某种实体的介绍 |
这两种事件适用于所有继承BaseComponent
的类,可以调用setClickEvent
和setHoverEvent
方法来添加事件。
TextComponent message = new TextComponent("点我");
message.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/kill @s"));
message.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("123")));
这样当玩家点击“点我”文字时就会执行/kill @s
指令。
至于HoverEvent
,它的后一个参数类型是Text
类,可以当成TextComponent
,因为Text
还有另外一种构造方法,传的为BaseComponent
数组,这可能说明我们可以传入多行文本。
2.命令补全
之前所重载的onCommand
方法固然方便,但是当玩家输入指令时,会不可避免地出现这个参数填什么,这个参数该怎么填的情况。于是一般的插件都会将一个指令做成一个类,再到主类去注册这些指令。
为什么需要在指令上大刀阔斧呢?因为有了命令补全,玩家在输入到某个指令时会有对应的提示,知道该输入什么。
这里以FastSpeed
类作为示例,我们需要实现接口TabExecutor
。
示例指令:fastspeed <player-name> <time> <is-fly>
。
我们需要实现TabExecutor
中的两个方法:onCommand
和onTabComplete
。onCommand
相信大家已经很熟悉了,onTabComplete
方法就是我们所说的命令补全。
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args);
参数和onCommand
一样,但是返回类型是List<String>
,是提示玩家的消息。
怎么提示玩家呢,当玩家准备或正在输入第一个参数player-name
时,我们需要提示这里输入的是玩家名。以此类推,第二个参数要提示输入飞行的时间,第三个参数要输入是否飞行。
那么我们只需判断玩家当前正在或准备输入第几个参数就行了。方法中的第三个参数args
就是玩家当前输入的所有参数,可以用arg.length
判断已经输入了几个参数。
注:fastspeed
本身不是参数,后面的才是参数。
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if(sender instanceof Player) {//是否为玩家
if(args.length > 3) return null;//三个参数输入完了,不提示
if(args.length == 0 || args.length == 1) return Collections.singletonList("请输入玩家名");//没有参数,或者已有一个参数
if(args.length == 2) return Collections.singletonList("请输入奔跑时间");//以此类推
return Collections.singletonList("是否飞行,仅限输入true或false");
}
return null;
}
最后我们需要在onEnable
方法中注册这个指令:
Objects.requireNonNull(Bukkit.getPluginCommand("fastspeed")).setExecutor(new FastSpeed());
Objects.requireNonNull(Bukkit.getPluginCommand("fastspeed")).setTabCompleter(new FastSpeed());
至此,命令部分暂时告一段落。
上一篇:我的世界Bukkit服务器插件开发教程(十三)资源包与玩家资料
下一篇:我的世界Bukkit服务器插件开发教程(十五)世界生成器