Added some packets

This commit is contained in:
2026-09-05 18:19:58 +08:00
parent a2eebe3d4e
commit 94cca61df1
22 files changed
+326 -45

No files matched your search

@@ -88,6 +88,8 @@ internal class InternalPacketDispatcher(private val client: MinecraftClient) {
client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
}
is ClientboundSystemChatMessagePacket -> {}
}
}
}
@@ -0,0 +1,18 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
public data object ClientboundDelimiterPacket : ClientboundPlayPacket, PacketCodec<ClientboundDelimiterPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundDelimiterPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDelimiterPacket {
return ClientboundDelimiterPacket
}
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.types.Animations
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Entity_Animation
*/
public data class ClientboundEntityAnimationPacket(val entityId: Int, val animation: Animations) :
ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundEntityAnimationPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundEntityAnimationPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundEntityAnimationPacket {
val entityId = buffer.readVarInt()
val animation = Animations.fromID(buffer.readByte())
return ClientboundEntityAnimationPacket(entityId, animation)
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.common.readUuid
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.protocol.types.Angle
import cn.rtast.libmc.protocol.types.LpVec3
import cn.rtast.libmc.protocol.types.readAngle
import cn.rtast.libmc.protocol.types.readLpVec3
import kotlin.uuid.Uuid
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Spawn_Entity
*/
public data class ClientboundSpawnEntityPacket(
val entityId: Int,
val entityUuid: Uuid,
val type: Int,
val x: Double,
val y: Double,
val z: Double,
val velocity: LpVec3,
val pitch: Angle,
val yaw: Angle,
val headYaw: Angle,
val data: Int,
) : ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundSpawnEntityPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSpawnEntityPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSpawnEntityPacket {
val entityId = buffer.readVarInt()
val entityUuid = buffer.readUuid()
val type = buffer.readVarInt()
val x = buffer.readDouble()
val y = buffer.readDouble()
val z = buffer.readDouble()
val velocity = buffer.readLpVec3()
val pitch = buffer.readAngle()
val yaw = buffer.readAngle()
val headYaw = buffer.readAngle()
val data = buffer.readVarInt()
return ClientboundSpawnEntityPacket(
entityId, entityUuid,
type, x, y, z, velocity,
pitch, yaw, headYaw, data
)
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ClientboundSystemChatMessagePacket(val content: NBTCompound, val overlay: Boolean) :
ClientboundPlayPacket {
public companion object Codec : PacketCodec<ClientboundSystemChatMessagePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSystemChatMessagePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSystemChatMessagePacket {
val content = buffer.readNetworkNBTCompound()
val overlay = buffer.readBoolean()
return ClientboundSystemChatMessagePacket(content, overlay)
}
}
}
@@ -8,11 +8,7 @@ package cn.rtast.libmc.protocol.protocol
import cn.rtast.libmc.protocol.packet.configuration.*
import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket
import cn.rtast.libmc.protocol.packet.login.ClientboundDisconnectLoginPacket
import cn.rtast.libmc.protocol.packet.login.ClientboundLoginSuccessPacket
import cn.rtast.libmc.protocol.packet.login.ClientboundSetCompressionPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginAcknowledgedPacket
import cn.rtast.libmc.protocol.packet.login.ServerboundLoginStartPacket
import cn.rtast.libmc.protocol.packet.login.*
import cn.rtast.libmc.protocol.packet.play.*
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
import cn.rtast.libmc.protocol.protocol.state.ProtocolStateRegistry
@@ -40,6 +36,7 @@ internal object GameProtocols {
register(0x3d, ClientboundPingPlayPacket)
register(0x41, ClientboundPlayerChatMessagePacket)
register(0x76, ClientboundStartConfigurationPacket)
register(0x79, ClientboundSystemChatMessagePacket)
}
}
@@ -58,6 +55,9 @@ internal object GameProtocols {
register(0x03, ServerboundLoginAcknowledgedPacket)
}
register(ProtocolState.PLAY) {
register(0x00, ClientboundDelimiterPacket)
register(0x01, ClientboundSpawnEntityPacket)
register(0x02, ClientboundEntityAnimationPacket)
register(0x09, ServerboundChatMessagePacket)
register(0x2d, ServerboundPongPlayPacket)
register(0x10, ServerboundConfigurationAcknowledgedPacket)
@@ -27,6 +27,7 @@ public value class Identifier internal constructor(public val raw: String) {
public companion object Codec : PacketCodec<Identifier> {
public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path")
public fun of(full: String): Identifier = Identifier(full)
override fun encode(buffer: BytesBuffer, value: Identifier) {
buffer.writeMcString(value.toString())
@@ -0,0 +1,31 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
import cn.rtast.libmc.common.BytesBuffer
import kotlin.jvm.JvmInline
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Type:Angle
*/
@JvmInline
public value class Angle(public val raw: Byte) {
/**
* convert the Angle(byte) to 360-degree float angles
*/
public fun toDegrees(): Float = (raw.toInt() and 0xFF) * 360.0f / 256.0f
/**
* encode 360-degress float angles into Angle(byte)
*/
public fun toAngleByte(): Byte = ((raw % 360.0f) * 256.0f / 360.0f).toInt().toByte()
}
internal fun BytesBuffer.readAngle(): Angle = Angle(this.readByte())
internal fun BytesBuffer.writeAngle(value: Angle) = this.writeByte(value.raw)
@@ -0,0 +1,24 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
/**
* ref: https://minecraft.wiki/w/Java_Edition_protocol/Packets#Entity_Animation
*/
public enum class Animations(public val animationId: Byte) {
SwingMainArm(0),
LeaveBed(2),
SwingOffhand(3),
CriticalEffect(4),
MagicCriticalEffect(5);
public companion object {
public fun fromID(id: Byte): Animations =
entries.firstOrNull { it.animationId == id } ?: throw IllegalArgumentException("Unknown animation ID")
}
}
@@ -0,0 +1,75 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.types
import cn.rtast.libmc.common.ByteOrder
import cn.rtast.libmc.common.BytesBuffer
import cn.rtast.libmc.common.readVarInt
import cn.rtast.libmc.common.writeVarInt
import kotlin.math.abs
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
public data class LpVec3(
val x: Double,
val y: Double,
val z: Double,
) {
@Suppress("UNUSED")
public companion object {
public val ZERO: LpVec3 = LpVec3(0.0, 0.0, 0.0)
private const val MAX_QUANTIZED_VALUE = 32766.0
private const val CONTINUATION_FLAG = 0x04L
private const val SCALE_BITS = 0x03L
public fun pack(value: Long): Long =
((value * 0.5 + 0.5) * MAX_QUANTIZED_VALUE).toLong()
public fun unpack(value: Long): Double =
min((value and 32767L).toDouble(), MAX_QUANTIZED_VALUE) * 2.0 / MAX_QUANTIZED_VALUE - 1.0
}
}
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
internal fun BytesBuffer.readLpVec3(): LpVec3 {
val byte1 = readByte().toInt() and 0xFF
if (byte1 == 0) return LpVec3.ZERO
val byte2 = readByte().toInt() and 0xFF
val bytes3To6 = readInt(ByteOrder.BIG_ENDIAN).toLong() and 0xFFFFFFFFL
val packed = (bytes3To6 shl 16) or (byte2.toLong() shl 8) or byte1.toLong()
var scaleFactor = byte1.toLong() and 0x03L
if ((byte1.toLong() and 0x04L) != 0L) scaleFactor = scaleFactor or (readVarInt().toLong() shl 2)
val scale = scaleFactor.toDouble()
val x = LpVec3.unpack(packed shr 3) * scale
val y = LpVec3.unpack(packed shr 18) * scale
val z = LpVec3.unpack(packed shr 33) * scale
return LpVec3(x, y, z)
}
// ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3
internal fun BytesBuffer.writeLpVec3(vec3: LpVec3) {
val maxCoordinate = max(abs(vec3.x), max(abs(vec3.y), abs(vec3.z)))
if (maxCoordinate.isNaN() || maxCoordinate < 1.0 / 32766.0) {
writeByte(0x00)
return
}
val scaleFactor = ceil(maxCoordinate).toLong()
val needContinuation = (scaleFactor and 0x03L) != scaleFactor
val packedScale = if (needContinuation) ((scaleFactor and 0x03L) or 0x04L) else scaleFactor
val packedX = LpVec3.pack((vec3.x / scaleFactor.toDouble()).toLong()) shl 3
val packedY = LpVec3.pack((vec3.y / scaleFactor.toDouble()).toLong()) shl 18
val packedZ = LpVec3.pack((vec3.z / scaleFactor.toDouble()).toLong()) shl 33
val packed = packedZ or packedY or packedX or packedScale
writeByte(packed.toByte())
writeByte((packed shr 8).toByte())
writeInt((packed shr 16).toInt(), ByteOrder.BIG_ENDIAN)
if (needContinuation) writeVarInt((scaleFactor shr 2).toInt())
}
@@ -10,6 +10,7 @@ package test
import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.ClientboundDisconnectPlayPacket
import cn.rtast.libmc.protocol.packet.play.ClientboundLoginPlayPacket
import cn.rtast.libmc.protocol.packet.play.ClientboundSystemChatMessagePacket
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
@@ -27,6 +28,9 @@ class TestClient {
cli.on<ClientboundDisconnectPlayPacket> {
println(it)
}
cli.on<ClientboundSystemChatMessagePacket> {
println(it)
}
while (true) {
}
}