diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundLightUpdatePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundLightUpdatePacket.kt new file mode 100644 index 0000000..d0f71f3 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundLightUpdatePacket.kt @@ -0,0 +1,36 @@ +/* + * 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.world.LightData + +public data class ClientboundLightUpdatePacket( + /** + * Chunk coordinate (block coordinate divided by 16, rounded down) + */ + val chunkX: Int, + /** + * Chunk coordinate (block coordinate divided by 16, rounded down) + */ + val chunkZ: Int, + val data: LightData, +) : MinecraftPacket { + internal companion object Codec : PacketCodec { + override fun encode(buffer: BytesBuffer, value: ClientboundLightUpdatePacket) {} + override fun decode(buffer: BytesBuffer): ClientboundLightUpdatePacket { + val chunkX = buffer.readVarInt() + val chunkZ = buffer.readVarInt() + val data = LightData.decode(buffer) + return ClientboundLightUpdatePacket(chunkX, chunkZ, data) + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/world/LightData.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/world/LightData.kt new file mode 100644 index 0000000..d306838 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/world/LightData.kt @@ -0,0 +1,98 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/11 + */ + + +package cn.rtast.libmc.protocol.protocol.game.world + +import cn.rtast.libmc.network.BytesBuffer +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.primitives.* + +public data class LightData( + val skyLightMask: BitSet, + val blockLightMask: BitSet, + val emptySkyLightMask: BitSet, + val emptyBlockLightMask: BitSet, + val skyLightArrays: List, // fixed 2048 + val blockLightArrays: List, // fixed 2048 +) { + internal companion object Codec : PacketCodec { + private const val LIGHT_ARRAY_SIZE = 2048 + override fun encode(buffer: BytesBuffer, value: LightData) { + buffer.writeBitSet(value.skyLightMask) + buffer.writeBitSet(value.blockLightMask) + buffer.writeBitSet(value.emptySkyLightMask) + buffer.writeBitSet(value.emptyBlockLightMask) + buffer.writeVarInt(value.skyLightArrays.size) + buffer.writePrefixed(value.skyLightArrays) { array -> + require(array.size == LIGHT_ARRAY_SIZE) + writeVarInt(array.size) + writeBytes(array) + } + + buffer.writePrefixed(value.blockLightArrays) { array -> + require(array.size == LIGHT_ARRAY_SIZE) + writeVarInt(array.size) + writeBytes(array) + } + } + + override fun decode(buffer: BytesBuffer): LightData { + val skyLightMask = buffer.readBitSet() + val blockLightMask = buffer.readBitSet() + val emptySkyLightMask = buffer.readBitSet() + val emptyBlockLightMask = buffer.readBitSet() + val skyLightArrays = buffer.readPrefixed { + val length = readVarInt() + require(length == LIGHT_ARRAY_SIZE) + readBytes(length) + } + val blockLightArrays = buffer.readPrefixed { + val length = readVarInt() + require(length == LIGHT_ARRAY_SIZE) + readBytes(length) + } + return LightData( + skyLightMask, blockLightMask, emptySkyLightMask, + emptyBlockLightMask, skyLightArrays, blockLightArrays + ) + } + } + + public fun hasSkyLight(sectionY: Int, minSectionY: Int): Boolean { + val bitIndex = sectionY - (minSectionY - 1) + return skyLightMask.getBit(bitIndex) + } + + + public fun hasBlockLight(sectionY: Int, minSectionY: Int): Boolean { + val bitIndex = sectionY - (minSectionY - 1) + return blockLightMask.getBit(bitIndex) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + other as LightData + if (!skyLightMask.contentEquals(other.skyLightMask)) return false + if (!blockLightMask.contentEquals(other.blockLightMask)) return false + if (!emptySkyLightMask.contentEquals(other.emptySkyLightMask)) return false + if (!emptyBlockLightMask.contentEquals(other.emptyBlockLightMask)) return false + if (skyLightArrays != other.skyLightArrays) return false + if (blockLightArrays != other.blockLightArrays) return false + return true + } + + override fun hashCode(): Int { + var result = skyLightMask.contentHashCode() + result = 31 * result + blockLightMask.contentHashCode() + result = 31 * result + emptySkyLightMask.contentHashCode() + result = 31 * result + emptyBlockLightMask.contentHashCode() + result = 31 * result + skyLightArrays.hashCode() + result = 31 * result + blockLightArrays.hashCode() + return result + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GamePacketsProtocolRegistry.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GamePacketsProtocolRegistry.kt index d04193d..3e59d5c 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GamePacketsProtocolRegistry.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GamePacketsProtocolRegistry.kt @@ -108,7 +108,7 @@ internal object GamePacketsProtocolRegistry { // register(0x2D, ClientboundLevelChunkUpdateWithLightPacket) register(0x2E, ClientboundLevelEventPacket) register(0x2F, ClientboundLevelParticlePacket) -// register(0x30, ClientboundLightUpdatePacket) + register(0x30, ClientboundLightUpdatePacket) register(0x31, ClientboundLoginPlayPacket) register(0x32, ClientboundLowDiskSpaceWarningPacket) register(0x33, ClientboundMapItemDataPacket) diff --git a/libmc-protocol/src/commonTest/kotlin/client/TestClient.kt b/libmc-protocol/src/commonTest/kotlin/client/TestClient.kt index 77dc1d8..11deb9f 100644 --- a/libmc-protocol/src/commonTest/kotlin/client/TestClient.kt +++ b/libmc-protocol/src/commonTest/kotlin/client/TestClient.kt @@ -11,6 +11,7 @@ import cn.rtast.libmc.crypto.AuthenticationProvider import cn.rtast.libmc.protocol.client.createMinecraftClient import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundContainerSetSlotPacket import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLevelParticlePacket +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.protocol.session.SessionEvent @@ -101,9 +102,8 @@ class TestClient { println(it) } cli.onPacket { chatTracker.onReceivePlayerChat(it.messageSignature) } - cli.onPacket { println(it) } - cli.onPacket { println(it) } -// cli.on { packet, direction -> println("$direction -> $packet") } + cli.onPacket { println(it) } + cli.on { packet, direction -> println("$direction -> $packet") } cli.connect() // awaitCancellation()