使用ProtocolLib实现Spigot插件的国际化翻译(I18N)—— ProtocolLib基础(第1章)

在阅读本教程前,需要你具备基本的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/

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇