From 94cca61df1787e1d3c52939bd2e9c3afa7f624c0 Mon Sep 17 00:00:00 2001 From: RTAkland Date: Sat, 5 Sep 2026 18:19:58 +0800 Subject: [PATCH] Added some packets --- .../kotlin/cn/rtast/libmc/common/buffer.kt | 5 +- .../kotlin/cn/rtast/libmc/common/channels.kt | 2 - .../kotlin/cn/rtast/libmc/common/socket.kt | 4 +- .../cn/rtast/libmc/common/buffer.jvm.kt | 25 ++++++- .../cn/rtast/libmc/common/channels.jvm.kt | 2 - .../cn/rtast/libmc/common/socket.jvm.kt | 4 +- .../cn/rtast/libmc/common/buffer.native.kt | 40 +++++----- .../cn/rtast/libmc/common/channels.native.kt | 2 - .../cn/rtast/libmc/common/socket.native.kt | 4 +- .../libmc/mcping/bedrock/packet_writer.kt | 4 +- .../libmc/mcping/bedrock/ping_bedrock.kt | 4 +- .../client/InternalPacketDispatcher.kt | 2 + .../packet/play/ClientboundDelimiterPacket.kt | 18 +++++ .../play/ClientboundEntityAnimationPacket.kt | 28 +++++++ .../play/ClientboundSpawnEntityPacket.kt | 57 ++++++++++++++ .../ClientboundSystemChatMessagePacket.kt | 25 +++++++ .../libmc/protocol/protocol/GameProtocols.kt | 10 +-- .../protocol/protocol/game/Identifier.kt | 1 + .../cn/rtast/libmc/protocol/types/Angle.kt | 31 ++++++++ .../rtast/libmc/protocol/types/Animations.kt | 24 ++++++ .../cn/rtast/libmc/protocol/types/LpVec3.kt | 75 +++++++++++++++++++ .../src/commonTest/kotlin/test/TestClient.kt | 4 + 22 files changed, 326 insertions(+), 45 deletions(-) create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDelimiterPacket.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundEntityAnimationPacket.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSpawnEntityPacket.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSystemChatMessagePacket.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Angle.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Animations.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/LpVec3.kt diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/buffer.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/buffer.kt index 3fd00f0..c429459 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/buffer.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/buffer.kt @@ -9,7 +9,6 @@ package cn.rtast.libmc.common import kotlin.uuid.Uuid -@Suppress("CLASSNAME") public expect class BytesBuffer { public constructor() public constructor(bytes: ByteArray) @@ -18,6 +17,8 @@ public expect class BytesBuffer { public fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN) + public fun writeDouble(value: Double, endian: ByteOrder = ByteOrder.BIG_ENDIAN) + public fun writeFloat(value: Float, endian: ByteOrder = ByteOrder.BIG_ENDIAN) public fun writeBytes(bytes: ByteArray) public fun writeBoolean(value: Boolean) @@ -25,6 +26,8 @@ public expect class BytesBuffer { public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long + public fun readDouble(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Double + public fun readFloat(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Float public fun readBytes(length: Int): ByteArray public fun readBoolean(): Boolean public fun readRemainingBytes(): ByteArray diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/channels.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/channels.kt index b65b4c3..b952056 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/channels.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/channels.kt @@ -7,7 +7,6 @@ package cn.rtast.libmc.common -@Suppress("CLASSNAME") public expect class ReadChannel { public fun readByte(): Byte public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short @@ -17,7 +16,6 @@ public expect class ReadChannel { public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size) } -@Suppress("CLASSNAME") public expect class WriteChannel { public fun writeFully(value: ByteArray, startIndex: Int = 0, endIndex: Int = value.size) public fun flush() diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/socket.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/socket.kt index ae2cf12..8dca9cc 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/socket.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/common/socket.kt @@ -7,15 +7,13 @@ package cn.rtast.libmc.common -@Suppress("CLASSNAME") public expect class Socket public constructor(host: String, port: Int, context: LibMCContext) { public fun openReadChannel(): ReadChannel public fun openWriteChannel(): WriteChannel public fun close() } -@Suppress("CLASSNAME") -public expect class _UdpSocket public constructor(host: String, port: Int, context: LibMCContext) { +public expect class UdpSocket public constructor(host: String, port: Int, context: LibMCContext) { public fun sendAndReceive(data: ByteArray): ByteArray public fun close() } \ No newline at end of file diff --git a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/buffer.jvm.kt b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/buffer.jvm.kt index ce813a5..0f8e000 100644 --- a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/buffer.jvm.kt +++ b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/buffer.jvm.kt @@ -8,7 +8,6 @@ package cn.rtast.libmc.common import java.io.ByteArrayOutputStream -@Suppress("CLASSNAME") public actual class BytesBuffer { private val outStream = ByteArrayOutputStream() private var readBuffer: ByteArray? = null @@ -26,6 +25,7 @@ public actual class BytesBuffer { } public actual fun writeShort(value: Short, endian: ByteOrder) { + readBuffer = null val v = value.toInt() if (endian == ByteOrder.BIG_ENDIAN) { outStream.write(v shr 8) @@ -37,6 +37,7 @@ public actual class BytesBuffer { } public actual fun writeInt(value: Int, endian: ByteOrder) { + readBuffer = null if (endian == ByteOrder.BIG_ENDIAN) { outStream.write(value shr 24) outStream.write(value shr 16) @@ -51,6 +52,7 @@ public actual class BytesBuffer { } public actual fun writeLong(value: Long, endian: ByteOrder) { + readBuffer = null if (endian == ByteOrder.BIG_ENDIAN) { for (i in 56 downTo 0 step 8) outStream.write((value shr i).toInt()) } else { @@ -58,6 +60,14 @@ public actual class BytesBuffer { } } + public actual fun writeDouble(value: Double, endian: ByteOrder) { + writeLong(value.toRawBits(), endian) + } + + public actual fun writeFloat(value: Float, endian: ByteOrder) { + writeInt(value.toRawBits(), endian) + } + public actual fun writeBytes(bytes: ByteArray) { readBuffer = null outStream.write(bytes) @@ -83,6 +93,15 @@ public actual class BytesBuffer { public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian) public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian) public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian) + + public actual fun readDouble(endian: ByteOrder): Double { + return Double.fromBits(readLong(endian)) + } + + public actual fun readFloat(endian: ByteOrder): Float { + return Float.fromBits(readInt(endian)) + } + public actual fun readBytes(length: Int): ByteArray { val array = ensureReadArray() if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow") @@ -96,7 +115,9 @@ public actual class BytesBuffer { public actual fun readRemainingBytes(): ByteArray { val array = ensureReadArray() if (readOffset >= array.size) return byteArrayOf() - return array.copyOfRange(readOffset, array.size) + val result = array.copyOfRange(readOffset, array.size) + readOffset = array.size + return result } public actual fun toByteArray(): ByteArray = outStream.toByteArray() diff --git a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/channels.jvm.kt b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/channels.jvm.kt index cb16681..5e1e5ad 100644 --- a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/channels.jvm.kt +++ b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/channels.jvm.kt @@ -11,7 +11,6 @@ import java.io.EOFException import java.io.InputStream import java.io.OutputStream -@Suppress("CLASSNAME") public actual class ReadChannel(private val _inputStream: InputStream) { public actual fun readByte(): Byte = _inputStream.read().toByte() @@ -32,7 +31,6 @@ public actual class ReadChannel(private val _inputStream: InputStream) { public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian) } -@Suppress("CLASSNAME") public actual class WriteChannel { private val _outputStream: OutputStream diff --git a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/socket.jvm.kt b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/socket.jvm.kt index 8172de1..74b900f 100644 --- a/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/socket.jvm.kt +++ b/libmc-common/src/jvmMain/kotlin/cn/rtast/libmc/common/socket.jvm.kt @@ -11,7 +11,6 @@ import java.net.DatagramSocket import java.net.InetSocketAddress import java.net.Socket as JvmSocket -@Suppress("CLASSNAME") public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) { private val socket = JvmSocket(host, port) @@ -20,8 +19,7 @@ public actual class Socket public actual constructor(host: String, port: Int, co public actual fun close(): Unit = socket.close() } -@Suppress("CLASSNAME") -public actual class _UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) { +public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) { private val socket = DatagramSocket().apply { soTimeout = 3000 connect(InetSocketAddress(host, port)) diff --git a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/buffer.native.kt b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/buffer.native.kt index e11658c..92ea372 100644 --- a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/buffer.native.kt +++ b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/buffer.native.kt @@ -6,12 +6,10 @@ package cn.rtast.libmc.common -import io.ktor.utils.io.bits.* import io.ktor.utils.io.core.* +import kotlinx.io.* import kotlinx.io.Buffer -import kotlinx.io.readByteArray -@Suppress("CLASSNAME") public actual class BytesBuffer { private val _delegateBuf: Buffer @@ -26,37 +24,43 @@ public actual class BytesBuffer { public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value) public actual fun writeShort(value: Short, endian: ByteOrder) { if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value) - else _delegateBuf.writeShort(value.reverseByteOrder()) + else _delegateBuf.writeShortLe(value) } public actual fun writeInt(value: Int, endian: ByteOrder) { if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value) - else _delegateBuf.writeInt(value.reverseByteOrder()) + else _delegateBuf.writeIntLe(value) } public actual fun writeLong(value: Long, endian: ByteOrder) { if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value) - else _delegateBuf.writeLong(value.reverseByteOrder()) + else _delegateBuf.writeLongLe(value) } + public actual fun writeDouble(value: Double, endian: ByteOrder): Unit = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeDouble(value) else _delegateBuf.writeDoubleLe(value) + + public actual fun writeFloat(value: Float, endian: ByteOrder): Unit = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeFloat(value) else _delegateBuf.writeFloatLe(value) + public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes) public actual fun writeBoolean(value: Boolean): Unit = _delegateBuf.writeByte(if (value) 0x01 else 0x00) public actual fun readByte(): Byte = _delegateBuf.readByte() - public actual fun readShort(endian: ByteOrder): Short { - val v = _delegateBuf.readShort() - return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder() - } + public actual fun readShort(endian: ByteOrder): Short = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readShort() else _delegateBuf.readShortLe() - public actual fun readInt(endian: ByteOrder): Int { - val v = _delegateBuf.readInt() - return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder() - } + public actual fun readInt(endian: ByteOrder): Int = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readInt() else _delegateBuf.readIntLe() - public actual fun readLong(endian: ByteOrder): Long { - val v = _delegateBuf.readLong() - return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder() - } + public actual fun readLong(endian: ByteOrder): Long = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readLong() else _delegateBuf.readLongLe() + + public actual fun readDouble(endian: ByteOrder): Double = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readDouble() else _delegateBuf.readDoubleLe() + + public actual fun readFloat(endian: ByteOrder): Float = + if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.readFloat() else _delegateBuf.readFloatLe() public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length) public actual fun readBoolean(): Boolean = _delegateBuf.readByte() != 0x00.toByte() diff --git a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/channels.native.kt b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/channels.native.kt index 65b78f2..48b9e06 100644 --- a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/channels.native.kt +++ b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/channels.native.kt @@ -10,7 +10,6 @@ import io.ktor.utils.io.* import io.ktor.utils.io.bits.reverseByteOrder import kotlinx.coroutines.runBlocking -@Suppress("CLASSNAME") public actual class ReadChannel(private val _readChannel: ByteReadChannel) { public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() } @@ -34,7 +33,6 @@ public actual class ReadChannel(private val _readChannel: ByteReadChannel) { } } -@Suppress("CLASSNAME") public actual class WriteChannel { private val _writeChannel: ByteWriteChannel diff --git a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/socket.native.kt b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/socket.native.kt index 1375b72..8dab80b 100644 --- a/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/socket.native.kt +++ b/libmc-common/src/nativeMain/kotlin/cn/rtast/libmc/common/socket.native.kt @@ -11,7 +11,6 @@ import io.ktor.utils.io.core.* import kotlinx.coroutines.runBlocking import kotlinx.io.readByteArray -@Suppress("CLASSNAME") public actual class Socket public actual constructor(host: String, port: Int, context: LibMCContext) { private val ctx = context private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) } @@ -26,8 +25,7 @@ public actual class Socket public actual constructor(host: String, port: Int, co } } -@Suppress("CLASSNAME") -public actual class _UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) { +public actual class UdpSocket public actual constructor(host: String, port: Int, context: LibMCContext) { private val ctx = context // use bind to create an unconnected socket diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/packet_writer.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/packet_writer.kt index 672fad7..82515a7 100644 --- a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/packet_writer.kt +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/packet_writer.kt @@ -9,10 +9,10 @@ package cn.rtast.libmc.mcping.bedrock import cn.rtast.libmc.common.PacketCodec import cn.rtast.libmc.common.BytesBuffer -import cn.rtast.libmc.common._UdpSocket +import cn.rtast.libmc.common.UdpSocket -internal fun _UdpSocket.sendPacket(packet: T, codec: PacketCodec): ByteArray { +internal fun UdpSocket.sendPacket(packet: T, codec: PacketCodec): ByteArray { val buf = BytesBuffer() codec.encode(buf, packet) return sendAndReceive(buf.toByteArray()) diff --git a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt index e20748e..a68781c 100644 --- a/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt +++ b/libmc-mcping/src/commonMain/kotlin/cn/rtast/libmc/mcping/bedrock/ping_bedrock.kt @@ -8,13 +8,13 @@ package cn.rtast.libmc.mcping.bedrock import cn.rtast.libmc.common.LibMCContext -import cn.rtast.libmc.common._UdpSocket +import cn.rtast.libmc.common.UdpSocket import cn.rtast.libmc.common.wrap import cn.rtast.libmc.mcping.PingResponse import kotlin.time.Clock internal fun pingBedrockServer(host: String, port: Int, context: LibMCContext): PingResponse { - val socket = _UdpSocket(host, port, context) + val socket = UdpSocket(host, port, context) return try { val sendTime = Clock.System.now().toEpochMilliseconds() val requestPacket = BedrockRequestPacket(sendTime) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt index 0287c97..ce94cde 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/InternalPacketDispatcher.kt @@ -88,6 +88,8 @@ internal class InternalPacketDispatcher(private val client: MinecraftClient) { client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket) client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) } + + is ClientboundSystemChatMessagePacket -> {} } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDelimiterPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDelimiterPacket.kt new file mode 100644 index 0000000..9007171 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundDelimiterPacket.kt @@ -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 { + override fun encode(buffer: BytesBuffer, value: ClientboundDelimiterPacket) {} + override fun decode(buffer: BytesBuffer): ClientboundDelimiterPacket { + return ClientboundDelimiterPacket + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundEntityAnimationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundEntityAnimationPacket.kt new file mode 100644 index 0000000..db33c49 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundEntityAnimationPacket.kt @@ -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 { + 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) + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSpawnEntityPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSpawnEntityPacket.kt new file mode 100644 index 0000000..aa034c4 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSpawnEntityPacket.kt @@ -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 { + 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 + ) + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSystemChatMessagePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSystemChatMessagePacket.kt new file mode 100644 index 0000000..9d01475 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/ClientboundSystemChatMessagePacket.kt @@ -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 { + 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) + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt index 052b0fa..f1d6dcd 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt @@ -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) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt index 967ee44..3c02c03 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/Identifier.kt @@ -27,6 +27,7 @@ public value class Identifier internal constructor(public val raw: String) { public companion object Codec : PacketCodec { 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()) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Angle.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Angle.kt new file mode 100644 index 0000000..f9ff414 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Angle.kt @@ -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) \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Animations.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Animations.kt new file mode 100644 index 0000000..2912db5 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/Animations.kt @@ -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") + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/LpVec3.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/LpVec3.kt new file mode 100644 index 0000000..4b2f551 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/types/LpVec3.kt @@ -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()) +} \ No newline at end of file diff --git a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt index c5bf604..1e33c18 100644 --- a/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt +++ b/libmc-protocol/src/commonTest/kotlin/test/TestClient.kt @@ -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 { println(it) } + cli.on { + println(it) + } while (true) { } }