temporarily disable 0x12-ClientboundContainerSetContentPacket packet parsing; implemented 0x85-ClientboundUpdateRecipesPacket

This commit is contained in:
2026-09-11 13:04:12 +08:00
parent 787ff091ad
commit 3cca64d9c1
16 files changed
+371 -91

No files matched your search

@@ -18,7 +18,6 @@ internal class ClientStateMachine(private val session: Session) {
private set
suspend fun transitionTo(newState: ProtocolState) {
println("Changing State $currentState to $newState")
currentState = newState
session.emitEvent(
when (newState) {
@@ -12,21 +12,21 @@ 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.ItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.readItemStack
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 itemStackData: List<ItemStack>,
val carriedItem: ItemStack?,
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 { readItemStack() }
val slotData = buffer.readPrefixed { readSlot() }
// val carriedItem = buffer.readSlot() // TODO FIX ME
return ClientboundContainerSetContentPacket(windowId, stateId, slotData, null)
}
@@ -11,14 +11,14 @@ 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.ItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.readItemStack
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 itemStackData: ItemStack,
val slotData: Slot,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundContainerSetSlotPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundContainerSetSlotPacket) {}
@@ -26,7 +26,7 @@ public data class ClientboundContainerSetSlotPacket(
val windowId = buffer.readVarInt()
val stateId = buffer.readVarInt()
val slot = buffer.readShort()
val slotData = buffer.readItemStack()
val slotData = buffer.readSlot()
return ClientboundContainerSetSlotPacket(windowId, stateId, slot, slotData)
}
}
@@ -0,0 +1,44 @@
/*
* 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.IdSet
import cn.rtast.libmc.primitives.readIdSet
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.item.slot.SlotDisplay
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlotDisplay
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
public data class ClientboundUpdateRecipesPacket(
val propertySets: List<PropertySet>,
val stonecutterRecipes: List<StonecutterRecipes>,
) : MinecraftPacket {
public data class PropertySet(val id: Identifier, val items: List<Int>)
public data class StonecutterRecipes(val ingredients: IdSet, val slotDisplay: SlotDisplay)
internal companion object Codec : PacketCodec<ClientboundUpdateRecipesPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundUpdateRecipesPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundUpdateRecipesPacket {
val propertySets = buffer.readPrefixed {
val id = readIdentifier()
val items = readPrefixed { readVarInt() }
PropertySet(id, items)
}
val stonecutterRecipes = buffer.readPrefixed {
val ingredients = readIdSet()
val slotDisplay = readSlotDisplay()
StonecutterRecipes(ingredients, slotDisplay)
}
return ClientboundUpdateRecipesPacket(propertySets, stonecutterRecipes)
}
}
}
@@ -10,14 +10,14 @@ package cn.rtast.libmc.protocol.packet.play.serverbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.protocol.protocol.game.item.slot.ItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.writeItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.writeSlot
public data class ServerboundSetCreativeModeSlotPacket(val slot: Short, val clickedItem: ItemStack) : MinecraftPacket {
public data class ServerboundSetCreativeModeSlotPacket(val slot: Short, val clickedItem: Slot) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundSetCreativeModeSlotPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundSetCreativeModeSlotPacket) {
buffer.writeShort(value.slot)
buffer.writeItemStack(value.clickedItem)
buffer.writeSlot(value.clickedItem)
}
override fun decode(buffer: BytesBuffer): ServerboundSetCreativeModeSlotPacket =
@@ -25,9 +25,9 @@ import cn.rtast.libmc.protocol.protocol.game.entity.*
import cn.rtast.libmc.protocol.protocol.game.item.ItemUseAnimation
import cn.rtast.libmc.protocol.protocol.game.item.PaintingVariantType
import cn.rtast.libmc.protocol.protocol.game.item.readPaintingVariantType
import cn.rtast.libmc.protocol.protocol.game.item.slot.ItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.readItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.writeItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
import cn.rtast.libmc.protocol.protocol.game.item.slot.writeSlot
import cn.rtast.libmc.protocol.protocol.game.item.writePaintingVariantType
import cn.rtast.libmc.protocol.protocol.game.potion.*
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
@@ -335,14 +335,14 @@ public sealed interface DataComponent {
}
}
public data class UseRemainderComponent(val remainder: ItemStack) : DataComponent {
public data class UseRemainderComponent(val remainder: Slot) : DataComponent {
internal companion object Codec : PacketCodec<UseRemainderComponent> {
override fun encode(buffer: BytesBuffer, value: UseRemainderComponent) {
buffer.writeItemStack(value.remainder)
buffer.writeSlot(value.remainder)
}
override fun decode(buffer: BytesBuffer): UseRemainderComponent =
UseRemainderComponent(buffer.readItemStack())
UseRemainderComponent(buffer.readSlot())
}
}
@@ -722,25 +722,25 @@ public sealed interface DataComponent {
}
}
public data class ChargedProjectilesComponent(val projectiles: List<ItemStack>) : DataComponent {
public data class ChargedProjectilesComponent(val projectiles: List<Slot>) : DataComponent {
internal companion object Codec : PacketCodec<ChargedProjectilesComponent> {
override fun encode(buffer: BytesBuffer, value: ChargedProjectilesComponent) {
buffer.writePrefixed(value.projectiles) { writeItemStack(it) }
buffer.writePrefixed(value.projectiles) { writeSlot(it) }
}
override fun decode(buffer: BytesBuffer): ChargedProjectilesComponent =
ChargedProjectilesComponent(buffer.readPrefixed { readItemStack() })
ChargedProjectilesComponent(buffer.readPrefixed { readSlot() })
}
}
public data class BundleContentsComponent(val items: List<ItemStack>) : DataComponent {
public data class BundleContentsComponent(val items: List<Slot>) : DataComponent {
internal companion object Codec : PacketCodec<BundleContentsComponent> {
override fun encode(buffer: BytesBuffer, value: BundleContentsComponent) {
buffer.writePrefixed(value.items) { writeItemStack(it) }
buffer.writePrefixed(value.items) { writeSlot(it) }
}
override fun decode(buffer: BytesBuffer): BundleContentsComponent =
BundleContentsComponent(buffer.readPrefixed { readItemStack() })
BundleContentsComponent(buffer.readPrefixed { readSlot() })
}
}
@@ -1257,14 +1257,14 @@ public sealed interface DataComponent {
}
}
public data class ContainerComponent(val items: List<ItemStack>) : DataComponent {
public data class ContainerComponent(val items: List<Slot>) : DataComponent {
internal companion object Codec : PacketCodec<ContainerComponent> {
override fun encode(buffer: BytesBuffer, value: ContainerComponent) {
buffer.writePrefixed(value.items) { writeItemStack(it) }
buffer.writePrefixed(value.items) { writeSlot(it) }
}
override fun decode(buffer: BytesBuffer): ContainerComponent {
return ContainerComponent(buffer.readPrefixed { readItemStack() })
return ContainerComponent(buffer.readPrefixed { readSlot() })
}
}
}
@@ -1341,14 +1341,14 @@ public sealed interface DataComponent {
}
}
public data class SulfurCubeContentComponent(val content: ItemStack) : DataComponent {
public data class SulfurCubeContentComponent(val content: Slot) : DataComponent {
internal companion object Codec : PacketCodec<SulfurCubeContentComponent> {
override fun encode(buffer: BytesBuffer, value: SulfurCubeContentComponent) {
buffer.writeItemStack(value.content)
buffer.writeSlot(value.content)
}
override fun decode(buffer: BytesBuffer): SulfurCubeContentComponent =
SulfurCubeContentComponent(buffer.readItemStack())
SulfurCubeContentComponent(buffer.readSlot())
}
}
@@ -14,7 +14,7 @@ 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 data class ItemStack(
public data class Slot(
val count: Int,
val itemId: Int?,
val componentsToAdd: List<DataComponent>,
@@ -23,13 +23,13 @@ public data class ItemStack(
val isEmpty: Boolean get() = count <= 0
public companion object {
public val EMPTY: ItemStack = ItemStack(0, null, emptyList(), emptyList())
public val EMPTY: Slot = Slot(0, null, emptyList(), emptyList())
}
}
internal fun BytesBuffer.readItemStack(): ItemStack {
internal fun BytesBuffer.readSlot(): Slot {
val count = readVarInt()
if (count <= 0) return ItemStack.EMPTY
if (count <= 0) return Slot.EMPTY
val itemId = readVarInt()
val addCount = readVarInt()
val removeCount = readVarInt()
@@ -40,18 +40,18 @@ internal fun BytesBuffer.readItemStack(): ItemStack {
}
val componentsToRemove = ArrayList<Int>(removeCount)
repeat(removeCount) { componentsToRemove.add(readVarInt()) }
return ItemStack(count, itemId, componentsToAdd, componentsToRemove)
return Slot(count, itemId, componentsToAdd, componentsToRemove)
}
internal fun BytesBuffer.writeItemStack(itemStack: ItemStack) {
if (itemStack.isEmpty || itemStack.itemId == null) {
internal fun BytesBuffer.writeSlot(slot: Slot) {
if (slot.isEmpty || slot.itemId == null) {
writeVarInt(0)
return
}
writeVarInt(itemStack.count)
writeVarInt(itemStack.itemId)
writeVarInt(itemStack.componentsToAdd.size)
writeVarInt(itemStack.componentsToRemove.size)
for (component in itemStack.componentsToAdd) writeDataComponent(component)
for (typeId in itemStack.componentsToRemove) writeVarInt(typeId)
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,27 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
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.registry.readSlotDisplayData
import cn.rtast.libmc.protocol.registry.writeSlotDisplayData
public data class SlotDisplay(val itemDisplayType: Int, val data: SlotDisplayData)
internal fun BytesBuffer.readSlotDisplay(): SlotDisplay {
val typeId = readVarInt()
val data = readSlotDisplayData(typeId)
return SlotDisplay(typeId, data)
}
internal fun BytesBuffer.writeSlotDisplay(slot: SlotDisplay) {
writeVarInt(slot.itemDisplayType)
writeSlotDisplayData(slot.data)
}
@@ -0,0 +1,139 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.item.slot
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.EmptyPacketCodec
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writePrefixed
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public sealed interface SlotDisplayData {
public data object Empty : SlotDisplayData, EmptyPacketCodec<Empty>(Empty)
public data object AnyFuel : SlotDisplayData, EmptyPacketCodec<Empty>(Empty)
public data class WithAnyPotion(val base: SlotDisplay) : SlotDisplayData {
internal companion object Codec : PacketCodec<WithAnyPotion> {
override fun encode(buffer: BytesBuffer, value: WithAnyPotion) {
buffer.writeSlotDisplay(value.base)
}
override fun decode(buffer: BytesBuffer): WithAnyPotion = WithAnyPotion(buffer.readSlotDisplay())
}
}
public data class OnlyWithComponent(val base: SlotDisplay, val componentTypeId: Int) : SlotDisplayData {
internal companion object Codec : PacketCodec<OnlyWithComponent> {
override fun encode(buffer: BytesBuffer, value: OnlyWithComponent) {
buffer.writeSlotDisplay(value.base)
buffer.writeVarInt(value.componentTypeId)
}
override fun decode(buffer: BytesBuffer): OnlyWithComponent {
val base = buffer.readSlotDisplay()
val componentTypeId = buffer.readVarInt()
return OnlyWithComponent(base, componentTypeId)
}
}
}
public data class Item(val itemType: Int) : SlotDisplayData {
internal companion object Codec : PacketCodec<Item> {
override fun encode(buffer: BytesBuffer, value: Item) {
buffer.writeVarInt(value.itemType)
}
override fun decode(buffer: BytesBuffer): Item = Item(buffer.readVarInt())
}
}
public data class ItemStack(val item: Slot) : SlotDisplayData {
internal companion object Codec : PacketCodec<ItemStack> {
override fun encode(buffer: BytesBuffer, value: ItemStack) {
buffer.writeSlot(value.item)
}
override fun decode(buffer: BytesBuffer): ItemStack = ItemStack(buffer.readSlot())
}
}
public data class Tags(val tag: Identifier) : SlotDisplayData {
internal companion object Codec : PacketCodec<Tags> {
override fun encode(buffer: BytesBuffer, value: Tags) {
buffer.writeIdentifier(value.tag)
}
override fun decode(buffer: BytesBuffer): Tags = Tags(buffer.readIdentifier())
}
}
public data class Dyed(val dye: SlotDisplay, val target: SlotDisplay) : SlotDisplayData {
internal companion object Codec : PacketCodec<Dyed> {
override fun encode(buffer: BytesBuffer, value: Dyed) {
buffer.writeSlotDisplay(value.dye)
}
override fun decode(buffer: BytesBuffer): Dyed {
val dye = buffer.readSlotDisplay()
val target = buffer.readSlotDisplay()
return Dyed(dye, target)
}
}
}
public data class SmithingTrim(val base: SlotDisplay, val material: SlotDisplay, val pattern: Int) :
SlotDisplayData {
internal companion object Codec : PacketCodec<SmithingTrim> {
override fun encode(buffer: BytesBuffer, value: SmithingTrim) {
buffer.writeSlotDisplay(value.base)
buffer.writeSlotDisplay(value.material)
buffer.writeVarInt(value.pattern)
}
override fun decode(buffer: BytesBuffer): SmithingTrim {
val base = buffer.readSlotDisplay()
val material = buffer.readSlotDisplay()
val pattern = buffer.readVarInt()
return SmithingTrim(base, material, pattern)
}
}
}
public data class WithRemainder(val ingredient: SlotDisplay, val remainder: SlotDisplay) : SlotDisplayData {
internal companion object Codec : PacketCodec<WithRemainder> {
override fun encode(buffer: BytesBuffer, value: WithRemainder) {
buffer.writeSlotDisplay(value.ingredient)
buffer.writeSlotDisplay(value.remainder)
}
override fun decode(buffer: BytesBuffer): WithRemainder {
val ingredient = buffer.readSlotDisplay()
val remainder = buffer.readSlotDisplay()
return WithRemainder(ingredient, remainder)
}
}
}
public data class Composite(val optionCount: Int, val options: List<SlotDisplay>) : SlotDisplayData {
internal companion object Codec : PacketCodec<Composite> {
override fun encode(buffer: BytesBuffer, value: Composite) {
buffer.writePrefixed(value.options) { writeSlotDisplay(it) }
}
override fun decode(buffer: BytesBuffer): Composite {
val count = buffer.readVarInt()
val options = ArrayList<SlotDisplay>(count)
repeat(count) { options.add(buffer.readSlotDisplay()) }
return Composite(count, options)
}
}
}
}
@@ -13,8 +13,8 @@ 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.item.slot.ItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.readItemStack
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
import cn.rtast.libmc.protocol.protocol.game.math.Vec3d
import cn.rtast.libmc.protocol.protocol.game.math.readVec3d
@@ -42,7 +42,7 @@ public sealed interface ParticleData {
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 itemStack: ItemStack) : ParticleData
public data class Item(val slot: Slot) : ParticleData
}
internal fun BytesBuffer.readParticleData(id: Int): ParticleData = readParticleData(ParticleType.fromID(id))
@@ -184,7 +184,7 @@ internal fun BytesBuffer.readParticleData(type: ParticleType): ParticleData {
ParticleType.INSTANT_EFFECT,
-> ParticleData.Effect(this.readColor24(), this.readFloat())
ParticleType.ITEM -> ParticleData.Item(readItemStack())
ParticleType.ITEM -> ParticleData.Item(readSlot())
ParticleType.VIBRATION -> {
val source = when (val sourceTypeId = this.readVarInt()) {
0 -> ParticleData.Vibration.VibrationSource.Block(this.readBlockPos())
@@ -7,45 +7,9 @@
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)
}
internal object DataComponentRegistry : ArrayIndexedRegistry<DataComponent>("DataComponentRegistry") {
init {
register(DataComponent.CustomDataComponent)
register(DataComponent.MaxStackSizeComponent)
@@ -78,7 +78,7 @@ internal object GamePacketsProtocolRegistry {
register(0x0F, ClientboundCommandSuggestionsPacket)
register(0x10, ClientboundCommandsPacket)
register(0x11, ClientboundContainerClosePacket)
register(0x12, ClientboundContainerSetContentPacket)
// register(0x12, ClientboundContainerSetContentPacket) // TODO FIX ME
register(0x13, ClientboundContainerSetDataPacket)
register(0x14, ClientboundContainerSetSlotPacket)
register(0x15, ClientboundCookieRequestPacket)
@@ -193,7 +193,7 @@ internal object GamePacketsProtocolRegistry {
// register(0x82, ClientboundUpdateAdvancementsPacket)
register(0x83, ClientboundUpdateAttributesPacket)
register(0x84, ClientboundEntityEffectPacket)
// register(0x85, ClientboundUpdateRecipesPacket)
register(0x85, ClientboundUpdateRecipesPacket)
register(0x86, ClientboundUpdateTagsPacket)
register(0x87, ClientboundProjectilePowerPacket)
register(0x88, ClientboundCustomReportDetailsPacket)
@@ -0,0 +1,74 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
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 kotlin.concurrent.Volatile
import kotlin.reflect.KClass
internal interface Registry<T : Any> {
fun freeze()
fun isFrozen(): Boolean
fun getCodec(id: Int): PacketCodec<out T>?
fun getId(kClass: KClass<out T>): Int?
}
internal open class ArrayIndexedRegistry<T : Any>(private val registryName: String = "Registry") : Registry<T> {
private val codecMap = mutableMapOf<Int, PacketCodec<out T>>()
private val classToIdMap = mutableMapOf<KClass<out T>, Int>()
@Volatile
private var frozen = false
private lateinit var codecArray: Array<PacketCodec<T>>
fun <U : T> register(kClass: KClass<U>, codec: PacketCodec<U>) {
check(!frozen) { "$registryName is frozen and cannot accept new registrations." }
val id = codecMap.size
codecMap[id] = codec
classToIdMap[kClass] = id
}
inline fun <reified U : T> register(codec: PacketCodec<U>) = register(U::class, codec)
override fun freeze() {
if (frozen) return
val maxId = codecMap.keys.maxOrNull() ?: -1
codecArray = Array(maxId + 1) { index ->
@Suppress("UNCHECKED_CAST")
codecMap[index] as? PacketCodec<T> ?: error("[$registryName] Missing codec for ID: $index")
}
codecMap.clear()
frozen = true
}
override fun isFrozen(): Boolean = frozen
override fun getCodec(id: Int): PacketCodec<T>? {
check(frozen) { "$registryName is not frozen yet!" }
return codecArray.getOrNull(id)
}
override fun getId(kClass: KClass<out T>): Int? {
return classToIdMap[kClass]
}
fun read(typeId: Int, buffer: BytesBuffer): T {
val codec = getCodec(typeId) ?: error("[$registryName] Unknown type ID: $typeId")
return codec.decode(buffer)
}
fun write(buffer: BytesBuffer, value: T) {
val kClass = value::class
val typeId = getId(kClass) ?: error("[$registryName] Unregistered class: $kClass")
val codec = getCodec(typeId) ?: error("[$registryName] Missing codec for frozen ID: $typeId")
buffer.writeVarInt(typeId)
codec.encode(buffer, value)
}
}
@@ -0,0 +1,32 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.registry
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.protocol.protocol.game.item.slot.SlotDisplayData
internal object SlotDisplayDataRegistry : ArrayIndexedRegistry<SlotDisplayData>("SlotDisplayRegistry") {
init {
register(SlotDisplayData.Empty)
register(SlotDisplayData.AnyFuel)
register(SlotDisplayData.WithAnyPotion)
register(SlotDisplayData.OnlyWithComponent)
register(SlotDisplayData.Item)
register(SlotDisplayData.ItemStack)
register(SlotDisplayData.Tags)
register(SlotDisplayData.Dyed)
register(SlotDisplayData.SmithingTrim)
register(SlotDisplayData.WithRemainder)
register(SlotDisplayData.Composite)
freeze()
}
}
internal fun BytesBuffer.readSlotDisplayData(typeId: Int): SlotDisplayData = SlotDisplayDataRegistry.read(typeId, this)
internal fun BytesBuffer.writeSlotDisplayData(display: SlotDisplayData) = SlotDisplayDataRegistry.write(this, display)
@@ -15,6 +15,7 @@ import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLevelParticleP
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLightUpdatePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStepTickPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundUpdateRecipesPacket
import cn.rtast.libmc.protocol.protocol.session.SessionEvent
import cn.rtast.libmc.protocol.protocol.session.onEvent
import cn.rtast.libmc.protocol.util.generateOfflineUuid
@@ -75,8 +76,8 @@ class TestClient {
@Test
fun `test client offline mode`() = runTest {
val cli = createMinecraftClient(
"127.0.0.1", 25566, "11",
generateOfflineUuid("11"), null,
"127.0.0.1", 25566, "222",
generateOfflineUuid("222"), null,
context = {
socketEngine = KtorNetworkEngine()
}
@@ -103,7 +104,7 @@ class TestClient {
println(it)
}
cli.onPacket<ClientboundPlayerChatMessagePacket> { chatTracker.onReceivePlayerChat(it.messageSignature) }
cli.onPacket<ClientboundExplodePacket> { println(it) }
cli.onPacket<ClientboundUpdateRecipesPacket> { println(it) }
// cli.on { packet, direction -> println("$direction -> $packet") }
cli.connect()