diff --git a/docs/Get-started.md b/docs/Get-started.md index eb47ad1..c1afb69 100644 --- a/docs/Get-started.md +++ b/docs/Get-started.md @@ -79,4 +79,54 @@ cli.onSent { client.networkChannel.sendPacket( ServerboundChatCommandPacket(command = "say Hello from libmc") ) +``` + +# Respond velocity and update client motion + +> This part uses math calculations + +When joined to the level(aka `world`), the server will send a packet +`ClientboundSetEntityVelocityPacket` to the client, packet contains a vec3 and entity id, +The client sync this data to the player and sends it to the server during the next tick loop +to inform the server: "Hi, I know my current position; here is the result of my calculations. I'm sending it to you". + +```kotlin +private var entityId = -1 +private var motionX = 0 +private var motionY = 0 +private var motionZ = 0 +private var isOnGround = false + +fun main() { + // Set entity id + client.onPacket { entityId = it.entityId } + + client.onPacket { + if (it.entityId == entityId) { + motionX = it.velocity.x / 8000.0 + motionY = it.velocity.y / 8000.0 + motionZ = it.velocity.z / 8000.0 + if (client.motionY > 0) client.isOnGround = false + } + } + + // Update current position and velocity + client.onTick { + if (client.stateMachine.currentState != ProtocolState.PLAY) return@registerListener + syncPlayerPosition() + } +} + +internal suspend fun syncPlayerPosition() { + position.x += motionX + position.y += motionY + position.z += motionZ + motionX *= 0.91 + motionY *= 0.98 + motionZ *= 0.91 + if (!isOnGround) motionY -= 0.08 else { + if (motionY < 0) motionY = 0.0 + } + networkChannel.sendPacket(ServerboundSetPlayerPositionPacket(position.x, position.y, position.z, isOnGround)) +} ``` \ No newline at end of file diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt index f4d5ebd..b43598b 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt @@ -25,7 +25,7 @@ public class ProtocolContextBuilder(private val onlineMode: Boolean) { if (::authProvider.isInitialized) authProvider else error("authProvider is required in online mode") } else if (::authProvider.isInitialized) authProvider else null, - engine = if (::socketEngine.isInitialized) socketEngine else error("SocketEngine is not configured") + engine = if (::socketEngine.isInitialized) socketEngine else error("SocketEngine is not configured"), ) } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/ClientTickingLoop.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/ClientTickingLoop.kt new file mode 100644 index 0000000..df41ee2 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/ClientTickingLoop.kt @@ -0,0 +1,127 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/9 + */ + +package cn.rtast.libmc.protocol.client + +import cn.rtast.libmc.protocol.crypto.minecraftServerIdHash +import cn.rtast.libmc.protocol.crypto.rsaEncrypt +import cn.rtast.libmc.protocol.packet.configuration.clientbound.* +import cn.rtast.libmc.protocol.packet.configuration.serverbound.* +import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundDisconnectLoginPacket +import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundHelloPacket +import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundLoginSuccessPacket +import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundSetCompressionPacket +import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundKeyPacket +import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundLoginAcknowledgedPacket +import cn.rtast.libmc.protocol.packet.play.clientbound.* +import cn.rtast.libmc.protocol.packet.play.serverbound.* +import cn.rtast.libmc.protocol.protocol.state.ProtocolState +import cn.rtast.libmc.protocol.util.generateRandom16Bytes +import kotlinx.coroutines.* +import kotlin.concurrent.Volatile +import kotlin.math.abs +import kotlin.time.Clock +import kotlin.time.Duration.Companion.milliseconds + +public class ClientTickingLoop internal constructor(private val client: MinecraftClient) { + private var tickJob: Job? = null + private val tickIntervalMs = 50L + public var currentTick: Long = 0L + private set + + @Volatile + public var isRunning: Boolean = false + private set + + private val listeners = mutableListOf Unit>() + internal fun registerListener(action: suspend (Long) -> Unit) = listeners.add(action) + + init { + client.onPacket { handleLoginSuccess() } + client.onPacket { client.networkChannel.setCompression(it.threshold) } + client.onPacket { handleEncryptRequest(it) } + client.onPacket { client.close() } + client.onPacket { client.close() } + client.onPacket { client.close() } + client.onPacket { syncServerTick(it.worldAge) } + client.onPacket { client.networkChannel.sendPacket(ServerboundPongPlayPacket(it.id)) } + client.onPacket { + client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList())) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundAcceptCodeOfConductPacket) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(it.id)) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundPongConfigurationPacket(it.id)) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket) + client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundKeepAliveConfigurationPacket(it.id)) + } + client.onPacket { + client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket) + client.stateMachine.transitionTo(ProtocolState.PLAY) + } + } + + internal suspend fun handleLoginSuccess() { + client.networkChannel.sendPacket(ServerboundLoginAcknowledgedPacket) + client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) + } + + internal suspend fun handleEncryptRequest(packet: ClientboundHelloPacket) { + val sharedSecret = generateRandom16Bytes() + if (client.isOnlineMode) { + val serverHash = minecraftServerIdHash(packet.serverId, sharedSecret, packet.publicKey) + client.protocolContext.authProvider!!.joinServer( + "https://sessionserver.mojang.com/session/minecraft/join", + client.accessToken!!, client.uuid.toString().replace("-", ""), serverHash + ) + } + val encryptedSecret = rsaEncrypt(packet.publicKey, sharedSecret) + val encryptedVerifyToken = rsaEncrypt(packet.publicKey, packet.verifyToken) + client.networkChannel.sendPacket(ServerboundKeyPacket(encryptedSecret, encryptedVerifyToken)) + client.networkChannel.session.enableEncryption(sharedSecret) + } + + internal fun syncServerTick(serverWorldAge: Long) { + if (abs(this.currentTick - serverWorldAge) > 2) this.currentTick = serverWorldAge + } + + internal fun start() { + if (isRunning) return + isRunning = true + tickJob = client.launch(CoroutineName("LibMC-ClientTickingLoop")) { + var nextTickTime = Clock.System.now().toEpochMilliseconds() + while (isActive && isRunning) { + val now = Clock.System.now().toEpochMilliseconds() + if (now >= nextTickTime) { + try { + listeners.forEach { it.invoke(currentTick) } + } catch (e: Exception) { + if (e is CancellationException) throw e + e.printStackTrace() + } + currentTick++ + nextTickTime += tickIntervalMs + if (now - nextTickTime > tickIntervalMs * 5) nextTickTime = now + tickIntervalMs + } else delay((nextTickTime - now).milliseconds) + } + } + } + + internal fun stop() { + isRunning = false + tickJob?.cancel() + tickJob = null + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt index 063d9df..45b39a5 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt @@ -8,8 +8,6 @@ package cn.rtast.libmc.protocol.client import cn.rtast.libmc.crypto.ProtocolContext import cn.rtast.libmc.crypto.ProtocolContextBuilder -import cn.rtast.libmc.protocol.event.InternalPacketDispatcher -import cn.rtast.libmc.protocol.event.PacketEventDispatcher import cn.rtast.libmc.protocol.network.NetworkChannel import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundLoginStartPacket @@ -32,21 +30,26 @@ public class MinecraftClient internal constructor( internal val protocolContext: ProtocolContext, ) : PacketEventDispatcher(), CoroutineScope { internal val stateMachine = ClientStateMachine() - public val networkChannel: NetworkChannel = NetworkChannel( - host, port, stateMachine, - this, protocolContext - ) - - private val internalPacketDispatcher = InternalPacketDispatcher(this) + public val networkChannel: NetworkChannel = NetworkChannel(host, port, stateMachine, this, protocolContext) private val clientJob = SupervisorJob(parentJob) private var listenJob: Job? = null public val isOnlineMode: Boolean = accessToken != null + public val transactionManager: TransactionIdManager = TransactionIdManager() + public val clientTickingLoop: ClientTickingLoop = ClientTickingLoop(this) + + /** + * Register a client ticking event callback. + * NOTE: Blocking operations will **block** the bot thread. + * Using #launch to avoid blocking. + */ + public fun onTick(action: suspend (Long) -> Unit): Unit = run { clientTickingLoop.registerListener(action) } public suspend fun connect(protocolVersion: Int = CURRENT_MINECRAFT_PROTOCOL_VERSION) { networkChannel.connect() startListening() + clientTickingLoop.start() networkChannel.sendPacket( ServerboundHandshakePacket( protocolVersion, @@ -63,7 +66,7 @@ public class MinecraftClient internal constructor( private fun startListening() { listenJob = launch { try { - while (isActive) internalPacketDispatcher.handleIncomingPackets(networkChannel.readNextPacket()) + while (isActive) networkChannel.readNextPacket() } catch (e: Exception) { if (e is CancellationException) throw e if (isActive) { @@ -77,6 +80,7 @@ public class MinecraftClient internal constructor( public fun close() { networkChannel.close() + clientTickingLoop.stop() clientJob.cancel() } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/PacketEventDispatcher.kt similarity index 96% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/PacketEventDispatcher.kt index 0f4c264..f0d70c7 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/PacketEventDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/PacketEventDispatcher.kt @@ -4,7 +4,7 @@ * Date: 2026/9/5 */ -package cn.rtast.libmc.protocol.event +package cn.rtast.libmc.protocol.client import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.protocol.protocol.PacketDirection @@ -14,7 +14,7 @@ import kotlin.reflect.KClass private typealias Handler = suspend (MinecraftPacket) -> Unit private typealias DirectionalHandler = suspend (MinecraftPacket, PacketDirection) -> Unit -public open class PacketEventDispatcher { +public abstract class PacketEventDispatcher { @Volatile @PublishedApi internal var receiveHandlers: Map, List> = emptyMap() diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt deleted file mode 100644 index b7253f2..0000000 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright © 2026 RTAkland - * Author: RTAkland - * Date: 2026/9/5 - */ - - -package cn.rtast.libmc.protocol.event - -import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.protocol.client.MinecraftClient -import cn.rtast.libmc.protocol.crypto.minecraftServerIdHash -import cn.rtast.libmc.protocol.crypto.rsaEncrypt -import cn.rtast.libmc.protocol.packet.configuration.clientbound.* -import cn.rtast.libmc.protocol.packet.configuration.serverbound.* -import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundDisconnectLoginPacket -import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundHelloPacket -import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundLoginSuccessPacket -import cn.rtast.libmc.protocol.packet.login.clientbound.ClientboundSetCompressionPacket -import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundKeyPacket -import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundLoginAcknowledgedPacket -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundDisconnectPlayPacket -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundKeepAlivePlayPacket -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPingPacket -import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStartConfigurationPacket -import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundConfigurationAcknowledgedPacket -import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundKeepAlivePlayPacket -import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundPongPlayPacket -import cn.rtast.libmc.protocol.protocol.state.ProtocolState -import cn.rtast.libmc.protocol.util.generateRandom16Bytes - -/** - * Internal simple state machine trigger, - * Auto respond packets the server needed. - * Only including `Handshake`, `Login` and `Configuration` State - */ -public class InternalPacketDispatcher(private val client: MinecraftClient) { - public suspend fun handleIncomingPackets(packet: MinecraftPacket) { - when (packet) { - // login - is ClientboundDisconnectLoginPacket -> client.close() - is ClientboundSetCompressionPacket -> client.networkChannel.setCompression(packet.threshold) - is ClientboundLoginSuccessPacket -> { - client.networkChannel.sendPacket(ServerboundLoginAcknowledgedPacket) - client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) - } - - is ClientboundHelloPacket -> { - val sharedSecret = generateRandom16Bytes() - if (client.isOnlineMode) { - val serverHash = minecraftServerIdHash(packet.serverId, sharedSecret, packet.publicKey) - client.protocolContext.authProvider!!.joinServer( - "https://sessionserver.mojang.com/session/minecraft/join", - client.accessToken!!, - client.uuid.toString().replace("-", ""), - serverHash - ) - } - val encryptedSecret = rsaEncrypt(packet.publicKey, sharedSecret) - val encryptedVerifyToken = rsaEncrypt(packet.publicKey, packet.verifyToken) - client.networkChannel.sendPacket(ServerboundKeyPacket(encryptedSecret, encryptedVerifyToken)) - client.networkChannel.session.enableEncryption(sharedSecret) - } - - // configuration - ClientboundFinishConfigurationPacket -> { - client.networkChannel.sendPacket(ServerboundAckFinishConfigurationPacket) - client.stateMachine.transitionTo(ProtocolState.PLAY) - } - - is ClientboundKeepAliveConfigurationPacket -> client.networkChannel.sendPacket( - ServerboundKeepAliveConfigurationPacket(packet.id) - ) - - is ClientboundPingConfigurationPacket -> client.networkChannel.sendPacket( - ServerboundPongConfigurationPacket(packet.id) - ) - - is ClientboundSelectKnownPacksPacket -> client.networkChannel.sendPacket( - ServerboundSelectKnownPacksPacket(emptyList()) - ) // TODO empty resource packs list - is ClientboundCodeOfConductPacket -> client.networkChannel.sendPacket(ServerboundAcceptCodeOfConductPacket) - - // play - is ClientboundDisconnectPlayPacket -> client.close() - is ClientboundKeepAlivePlayPacket -> client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(id = packet.id)) - is ClientboundPingPacket -> client.networkChannel.sendPacket(ServerboundPongPlayPacket(packet.id)) - ClientboundStartConfigurationPacket -> { - client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket) - client.stateMachine.transitionTo(ProtocolState.CONFIGURATION) - } - - else -> {} - } - } -} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt index 6f593a0..45ca5c0 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/network/NetworkChannel.kt @@ -14,7 +14,7 @@ import cn.rtast.libmc.packet.writeBuffer import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.writeVarInt import cn.rtast.libmc.protocol.client.ClientStateMachine -import cn.rtast.libmc.protocol.event.PacketEventDispatcher +import cn.rtast.libmc.protocol.client.PacketEventDispatcher import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.clientboundGameProtocols import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.serverboundGameProtocols import cn.rtast.libmc.zlibCompress diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetEntityVelocityPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetEntityVelocityPacket.kt index 2f24891..db81db5 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetEntityVelocityPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetEntityVelocityPacket.kt @@ -11,10 +11,10 @@ import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.protocol.protocol.game.math.LpVec3 +import cn.rtast.libmc.protocol.protocol.game.math.LpVec3d import cn.rtast.libmc.protocol.protocol.game.math.readLpVec3 -public data class ClientboundSetEntityVelocityPacket(val entityId: Int, val velocity: LpVec3) : MinecraftPacket { +public data class ClientboundSetEntityVelocityPacket(val entityId: Int, val velocity: LpVec3d) : MinecraftPacket { internal companion object Codec : PacketCodec { override fun encode(buffer: BytesBuffer, value: ClientboundSetEntityVelocityPacket) {} override fun decode(buffer: BytesBuffer): ClientboundSetEntityVelocityPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSpawnEntityPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSpawnEntityPacket.kt index 1a2f4b6..1acc4f9 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSpawnEntityPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSpawnEntityPacket.kt @@ -13,7 +13,7 @@ import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readUuid import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.protocol.protocol.game.math.Angle -import cn.rtast.libmc.protocol.protocol.game.math.LpVec3 +import cn.rtast.libmc.protocol.protocol.game.math.LpVec3d import cn.rtast.libmc.protocol.protocol.game.math.readAngle import cn.rtast.libmc.protocol.protocol.game.math.readLpVec3 import kotlin.uuid.Uuid @@ -28,7 +28,7 @@ public data class ClientboundSpawnEntityPacket( val x: Double, val y: Double, val z: Double, - val velocity: LpVec3, + val velocity: LpVec3d, val pitch: Angle, val yaw: Angle, val headYaw: Angle, diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundInteractPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundInteractPacket.kt index 4ae677f..22be00d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundInteractPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundInteractPacket.kt @@ -11,14 +11,14 @@ import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.primitives.writeVarInt -import cn.rtast.libmc.protocol.protocol.game.math.LpVec3 +import cn.rtast.libmc.protocol.protocol.game.math.LpVec3d import cn.rtast.libmc.protocol.protocol.game.math.writeLpVec3 import cn.rtast.libmc.protocol.protocol.game.player.Hand public data class ServerboundInteractPacket( val entityId: Int, val hand: Hand, - val targetOffset: LpVec3, + val targetOffset: LpVec3d, val isSneaking: Boolean, ) : MinecraftPacket { internal companion object Codec : PacketCodec { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundSetPlayerPositionPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundSetPlayerPositionPacket.kt index edd4ac2..e3129b3 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundSetPlayerPositionPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundSetPlayerPositionPacket.kt @@ -8,22 +8,25 @@ package cn.rtast.libmc.protocol.packet.play.serverbound import cn.rtast.libmc.network.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.protocol.protocol.game.player.PlayerPositionFlag +import cn.rtast.libmc.packet.PacketCodec public data class ServerboundSetPlayerPositionPacket( val x: Double, val feetY: Double, val z: Double, - val flags: PlayerPositionFlag, + val onGround: Boolean, + val pushingAgainstWall: Boolean = false, ) : MinecraftPacket { internal companion object Codec : PacketCodec { override fun encode(buffer: BytesBuffer, value: ServerboundSetPlayerPositionPacket) { buffer.writeDouble(value.x) buffer.writeDouble(value.feetY) buffer.writeDouble(value.z) - buffer.writeByte(value.flags.flag) + var flags = 0 + if (value.onGround) flags = flags or 0x01 + if (value.pushingAgainstWall) flags = flags or 0x02 + buffer.writeByte(flags.toByte()) } override fun decode(buffer: BytesBuffer): ServerboundSetPlayerPositionPacket = diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3d.kt similarity index 73% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3d.kt index 09659ae..3dbcaf6 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/LpVec3d.kt @@ -7,7 +7,6 @@ package cn.rtast.libmc.protocol.protocol.game.math -import cn.rtast.libmc.network.ByteOrder import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.writeVarInt @@ -17,14 +16,10 @@ 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, -) { +public data class LpVec3d(var x: Double, var y: Double, var z: Double) { @Suppress("UNUSED") public companion object { - public val ZERO: LpVec3 = LpVec3(0.0, 0.0, 0.0) + public val ZERO: LpVec3d = LpVec3d(0.0, 0.0, 0.0) private const val MAX_QUANTIZED_VALUE = 32766.0 private const val CONTINUATION_FLAG = 0x04L @@ -39,23 +34,23 @@ public data class LpVec3( } // ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3 -internal fun BytesBuffer.readLpVec3(): LpVec3 { +internal fun BytesBuffer.readLpVec3(): LpVec3d { val byte1 = readByte().toInt() and 0xFF - if (byte1 == 0) return LpVec3.ZERO + if (byte1 == 0) return LpVec3d.ZERO val byte2 = readByte().toInt() and 0xFF val bytes3To6 = readInt().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) + val x = LpVec3d.unpack(packed shr 3) * scale + val y = LpVec3d.unpack(packed shr 18) * scale + val z = LpVec3d.unpack(packed shr 33) * scale + return LpVec3d(x, y, z) } // ref: https://minecraft.wiki/w/Java_Edition_protocol/Data_types#LpVec3 -internal fun BytesBuffer.writeLpVec3(vec3: LpVec3) { +internal fun BytesBuffer.writeLpVec3(vec3: LpVec3d) { val maxCoordinate = max(abs(vec3.x), max(abs(vec3.y), abs(vec3.z))) if (maxCoordinate.isNaN() || maxCoordinate < 1.0 / 32766.0) { writeByte(0x00) @@ -64,9 +59,9 @@ internal fun BytesBuffer.writeLpVec3(vec3: LpVec3) { 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 packedX = LpVec3d.pack((vec3.x / scaleFactor.toDouble()).toLong()) shl 3 + val packedY = LpVec3d.pack((vec3.y / scaleFactor.toDouble()).toLong()) shl 18 + val packedZ = LpVec3d.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()) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/Vec3d.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/Vec3d.kt index 0e8604a..e87622f 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/Vec3d.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/math/Vec3d.kt @@ -9,7 +9,7 @@ package cn.rtast.libmc.protocol.protocol.game.math import cn.rtast.libmc.network.BytesBuffer -public data class Vec3d(val x: Double, val y: Double, val z: Double) { +public data class Vec3d(var x: Double, var y: Double, var z: Double) { public companion object { public val ZERO: Vec3d = Vec3d(0.0, 0.0, 0.0) } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/InteractType.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/InteractType.kt index 375b145..f916edb 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/InteractType.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/InteractType.kt @@ -7,10 +7,10 @@ package cn.rtast.libmc.protocol.protocol.game.player -import cn.rtast.libmc.protocol.protocol.game.math.LpVec3 +import cn.rtast.libmc.protocol.protocol.game.math.LpVec3d public sealed class InteractType(public val id: Int) { public data class Interact(val hand: Hand) : InteractType(0) public data object Attack : InteractType(1) - public data class InteractAt(val targetOffset: LpVec3, val hand: Hand) : InteractType(2) + public data class InteractAt(val targetOffset: LpVec3d, val hand: Hand) : InteractType(2) } \ No newline at end of file