Fix set player position flag

This commit is contained in:
2026-09-09 21:06:16 +08:00
parent 7bbe99ba01
commit 19697d2022
14 files changed
+222 -139

No files matched your search

+50
View File
@@ -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"),
)
}
@@ -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
}
}
@@ -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()
}
@@ -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()
@@ -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 -> {}
}
}
}
@@ -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
@@ -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 {
@@ -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,
@@ -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> {
@@ -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 =
@@ -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())
@@ -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)
}
@@ -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)
}