This commit is contained in:
boybook
2025-12-01 20:59:16 +08:00
parent 12738a142c
commit 760c2dd9ad
5535 changed files with 21070 additions and 2021 deletions

View File

@@ -0,0 +1,106 @@
---
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 处理