在阅读本教程前,需要你具备基本的Java知识和非常基本的Spigot插件开发知识
I18N,即internationalization(国际化)的简称。因其首末字母之间间隔了18个字母,为了交流的方便,故经常以I18N代称
什么是ProtocolLib?
(图中,左边为客户端,右边为服务端)
ProtocolLib作为一款插件,更作为插件开发过程中常用到的一个依赖项,其作用大致就是拦截所有客户端发送到服务端/服务端发送往客户端的数据包,从而获取该数据包的内容,或对其加以修改,以达到目的
拦截的原理我猜主要是类似hook技术,这里我不深究,只是我的一个猜想
ProtocolLib在通信中所处的位置我认为就如上图所表示的,相当于在客户端与服务端之间横插一脚,和中间人攻击(Man-in-the-Middle Attack,简称“MitM攻击”)类似,但是作为一款安装在服务端上的插件,其应当更偏向于服务端一侧,且从某种意义上说,服务端是知晓这个“拦截者”的存在的
为什么要用ProtocolLib,Spigot API不是也有相应的修改DisplayName, Lore等等与显示属性相关的方法吗?
当然,Spigot API确实自带一些修改属性的方法。最常用的莫过于ItemMeta的setDisplayName和setLore这两个方法了。表面上看,这样的方法也能够实现简单意义上的国际化
可是,如果你深究起来,这种方法有着种种的弊端
其中最明显的一点,也是我个人认为的最不方便的一点,就是无法使用ItemStack自带的isSimiliar和equals比较的方法
因为这两个方法在实现中,都会比较ItemStack的ItemMeta里的DisplayName和Lore。既然你为了实现国际化,为不同语言的玩家的物品分别设置了不同的DisplayName和Lore,那么这两个方法对你而言就注定无法使用了。除非你实现一套自己的判断方法,虽然说不难,但需要自己重新发明轮子,实在是不够优雅
更何况,对于某些情况,Spigot API并没有提供相应的方法。比如说Inventory的标题。在InventoryView中,只有getTitle方法,而没有setTitle的方法。如果只依靠Spigot API实现Inventory的国际化,目前的思路就是监听玩家打开Inventory的事件。每当玩家打开一个Inventory,就来一个Bukkit.createInventory,(只有这个方法可以创建自定义标题的物品栏),然后关闭原来的Inventory,显示我们自己创建的Inventory,还要在打开前/关闭后实现两个Inventory之间物品的同步,同时还要考虑漏斗的情况。虽然不是不可行,但实在是太蛋疼了,或者说,不够优雅
总之,单纯地凭借Spigot API实现国际化,是不优雅的,也是不可取的
我们是时候该给自己提升一个维度了!
正确的打开方式
我们看到原版的Minecraft中,就算两个客户端的语言不同,只要两个玩家都拿着同一个物品(不考虑附魔等特殊情况),服务端这边总是能判断isSimiliar为true
于是,我们不难推断:其实这些物品在服务端上本质都是一样的,只是客户端上显示的不同罢了
顺着这个思路,由于Minecraft是典型的C/S架构。客户端和服务端之所以能实现数据的同步,不就是依靠客户端和服务端之间的通信吗?如果我们能够截获两者的通信,并在中间加以修改,岂不就可以悄无声息地“骗”过服务端和客户端了吗?既让客户端相信这个物品的DisplayName和Lore是不同的,要在不同语言环境下分别显示,又让服务端相信,这些在客户端看来不同的物品,在实质上是相同的
幸运的是,就像前面我对ProtocolLib的介绍,这个插件完美地适应了我们的需求
能否不用ProtocolLib?
当然可以
毕竟ProtocolLib在实现上也是用NMS的。你当然可以绕过ProtocolLib,自己用NMS实现一遍
我不是鼓励大家一遇到问题就想着用别人造好的轮子,只是说NMS这个东西实在比较邪乎,Spigot官方也是推荐能不用就不用
ProtocolLib这个东西也不是什么新出来的,已经有很多年的发展了,技术积淀深厚。人家搞NMS有一套的,总比我们从零开始研究的靠谱
更何况ProtocolLib是很多插件的共同依赖。你不依赖,其他的插件也会依赖。就算是中小型服务器,ProtocolLib迟早也会被装上的
我认为,ProtocolLib算得上是现在Minecraft服务器的必装插件之一了。所以,用的时候不要有什么心理负担
下面,我们正式开始教程
引入依赖
本系列教程均使用IntelliJ IDEA作为IDE
本系列教程除特殊说明处外,皆围绕Minecraft 1.19.2版本开展
由于截至撰稿日期时,适配1.19.2版本的ProtocolLib并未推出正式版,只能从Jenkins上下载构建版本,因此暂时无法使用Maven进行项目依赖管理
按照官方的说法,对于1.19.2版本,应当使用构建版本#606
前往下载该构建工件,并在IDEA中添加该jar作为项目的库
至于如何在IDEA中导入外部jar包,我不在此赘述。不了解的可以自己去查一查
当然,如果你坚持要用Maven的话,可以使用5.0.0-SNAPSHOT版本,在pom.xml中的适当位置加入如下内容:
<repositories>
<repository>
<id>dmulloy2-repo</id>
<url>https://repo.dmulloy2.net/repository/public/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>5.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
修改完毕后,刷新一下,5.0.0-SNAPSHOT版本的ProtocolLib依赖就配置好了
但注意,官方标明latest dev build是适配Minecraft 1.19.3版本的。虽然不是说不能用,但总归和1.19.2版本有些不一样的地方
因此,我还是推荐使用构建版本#606
关于二者的区别,我们会在下一篇教程中涉及
代码时间
本系列教程的代码旨在实现特定的功能,相当于一个原型prototype或者说草稿。为了后续开发和维护的方便,强烈建议在实际开发过程中对教程中的代码重新进行合理的组织
在插件的onEnable方法处加入如下的代码:
注意:请不要调用super.onPacketSending(event)
,否则将会抛出异常
ProtocolLibrary.getProtocolManager().addPacketListener(
new PacketAdapter(this, PacketType.Play.Server.OPEN_WINDOW) {
@Override
public void onPacketSending(PacketEvent event) {
event.getPlayer().sendMessage("OPEN_WINDOW");
}
}
);
同时,在开头处需要引入如下几个包。可以使用Alt+Enter快速导入
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
下面我们开始简要地分析一下这段代码:
ProtocolLibrary.getProtocolManager().addPacketListener
这应该没什么问题,就是增加一个数据包监听器,是固定的写法
同时,该方法接受一个PacketListener
接口
方法定义:void addPacketListener(PacketListener var1);
new PacketAdapter(this, PacketType.Play.Server.OPEN_WINDOW)
我们创建了一个匿名类,并将其作为参数传入
该匿名类属于PacketAdapter
,它是一个实现了PacketListener
接口的抽象类
this
就是我们的插件
PacketType.Play.Server.OPEN_WINDOW
就是我们要监听的数据包的类型,它表示打开一个Inventory
作为最简单的入门,我们以该数据包为例
当然还有其他很多种类的数据包,我在之后的教程中会娓娓道来
public void onPacketSending(PacketEvent event)
从这里可以看出来,ProtocolLib采用的是事件模型
顾名思义,当一个数据包从服务端发出时,该方法会被调用
由于我们前面指定了我们只监听PacketType.Play.Server.OPEN_WINDOW
,所以只有当该类型的数据包发出时,才会执行该方法的逻辑
event.getPlayer().sendMessage("OPEN_WINDOW");
向接收该数据包的玩家发送一条内容为OPEN_WINDOW的消息
结语
简而言之,这段代码的意思就是:当玩家打开一个Inventory的时候,就会收到一条内容为OPEN_WINDOW的消息
由于我们此时并没有任何读取/修改数据包的操作,因此该例子也相当简单
希望读者们在阅读完本篇教程并亲手实践后,能对ProtocolLib有一个大概的了解
下一篇教程,我们将对数据包展开分析
相关链接
ProtocolLib在SpigotMC上的主页: https://www.spigotmc.org/resources/protocollib.1997/
ProtocolLib的构建版本#606(适用于Minecraft 1.19 – 1.19.2): https://ci.dmulloy2.net/job/ProtocolLib/606/
ProtocolLib的最新构建版本(适用于Minecraft 1.19.3): https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/
Somebody essentially lend a hand to make significantly articles Id state That is the very first time I frequented your website page and up to now I surprised with the research you made to make this actual submit amazing Wonderful task