Implemented 0x12-ClientboundContainerSetContentPacket, 0x14-ClientboundContainerSetSlotPacket, implemented slot data codec

This commit is contained in:
2026-09-11 01:19:59 +08:00
parent 7c22cdd158
commit 59189d2fad
69 files changed
+3146 -67

No files matched your search

@@ -9,17 +9,8 @@ package cn.rtast.libmc.primitives
import cn.rtast.libmc.network.BytesBuffer
public inline fun <T> BytesBuffer.readOptional(block: BytesBuffer.() -> T): T? {
val hasData = this.readBoolean()
return if (hasData) block.invoke(this) else null
}
public inline fun <T> BytesBuffer.readOptional(condition: Boolean, block: BytesBuffer.() -> T): T? =
if (condition) block() else null
/**
* buffer.writeOptional(value.someValue) { writeBlockPos(it) }
*/
public inline fun <T> BytesBuffer.writeOptional(value: T?, block: BytesBuffer.(T) -> Unit) {
if (value != null) {
this.writeBoolean(true)
block.invoke(this, value)
} else this.writeBoolean(false)
}
public inline fun <T> BytesBuffer.writeOptional(value: T?, block: BytesBuffer.(T) -> Unit): Unit =
if (value != null) block.invoke(this, value) else Unit
@@ -44,10 +44,7 @@ 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) {
"Prefixed array count $count is invalid (expected 0..4096). " +
"Stream offset is corrupted. Check Heightmaps/NBT encoding."
}
require(count in 0..4096)
val list = ArrayList<T>(count)
repeat(count) { _ -> list.add(this.reader()) }
return list
@@ -80,6 +80,7 @@ public fun NBTInput.readNetworkCompound(): NBTCompound {
return when (val type = NBTType.fromID(readByte().toInt() and 0xFF)) {
NBTType.Compound -> NBTCompound("", readCompoundTag())
NBTType.String -> NBTCompound("", NBTTag.CompoundTag(linkedMapOf("text" to NBTTag.StringTag(readStringTag()))))
NBTType.End -> NBTCompound("", NBTTag.CompoundTag(linkedMapOf()))
else -> throw UnsupportedOperationException("Unsupported network nbt tag 0x${type.id.toString(16).uppercase()}")
}
}
@@ -7,16 +7,28 @@
package cn.rtast.libmc.protocol.client
import cn.rtast.libmc.protocol.protocol.session.Session
import cn.rtast.libmc.protocol.protocol.session.SessionEvent
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
import kotlin.concurrent.Volatile
internal class ClientStateMachine {
internal class ClientStateMachine(private val session: Session) {
@Volatile
var currentState: ProtocolState = ProtocolState.HANDSHAKE
private set
fun transitionTo(newState: ProtocolState) {
suspend fun transitionTo(newState: ProtocolState) {
println("Changing State $currentState to $newState")
currentState = newState
session.emitEvent(
when (newState) {
ProtocolState.HANDSHAKE -> SessionEvent.ChangedState.HANDSHAKE
ProtocolState.LOGIN -> SessionEvent.ChangedState.LOGIN
ProtocolState.CONFIGURATION -> SessionEvent.ChangedState.CONFIGURATION
ProtocolState.PLAY -> SessionEvent.ChangedState.PLAY
ProtocolState.STATUS -> SessionEvent.ChangedState.STATUS
ProtocolState.DISCONNECTED -> SessionEvent.ChangedState.DISCONNECTED
}
)
}
}
@@ -30,7 +30,7 @@ public class MinecraftClient internal constructor(
internal val protocolContext: ProtocolContext,
public val session: SessionImpl = SessionImpl(),
) : PacketEventDispatcher(), CoroutineScope, Session by session {
internal val stateMachine = ClientStateMachine()
internal val stateMachine = ClientStateMachine(session)
public val networkChannel: NetworkChannel = NetworkChannel(this)
private val clientJob = SupervisorJob(parentJob)
private var listenJob: Job? = null
@@ -52,8 +52,9 @@ public class MinecraftClient internal constructor(
try {
while (isActive) networkChannel.readNextPacket()
} catch (e: Throwable) {
if (e is CancellationException) return@launch
e.printStackTrace()
println("Network read loop exception: ${e.message}")
if (e is CancellationException) return@launch
} finally {
networkChannel.close()
}
@@ -14,8 +14,8 @@ import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.client.MinecraftClient
import cn.rtast.libmc.protocol.protocol.event.PacketEventDispatcher
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.clientboundGameProtocols
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec.serverboundGameProtocols
import cn.rtast.libmc.protocol.registry.GamePacketsProtocolRegistry.clientboundGameProtocols
import cn.rtast.libmc.protocol.registry.GamePacketsProtocolRegistry.serverboundGameProtocols
import cn.rtast.libmc.zlibCompress
import cn.rtast.libmc.zlibDecompress
import kotlin.concurrent.Volatile
@@ -0,0 +1,34 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
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
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
public data class ClientboundContainerSetContentPacket(
val windowId: Int,
val stateId: Int,
val slotData: List<Slot>,
val carriedItem: Slot?,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundContainerSetContentPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundContainerSetContentPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundContainerSetContentPacket {
val windowId = buffer.readVarInt()
val stateId = buffer.readVarInt()
val slotData = buffer.readPrefixed { readSlot() }
// val carriedItem = buffer.readSlot() // TODO FIX ME
return ClientboundContainerSetContentPacket(windowId, stateId, slotData, null)
}
}
}
@@ -0,0 +1,33 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
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.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
public data class ClientboundContainerSetSlotPacket(
val windowId: Int,
val stateId: Int,
val slot: Short,
val slotData: Slot,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundContainerSetSlotPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundContainerSetSlotPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundContainerSetSlotPacket {
val windowId = buffer.readVarInt()
val stateId = buffer.readVarInt()
val slot = buffer.readShort()
val slotData = buffer.readSlot()
return ClientboundContainerSetSlotPacket(windowId, stateId, slot, slotData)
}
}
}
@@ -18,7 +18,7 @@ public data class ClientboundDeleteChatPacket(val messageId: Int, val signature:
override fun encode(buffer: BytesBuffer, value: ClientboundDeleteChatPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundDeleteChatPacket {
val messageId = buffer.readVarInt()
val signature = buffer.readOptional { buffer.readBytes(256) }
val signature = buffer.readOptional(messageId == 0) { buffer.readBytes(256) }
return ClientboundDeleteChatPacket(messageId, signature)
}
}
@@ -26,12 +26,9 @@ public data class ClientboundDeleteChatPacket(val messageId: Int, val signature:
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as ClientboundDeleteChatPacket
if (messageId != other.messageId) return false
if (!signature.contentEquals(other.signature)) return false
return true
}
@@ -7,16 +7,16 @@
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.IdOrX
import cn.rtast.libmc.primitives.readIdOrX
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.protocol.protocol.game.chat.InlineChatType
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readInlineChatType
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundDisguisedChatMessagePacket(
val message: TextComponent,
@@ -30,7 +30,7 @@ public data class ClientboundDisguisedChatMessagePacket(
val message = buffer.readTextComponent()
val chatType = buffer.readIdOrX { readInlineChatType() }
val senderName = buffer.readTextComponent()
val targetName = buffer.readOptional { readTextComponent() }
val targetName = buffer.readPrefixOptional { readTextComponent() }
return ClientboundDisguisedChatMessagePacket(message, chatType, senderName, targetName)
}
}
@@ -7,9 +7,11 @@
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.readOptional
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readPrefixedByteArray
import cn.rtast.libmc.primitives.readVarInt
@@ -17,7 +19,6 @@ import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.protocol.protocol.game.item.MapItemColorPatch
import cn.rtast.libmc.protocol.protocol.game.item.MapItemIcon
import cn.rtast.libmc.protocol.protocol.game.item.MapItemIconType
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundMapItemDataPacket(
val mapId: Int,
@@ -32,24 +33,24 @@ public data class ClientboundMapItemDataPacket(
val mapId = buffer.readVarInt()
val scale = buffer.readByte()
val locked = buffer.readBoolean()
val icons = buffer.readOptional {
val icons = buffer.readPrefixOptional {
readPrefixed {
val type = MapItemIconType.fromID(readVarInt())
val x = readByte()
val z = readByte()
val direction = readByte()
val displayName = readOptional { readTextComponent() }
val displayName = readPrefixOptional { readTextComponent() }
MapItemIcon(type, x, z, direction, displayName)
}
}
val columns = buffer.readUByte()
val colorPatch = if (columns > 0u) {
val colorPatch = buffer.readOptional(columns > 0u) {
val rows = buffer.readUByte()
val xOffset = buffer.readUByte()
val zOffset = buffer.readUByte()
val data = buffer.readPrefixedByteArray()
MapItemColorPatch(columns, rows, xOffset, zOffset, data)
} else null
}
return ClientboundMapItemDataPacket(mapId, scale, locked, icons, colorPatch)
}
}
@@ -43,7 +43,7 @@ public data class ClientboundPlayerInfoUpdatePacket(
}
PlayerUpdateInfoAction.INITIALIZE_CHAT -> {
buffer.readOptional {
buffer.readPrefixOptional {
SinglePlayerAction.InitializeChat(
readUuid(), readLong(),
readBytes(512), readBytes(4096)
@@ -7,19 +7,18 @@
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.readMcString
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readPrefixOptional
public data class ClientboundResetScorePacket(val entityName: String, val objectiveName: String?) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundResetScorePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundResetScorePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundResetScorePacket {
val entityName = buffer.readMcString()
val objectiveName = buffer.readPrefixed { readOptional { readMcString() } }.first() // ?
val objectiveName = buffer.readPrefixOptional { readMcString() }
return ClientboundResetScorePacket(entityName, objectiveName)
}
}
@@ -8,8 +8,8 @@
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.packet.PacketCodec
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.GameMode
@@ -45,8 +45,8 @@ public data class ClientboundRespawnPacket(
val isDebug = buffer.readBoolean()
val isFlat = buffer.readBoolean()
val hasDeathLocation = buffer.readBoolean()
val deathDimensionName = buffer.readOptional { readIdentifier() }
val deathLocation = buffer.readOptional { readBlockPos() }
val deathDimensionName = buffer.readOptional(hasDeathLocation) { readIdentifier() }
val deathLocation = buffer.readOptional(hasDeathLocation) { readBlockPos() }
val portalCooldown = buffer.readVarInt()
val seaLevel = buffer.readVarInt()
val dataKept = RespawnDataToKeep.fromByte(buffer.readByte())
@@ -8,9 +8,9 @@
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.readOptional
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
@@ -18,7 +18,7 @@ public data class ClientboundSelectAdvancementsTabPacket(val tabId: Identifier?)
internal companion object Codec : PacketCodec<ClientboundSelectAdvancementsTabPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSelectAdvancementsTabPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSelectAdvancementsTabPacket {
return ClientboundSelectAdvancementsTabPacket(buffer.readOptional { readIdentifier() })
return ClientboundSelectAdvancementsTabPacket(buffer.readPrefixOptional { readIdentifier() })
}
}
}
@@ -7,20 +7,20 @@
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.readOptional
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundServerDataPacket(val motd: TextComponent, val icon: ByteArray?) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundServerDataPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundServerDataPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundServerDataPacket {
val motd = buffer.readTextComponent()
val icon = buffer.readOptional { val length = readVarInt(); readBytes(length) }
val icon = buffer.readPrefixOptional { val length = readVarInt(); readBytes(length) }
return ClientboundServerDataPacket(motd, icon)
}
}
@@ -7,12 +7,12 @@
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.readOptional
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundTestInstanceBlockStatusPacket(
val status: TextComponent,
@@ -26,9 +26,9 @@ public data class ClientboundTestInstanceBlockStatusPacket(
override fun decode(buffer: BytesBuffer): ClientboundTestInstanceBlockStatusPacket {
val status = buffer.readTextComponent()
val hasSize = buffer.readBoolean()
val sizeX = buffer.readOptional { readDouble() } // ?
val sizeY = buffer.readOptional { readDouble() } // ?
val sizeZ = buffer.readOptional { readDouble() } // ?
val sizeX = buffer.readOptional(hasSize) { readDouble() }
val sizeY = buffer.readOptional(hasSize) { readDouble() }
val sizeZ = buffer.readOptional(hasSize) { readDouble() }
return ClientboundTestInstanceBlockStatusPacket(status, hasSize, sizeX, sizeY, sizeZ)
}
}
@@ -8,16 +8,16 @@
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.nbt.NBTTag
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import cn.rtast.libmc.network.BytesBuffer
public data class ClientboundUpdateScorePacket(
val entityName: String,
@@ -32,8 +32,8 @@ public data class ClientboundUpdateScorePacket(
val entityName = buffer.readMcString()
val objectiveName = buffer.readMcString()
val value = buffer.readVarInt()
val displayName = buffer.readOptional { readTextComponent() }
val numberFormat = buffer.readOptional {
val displayName = buffer.readPrefixOptional { readTextComponent() }
val numberFormat = buffer.readPrefixOptional {
when (val type = readVarInt()) {
0 -> ScoreNumberFormat.Blank
1 -> ScoreNumberFormat.Styled(styling = readNetworkNBTCompound().element as NBTTag.CompoundTag) // fix me
@@ -34,7 +34,7 @@ public data class ClientboundWaypointPacket(
readRight = { readIdentifier() }
)
val iconStyle = buffer.readIdentifier()
val color = buffer.readOptional {
val color = buffer.readPrefixOptional {
val red = readUByte()
val green = readUByte()
val blue = readUByte()
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.block
public data class BlockStateProperty(val name: String, val value: String)
@@ -0,0 +1,31 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.color
public enum class DyeColor(public val id: Int) {
WHITE(0),
ORANGE(1),
MAGENTA(2),
LIGHT_BLUE(3),
YELLOW(4),
LIME(5),
PINK(6),
GRAY(7),
LIGHT_GRAY(8),
CYAN(9),
PURPLE(10),
BLUE(11),
BROWN(12),
GREEN(13),
RED(14),
BLACK(15);
public companion object {
public fun fromID(id: Int): DyeColor = entries.first { it.id == id }
}
}
@@ -0,0 +1,18 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.protocol.protocol.game.Identifier
public data class AttributeModifierEntry(
val attributeId: Int,
val modifierId: Identifier,
val value: Double,
val operation: AttributeOperation,
val slot: AttributeSlot,
)
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class AttributeOperation(public val id: Int) {
ADD(0),
MULTIPLY_BASE(1),
MULTIPLY_TOTAL(2);
public companion object {
public fun fromID(id: Int): AttributeOperation = entries.first { it.id == id }
}
}
@@ -0,0 +1,24 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class AttributeSlot(public val id: Int) {
ANY(0),
MAINHAND(1),
OFFHAND(2),
HAND(3),
FEET(4),
LEGS(5),
CHEST(6),
HEAD(7),
ARMOR(8),
BODY(9);
public companion object {
public fun fromID(id: Int): AttributeSlot = entries.first { it.id == id }
}
}
@@ -0,0 +1,41 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.*
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.color.DyeColor
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class BannerPattern(val assetId: Identifier, val translationKey: String)
public data class BannerPatternLayer(val pattern: IdOrX<BannerPattern>, val color: DyeColor)
internal fun BytesBuffer.readBannerPatternLayer(): BannerPatternLayer {
val pattern = readIdOrX { readBannerPattern() }
val color = DyeColor.fromID(readVarInt())
return BannerPatternLayer(pattern, color)
}
internal fun BytesBuffer.writeBannerPatternLayer(layer: BannerPatternLayer) {
writeIdOrX(layer.pattern) { writeBannerPattern(it) }
writeVarInt(layer.color.id)
}
internal fun BytesBuffer.readBannerPattern(): BannerPattern {
val assetId = readIdentifier()
val translationKey = readMcString()
return BannerPattern(assetId, translationKey)
}
internal fun BytesBuffer.writeBannerPattern(pattern: BannerPattern) {
writeIdentifier(pattern.assetId)
writeMcString(pattern.translationKey)
}
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.nbt.NBTCompound
public data class BeeAttribute(
val entityType: Int,
val entityData: NBTCompound,
val ticksInHive: Int,
val minTicksInHive: Int,
)
@@ -0,0 +1,137 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.*
import cn.rtast.libmc.protocol.protocol.game.data.component.DataComponent
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound
import cn.rtast.libmc.protocol.registry.readDataComponent
import cn.rtast.libmc.protocol.registry.writeDataComponent
public data class BlockPredicate(
val blocks: IdSet?,
val properties: List<PropertyMatcher>?,
val nbt: NBTCompound?,
val components: List<ExactDataComponentMatcher>,
val partialComponents: List<PartialDataComponentMatcher>,
)
public sealed interface PropertyMatcher {
public val name: String
public data class Exact(override val name: String, val value: String) :
PropertyMatcher
public data class Ranged(override val name: String, val minValue: String?, val maxValue: String?) :
PropertyMatcher
}
public data class ExactDataComponentMatcher(val typeId: Int, val value: DataComponent)
public enum class PartialComponentPredicateType(public val id: Int) {
DAMAGE(0),
ENCHANTMENTS(1),
STORED_ENCHANTMENTS(2),
POTION_CONTENTS(3),
CUSTOM_DATA(4),
CONTAINER(5),
BUNDLE_CONTENTS(6),
FIREWORK_EXPLOSION(7),
FIREWORKS(8),
WRITABLE_BOOK_CONTENT(9),
WRITTEN_BOOK_CONTENT(10),
ATTRIBUTE_MODIFIERS(11),
TRIM(12),
JUKEBOX_PLAYABLE(13);
public companion object {
public fun fromID(id: Int): PartialComponentPredicateType = entries.first { it.id == id }
}
}
public data class PartialDataComponentMatcher(val type: PartialComponentPredicateType, val nbt: NBTCompound)
internal fun BytesBuffer.readBlockPredicate(): BlockPredicate {
val blocks = readPrefixOptional { readIdSet() }
val properties = readPrefixOptional { readPrefixed { readPropertyMatcher() } }
val nbt = readPrefixOptional { readNetworkNBTCompound() }
val components = readPrefixed { readExactDataComponentMatcher() }
val partialComponents = readPrefixed { readPartialDataComponentMatcher() }
return BlockPredicate(
blocks = blocks,
properties = properties,
nbt = nbt,
components = components,
partialComponents = partialComponents
)
}
internal fun BytesBuffer.writeBlockPredicate(value: BlockPredicate) {
writePrefixedOptional(value.blocks) { writeIdSet(it) }
writePrefixedOptional(value.properties) { list ->
writePrefixed(list) { writePropertyMatcher(it) }
}
writePrefixedOptional(value.nbt) { writeNetworkNBTCompound(it) }
writePrefixed(value.components) { writeExactDataComponentMatcher(it) }
writePrefixed(value.partialComponents) { writePartialDataComponentMatcher(it) }
}
internal fun BytesBuffer.readPropertyMatcher(): PropertyMatcher {
val name = readMcString()
val isExactMatch = readBoolean()
return if (isExactMatch) {
PropertyMatcher.Exact(name, readMcString())
} else {
val minValue = readPrefixOptional { readMcString() }
val maxValue = readPrefixOptional { readMcString() }
PropertyMatcher.Ranged(name, minValue, maxValue)
}
}
internal fun BytesBuffer.writePropertyMatcher(value: PropertyMatcher) {
writeMcString(value.name)
when (value) {
is PropertyMatcher.Exact -> {
writeBoolean(true)
writeMcString(value.value)
}
is PropertyMatcher.Ranged -> {
writeBoolean(false)
writePrefixedOptional(value.minValue) { writeMcString(it) }
writePrefixedOptional(value.maxValue) { writeMcString(it) }
}
}
}
internal fun BytesBuffer.readExactDataComponentMatcher(): ExactDataComponentMatcher {
val typeId = readVarInt()
val component = readDataComponent(typeId)
return ExactDataComponentMatcher(typeId, component)
}
internal fun BytesBuffer.writeExactDataComponentMatcher(value: ExactDataComponentMatcher) {
writeVarInt(value.typeId)
writeDataComponent(value.value)
}
internal fun BytesBuffer.readPartialDataComponentMatcher(): PartialDataComponentMatcher {
val typeId = readVarInt()
val predicateNbt = readNetworkNBTCompound()
return PartialDataComponentMatcher(PartialComponentPredicateType.fromID(typeId), predicateNbt)
}
internal fun BytesBuffer.writePartialDataComponentMatcher(value: PartialDataComponentMatcher) {
writeVarInt(value.type.id)
writeNetworkNBTCompound(value.nbt)
}
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.primitives.IdSet
public data class DamageReduction(
val horizontalBlockingAngle: Float,
val type: IdSet?,
val base: Float,
val factor: Float,
)
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public data class EnchantmentEntry(val typeId: Int, val level: Int)
@@ -0,0 +1,22 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class EquipmentSlot(public val id: Int) {
MAINHAND(0),
FEET(1),
LEGS(2),
CHEST(3),
HEAD(4),
OFFHAND(5),
BODY(6);
public companion object {
public fun fromID(id: Int): EquipmentSlot = entries.first { it.id == id }
}
}
@@ -0,0 +1,47 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writePrefixed
import cn.rtast.libmc.primitives.writeVarInt
public data class FireworkExplosionAttribute(
val shape: FireworkExplosionShape,
val colors: List<Int>,
val fadeColors: List<Int>,
val hasTrail: Boolean,
val hasTwinkle: Boolean,
)
public enum class FireworkExplosionShape(public val id: Int) {
SmallBall(0), LargeBall(1), Star(2), Creeper(3), Burst(4);
public companion object {
public fun fromID(id: Int): FireworkExplosionShape = entries.first { it.id == id }
}
}
internal fun BytesBuffer.readFireworkExplosion(): FireworkExplosionAttribute {
val shape = FireworkExplosionShape.Companion.fromID(readVarInt())
val colors = readPrefixed { readInt() }
val fadeColors = readPrefixed { readInt() }
val hasTrail = readBoolean()
val hasTwinkle = readBoolean()
return FireworkExplosionAttribute(shape, colors, fadeColors, hasTrail, hasTwinkle)
}
internal fun BytesBuffer.writeFireworkExplosion(explosion: FireworkExplosionAttribute) {
writeVarInt(explosion.shape.id)
writePrefixed(explosion.colors) { writeInt(it) }
writePrefixed(explosion.fadeColors) { writeInt(it) }
writeBoolean(explosion.hasTrail)
writeBoolean(explosion.hasTwinkle)
}
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.primitives.IdOrX
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.sound.SoundEvent
public data class InstrumentAttribute(
val sound: IdOrX<SoundEvent>,
val useDuration: Float,
val range: Float,
val description: TextComponent,
)
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class ItemRarity(public val id: Int) {
COMMON(0),
UNCOMMON(1),
RARE(2),
EPIC(3);
public companion object {
public fun fromID(id: Int): ItemRarity = entries.first { it.id == id }
}
}
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.primitives.IdOrX
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.sound.SoundEvent
public data class JukeboxSong(
val sound: IdOrX<SoundEvent>,
val description: TextComponent,
val duration: Float,
val output: Int,
)
@@ -0,0 +1,31 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
public data class KineticWeaponCondition(
val maxDurationTicks: Int,
val minSpeed: Float,
val minRelativeSpeed: Float,
)
internal fun BytesBuffer.readKineticWeaponCondition(): KineticWeaponCondition {
val duration = readVarInt()
val minSpeed = readFloat()
val minRelativeSpeed = readFloat()
return KineticWeaponCondition(duration, minSpeed, minRelativeSpeed)
}
internal fun BytesBuffer.writeKineticWeaponCondition(condition: KineticWeaponCondition) {
writeVarInt(condition.maxDurationTicks)
writeFloat(condition.minSpeed)
writeFloat(condition.minRelativeSpeed)
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class MapPostProcessingType(public val id: Int) {
LOCK(0), SCALE(1);
public companion object {
public fun fromID(id: Int): MapPostProcessingType = entries.first { it.id == id }
}
}
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public data class StoredEnchantment(val enchantmentId: Int, val level: Int)
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public enum class SwingAnimationType(public val id: Int) {
NONE(0), WHACK(1), STAB(2);
public companion object {
public fun fromID(id: Int): SwingAnimationType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.primitives.IdSet
public data class ToolRule(
val blocks: IdSet,
val speed: Float?,
val correctForDrops: Boolean?,
)
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public data class TrimMaterial(
val suffix: String,
val overrides: List<TrimMaterialOverride>,
val description: TextComponent,
)
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.protocol.protocol.game.Identifier
public data class TrimMaterialOverride(val armorMaterialType: Identifier, val overriddenAssetName: String)
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public data class TrimPattern(
val assetName: String,
val templateItem: Int,
val description: TextComponent,
val decal: Boolean,
)
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
public data class WritablePage(val rawContent: String, val filteredContent: String?)
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.data.component.attributes
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public data class WrittenPage(val rawContent: TextComponent, val filteredContent: TextComponent?)
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class AxolotlVariantType(public val id: Int) {
LUCY(0), WILD(1), GOLD(2), CYAN(3), BLUE(4);
public companion object {
public fun fromID(id: Int): AxolotlVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class FoxVariantType(public val id: Int) {
RED(0), SNOW(1);
public companion object {
public fun fromID(id: Int): FoxVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class HorseVariantType(public val id: Int) {
WHITE(0), CREAMY(1), CHESTNUT(2), BROWN(3), BLACK(4), GRAY(5), DARK_BROWN(6);
public companion object {
public fun fromID(id: Int): HorseVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class LlamaVariantType(public val id: Int) {
CREAMY(0), WHITE(1), BROWN(2), GRAY(3);
public companion object {
public fun fromID(id: Int): LlamaVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class MooshroomVariantType(public val id: Int) {
RED(0), BROWN(1);
public companion object {
public fun fromID(id: Int): MooshroomVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class ParrotVariantType(public val id: Int) {
RED_BLUE(0), BLUE(1), GREEN(2), YELLOW_BLUE(3), GRAY(4);
public companion object {
public fun fromID(id: Int): ParrotVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,17 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class RabbitVariantType(public val id: Int) {
BROWN(0), WHITE(1), BLACK(2), WHITE_SPLOTCHED(3),
GOLD(4), SALT(5), EVIL(6);
public companion object {
public fun fromID(id: Int): RabbitVariantType = entries.first { it.id == id }
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class SalmonSizeType(public val id: Int) {
SMALL(0), MEDIUM(1), LARGE(2);
public companion object {
public fun fromID(id: Int): SalmonSizeType = entries.first { it.id == id }
}
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class TropicalFishPatternType(public val id: Int) {
KOB(0),
SUNSTREAK(1),
SNOOPER(2),
DASHER(3),
BRINELY(4),
SPOTTY(5),
FLOPPER(6),
STRIPEY(7),
GLITTER(8),
BLOCKFISH(9),
BETTY(10),
CLAYFISH(11);
public companion object {
public fun fromID(id: Int): TropicalFishPatternType = entries.first { it.id == id }
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.item
public enum class ItemUseAnimation(public val id: Int) {
NONE(0),
EAT(1),
DRINK(2),
BLOCK(3),
BOW(4),
SPEAR(5),
CROSSBOW(6),
SPYGLASS(7),
TOOT_HORN(8),
BRUSH(9);
public companion object {
public fun fromID(id: Int): ItemUseAnimation = entries.first { it.id == id }
}
}
@@ -0,0 +1,43 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.item
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.writePrefixedOptional
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.writeTextComponent
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public data class PaintingVariantType(
val width: Int,
val height: Int,
val assetId: Identifier,
val title: TextComponent?,
val author: TextComponent?,
)
internal fun BytesBuffer.readPaintingVariantType(): PaintingVariantType {
val width = readInt()
val height = readInt()
val assetId = readIdentifier()
val title = readPrefixOptional { readTextComponent() }
val author = readPrefixOptional { readTextComponent() }
return PaintingVariantType(width, height, assetId, title, author)
}
internal fun BytesBuffer.writePaintingVariantType(variant: PaintingVariantType) {
writeInt(variant.width)
writeInt(variant.height)
writeIdentifier(variant.assetId)
writePrefixedOptional(variant.title) { writeTextComponent(it) }
writePrefixedOptional(variant.author) { writeTextComponent(it) }
}
@@ -0,0 +1,9 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.item.slot
@@ -0,0 +1,59 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.item.slot
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.data.component.DataComponent
import cn.rtast.libmc.protocol.registry.readDataComponent
import cn.rtast.libmc.protocol.registry.writeDataComponent
public typealias ItemStack = Slot
public data class Slot(
val count: Int,
val itemId: Int?,
val componentsToAdd: List<DataComponent>,
val componentsToRemove: List<Int>,
) {
val isEmpty: Boolean get() = count <= 0
public companion object {
public val EMPTY: Slot = Slot(0, null, emptyList(), emptyList())
}
}
internal fun BytesBuffer.readSlot(): Slot {
val count = readVarInt()
if (count <= 0) return Slot.EMPTY
val itemId = readVarInt()
val addCount = readVarInt()
val removeCount = readVarInt()
val componentsToAdd = ArrayList<DataComponent>(addCount)
repeat(addCount) {
val componentId = readVarInt()
componentsToAdd.add(readDataComponent(componentId))
}
val componentsToRemove = ArrayList<Int>(removeCount)
repeat(removeCount) { componentsToRemove.add(readVarInt()) }
return Slot(count, itemId, componentsToAdd, componentsToRemove)
}
internal fun BytesBuffer.writeSlot(slot: Slot) {
if (slot.isEmpty || slot.itemId == null) {
writeVarInt(0)
return
}
writeVarInt(slot.count)
writeVarInt(slot.itemId)
writeVarInt(slot.componentsToAdd.size)
writeVarInt(slot.componentsToRemove.size)
for (component in slot.componentsToAdd) writeDataComponent(component)
for (typeId in slot.componentsToRemove) writeVarInt(typeId)
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.player.skin
public enum class TextureModelType(public val id: Int) {
WIDE(0), SLIM(1);
public companion object {
public fun fromID(id: Int): TextureModelType = entries.first { it.id == id }
}
}
@@ -0,0 +1,90 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.potion
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.IdSet
import cn.rtast.libmc.primitives.readIdSet
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeIdSet
import cn.rtast.libmc.primitives.writePrefixed
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.sound.SoundEvent
import cn.rtast.libmc.protocol.protocol.game.sound.readSoundEvent
import cn.rtast.libmc.protocol.protocol.game.sound.writeSoundEvent
public sealed interface ConsumeEffect {
public data class ApplyEffects(val effects: List<PotionEffect>, val probability: Float) : ConsumeEffect
public data class RemoveEffects(val effects: IdSet) : ConsumeEffect
public data object ClearAllEffects : ConsumeEffect
public data class TeleportRandomly(val diameter: Float) : ConsumeEffect
public data class PlaySound(val sound: SoundEvent) : ConsumeEffect
}
internal fun BytesBuffer.readConsumeEffects(): List<ConsumeEffect> {
return readPrefixed {
when (val typeId = readVarInt()) {
0 -> {
val effects = readPrefixed { readPotionEffect() }
val probability = readFloat()
ConsumeEffect.ApplyEffects(effects, probability)
}
1 -> {
val effects = readIdSet()
ConsumeEffect.RemoveEffects(effects)
}
2 -> ConsumeEffect.ClearAllEffects
3 -> {
val diameter = readFloat()
ConsumeEffect.TeleportRandomly(diameter)
}
4 -> {
val sound = readSoundEvent()
ConsumeEffect.PlaySound(sound)
}
else -> throw IllegalArgumentException("Unknown ConsumeEffect type ID: $typeId")
}
}
}
internal fun BytesBuffer.writeConsumeEffects(effects: List<ConsumeEffect>) {
writePrefixed(effects) { effect ->
when (effect) {
is ConsumeEffect.ApplyEffects -> {
writeVarInt(0)
writePrefixed(effect.effects) { writePotionEffect(it) }
writeFloat(effect.probability)
}
is ConsumeEffect.RemoveEffects -> {
writeVarInt(1)
writeIdSet(effect.effects)
}
is ConsumeEffect.ClearAllEffects -> {
writeVarInt(2)
}
is ConsumeEffect.TeleportRandomly -> {
writeVarInt(3)
writeFloat(effect.diameter)
}
is ConsumeEffect.PlaySound -> {
writeVarInt(4)
writeSoundEvent(effect.sound)
}
}
}
}
@@ -0,0 +1,45 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.potion
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writePrefixedOptional
import cn.rtast.libmc.primitives.writeVarInt
public data class PotionEffect(
val effectId: Int,
val amplifier: Int,
val duration: Int,
val ambient: Boolean,
val showParticles: Boolean,
val showIcon: Boolean,
val hideEffect: PotionEffect?,
)
internal fun BytesBuffer.readPotionEffect(): PotionEffect {
val effectId = readVarInt()
val amplifier = readVarInt()
val duration = readVarInt()
val ambient = readBoolean()
val showParticles = readBoolean()
val showIcon = readBoolean()
val hideEffect = readPrefixOptional { readPotionEffect() }
return PotionEffect(effectId, amplifier, duration, ambient, showParticles, showIcon, hideEffect)
}
internal fun BytesBuffer.writePotionEffect(effect: PotionEffect) {
writeVarInt(effect.effectId)
writeVarInt(effect.amplifier)
writeVarInt(effect.duration)
writeBoolean(effect.ambient)
writeBoolean(effect.showParticles)
writeBoolean(effect.showIcon)
writePrefixedOptional(effect.hideEffect) { writePotionEffect(it) }
}
@@ -0,0 +1,28 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.potion
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
public data class SuspiciousStewEffect(val effectId: Int, val duration: Int)
internal fun BytesBuffer.readSuspiciousStewEffect(): SuspiciousStewEffect {
val effectId = readVarInt()
val duration = readVarInt()
return SuspiciousStewEffect(
effectId = effectId,
duration = duration
)
}
internal fun BytesBuffer.writeSuspiciousStewEffect(effect: SuspiciousStewEffect) {
writeVarInt(effect.effectId)
writeVarInt(effect.duration)
}
@@ -0,0 +1,69 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.session
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.*
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.player.skin.TextureModelType
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
import kotlin.uuid.Uuid
public data class ResolvableProfile(
val profile: UnpackedProfile,
val body: Identifier?,
val cape: Identifier?,
val elytra: Identifier?,
val model: TextureModelType?,
) {
public sealed interface UnpackedProfile {
public data class PartialProfile(
val username: String?,
val uuid: Uuid?,
val properties: List<GameProfile.Property>,
) : UnpackedProfile
public data class CompleteProfile(val profile: GameProfile) : UnpackedProfile
}
}
internal fun BytesBuffer.readResolvableProfile(): ResolvableProfile {
val kind = readVarInt()
val profile = if (kind == 0) {
val username = readPrefixOptional { readMcString() }
val uuid = readPrefixOptional { readUuid() }
val properties = readPrefixed { GameProfile.Property.decode(this) }
ResolvableProfile.UnpackedProfile.PartialProfile(username, uuid, properties)
} else ResolvableProfile.UnpackedProfile.CompleteProfile(GameProfile.decode(this))
val body = readPrefixOptional { readIdentifier() }
val cape = readPrefixOptional { readIdentifier() }
val elytra = readPrefixOptional { readIdentifier() }
val model = readPrefixOptional { TextureModelType.fromID(readVarInt()) }
return ResolvableProfile(profile, body, cape, elytra, model)
}
internal fun BytesBuffer.writeResolvableProfile(profile: ResolvableProfile) {
when (profile.profile) {
is ResolvableProfile.UnpackedProfile.CompleteProfile -> {
writeVarInt(1)
GameProfile.encode(this, profile.profile.profile)
}
is ResolvableProfile.UnpackedProfile.PartialProfile -> {
writeVarInt(0)
writePrefixedOptional(profile.profile.username) { writeMcString(it) }
writePrefixedOptional(profile.profile.uuid) { writeUuid(it) }
writePrefixed(profile.profile.properties) { GameProfile.Property.encode(this, it) }
}
}
writePrefixedOptional(profile.body) { writeIdentifier(it) }
writePrefixedOptional(profile.cape) { writeIdentifier(it) }
writePrefixedOptional(profile.elytra) { writeIdentifier(it) }
writePrefixedOptional(profile.model) { writeVarInt(it.id) }
}
@@ -19,7 +19,7 @@ public data class SoundEvent(val name: Identifier, val hasFixedRange: Boolean, v
internal fun BytesBuffer.readSoundEvent(): SoundEvent {
val name = readIdentifier()
val hasFixedRange = readBoolean()
val fixedRange = readOptional { readFloat() }
val fixedRange = readOptional(hasFixedRange) { readFloat() }
return SoundEvent(name, hasFixedRange, fixedRange)
}
@@ -0,0 +1,13 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.protocol.game.world
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.block.BlockPos
public data class GlobalPos(val dimension: Identifier, val pos: BlockPos)
@@ -13,4 +13,12 @@ import cn.rtast.libmc.protocol.protocol.state.ProtocolState
public sealed interface SessionEvent {
public data class DisconnectedEvent(val reason: TextComponent, val state: ProtocolState) : SessionEvent
public object ConnectedEvent : SessionEvent
public sealed interface ChangedState : SessionEvent {
public data object STATUS : ChangedState
public data object HANDSHAKE : ChangedState
public data object LOGIN : ChangedState
public data object CONFIGURATION : ChangedState
public data object PLAY : ChangedState
public data object DISCONNECTED : ChangedState
}
}
@@ -0,0 +1,171 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/10
*/
package cn.rtast.libmc.protocol.registry
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.data.component.DataComponent
import kotlin.reflect.KClass
internal object DataComponentRegistry {
private val codecMap = mutableMapOf<Int, PacketCodec<out DataComponent>>()
private val classToIdMap = mutableMapOf<KClass<out DataComponent>, Int>()
private lateinit var codecArray: Array<PacketCodec<DataComponent>>
private fun <T : DataComponent> register(kClass: KClass<T>, codec: PacketCodec<T>) {
val id = codecMap.size
codecMap[id] = codec
classToIdMap[kClass] = id
}
inline fun <reified T : DataComponent> register(codec: PacketCodec<T>) = register(T::class, codec)
fun freeze() {
val maxId = codecMap.keys.maxOrNull() ?: -1
codecArray = Array(maxId + 1) { index ->
@Suppress("UNCHECKED_CAST")
codecMap[index] as? PacketCodec<DataComponent> ?: error("Missing component codec for $index")
}
codecMap.clear()
}
internal fun read(typeId: Int, buffer: BytesBuffer): DataComponent {
val codec = codecArray.getOrNull(typeId) ?: error("Unknown DataComponent ID: $typeId")
return codec.decode(buffer)
}
internal fun write(buffer: BytesBuffer, component: DataComponent) {
val typeId = classToIdMap[component::class] ?: error("Unregistered DataComponent class: ${component::class}")
val codec = codecArray[typeId]
buffer.writeVarInt(typeId)
codec.encode(buffer, component)
}
init {
register(DataComponent.CustomDataComponent)
register(DataComponent.MaxStackSizeComponent)
register(DataComponent.MaxDamageComponent)
register(DataComponent.DamageComponent)
register(DataComponent.UnbreakableComponent)
register(DataComponent.UseEffectsComponent)
register(DataComponent.CustomNameComponent)
register(DataComponent.MinimumAttackChargeComponent)
register(DataComponent.DamageTypeComponent)
register(DataComponent.ItemNameComponent)
register(DataComponent.ItemModelComponent)
register(DataComponent.LoreComponent)
register(DataComponent.RarityComponent)
register(DataComponent.EnchantmentsComponent)
register(DataComponent.CanPlaceOnComponent)
register(DataComponent.CanBreakComponent)
register(DataComponent.AttributeModifiersComponent)
register(DataComponent.CustomModelDataComponent)
register(DataComponent.TooltipDisplayComponent)
register(DataComponent.RepairCostComponent)
register(DataComponent.CreativeSlotLockComponent)
register(DataComponent.EnchantmentGlintOverrideComponent)
register(DataComponent.IntangibleProjectileComponent)
register(DataComponent.FoodComponent)
register(DataComponent.ConsumableComponent)
register(DataComponent.UseRemainderComponent)
register(DataComponent.UseCooldownComponent)
register(DataComponent.DamageResistantComponent)
register(DataComponent.ToolComponent)
register(DataComponent.WeaponComponent)
register(DataComponent.AttackRangeComponent)
register(DataComponent.EnchantableComponent)
register(DataComponent.EquippableComponent)
register(DataComponent.RepairableComponent)
register(DataComponent.GliderComponent)
register(DataComponent.TooltipStyleComponent)
register(DataComponent.DeathProtectionComponent)
register(DataComponent.BlocksAttacksComponent)
register(DataComponent.PiercingWeaponComponent)
register(DataComponent.KineticWeaponComponent)
register(DataComponent.SwingAnimationComponent)
register(DataComponent.AdditionalTradeCostComponent)
register(DataComponent.StoredEnchantmentsComponent)
register(DataComponent.DyeComponent)
register(DataComponent.DyedColorComponent)
register(DataComponent.MapColorComponent)
register(DataComponent.MapIdComponent)
register(DataComponent.MapDecorationsComponent)
register(DataComponent.MapPostProcessingComponent)
register(DataComponent.ChargedProjectilesComponent)
register(DataComponent.BundleContentsComponent)
register(DataComponent.PotionContentsComponent)
register(DataComponent.PotionDurationScaleComponent)
register(DataComponent.SuspiciousStewEffectsComponent)
register(DataComponent.WritableBookContentComponent)
register(DataComponent.WrittenBookContentComponent)
register(DataComponent.TrimComponent)
register(DataComponent.DebugStickStateComponent)
register(DataComponent.EntityDataComponent)
register(DataComponent.BucketEntityDataComponent)
register(DataComponent.BlockEntityDataComponent)
register(DataComponent.InstrumentComponent)
register(DataComponent.ProvidesTrimMaterialComponent)
register(DataComponent.OminousBottleAmplifierComponent)
register(DataComponent.JukeboxPlayableComponent)
register(DataComponent.ProvidesBannerPatternsComponent)
register(DataComponent.RecipesComponent)
register(DataComponent.LodestoneTrackerComponent)
register(DataComponent.FireworkExplosionComponent)
register(DataComponent.FireworksComponent)
register(DataComponent.ProfileComponent)
register(DataComponent.NoteBlockSoundComponent)
register(DataComponent.BannerPatternsComponent)
register(DataComponent.BaseColorComponent)
register(DataComponent.PotDecorationsComponent)
register(DataComponent.ContainerComponent)
register(DataComponent.BlockStateComponent)
register(DataComponent.BeesComponent)
register(DataComponent.SulfurCubeContentComponent)
register(DataComponent.LockComponent)
register(DataComponent.ContainerLootComponent)
register(DataComponent.BreakSoundComponent)
register(DataComponent.VillagerVariantComponent)
register(DataComponent.WolfVariantComponent)
register(DataComponent.WolfSoundVariantComponent)
register(DataComponent.WolfCollarComponent)
register(DataComponent.FoxVariantComponent)
register(DataComponent.SalmonSizeComponent)
register(DataComponent.ParrotVariantComponent)
register(DataComponent.TropicalFishPatternComponent)
register(DataComponent.TropicalFishBaseColorComponent)
register(DataComponent.TropicalFishPatternColorComponent)
register(DataComponent.MooshroomVariantComponent)
register(DataComponent.RabbitVariantComponent)
register(DataComponent.PigVariantComponent)
register(DataComponent.PigSoundVariantComponent)
register(DataComponent.CowVariantComponent)
register(DataComponent.CowSoundVariantComponent)
register(DataComponent.ChickenVariantComponent)
register(DataComponent.ChickenSoundVariantComponent)
register(DataComponent.ZombieNautilusVariantComponent)
register(DataComponent.FrogVariantComponent)
register(DataComponent.HorseVariantComponent)
register(DataComponent.PaintingVariantComponent)
register(DataComponent.LlamaVariantComponent)
register(DataComponent.AxolotlVariantComponent)
register(DataComponent.CatVariantComponent)
register(DataComponent.CatSoundVariantComponent)
register(DataComponent.CatCollarComponent)
register(DataComponent.SheepColorComponent)
register(DataComponent.ShulkerColorComponent)
freeze()
}
}
internal fun BytesBuffer.readDataComponent(typeId: Int): DataComponent =
DataComponentRegistry.read(typeId, this)
internal fun BytesBuffer.writeDataComponent(component: DataComponent) {
DataComponentRegistry.write(this, component)
}
@@ -4,7 +4,7 @@
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.protocol
package cn.rtast.libmc.protocol.registry
import cn.rtast.libmc.protocol.packet.configuration.clientbound.*
import cn.rtast.libmc.protocol.packet.configuration.serverbound.*
@@ -23,7 +23,7 @@ import cn.rtast.libmc.protocol.packet.status.serverbound.ServerboundStatusReques
import cn.rtast.libmc.protocol.protocol.state.ProtocolState
import cn.rtast.libmc.protocol.protocol.state.ProtocolStateRegistry
internal object GamePacketsProtocolCodec {
internal object GamePacketsProtocolRegistry {
val clientboundGameProtocols = ProtocolStateRegistry().apply {
register(ProtocolState.STATUS) {
register(0x00, ClientboundStatusResponsePacket)
@@ -78,9 +78,9 @@ internal object GamePacketsProtocolCodec {
register(0x0F, ClientboundCommandSuggestionsPacket)
register(0x10, ClientboundCommandsPacket)
register(0x11, ClientboundContainerClosePacket)
// register(0x12, ClientboundContainerSetContentPacket)
register(0x12, ClientboundContainerSetContentPacket)
register(0x13, ClientboundContainerSetDataPacket)
// register(0x14, ClientboundContainerSetSlotPacket)
register(0x14, ClientboundContainerSetSlotPacket)
register(0x15, ClientboundCookieRequestPacket)
register(0x16, ClientboundCooldownPacket)
register(0x17, ClientboundCustomChatCompletionsPacket)
@@ -9,7 +9,10 @@ package client
import cn.rtast.libmc.crypto.AuthenticationProvider
import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundContainerSetContentPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundContainerSetSlotPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundServerDataPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStepTickPacket
import cn.rtast.libmc.protocol.protocol.session.SessionEvent
import cn.rtast.libmc.protocol.protocol.session.onEvent
@@ -95,13 +98,14 @@ class TestClient {
login()
// disconnect()
}
cli.onEvent<SessionEvent.DisconnectedEvent> {
println(it.reason.toJsonString())
cli.onEvent<SessionEvent.ChangedState> {
println(it)
}
cli.onPacket<ClientboundPlayerChatMessagePacket> { chatTracker.onReceivePlayerChat(it.messageSignature) }
cli.onPacket<ClientboundStepTickPacket> { println(it) }
cli.on { packet, direction -> println("$direction -> $packet") }
cli.onPacket<ClientboundContainerSetSlotPacket> { println(it) }
// cli.on { packet, direction -> println("$direction -> $packet") }
cli.connect()
// awaitCancellation()
while (true) {
}