Implemented 0x2F-ClientboundLevelParticle, 0x4B-ClientboundRecipeBookRemovePacket, 0x4C-ClientboundRecipeBookSettingsPacket packets
This commit is contained in:
21 files changed
+521
-49
No files matched your search
@@ -167,7 +167,7 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
|
||||
- [x] `0x2C` Keep Alive (`ClientboundKeepAlivePlayPacket`)
|
||||
- [ ] `0x2D` Level Chunk Update With Light (`ClientboundLevelChunkUpdateWithLightPacket`)
|
||||
- [x] `0x2E` Level Event (`ClientboundLevelEventPacket`)
|
||||
- [ ] `0x2F` Particle (`ClientboundParticlePacket`)
|
||||
- [x] `0x2F` Particle (`ClientboundLevelParticlePacket`) **NOTE: Slot data are parsed as raw ByteArray**
|
||||
- [ ] `0x30` Light Update (`ClientboundLightUpdatePacket`)
|
||||
- [x] `0x31` Login Play (`ClientboundLoginPlayPacket`)
|
||||
- [x] `0x32` Low Disk Space Warning (`ClientboundLowDiskSpaceWarningPacket`)
|
||||
@@ -195,8 +195,8 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
|
||||
- [x] `0x48` Synchronize Player Position (`ClientboundSynchronizePlayerPositionPacket`)
|
||||
- [x] `0x49` Player Rotation (`ClientboundPlayerRotationPacket`)
|
||||
- [ ] `0x4A` Recipe Book Add (`ClientboundRecipeBookAddPacket`)
|
||||
- [ ] `0x4B` Recipe Book Remove (`ClientboundRecipeBookRemovePacket`)
|
||||
- [ ] `0x4C` Recipe Book Settings (`ClientboundRecipeBookSettingsPacket`)
|
||||
- [x] `0x4B` Recipe Book Remove (`ClientboundRecipeBookRemovePacket`)
|
||||
- [x] `0x4C` Recipe Book Settings (`ClientboundRecipeBookSettingsPacket`)
|
||||
- [x] `0x4D` Remove Entities (`ClientboundRemoveEntitiesPacket`)
|
||||
- [x] `0x4E` Remove Entity Effect (`ClientboundRemoveEntityEffectPacket`)
|
||||
- [x] `0x4F` Reset Score (`ClientboundResetScorePacket`)
|
||||
|
||||
@@ -42,7 +42,6 @@ public fun BytesBuffer.writePrefixedStringArray(value: List<String>) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
public inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List<T> {
|
||||
val count = this.readVarInt()
|
||||
require(count in 0..4096) {
|
||||
@@ -57,4 +56,14 @@ public inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): Lis
|
||||
public inline fun <T> BytesBuffer.writePrefixed(list: List<T>, writer: BytesBuffer.(T) -> Unit) {
|
||||
this.writeVarInt(list.size)
|
||||
for (item in list) this.writer(item)
|
||||
}
|
||||
|
||||
public fun <T> BytesBuffer.readPrefixOptional(reader: BytesBuffer.() -> T): T? {
|
||||
val hasValue = readBoolean()
|
||||
return if (hasValue) reader() else null
|
||||
}
|
||||
|
||||
public fun <T> BytesBuffer.writePrefixedOptional(value: T?, writer: BytesBuffer.(T) -> Unit) {
|
||||
writeBoolean(value != null)
|
||||
if (value != null) writer(value)
|
||||
}
|
||||
File renamed without changes.
+2
-1
@@ -10,6 +10,7 @@ package cn.rtast.libmc.protocol.packet.play.clientbound
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import cn.rtast.libmc.packet.PacketCodec
|
||||
import cn.rtast.libmc.packet.MinecraftPacket
|
||||
import cn.rtast.libmc.primitives.readPrefixOptional
|
||||
import cn.rtast.libmc.primitives.readVarInt
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.Vec3d
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.readVec3d
|
||||
@@ -30,7 +31,7 @@ public data class ClientboundDamageEventPacket(
|
||||
val sourceCauseId = if (rawCauseId > 0) rawCauseId - 1 else null
|
||||
val rawDirectId = buffer.readVarInt()
|
||||
val sourceDirectId = if (rawDirectId > 0) rawDirectId - 1 else null
|
||||
val position = buffer.readVec3d(true)
|
||||
val position = buffer.readPrefixOptional { readVec3d() }
|
||||
return ClientboundDamageEventPacket(entityId, sourceTypeId, sourceCauseId, sourceDirectId, position)
|
||||
}
|
||||
}
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.packet.play.clientbound
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import cn.rtast.libmc.packet.MinecraftPacket
|
||||
import cn.rtast.libmc.packet.PacketCodec
|
||||
import cn.rtast.libmc.primitives.readVarInt
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.Vec3d
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.Vec3f
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.readVec3d
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.readVec3f
|
||||
import cn.rtast.libmc.protocol.protocol.game.particle.ParticleData
|
||||
import cn.rtast.libmc.protocol.protocol.game.particle.ParticleType
|
||||
import cn.rtast.libmc.protocol.protocol.game.particle.readParticleData
|
||||
|
||||
public data class ClientboundLevelParticlePacket(
|
||||
val longDistance: Boolean,
|
||||
val alwaysVisible: Boolean,
|
||||
val position: Vec3d,
|
||||
val offset: Vec3f,
|
||||
val maxSpeed: Float,
|
||||
val particleCount: Int,
|
||||
val particleId: Int,
|
||||
val particleType: ParticleType,
|
||||
val data: ParticleData,
|
||||
) : MinecraftPacket {
|
||||
internal companion object Codec : PacketCodec<ClientboundLevelParticlePacket> {
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundLevelParticlePacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundLevelParticlePacket {
|
||||
val longDistance = buffer.readBoolean()
|
||||
val alwaysVisible = buffer.readBoolean()
|
||||
val position = buffer.readVec3d()
|
||||
val offset = buffer.readVec3f()
|
||||
val maxSpeed = buffer.readFloat()
|
||||
val particleCount = buffer.readInt()
|
||||
val particleId = buffer.readVarInt()
|
||||
val particleType = ParticleType.fromID(particleId)
|
||||
val data = buffer.readParticleData(particleType)
|
||||
return ClientboundLevelParticlePacket(
|
||||
longDistance, alwaysVisible, position,
|
||||
offset, maxSpeed, particleCount,
|
||||
particleId, particleType, data
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ public data class ClientboundMoveVehiclePacket(val position: Vec3d, val yaw: Flo
|
||||
internal companion object Codec : PacketCodec<ClientboundMoveVehiclePacket> {
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundMoveVehiclePacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundMoveVehiclePacket {
|
||||
val position = buffer.readVec3d()!!
|
||||
val position = buffer.readVec3d()
|
||||
val yaw = buffer.readFloat()
|
||||
val pitch = buffer.readFloat()
|
||||
return ClientboundMoveVehiclePacket(position, yaw, pitch)
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ public data class ClientboundPlayerLookAtPacket(
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundPlayerLookAtPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundPlayerLookAtPacket {
|
||||
val fromAnchor = AnchorPoint.fromID(buffer.readVarInt())
|
||||
val targetPosition = buffer.readVec3d()!!
|
||||
val targetPosition = buffer.readVec3d()
|
||||
val isEntity = buffer.readBoolean()
|
||||
val entityTarget = if (isEntity) {
|
||||
val entityId = buffer.readVarInt()
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.packet.play.clientbound
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import cn.rtast.libmc.packet.MinecraftPacket
|
||||
import cn.rtast.libmc.packet.PacketCodec
|
||||
import cn.rtast.libmc.primitives.readPrefixed
|
||||
import cn.rtast.libmc.primitives.readVarInt
|
||||
|
||||
public data class ClientboundRecipeBookRemovePacket(val recipes: List<Int>) : MinecraftPacket {
|
||||
internal companion object Codec : PacketCodec<ClientboundRecipeBookRemovePacket> {
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundRecipeBookRemovePacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundRecipeBookRemovePacket {
|
||||
return ClientboundRecipeBookRemovePacket(buffer.readPrefixed { readVarInt() })
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.packet.play.clientbound
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import cn.rtast.libmc.packet.MinecraftPacket
|
||||
import cn.rtast.libmc.packet.PacketCodec
|
||||
|
||||
public data class ClientboundRecipeBookSettingsPacket(
|
||||
val craftingBookOpen: Boolean,
|
||||
val craftingFilterActive: Boolean,
|
||||
val smeltingBookOpen: Boolean,
|
||||
val smeltingFilterActive: Boolean,
|
||||
val blastFurnaceBookOpen: Boolean,
|
||||
val blastFurnaceFilterActive: Boolean,
|
||||
val smokerBookOpen: Boolean,
|
||||
val smokerFilterActive: Boolean,
|
||||
) : MinecraftPacket {
|
||||
internal companion object Codec : PacketCodec<ClientboundRecipeBookSettingsPacket> {
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundRecipeBookSettingsPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundRecipeBookSettingsPacket {
|
||||
val craftingBookOpen = buffer.readBoolean()
|
||||
val craftingFilterActive = buffer.readBoolean()
|
||||
val smeltingBookOpen = buffer.readBoolean()
|
||||
val smeltingFilterActive = buffer.readBoolean()
|
||||
val blastFurnaceBookOpen = buffer.readBoolean()
|
||||
val blastFurnaceFilterActive = buffer.readBoolean()
|
||||
val smokerBookOpen = buffer.readBoolean()
|
||||
val smokerFilterActive = buffer.readBoolean()
|
||||
return ClientboundRecipeBookSettingsPacket(
|
||||
craftingBookOpen, craftingFilterActive,
|
||||
smeltingBookOpen, smeltingFilterActive,
|
||||
blastFurnaceBookOpen, blastFurnaceFilterActive,
|
||||
smokerBookOpen, smokerFilterActive
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -27,8 +27,8 @@ public data class ClientboundSynchronizePlayerPositionPacket(
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundSynchronizePlayerPositionPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundSynchronizePlayerPositionPacket {
|
||||
val teleportId = buffer.readVarInt()
|
||||
val position = buffer.readVec3d()!!
|
||||
val velocity = buffer.readVec3d()!!
|
||||
val position = buffer.readVec3d()
|
||||
val velocity = buffer.readVec3d()
|
||||
val yaw = buffer.readFloat()
|
||||
val pitch = buffer.readFloat()
|
||||
val rawFlags = buffer.readInt()
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ public data class ClientboundSynchronizeVehiclePositionPacket(
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundSynchronizeVehiclePositionPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundSynchronizeVehiclePositionPacket {
|
||||
val entityId = buffer.readVarInt()
|
||||
val position = buffer.readVec3d()!!
|
||||
val velocityPosition = buffer.readVec3d()!!
|
||||
val position = buffer.readVec3d()
|
||||
val velocityPosition = buffer.readVec3d()
|
||||
val yaw = buffer.readFloat()
|
||||
val pitch = buffer.readFloat()
|
||||
val flags = TeleportFlags.fromInt(buffer.readInt())
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ public data class ClientboundTeleportEntityPacket(
|
||||
override fun encode(buffer: BytesBuffer, value: ClientboundTeleportEntityPacket) {}
|
||||
override fun decode(buffer: BytesBuffer): ClientboundTeleportEntityPacket {
|
||||
val entityId = buffer.readVarInt()
|
||||
val position = buffer.readVec3d()!!
|
||||
val velocityPosition = buffer.readVec3d()!!
|
||||
val position = buffer.readVec3d()
|
||||
val velocityPosition = buffer.readVec3d()
|
||||
val yaw = buffer.readFloat()
|
||||
val pitch = buffer.readFloat()
|
||||
val onGround = buffer.readBoolean()
|
||||
|
||||
+3
-3
@@ -107,7 +107,7 @@ internal object GamePacketsProtocolCodec {
|
||||
register(0x2C, ClientboundKeepAlivePlayPacket)
|
||||
// register(0x2D, ClientboundLevelChunkUpdateWithLightPacket)
|
||||
register(0x2E, ClientboundLevelEventPacket)
|
||||
// register(0x2F, ClientboundParticlePacket)
|
||||
register(0x2F, ClientboundLevelParticlePacket)
|
||||
// register(0x30, ClientboundLightUpdatePacket)
|
||||
register(0x31, ClientboundLoginPlayPacket)
|
||||
register(0x32, ClientboundLowDiskSpaceWarningPacket)
|
||||
@@ -135,8 +135,8 @@ internal object GamePacketsProtocolCodec {
|
||||
register(0x48, ClientboundSynchronizePlayerPositionPacket)
|
||||
register(0x49, ClientboundPlayerRotationPacket)
|
||||
// register(0x4A, ClientboundRecipeBookAddPacket)
|
||||
// register(0x4B, ClientboundRecipeBookRemovePacket)
|
||||
// register(0x4C, ClientboundRecipeBookSettingsPacket)
|
||||
register(0x4B, ClientboundRecipeBookRemovePacket)
|
||||
register(0x4C, ClientboundRecipeBookSettingsPacket)
|
||||
register(0x4D, ClientboundRemoveEntitiesPacket)
|
||||
register(0x4E, ClientboundRemoveEntityEffectPacket)
|
||||
register(0x4F, ClientboundResetScorePacket)
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.protocol.game.color
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
@JvmInline
|
||||
public value class Color24(public val rawValue: Int) {
|
||||
public val rgb: Int get() = rawValue and 0xFFFFFF
|
||||
public val red: Int get() = (rgb shr 16) and 0xFF
|
||||
public val green: Int get() = (rgb shr 8) and 0xFF
|
||||
public val blue: Int get() = rgb and 0xFF
|
||||
|
||||
public val redFloat: Float get() = red / 255.0f
|
||||
public val greenFloat: Float get() = green / 255.0f
|
||||
public val blueFloat: Float get() = blue / 255.0f
|
||||
|
||||
override fun toString(): String = "Color24(r=$red, g=$green, b=$blue, hex=0x${rgb.toString(16).padStart(6, '0')})"
|
||||
|
||||
public companion object {
|
||||
public val BLACK: Color24 = Color24(0x000000)
|
||||
public val WHITE: Color24 = Color24(0xFFFFFF)
|
||||
public val RED: Color24 = Color24(0xFF0000)
|
||||
public val GREEN: Color24 = Color24(0x00FF00)
|
||||
public val BLUE: Color24 = Color24(0x0000FF)
|
||||
|
||||
public fun fromRGB(red: Int, green: Int, blue: Int): Color24 {
|
||||
val r = red.coerceIn(0, 255)
|
||||
val g = green.coerceIn(0, 255)
|
||||
val b = blue.coerceIn(0, 255)
|
||||
return Color24((r shl 16) or (g shl 8) or b)
|
||||
}
|
||||
|
||||
public fun fromRGBFloat(red: Float, green: Float, blue: Float): Color24 {
|
||||
return fromRGB((red * 255.0f).toInt(), (green * 255.0f).toInt(), (blue * 255.0f).toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.readColor24(): Color24 = Color24(readInt())
|
||||
internal fun BytesBuffer.writeColor24(color: Color24) = writeInt(color.rawValue)
|
||||
+2
-2
@@ -22,8 +22,8 @@ public data class MinecartStep(
|
||||
)
|
||||
|
||||
internal fun BytesBuffer.readMinecartStep(): MinecartStep {
|
||||
val position = readVec3d()!!
|
||||
val velocity = readVec3d()!!
|
||||
val position = readVec3d()
|
||||
val velocity = readVec3d()
|
||||
val yaw = readAngle()
|
||||
val pitch = readAngle()
|
||||
val weight = readFloat()
|
||||
|
||||
+5
-11
@@ -15,21 +15,15 @@ public data class Vec3d(val x: Double, val y: Double, val z: Double) {
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.readVec3d(optional: Boolean = false): Vec3d? {
|
||||
if (optional && !this.readBoolean()) return null
|
||||
internal fun BytesBuffer.readVec3d(): Vec3d {
|
||||
val x = this.readDouble()
|
||||
val y = this.readDouble()
|
||||
val z = this.readDouble()
|
||||
return Vec3d(x, y, z)
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.writeVec3d(vec3d: Vec3d?, optional: Boolean = false) {
|
||||
if (optional) {
|
||||
this.writeBoolean(vec3d != null)
|
||||
if (vec3d == null) return
|
||||
}
|
||||
val value = requireNotNull(vec3d) { "Vec3d cannot be null when optional is false" }
|
||||
this.writeDouble(value.x)
|
||||
this.writeDouble(value.y)
|
||||
this.writeDouble(value.z)
|
||||
internal fun BytesBuffer.writeVec3d(vec3d: Vec3d) {
|
||||
this.writeDouble(vec3d.x)
|
||||
this.writeDouble(vec3d.y)
|
||||
this.writeDouble(vec3d.z)
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.protocol.game.math
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
|
||||
public data class Vec3f(val x: Float, val y: Float, val z: Float) {
|
||||
public companion object {
|
||||
public val ZERO: Vec3f = Vec3f(0f, 0f, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.readVec3f(): Vec3f {
|
||||
val x = readFloat()
|
||||
val y = readFloat()
|
||||
val z = readFloat()
|
||||
return Vec3f(x, y, z)
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.writeVec3f(value: Vec3f) {
|
||||
writeFloat(value.x)
|
||||
writeFloat(value.y)
|
||||
writeFloat(value.z)
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.protocol.game.particle
|
||||
|
||||
import cn.rtast.libmc.network.BytesBuffer
|
||||
import cn.rtast.libmc.primitives.readVarInt
|
||||
import cn.rtast.libmc.protocol.protocol.game.block.BlockPos
|
||||
import cn.rtast.libmc.protocol.protocol.game.block.readBlockPos
|
||||
import cn.rtast.libmc.protocol.protocol.game.color.Color24
|
||||
import cn.rtast.libmc.protocol.protocol.game.color.readColor24
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.Vec3d
|
||||
import cn.rtast.libmc.protocol.protocol.game.math.readVec3d
|
||||
|
||||
public sealed interface ParticleData {
|
||||
public object Empty : ParticleData
|
||||
public data class Block(val blockStateId: Int) : ParticleData
|
||||
public data class Geyser(val waterBlocks: Int) : ParticleData
|
||||
public data class GeyserBase(val waterBlocks: Int, val burstImpulseBase: Float) : ParticleData
|
||||
public data class GeyserPoof(val waterBlocks: Int, val burstImpulseBase: Float) : ParticleData
|
||||
public data class GeyserPlume(val waterBlocks: Int) : ParticleData
|
||||
public data class DragonBreath(val power: Float) : ParticleData
|
||||
public data class Dust(val color: Color24, val scale: Float) : ParticleData
|
||||
public data class DustColorTransition(val fromColor: Color24, val toColor: Color24, val scale: Float) :
|
||||
ParticleData
|
||||
|
||||
public data class Effect(val color: Color24, val power: Float) : ParticleData
|
||||
public data class ColorARGB(val colorARGB: Int) : ParticleData
|
||||
public data class SculkCharge(val roll: Float) : ParticleData
|
||||
public data class Vibration(val source: VibrationSource, val ticks: Int) : ParticleData {
|
||||
public sealed interface VibrationSource {
|
||||
public data class Block(val position: BlockPos) : VibrationSource
|
||||
public data class Entity(val entityId: Int, val eyeHeight: Float) : VibrationSource
|
||||
}
|
||||
}
|
||||
|
||||
public data class Trail(val position: Vec3d, val color: Color24, val durationTicks: Int) : ParticleData
|
||||
public data class Shriek(val delay: Int) : ParticleData
|
||||
public data class Item(val slot: ByteArray) : ParticleData {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || this::class != other::class) return false
|
||||
other as Item
|
||||
return slot.contentEquals(other.slot)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return slot.contentHashCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytesBuffer.readParticleData(type: ParticleType): ParticleData {
|
||||
return when (type) {
|
||||
ParticleType.ANGRY_VILLAGER,
|
||||
ParticleType.BUBBLE,
|
||||
ParticleType.SULFUR_BUBBLES,
|
||||
ParticleType.NOXIOUS_GAS,
|
||||
ParticleType.NOXIOUS_GAS_CLOUD,
|
||||
ParticleType.CLOUD,
|
||||
ParticleType.COPPER_FIRE_FLAME,
|
||||
ParticleType.CRIT,
|
||||
ParticleType.DAMAGE_INDICATOR,
|
||||
ParticleType.DRIPPING_LAVA,
|
||||
ParticleType.FALLING_LAVA,
|
||||
ParticleType.LANDING_LAVA,
|
||||
ParticleType.DRIPPING_WATER,
|
||||
ParticleType.FALLING_WATER,
|
||||
ParticleType.ELDER_GUARDIAN,
|
||||
ParticleType.ENCHANTED_HIT,
|
||||
ParticleType.ENCHANT,
|
||||
ParticleType.END_ROD,
|
||||
ParticleType.EXPLOSION_EMITTER,
|
||||
ParticleType.EXPLOSION,
|
||||
ParticleType.GUST,
|
||||
ParticleType.SMALL_GUST,
|
||||
ParticleType.GUST_EMITTER_LARGE,
|
||||
ParticleType.GUST_EMITTER_SMALL,
|
||||
ParticleType.SONIC_BOOM,
|
||||
ParticleType.FIREWORK,
|
||||
ParticleType.FISHING,
|
||||
ParticleType.FLAME,
|
||||
ParticleType.INFESTED,
|
||||
ParticleType.CHERRY_LEAVES,
|
||||
ParticleType.PALE_OAK_LEAVES,
|
||||
ParticleType.SCULK_SOUL,
|
||||
ParticleType.SCULK_CHARGE_POP,
|
||||
ParticleType.SOUL_FIRE_FLAME,
|
||||
ParticleType.SOUL,
|
||||
ParticleType.HAPPY_VILLAGER,
|
||||
ParticleType.COMPOSTER,
|
||||
ParticleType.HEART,
|
||||
ParticleType.PAUSE_MOB_GROWTH,
|
||||
ParticleType.RESET_MOB_GROWTH,
|
||||
ParticleType.ITEM_SLIME,
|
||||
ParticleType.ITEM_COBWEB,
|
||||
ParticleType.ITEM_SNOWBALL,
|
||||
ParticleType.LARGE_SMOKE,
|
||||
ParticleType.LAVA,
|
||||
ParticleType.MYCELIUM,
|
||||
ParticleType.NOTE,
|
||||
ParticleType.POOF,
|
||||
ParticleType.PORTAL,
|
||||
ParticleType.RAIN,
|
||||
ParticleType.SMOKE,
|
||||
ParticleType.WHITE_SMOKE,
|
||||
ParticleType.SNEEZE,
|
||||
ParticleType.SPIT,
|
||||
ParticleType.SQUID_INK,
|
||||
ParticleType.SWEEP_ATTACK,
|
||||
ParticleType.TOTEM_OF_UNDYING,
|
||||
ParticleType.UNDERWATER,
|
||||
ParticleType.SPLASH,
|
||||
ParticleType.WITCH,
|
||||
ParticleType.BUBBLE_POP,
|
||||
ParticleType.CURRENT_DOWN,
|
||||
ParticleType.BUBBLE_COLUMN_UP,
|
||||
ParticleType.NAUTILUS,
|
||||
ParticleType.DOLPHIN,
|
||||
ParticleType.CAMPFIRE_COSY_SMOKE,
|
||||
ParticleType.CAMPFIRE_SIGNAL_SMOKE,
|
||||
ParticleType.DRIPPING_HONEY,
|
||||
ParticleType.FALLING_HONEY,
|
||||
ParticleType.LANDING_HONEY,
|
||||
ParticleType.FALLING_NECTAR,
|
||||
ParticleType.FALLING_SPORE_BLOSSOM,
|
||||
ParticleType.ASH,
|
||||
ParticleType.CRIMSON_SPORE,
|
||||
ParticleType.WARPED_SPORE,
|
||||
ParticleType.SPORE_BLOSSOM_AIR,
|
||||
ParticleType.DRIPPING_OBSIDIAN_TEAR,
|
||||
ParticleType.FALLING_OBSIDIAN_TEAR,
|
||||
ParticleType.LANDING_OBSIDIAN_TEAR,
|
||||
ParticleType.REVERSE_PORTAL,
|
||||
ParticleType.WHITE_ASH,
|
||||
ParticleType.SMALL_FLAME,
|
||||
ParticleType.SNOWFLAKE,
|
||||
ParticleType.DRIPPING_DRIPSTONE_LAVA,
|
||||
ParticleType.FALLING_DRIPSTONE_LAVA,
|
||||
ParticleType.DRIPPING_DRIPSTONE_WATER,
|
||||
ParticleType.FALLING_DRIPSTONE_WATER,
|
||||
ParticleType.GLOW_SQUID_INK,
|
||||
ParticleType.GLOW,
|
||||
ParticleType.WAX_ON,
|
||||
ParticleType.WAX_OFF,
|
||||
ParticleType.ELECTRIC_SPARK,
|
||||
ParticleType.SCRAPE,
|
||||
ParticleType.EGG_CRACK,
|
||||
ParticleType.DUST_PLUME,
|
||||
ParticleType.TRIAL_SPAWNER_DETECTED_PLAYER,
|
||||
ParticleType.TRIAL_SPAWNER_DETECTED_PLAYER_OMINOUS,
|
||||
ParticleType.VAULT_CONNECTION,
|
||||
ParticleType.OMINOUS_SPAWNING,
|
||||
ParticleType.RAID_OMEN,
|
||||
ParticleType.TRIAL_OMEN,
|
||||
ParticleType.FIREFLY,
|
||||
ParticleType.SULFUR_CUBE_GOO,
|
||||
-> ParticleData.Empty
|
||||
|
||||
ParticleType.BLOCK,
|
||||
ParticleType.BLOCK_MARKER,
|
||||
ParticleType.FALLING_DUST,
|
||||
ParticleType.DUST_PILLAR,
|
||||
ParticleType.BLOCK_CRUMBLE,
|
||||
-> ParticleData.Block(this.readVarInt())
|
||||
|
||||
ParticleType.GEYSER -> ParticleData.Geyser(this.readInt())
|
||||
ParticleType.GEYSER_BASE -> ParticleData.GeyserBase(this.readInt(), this.readFloat())
|
||||
ParticleType.GEYSER_POOF -> ParticleData.GeyserPoof(this.readInt(), this.readFloat())
|
||||
ParticleType.GEYSER_PLUME -> ParticleData.GeyserPlume(this.readInt())
|
||||
ParticleType.DRAGON_BREATH -> ParticleData.DragonBreath(this.readFloat())
|
||||
ParticleType.SCULK_CHARGE -> ParticleData.SculkCharge(this.readFloat())
|
||||
ParticleType.SHRIEK -> ParticleData.Shriek(this.readVarInt())
|
||||
ParticleType.ENTITY_EFFECT,
|
||||
ParticleType.TINTED_LEAVES,
|
||||
ParticleType.FLASH,
|
||||
-> ParticleData.ColorARGB(this.readInt())
|
||||
|
||||
ParticleType.DUST -> ParticleData.Dust(this.readColor24(), this.readFloat())
|
||||
ParticleType.DUST_COLOR_TRANSITION -> ParticleData.DustColorTransition(
|
||||
this.readColor24(),
|
||||
this.readColor24(),
|
||||
this.readFloat()
|
||||
)
|
||||
|
||||
ParticleType.EFFECT,
|
||||
ParticleType.INSTANT_EFFECT,
|
||||
-> ParticleData.Effect(this.readColor24(), this.readFloat())
|
||||
|
||||
// slot parsed as raw bytearray
|
||||
ParticleType.ITEM -> ParticleData.Item(this.toByteArray())
|
||||
ParticleType.VIBRATION -> {
|
||||
val source = when (val sourceTypeId = this.readVarInt()) {
|
||||
0 -> ParticleData.Vibration.VibrationSource.Block(this.readBlockPos())
|
||||
1 -> ParticleData.Vibration.VibrationSource.Entity(this.readVarInt(), this.readFloat())
|
||||
else -> error("Unknown vibration source type: $sourceTypeId")
|
||||
}
|
||||
ParticleData.Vibration(source, this.readVarInt())
|
||||
}
|
||||
|
||||
ParticleType.TRAIL -> ParticleData.Trail(readVec3d(), readColor24(), this.readVarInt())
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright © 2026 RTAkland
|
||||
* Author: RTAkland
|
||||
* Date: 2026/9/9
|
||||
*/
|
||||
|
||||
|
||||
package cn.rtast.libmc.protocol.protocol.game.particle
|
||||
|
||||
import cn.rtast.libmc.protocol.protocol.game.Identifier
|
||||
|
||||
public enum class ParticleType(public val id: Int, public val key: String) {
|
||||
ANGRY_VILLAGER(0, "angry_villager"), BLOCK(1, "block"), BLOCK_MARKER(2, "block_marker"),
|
||||
BUBBLE(3, "bubble"), SULFUR_BUBBLES(4, "sulfur_bubbles"), NOXIOUS_GAS(5, "noxious_gas"),
|
||||
NOXIOUS_GAS_CLOUD(6, "noxious_gas_cloud"), GEYSER(7, "geyser"), GEYSER_BASE(8, "geyser_base"),
|
||||
GEYSER_POOF(9, "geyser_poof"), GEYSER_PLUME(10, "geyser_plume"), CLOUD(11, "cloud"),
|
||||
COPPER_FIRE_FLAME(12, "copper_fire_flame"), CRIT(13, "crit"), DAMAGE_INDICATOR(14, "damage_indicator"),
|
||||
DRAGON_BREATH(15, "dragon_breath"), DRIPPING_LAVA(16, "dripping_lava"), FALLING_LAVA(17, "falling_lava"),
|
||||
LANDING_LAVA(18, "landing_lava"), DRIPPING_WATER(19, "dripping_water"), FALLING_WATER(20, "falling_water"),
|
||||
DUST(21, "dust"), DUST_COLOR_TRANSITION(22, "dust_color_transition"), EFFECT(23, "effect"),
|
||||
ELDER_GUARDIAN(24, "elder_guardian"), ENCHANTED_HIT(25, "enchanted_hit"), ENCHANT(26, "enchant"),
|
||||
END_ROD(27, "end_rod"), ENTITY_EFFECT(28, "entity_effect"), EXPLOSION_EMITTER(29, "explosion_emitter"),
|
||||
EXPLOSION(30, "explosion"), GUST(31, "gust"), SMALL_GUST(32, "small_gust"),
|
||||
GUST_EMITTER_LARGE(33, "gust_emitter_large"), GUST_EMITTER_SMALL(34, "gust_emitter_small"),
|
||||
SONIC_BOOM(35, "sonic_boom"), FALLING_DUST(36, "falling_dust"), FIREWORK(37, "firework"), FISHING(38, "fishing"),
|
||||
FLAME(39, "flame"), INFESTED(40, "infested"), CHERRY_LEAVES(41, "cherry_leaves"),
|
||||
PALE_OAK_LEAVES(42, "pale_oak_leaves"), TINTED_LEAVES(43, "tinted_leaves"), SCULK_SOUL(44, "sculk_soul"),
|
||||
SCULK_CHARGE(45, "sculk_charge"), SCULK_CHARGE_POP(46, "sculk_charge_pop"), SOUL_FIRE_FLAME(47, "soul_fire_flame"),
|
||||
SOUL(48, "soul"), FLASH(49, "flash"), HAPPY_VILLAGER(50, "happy_villager"), COMPOSTER(51, "composter"),
|
||||
HEART(52, "heart"), INSTANT_EFFECT(53, "instant_effect"), ITEM(54, "item"), VIBRATION(55, "vibration"),
|
||||
TRAIL(56, "trail"), PAUSE_MOB_GROWTH(57, "pause_mob_growth"), RESET_MOB_GROWTH(58, "reset_mob_growth"),
|
||||
ITEM_SLIME(59, "item_slime"), ITEM_COBWEB(60, "item_cobweb"), ITEM_SNOWBALL(61, "item_snowball"),
|
||||
LARGE_SMOKE(62, "large_smoke"), LAVA(63, "lava"), MYCELIUM(64, "mycelium"), NOTE(65, "note"), POOF(66, "poof"),
|
||||
PORTAL(67, "portal"), RAIN(68, "rain"), SMOKE(69, "smoke"), WHITE_SMOKE(70, "white_smoke"), SNEEZE(71, "sneeze"),
|
||||
SPIT(72, "spit"), SQUID_INK(73, "squid_ink"), SWEEP_ATTACK(74, "sweep_attack"),
|
||||
TOTEM_OF_UNDYING(75, "totem_of_undying"), UNDERWATER(76, "underwater"), SPLASH(77, "splash"), WITCH(78, "witch"),
|
||||
BUBBLE_POP(79, "bubble_pop"), CURRENT_DOWN(80, "current_down"), BUBBLE_COLUMN_UP(81, "bubble_column_up"),
|
||||
NAUTILUS(82, "nautilus"), DOLPHIN(83, "dolphin"), CAMPFIRE_COSY_SMOKE(84, "campfire_cosy_smoke"),
|
||||
CAMPFIRE_SIGNAL_SMOKE(85, "campfire_signal_smoke"), DRIPPING_HONEY(86, "dripping_honey"),
|
||||
FALLING_HONEY(87, "falling_honey"), LANDING_HONEY(88, "landing_honey"), FALLING_NECTAR(89, "falling_nectar"),
|
||||
FALLING_SPORE_BLOSSOM(90, "falling_spore_blossom"), ASH(91, "ash"), CRIMSON_SPORE(92, "crimson_spore"),
|
||||
WARPED_SPORE(93, "warped_spore"), SPORE_BLOSSOM_AIR(94, "spore_blossom_air"),
|
||||
DRIPPING_OBSIDIAN_TEAR(95, "dripping_obsidian_tear"), FALLING_OBSIDIAN_TEAR(96, "falling_obsidian_tear"),
|
||||
LANDING_OBSIDIAN_TEAR(97, "landing_obsidian_tear"), REVERSE_PORTAL(98, "reverse_portal"),
|
||||
WHITE_ASH(99, "white_ash"), SMALL_FLAME(100, "small_flame"), SNOWFLAKE(101, "snowflake"),
|
||||
DRIPPING_DRIPSTONE_LAVA(102, "dripping_dripstone_lava"), FALLING_DRIPSTONE_LAVA(103, "falling_dripstone_lava"),
|
||||
DRIPPING_DRIPSTONE_WATER(104, "dripping_dripstone_water"), FALLING_DRIPSTONE_WATER(105, "falling_dripstone_water"),
|
||||
GLOW_SQUID_INK(106, "glow_squid_ink"), GLOW(107, "glow"), WAX_ON(108, "wax_on"), WAX_OFF(109, "wax_off"),
|
||||
ELECTRIC_SPARK(110, "electric_spark"), SCRAPE(111, "scrape"), SHRIEK(112, "shriek"), EGG_CRACK(113, "egg_crack"),
|
||||
DUST_PLUME(114, "dust_plume"), TRIAL_SPAWNER_DETECTED_PLAYER(115, "trial_spawner_detection"),
|
||||
TRIAL_SPAWNER_DETECTED_PLAYER_OMINOUS(116, "trial_spawner_detection_ominous"),
|
||||
VAULT_CONNECTION(117, "vault_connection"), DUST_PILLAR(118, "dust_pillar"),
|
||||
OMINOUS_SPAWNING(119, "ominous_spawning"), RAID_OMEN(120, "raid_omen"), TRIAL_OMEN(121, "trial_omen"),
|
||||
BLOCK_CRUMBLE(122, "block_crumble"), FIREFLY(123, "firefly"), SULFUR_CUBE_GOO(124, "sulfur_cube_goo");
|
||||
|
||||
public val identifier: Identifier = Identifier.of("minecraft", key)
|
||||
|
||||
public companion object {
|
||||
private val CACHED_ID = entries
|
||||
public fun fromID(id: Int): ParticleType = CACHED_ID.first { it.id == id }
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,33 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
* Convert an int value to minecraft tick
|
||||
* Converts an [Int] representing a count of ticks into a [Duration]
|
||||
*/
|
||||
public inline val Int.ticks: Duration
|
||||
get() = (this * 50).milliseconds
|
||||
|
||||
/**
|
||||
* Convert a long value to minecraft tick
|
||||
* Converts a [Long] representing a count of ticks into a [Duration]
|
||||
*/
|
||||
public inline val Long.ticks: Duration
|
||||
get() = (this * 50).milliseconds
|
||||
|
||||
/**
|
||||
* Convert a double value to minecraft tick
|
||||
* Converts a [Double] representing a count of
|
||||
* ticks (including fractional ticks) into a [Duration]
|
||||
*/
|
||||
public inline val Double.ticks: Duration
|
||||
get() = (this * 50.0).milliseconds
|
||||
|
||||
/**
|
||||
* Convert [Duration] to ticks([Long])
|
||||
* Converts this [Duration] to the total number of whole ticks (floored)
|
||||
*/
|
||||
public inline val Duration.inWholeTicks: Long
|
||||
get() = this.inWholeMilliseconds / 50
|
||||
|
||||
/**
|
||||
* Convert [Duration] to ticks([Double])
|
||||
* Converts this [Duration] to the number of ticks as a [Double],
|
||||
* preserving fractional ticks for high-precision time calculations
|
||||
*/
|
||||
public inline val Duration.inTicksDouble: Double
|
||||
get() = this.inWholeNanoseconds / 50_000_000.0
|
||||
@@ -10,7 +10,11 @@ package client
|
||||
import cn.rtast.libmc.crypto.AuthenticationProvider
|
||||
import cn.rtast.libmc.packet.ClientboundUnknownPacket
|
||||
import cn.rtast.libmc.protocol.client.createMinecraftClient
|
||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLevelParticlePacket
|
||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
|
||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundRecipeBookRemovePacket
|
||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundRecipeBookSettingsPacket
|
||||
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStepTickPacket
|
||||
import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundChatMessagePacket
|
||||
import cn.rtast.libmc.protocol.util.generateOfflineUuid
|
||||
import io.ktor.client.*
|
||||
@@ -67,21 +71,20 @@ class TestClient {
|
||||
socketEngine = KtorNetworkEngine()
|
||||
}
|
||||
)
|
||||
cli.onPacket<ClientboundUnknownPacket> {
|
||||
println(it)
|
||||
val snapshot = chatTracker.prepareForOutgoingMessage()
|
||||
cli.networkChannel.sendPacket(
|
||||
ServerboundChatMessagePacket(
|
||||
"114514", Clock.System.now().toEpochMilliseconds(),
|
||||
Random.nextLong(),
|
||||
null, snapshot.messageCount, createAcknowledgedBitSet(snapshot.lastSeenSignatures).toByteArray(),
|
||||
ChatPacketUtils.computePacketChecksum(snapshot.lastSeenSignatures)
|
||||
)
|
||||
)
|
||||
}
|
||||
cli.onPacket<ClientboundPlayerChatMessagePacket> {
|
||||
chatTracker.onReceivePlayerChat(it.messageSignature)
|
||||
}
|
||||
// cli.onPacket<ClientboundUnknownPacket> {
|
||||
// println(it)
|
||||
// val snapshot = chatTracker.prepareForOutgoingMessage()
|
||||
// cli.networkChannel.sendPacket(
|
||||
// ServerboundChatMessagePacket(
|
||||
// "114514", Clock.System.now().toEpochMilliseconds(),
|
||||
// Random.nextLong(),
|
||||
// null, snapshot.messageCount, createAcknowledgedBitSet(snapshot.lastSeenSignatures).toByteArray(),
|
||||
// ChatPacketUtils.computePacketChecksum(snapshot.lastSeenSignatures)
|
||||
// )
|
||||
// )
|
||||
// }
|
||||
cli.onPacket<ClientboundPlayerChatMessagePacket> { chatTracker.onReceivePlayerChat(it.messageSignature) }
|
||||
cli.onPacket<ClientboundStepTickPacket> { println(it) }
|
||||
cli.launch { cli.connect() }
|
||||
while (true) {
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user