Fix set player position flag
This commit is contained in:
14 files changed
+222
-139
No files matched your search
@@ -80,3 +80,53 @@ 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<ClientboundLoginPlayPacket> { entityId = it.entityId }
|
||||
|
||||
client.onPacket<ClientboundSetEntityVelocityPacket> {
|
||||
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))
|
||||
}
|
||||
```
|
||||
@@ -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"),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+127
@@ -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<suspend (Long) -> Unit>()
|
||||
internal fun registerListener(action: suspend (Long) -> Unit) = listeners.add(action)
|
||||
|
||||
init {
|
||||
client.onPacket<ClientboundLoginSuccessPacket> { handleLoginSuccess() }
|
||||
client.onPacket<ClientboundSetCompressionPacket> { client.networkChannel.setCompression(it.threshold) }
|
||||
client.onPacket<ClientboundHelloPacket> { handleEncryptRequest(it) }
|
||||
client.onPacket<ClientboundDisconnectLoginPacket> { client.close() }
|
||||
client.onPacket<ClientboundDisconnectPlayPacket> { client.close() }
|
||||
client.onPacket<ClientboundDisconnectConfigurationPacket> { client.close() }
|
||||
client.onPacket<ClientboundSetTimePacket> { syncServerTick(it.worldAge) }
|
||||
client.onPacket<ClientboundPingPacket> { client.networkChannel.sendPacket(ServerboundPongPlayPacket(it.id)) }
|
||||
client.onPacket<ClientboundSelectKnownPacksPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundSelectKnownPacksPacket(emptyList()))
|
||||
}
|
||||
client.onPacket<ClientboundCodeOfConductPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundAcceptCodeOfConductPacket)
|
||||
}
|
||||
client.onPacket<ClientboundKeepAlivePlayPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundKeepAlivePlayPacket(it.id))
|
||||
}
|
||||
client.onPacket<ClientboundPingConfigurationPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundPongConfigurationPacket(it.id))
|
||||
}
|
||||
client.onPacket<ClientboundStartConfigurationPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundConfigurationAcknowledgedPacket)
|
||||
client.stateMachine.transitionTo(ProtocolState.CONFIGURATION)
|
||||
}
|
||||
client.onPacket<ClientboundKeepAliveConfigurationPacket> {
|
||||
client.networkChannel.sendPacket(ServerboundKeepAliveConfigurationPacket(it.id))
|
||||
}
|
||||
client.onPacket<ClientboundFinishConfigurationPacket> {
|
||||
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
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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<KClass<out MinecraftPacket>, List<Handler>> = emptyMap()
|
||||
-96
@@ -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 -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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<ClientboundSetEntityVelocityPacket> {
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundSetEntityVelocityPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundSetEntityVelocityPacket {
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+2
-2
@@ -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<ServerboundInteractPacket> {
|
||||
|
||||
+7
-4
@@ -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<ServerboundSetPlayerPositionPacket> {
|
||||
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 =
|
||||
|
||||
+12
-17
@@ -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())
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user