Implemented clientbound packet map_item_data 0x33

This commit is contained in:
2026-09-07 19:52:38 +08:00
parent b16ea33ee6
commit 7b2be12d23
7 files changed
+136 -11

No files matched your search

@@ -9,23 +9,25 @@ package cn.rtast.libmc.primitives
import cn.rtast.libmc.stream.BytesBuffer
public suspend fun BytesBuffer.readBitSet(): LongArray {
public typealias BitSet = LongArray
public suspend fun BytesBuffer.readBitSet(): BitSet {
val count = this.readVarInt()
return LongArray(count) { this.readLong() }
}
public suspend fun BytesBuffer.writeBitSet(data: LongArray) {
public suspend fun BytesBuffer.writeBitSet(data: BitSet) {
this.writeVarInt(data.size)
for (i in data.indices) this.writeLong(data[i])
}
public fun LongArray.countSetBits(): Int {
public fun BitSet.countSetBits(): Int {
var count = 0
for (i in indices) count += this[i].countOneBits()
return count
}
public fun LongArray.getBit(bitIndex: Int): Boolean {
public fun BitSet.getBit(bitIndex: Int): Boolean {
val longIndex = bitIndex shr 6
if (longIndex !in this.indices) return false
val bitOffset = bitIndex and 63
@@ -45,6 +45,10 @@ public suspend fun BytesBuffer.writePrefixedStringArray(value: List<String>) {
public suspend 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."
}
val list = ArrayList<T>(count)
repeat(count) { _ -> list.add(this.reader()) }
return list
@@ -14,7 +14,7 @@ import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.client.ClientStateMachine
import cn.rtast.libmc.protocol.event.PacketEventDispatcher
import cn.rtast.libmc.protocol.protocol.GameProtocols
import cn.rtast.libmc.protocol.protocol.GamePacketsProtocolCodec
import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.stream.wrap
import cn.rtast.libmc.zlibCompress
@@ -53,7 +53,7 @@ public class NetworkChannel internal constructor(
}
val currentState = stateMachine.currentState
val packetId = payloadBuf.readVarInt()
val packet = GameProtocols.clientboundGameProtocols
val packet = GamePacketsProtocolCodec.clientboundGameProtocols
.getRegistry(currentState)
.decodePacket(packetId, payloadBuf)
dispatcher.dispatchReceive(packet)
@@ -67,7 +67,7 @@ public class NetworkChannel internal constructor(
*/
public suspend fun sendPacket(packet: MinecraftPacket) {
val uncompressedBodyBuf = BytesBuffer()
GameProtocols.serverboundGameProtocols
GamePacketsProtocolCodec.serverboundGameProtocols
.getRegistry(stateMachine.currentState)
.encodePacket(uncompressedBodyBuf, packet)
val uncompressedData = uncompressedBodyBuf.toByteArray()
@@ -0,0 +1,56 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readPrefixedByteArray
import cn.rtast.libmc.primitives.readVarInt
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.stream.BytesBuffer
public data class ClientboundMapItemDataPacket(
val mapId: Int,
val scale: Byte,
val locked: Boolean,
val icons: List<MapItemIcon>?,
val colorPatch: MapItemColorPatch?,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundMapItemDataPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundMapItemDataPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundMapItemDataPacket {
val mapId = buffer.readVarInt()
val scale = buffer.readByte()
val locked = buffer.readBoolean()
val icons = buffer.readOptional {
readPrefixed {
val type = MapItemIconType.fromID(readVarInt())
val x = readByte()
val z = readByte()
val direction = readByte()
val displayName = readOptional { readTextComponent() }
MapItemIcon(type, x, z, direction, displayName)
}
}
val columns = buffer.readUByte()
val colorPatch = if (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)
}
}
}
@@ -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 GameProtocols {
internal object GamePacketsProtocolCodec {
val clientboundGameProtocols = ProtocolStateRegistry().apply {
register(ProtocolState.STATUS) {
register(0x00, ClientboundStatusResponsePacket)
@@ -111,7 +111,7 @@ internal object GameProtocols {
// register(0x30, ClientboundLightUpdatePacket)
register(0x31, ClientboundLoginPlayPacket)
register(0x32, ClientboundLowDiskSpaceWarningPacket)
// register(0x33, ClientboundMapItemDataPacket)
register(0x33, ClientboundMapItemDataPacket)
// register(0x34, ClientboundMerchantOffersPacket)
register(0x35, ClientboundUpdateEntityPositionPacket)
register(0x36, ClientboundUpdateEntityPositionAndRotationPacket)
@@ -0,0 +1,63 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/7
*/
package cn.rtast.libmc.protocol.protocol.game.item
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public enum class MapItemIconType(public val id: Int) {
WHITE_ARROW(0), GREEN_ARROW(1), RED_ARROW(2), BLUE_ARROW(3),
WHITE_CROSS(4), RED_POINTER(5), WHITE_CIRCLE(6), SMALL_WHITE_CIRCLE(7),
MANSION(8), MONUMENT(9), WHITE_BANNER(10), ORANGE_BANNER(11),
MAGENTA_BANNER(12), LIGHT_BLUE_BANNER(13), YELLOW_BANNER(14),
LIME_BANNER(15), PINK_BANNER(16), GRAY_BANNER(17), LIGHT_GRAY_BANNER(18),
CYAN_BANNER(19), PURPLE_BANNER(20), BLUE_BANNER(21), BROWN_BANNER(22),
GREEN_BANNER(23), RED_BANNER(24), BLACK_BANNER(25), TREASURE_MARKER(26),
DESERT_VILLAGE(27), PLAINS_VILLAGE(28), SAVANNA_VILLAGE(29), SNOWY_VILLAGE(30),
TAIGA_VILLAGE(31), JUNGLE_TEMPLE(32), SWAMP_HUT(33), TRIAL_CHAMBERS(34);
public companion object {
public fun fromID(id: Int): MapItemIconType = entries.find { it.id == id } ?: WHITE_ARROW
}
}
public data class MapItemIcon(
val type: MapItemIconType,
val x: Byte,
val z: Byte,
val direction: Byte,
val displayName: TextComponent?
)
public data class MapItemColorPatch(
val columns: UByte,
val rows: UByte,
val xOffset: UByte,
val zOffset: UByte,
val data: ByteArray
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as MapItemColorPatch
if (columns != other.columns) return false
if (rows != other.rows) return false
if (xOffset != other.xOffset) return false
if (zOffset != other.zOffset) return false
if (!data.contentEquals(other.data)) return false
return true
}
override fun hashCode(): Int {
var result = columns.hashCode()
result = 31 * result + rows.hashCode()
result = 31 * result + xOffset.hashCode()
result = 31 * result + zOffset.hashCode()
result = 31 * result + data.contentHashCode()
return result
}
}
@@ -81,8 +81,8 @@ class TestClientTestInJvm {
null,
crypto = DefaultProtocolContext
)
// cli.on { packet, direction -> println("$direction -> $packet") }
cli.onPacket<ClientboundSystemChatMessagePacket> { println(it.content.content) }
cli.on { packet, direction -> println("$direction -> $packet") }
// cli.onPacket<ClientboundSystemChatMessagePacket> { println(it.content.content) }
cli.launch { cli.connect() }
while (true) {
}