Files
netease-modsdk-wiki/docs/mcguide/28-电脑网络游戏/课程3:编写Bukkit插件/4-底层内容/2-自定义发包.md
boybook 760c2dd9ad 2.6
2025-12-01 20:59:16 +08:00

106 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
front:
hard: 高级
time: 30分钟
---
# 自定义发包
## 数据包介绍
翻阅NMS我们可以知道的是服务端的数据变更一般都是通过 DataWatcher 来判断以后通过
数据包的方式发送到客户端,客户端接收到数据包后对玩家客户端内容从而进行更改
这就是为什么上一个教程中,我们更改玩家的经验时不能直接通过赋值的方式进行更改
而是通过内置的一个“神秘(混淆了)”封装的方法去更改,这是因为只有通过此方法,才能将参数传参给父类
然后在通过父类的方法赋值给 DataWatcher
而翻阅 DataWatcher 类里的方法我们在一大堆混淆的代码发现了
```java
public void refresh(EntityPlayer to) {
if (!this.d()) {
List<b<?>> list = this.packAll();
if (list != null && to.getBukkitEntity().canSee(this.d.getBukkitEntity())) {
to.c.a(new PacketPlayOutEntityMetadata(this.d.af(), list));
}
}
}
```
此方法大概率就是发送数据包的方法了.
此时你会发现,他将 `PacketPlayOutEntityMetadata` 包发了出去
那这个包是干嘛的呢?
我们查阅[我的世界维基百科数据包](https://minecraft.wiki/w/Java_Edition_protocol/Packets?oldid=2772385)
可以发现其中的EntityMetadata 正好是更新现有生物实体的元数据,而经验值属于玩家实体特有的元数据,所以符合我们上述的说法
![](../images/0_20.png)
## 如何自定义一个数据包并发出去呢
说了那么多如何自定义一个数据包发出去呢
在这里做一个示例便是
在1.8更新我的世界更新了Title等一系列文字显示的内容但是Bukkit并没有及时的更新相关的API
直到1.12.2才更新快速发送Title的方法所以在1.8 - 1.11 期间开发者想要发送Title则需要自己通过封装NMS去手动发包
我们查阅对应版本的数据包在这里我拿1.12.2的wiki去演示 [我的世界维基百科数据包](https://minecraft.wiki/w/Java_Edition_protocol/Packets?oldid=2772385)
找到有关于Title的可以发现此数据包
![](../images/0_21.png)
我们可以看到基本介绍他是发送给客户端的状态是Play那么在NMS里应该就是 `PacketPlayOutTitle`
再查看PacketPlayOutTitle类虽然里面全部混淆了
但是对比成员变量与Wiki中Action描述可以不难猜出PacketPlayOutTitle包构造函数里的参数
按照顺序每个参数类型都是按照顺序填写的
![](../images/0_22.png)
我们发现有四个构造函数,那我们选择能够自定义程度最高的,也就是参数填入最多的那一个构造函数
第一个参数传入 Title 类型 TITLE、SUBTITLE
> 这就代表着我们要同时发2个包。一个是TITLE的数据包另一个是SUBTITLE的数据包。
第二个参数传入 `IChatBaseComponent` ,这个是我的世界的文本基础组件。
第三个参数传入 淡入时间
第四个参数传入 停留时间
第五个参数传入 淡出时间
当我们都填写好参数后,可以写出下述代码
```java
public void sendTitle(Player player, String title, String subtitle, int fadeIn, int stayIn, int fadeOut) {
IChatBaseComponent titlesend = IChatBaseComponent.ChatSerializer.a("{\"text\": \"" + title + "\"}");
IChatBaseComponent subtitlesend = IChatBaseComponent.ChatSerializer.a("{\"text\": \"" + subtitle + "\"}");
PacketPlayOutTitle packet = new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.TITLE, titlesend, fadeIn, stayIn, fadeOut);
PacketPlayOutTitle packet2 = new PacketPlayOutTitle(PacketPlayOutTitle.EnumTitleAction.SUBTITLE, subtitlesend, fadeIn, stayIn, fadeOut);
(((CraftPlayer)player).getHandle()).playerConnection.sendPacket((Packet)packet);
(((CraftPlayer)player).getHandle()).playerConnection.sendPacket((Packet)packet2);
}
```
此时我们即可快速调用自己写的 sendTitle 快速发送一份Title文字
但是翻阅各版本NMS可以发现每一个版本的Packet的参数混淆名、类的位置等都不一样
所以这就导致我们通过NMS去发送title就可能导致版本不互通
所以这就需要我们对各版本都有处理主要就是利用到Java的接口对不同版本都有一定的兼容这里就不再过多介绍
> 由于NMS兼容性确实很差一般来说开发者在操作Packet的时候都是通过第三方库 ProtocolLib 处理