在 Bukkit 和 Fabric/Forge 之间进行更简单的数据传输,且允许数据包大小超过 Minecraft 规定的 1 MB。
定义数据包:
data class MyPacket(val who: Int, val data: String) : ModLinkPacket(0) {
override fun write(writer: BinaryWriter) {
writer.writeInt(who)
writer.writeString(data)
}
}注册数据包:
val modlink: Modlink = ...
modlink.codecRegistry.registerDecoder(0) {
MyPacket(readInt(), readString())
}创建 Modlink 实例(使用 TabooLib 环境):
val modlink by lazy { Modlink(bukkitPlugin, "modlink:default") }发送数据包:
modlink.sendPacket(player, MyPacket(1, "Hello, World!"))接收数据包:
modlink.onReceive<MyPacket> { player, packet ->
println("${player.name} received packet: $packet")
}创建 Modlink 实例(使用 Fabric 1.20.4 环境):
val modlink = Modlink()
// 注册数据包通道
// 由于 Fabric 的通讯写法在不同版本可能存在差异,因此需要手动注册
val channel = Identifier("modlink", "default")
ClientPlayNetworking.registerGlobalReceiver(channel) { _, _, buf, _ ->
modlink.handleMessageReceived(buf.array())
}发送数据包:
modlink.sendPacket(MyPacket(1, "Hello, World!")) { bytes ->
// 为什么会设计成这种形式?
// 如果数据包的大小超过 512 KB,则会被分片发送
ClientPlayNetworking.send(channel, PacketByteBuf(Unpooled.wrappedBuffer(bytes)))
}接收数据包:
modlink.onReceive<MyPacket> { packet ->
println("Received packet: $packet")
}对于1.12.2-1.13+ 请参考海螺的博客
对于1.16.5-1.20.1,一种方法是使用Mixin手动接管数据包处理
下面以1.20.1 Forge 47.4.3 为例
@Mixin({ClientPacketListener.class})
public class ClientPacketListenerMixin {
@Inject(
method = {"handleCustomPayload"},
at = {@At("HEAD")},
cancellable = true
)
public void onCustomPayload(ClientboundCustomPayloadPacket packet, CallbackInfo callbackInfo) {
if (packet.getIdentifier().equals(ResourceLocation.tryBuild("modlink", "default"))) {
CustomPacketEvent event = new CustomPacketEvent(packet.getData());
MinecraftForge.EVENT_BUS.post(event);
callbackInfo.cancel();
}
}
}创建事件类
class CustomPacketEvent(val buf: FriendlyByteBuf): Event()之后实现收发信逻辑
// 此事件需要在Forge的EVENT_BUS上注册
fun onCustomPacketReceive(event: CustomPacketEvent) {
Minecraft.getInstance().execute {
val buf = event.buf
val readableBytes = buf.readableBytes()
if (readableBytes <= 0) {
return@execute
}
val data = ByteArray(readableBytes)
buf.readBytes(data)
modlink.handleMessageReceived(data)
}
}
fun sendToServer(packet: ModLinkPacket) {
modlink.sendPacket(packet) { bytes ->
Minecraft.getInstance().connection?.send(
ServerboundCustomPayloadPacket(
ResourceLocation.tryBuild("modlink", "default"),
FriendlyByteBuf(Unpooled.wrappedBuffer(bytes))
)
)
}
}已知在1.20.4时,使用上面的方法Mixin的类要换为ClientCommonPacketListenerImpl
同时由于Forge在此版本又出现了#5730中的问题,需要在Bukkit端手动注册通道
// 使用Taboolib
@SubscribeEvent
fun onPlayerJoin(event: PlayerJoinEvent) {
(event.player as CraftPlayer).addChannel("modlink:default")
}之后便可正常通过CustomPacketEvent收到数据包的数据
在 Channel 没有注册前是不能发包给玩家的,通常表现在 Join 事件前后发包无效。下面是一个简单的解决办法:
private val modlink by lazy { Modlink(bukkitPlugin) }
private val packetBuffer = ConcurrentHashMap<String, MutableList<ModLinkPacket>>()
/**
* 向指定玩家发送 ModLink 数据包
*
* @param player 目标玩家
* @param packet 要发送的数据包
*/
fun sendPacket(player: Player, packet: ModLinkPacket) {
// 玩家是否已注册通道
if (player.hasMeta("modlink")) {
modlink.sendPacket(player, packet)
} else {
packetBuffer.getOrPut(player.name) { mutableListOf() } += packet
}
}
@SubscribeEvent
private fun onJoin(e: PlayerJoinEvent) {
e.player.removeMeta("modlink")
}
@SubscribeEvent
private fun onChannel(e: PlayerRegisterChannelEvent) {
if (e.channel == Modlink.channelId) {
e.player.setMeta("modlink", true)
// 发送缓存中的数据包
packetBuffer.remove(e.player.name)?.forEach { modlink.sendPacket(e.player, it) }
}
}