From b16ea33ee68856be4867bf6ae754f9413770db1f Mon Sep 17 00:00:00 2001 From: RTAkland Date: Mon, 7 Sep 2026 18:21:42 +0800 Subject: [PATCH] Support TextComponent AST parser and command binary graph AST parser --- README.md | 17 +- .../cn/rtast/libmc/crypto/ProtocolContext.kt | 7 +- .../libmc/protocol/client/MinecraftClient.kt | 2 +- .../event/InternalPacketDispatcher.kt | 4 +- .../ClientboundAddResourcePackPacket.kt | 8 +- ...lientboundDisconnectConfigurationPacket.kt | 10 +- .../ClientboundServerLinksPacket.kt | 2 +- .../ClientboundDisconnectLoginPacket.kt | 10 +- .../ClientboundLoginSuccessPacket.kt | 2 +- .../clientbound/ClientboundBossEventPacket.kt | 10 +- .../ClientboundChangeDifficultyPacket.kt | 2 +- .../ClientboundCommandSuggestionsPacket.kt | 17 +- .../clientbound/ClientboundCommandsPacket.kt | 26 + .../ClientboundDisconnectPlayPacket.kt | 10 +- .../ClientboundDisguisedChatMessagePacket.kt | 18 +- .../ClientboundOpenScreenPacket.kt | 12 +- .../ClientboundPlayerChatMessagePacket.kt | 18 +- .../ClientboundPlayerCombatDeathPacket.kt | 12 +- .../ClientboundServerDataPacket.kt | 12 +- .../ClientboundSetActionBarTextPacket.kt | 12 +- .../ClientboundSetSubtitleTextPacket.kt | 12 +- ...entboundSetTabListHeaderAndFooterPacket.kt | 14 +- .../ClientboundSetTitleTextPacket.kt | 12 +- .../ClientboundSystemChatMessagePacket.kt | 10 +- ...lientboundTestInstanceBlockStatusPacket.kt | 12 +- .../ClientboundUpdateObjectivePacket.kt | 11 +- .../ClientboundUpdateScorePacket.kt | 13 +- .../ServerboundChangeDifficultyPacket.kt | 2 +- .../ServerboundClientActionPacket.kt | 2 +- ...erverboundTestInstanceBlockActionPacket.kt | 6 +- .../libmc/protocol/protocol/GameProtocols.kt | 2 +- .../protocol/game/bossbar/BossBarAction.kt | 6 +- .../protocol/game/chat/ChatTypeDecoration.kt | 2 +- .../protocol/game/chat/TextComponent.kt | 622 ++++++++++++++++++ .../game/command/CommandArgumentProperties.kt | 42 ++ .../protocol/game/command/CommandNode.kt | 233 +++++++ .../game/command/CommandSuggestionsType.kt | 22 + .../game}/registry/ClientAction.kt | 2 +- .../game}/registry/GameDifficulty.kt | 2 +- .../registry/report/BuiltinServerLinkType.kt | 2 +- .../game}/registry/report/ServerLink.kt | 14 +- .../game/scoreboard/ObjectivePayload.kt | 4 +- .../game/scoreboard/ScoreNumberFormat.kt | 4 +- .../game}/session/GameProfile.kt | 2 +- .../{session => util}/TransactionIdManager.kt | 2 +- .../kotlin/test/TestClientTestInJvm.kt | 53 +- 46 files changed, 1114 insertions(+), 205 deletions(-) create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandsPacket.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/TextComponent.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandArgumentProperties.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandNode.kt create mode 100644 libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandSuggestionsType.kt rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{ => protocol/game}/registry/ClientAction.kt (91%) rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{ => protocol/game}/registry/GameDifficulty.kt (87%) rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{ => protocol/game}/registry/report/BuiltinServerLinkType.kt (87%) rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{ => protocol/game}/registry/report/ServerLink.kt (75%) rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{ => protocol/game}/session/GameProfile.kt (97%) rename libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/{session => util}/TransactionIdManager.kt (94%) diff --git a/README.md b/README.md index bc3a77f..2a7e8ba 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,16 @@ A lightweight minecraft client-side protocol library and related library, includ ## Implementation details -- [x] **Online Mode Authentication & Encryption**: See [Implement Encryption](docs/implement-encryption.md) -- [ ] **Structured `TextComponent` Parser**: Rich Chat Component AST decoder (currently falling back to raw - `NBTCompound`). -- [ ] **Command Tree Parser (0x10)**: Full binary graph decoder for brigadier nodes, argument types, and suggestions. +- [x] **Online Mode Authentication & Encryption**: See [Implement Encryption](docs/implement-encryption.md) +- [x] **Structured `TextComponent` Parser**: TextComponent AST decoder (Some packets fallback to Raw NBTCompound) +- [x] **Command Tree Parser (0x10)**: Full binary graph decoder for brigadier nodes, argument types, and suggestions - [ ] **Recipe Book & Recipe Data (0x3F, 0x4A, 0x4B, 0x4C, 0x85)**: Recipe layout declarations and client-side recipe - settings. + settings - [ ] **Chunk & World Data (0x2D)**: Level Chunk Data with Light decoder (Bitsets, Paletted Containers, Direct/Indirect - Palettes). -- [ ] **Light Engine Update (0x30)**: Sky & Block light nibble array parser. -- [ ] **Explosion Event Decoder (0x24)**: Knockback vectors and destroyed block offsets array. -- [ ] **Debug Packets Parsing (0x1A - 0x1E)**: Debug subs, block/entity states, and game performance sample events. + Palettes) +- [ ] **Light Engine Update (0x30)**: Sky & Block light nibble array parser +- [ ] **Explosion Event Decoder (0x24)**: Knockback vectors and destroyed block offsets array +- [ ] **Debug Packets Parsing (0x1A - 0x1E)**: Debug subs, block/entity states, and game performance sample events - [ ] **Particle Parsing** - [ ] **Slot Data Parsing** diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt index f65d335..00fb032 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/crypto/ProtocolContext.kt @@ -11,7 +11,7 @@ public data class ProtocolContext( val rsaEncryptor: RSA1024Encryptor, val sha1Hasher: Sha1Hasher, val cipherFactory: (sharedKey: ByteArray) -> NetworkCipher, - val authProvider: AuthenticationProvider, + val authProvider: AuthenticationProvider?, ) public class ProtocolContextBuilder(private val onlineMode: Boolean) { @@ -25,8 +25,9 @@ public class ProtocolContextBuilder(private val onlineMode: Boolean) { rsaEncryptor = if (::rsaEncryptor.isInitialized) rsaEncryptor else error("rsaEncryptor is required"), sha1Hasher = if (::sha1Hasher.isInitialized) sha1Hasher else error("sha1Hasher is required"), cipherFactory = if (::cipherFactory.isInitialized) cipherFactory else error("cipherFactory is required"), - authProvider = if (::authProvider.isInitialized) if (onlineMode) authProvider - else error("authProvider is required") else error("authProvider is required") + authProvider = if (onlineMode) { + if (::authProvider.isInitialized) authProvider else error("authProvider is required in online mode") + } else if (::authProvider.isInitialized) authProvider else null ) } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt index e12e44d..470f970 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/client/MinecraftClient.kt @@ -16,7 +16,7 @@ import cn.rtast.libmc.protocol.packet.handshake.ServerboundHandshakePacket import cn.rtast.libmc.protocol.packet.login.serverbound.ServerboundLoginStartPacket import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent import cn.rtast.libmc.protocol.protocol.state.ProtocolState -import cn.rtast.libmc.protocol.session.TransactionIdManager +import cn.rtast.libmc.protocol.util.TransactionIdManager import cn.rtast.libmc.protocol.util.generateOfflineUuid import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt index 2568636..2127a14 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/event/InternalPacketDispatcher.kt @@ -35,7 +35,7 @@ import cn.rtast.libmc.protocol.util.generateRandom16Bytes */ public class InternalPacketDispatcher( private val client: MinecraftClient, - private val authProvider: AuthenticationProvider, + private val authProvider: AuthenticationProvider?, ) { public suspend fun handleIncomingPackets(packet: MinecraftPacket) { when (packet) { @@ -51,7 +51,7 @@ public class InternalPacketDispatcher( val sharedSecret = generateRandom16Bytes() if (client.isOnlineMode) { val serverHash = client.serverIdHasher.hash(packet.serverId, sharedSecret, packet.publicKey) - authProvider.joinServer( + authProvider!!.joinServer( "https://sessionserver.mojang.com/session/minecraft/join", client.accessToken!!, client.uuid.toString().replace("-", ""), diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundAddResourcePackPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundAddResourcePackPacket.kt index df4c978..261e6b4 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundAddResourcePackPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundAddResourcePackPacket.kt @@ -12,9 +12,9 @@ import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readUuid 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.stream.BytesBuffer -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound import kotlin.uuid.Uuid public data class ClientboundAddResourcePackPacket( @@ -22,7 +22,7 @@ public data class ClientboundAddResourcePackPacket( val url: String, val hash: String, val forced: Boolean, - val prompt: NBTCompound, + val prompt: TextComponent, ) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundAddResourcePackPacket) {} @@ -32,7 +32,7 @@ public data class ClientboundAddResourcePackPacket( val hash = buffer.readMcString() val forced = buffer.readBoolean() buffer.readVarInt() // ? - val prompt = buffer.readNetworkNBTCompound() // ? + val prompt = buffer.readTextComponent() // ? return ClientboundAddResourcePackPacket(uuid, url, hash, forced, prompt) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundDisconnectConfigurationPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundDisconnectConfigurationPacket.kt index 2c6ef30..c3b8b2d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundDisconnectConfigurationPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundDisconnectConfigurationPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.configuration.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundDisconnectConfigurationPacket(val reason: NBTCompound) : MinecraftPacket { +public data class ClientboundDisconnectConfigurationPacket(val reason: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket { - return ClientboundDisconnectConfigurationPacket(reason = buffer.readNetworkNBTCompound()) + return ClientboundDisconnectConfigurationPacket(reason = buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundServerLinksPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundServerLinksPacket.kt index c3f6bcc..b867f0a 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundServerLinksPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/configuration/clientbound/ClientboundServerLinksPacket.kt @@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.protocol.registry.report.ServerLink +import cn.rtast.libmc.protocol.protocol.game.registry.report.ServerLink public data class ClientboundServerLinksPacket(val links: List) : MinecraftPacket { internal companion object Codec : PacketCodec { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundDisconnectLoginPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundDisconnectLoginPacket.kt index f2a7df2..b6c9f36 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundDisconnectLoginPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundDisconnectLoginPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.login.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundDisconnectLoginPacket(val reason: NBTCompound) : MinecraftPacket { +public data class ClientboundDisconnectLoginPacket(val reason: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket { - return ClientboundDisconnectLoginPacket(reason = buffer.readNetworkNBTCompound()) + return ClientboundDisconnectLoginPacket(reason = buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundLoginSuccessPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundLoginSuccessPacket.kt index 88113db..b20f126 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundLoginSuccessPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/login/clientbound/ClientboundLoginSuccessPacket.kt @@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readUuid -import cn.rtast.libmc.protocol.session.GameProfile +import cn.rtast.libmc.protocol.protocol.game.session.GameProfile import kotlin.uuid.Uuid public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) : diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundBossEventPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundBossEventPacket.kt index 9fc0ebb..ec2d31f 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundBossEventPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundBossEventPacket.kt @@ -7,7 +7,6 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readUuid @@ -16,7 +15,8 @@ import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarAction import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarColor import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarDivision import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarFlags -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer import kotlin.uuid.Uuid public data class ClientboundBossEventPacket(val uuid: Uuid, val action: BossBarAction) : MinecraftPacket { @@ -26,7 +26,7 @@ public data class ClientboundBossEventPacket(val uuid: Uuid, val action: BossBar val uuid = buffer.readUuid() val action = when (val actionId = buffer.readVarInt()) { BossBarAction.ADD_ID -> { - val title = buffer.readNetworkNBTCompound() + val title = buffer.readTextComponent() val health = buffer.readFloat() val color = BossBarColor.fromID(buffer.readVarInt()) val division = BossBarDivision.fromID(buffer.readVarInt()) @@ -35,7 +35,7 @@ public data class ClientboundBossEventPacket(val uuid: Uuid, val action: BossBar } BossBarAction.REMOVE_ID -> BossBarAction.Remove - BossBarAction.UPDATE_TITLE_ID -> BossBarAction.UpdateTitle(buffer.readNetworkNBTCompound()) + BossBarAction.UPDATE_TITLE_ID -> BossBarAction.UpdateTitle(buffer.readTextComponent()) BossBarAction.UPDATE_STYLE_ID -> { val color = BossBarColor.fromID(buffer.readVarInt()) val division = BossBarDivision.fromID(buffer.readVarInt()) @@ -46,6 +46,8 @@ public data class ClientboundBossEventPacket(val uuid: Uuid, val action: BossBar BossBarAction.UpdateFlags(BossBarFlags.fromBitmask(buffer.readByte().toInt() and 0xFF)) } + BossBarAction.UPDATE_HEALTH_ID -> BossBarAction.UpdateHealth(buffer.readFloat()) + else -> throw IllegalArgumentException("Unknown action $actionId") } return ClientboundBossEventPacket(uuid, action) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundChangeDifficultyPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundChangeDifficultyPacket.kt index 0240bfb..7cc4b48 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundChangeDifficultyPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundChangeDifficultyPacket.kt @@ -10,7 +10,7 @@ package cn.rtast.libmc.protocol.packet.play.clientbound import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec -import cn.rtast.libmc.protocol.registry.GameDifficulty +import cn.rtast.libmc.protocol.protocol.game.registry.GameDifficulty public data class ClientboundChangeDifficultyPacket(val difficulty: GameDifficulty, val locked: Boolean) : MinecraftPacket { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandSuggestionsPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandSuggestionsPacket.kt index 2c1cd08..ed83b18 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandSuggestionsPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandSuggestionsPacket.kt @@ -11,11 +11,9 @@ import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.primitives.writeMcString +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound -import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound public data class ClientboundCommandSuggestionsPacket( val id: Int, @@ -23,18 +21,13 @@ public data class ClientboundCommandSuggestionsPacket( val length: Int, val matches: List, ) : MinecraftPacket { - public data class CommandSuggestionMatch(val match: String, val tooltip: NBTCompound?) { + public data class CommandSuggestionMatch(val match: String, val tooltip: TextComponent?) { internal companion object Codec : PacketCodec { - override suspend fun encode(buffer: BytesBuffer, value: CommandSuggestionMatch) { - buffer.writeMcString(value.match) - buffer.writeBoolean(value.tooltip != null) - if (value.tooltip != null) buffer.writeNetworkNBTCompound(value.tooltip) - } - + override suspend fun encode(buffer: BytesBuffer, value: CommandSuggestionMatch) {} override suspend fun decode(buffer: BytesBuffer): CommandSuggestionMatch { val match = buffer.readMcString() val hasTooltip = buffer.readBoolean() - val tooltip = if (hasTooltip) buffer.readNetworkNBTCompound() else null + val tooltip = if (hasTooltip) buffer.readTextComponent() else null return CommandSuggestionMatch(match, tooltip) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandsPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandsPacket.kt new file mode 100644 index 0000000..b1ae5ee --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundCommandsPacket.kt @@ -0,0 +1,26 @@ +/* + * 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.readPrefixed +import cn.rtast.libmc.primitives.readVarInt +import cn.rtast.libmc.protocol.protocol.game.command.CommandNode +import cn.rtast.libmc.stream.BytesBuffer + +public data class ClientboundCommandsPacket(val nodes: List, val rootIndex: Int) : MinecraftPacket { + internal companion object Codec : PacketCodec { + override suspend fun encode(buffer: BytesBuffer, value: ClientboundCommandsPacket) {} + override suspend fun decode(buffer: BytesBuffer): ClientboundCommandsPacket { + val nodes = buffer.readPrefixed { CommandNode.decode(buffer) } + val rootIndex = buffer.readVarInt() + return ClientboundCommandsPacket(nodes, rootIndex) + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisconnectPlayPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisconnectPlayPacket.kt index c9d7072..50471e4 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisconnectPlayPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisconnectPlayPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundDisconnectPlayPacket(val reason: NBTCompound) : MinecraftPacket { +public data class ClientboundDisconnectPlayPacket(val reason: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket { - return ClientboundDisconnectPlayPacket(reason = buffer.readNetworkNBTCompound()) + return ClientboundDisconnectPlayPacket(reason = buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisguisedChatMessagePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisguisedChatMessagePacket.kt index a840642..7e86770 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisguisedChatMessagePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundDisguisedChatMessagePacket.kt @@ -12,25 +12,25 @@ 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.stream.BytesBuffer -import cn.rtast.libmc.nbt.NBTCompound 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.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer public data class ClientboundDisguisedChatMessagePacket( - val message: NBTCompound, + val message: TextComponent, val chatType: IdOrX, - val senderName: NBTCompound, - val targetName: NBTCompound?, + val senderName: TextComponent, + val targetName: TextComponent?, ) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisguisedChatMessagePacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundDisguisedChatMessagePacket { - val message = buffer.readNetworkNBTCompound() + val message = buffer.readTextComponent() val chatType = buffer.readIdOrX { readInlineChatType() } - val senderName = buffer.readNetworkNBTCompound() - val targetName = buffer.readOptional { readNetworkNBTCompound() } + val senderName = buffer.readTextComponent() + val targetName = buffer.readOptional { readTextComponent() } return ClientboundDisguisedChatMessagePacket(message, chatType, senderName, targetName) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundOpenScreenPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundOpenScreenPacket.kt index bbde8bc..78bcf37 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundOpenScreenPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundOpenScreenPacket.kt @@ -7,24 +7,24 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket +import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer public data class ClientboundOpenScreenPacket( val windowId: Int, val windowType: Int, - val title: NBTCompound, + val title: TextComponent, ) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundOpenScreenPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundOpenScreenPacket { val windowId = buffer.readVarInt() val windowType = buffer.readVarInt() - val title = buffer.readNetworkNBTCompound() + val title = buffer.readTextComponent() return ClientboundOpenScreenPacket(windowId, windowType, title) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerChatMessagePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerChatMessagePacket.kt index 526dd38..61f1a7c 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerChatMessagePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerChatMessagePacket.kt @@ -7,14 +7,14 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readUuid import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer import kotlin.uuid.Uuid /** @@ -29,12 +29,12 @@ public data class ClientboundPlayerChatMessagePacket( val timestamp: Long, val salt: Long, val previousMessages: List, - val unsignedContent: NBTCompound?, + val unsignedContent: TextComponent?, val filterType: ChatFilterType, val filterMaskBits: LongArray?, val chatType: Int, - val senderName: NBTCompound, - val targetName: NBTCompound?, + val senderName: TextComponent, + val targetName: TextComponent?, ) : MinecraftPacket { public enum class ChatFilterType(public val id: Int) { PASS_THROUGH(0), @@ -90,7 +90,7 @@ public data class ClientboundPlayerChatMessagePacket( val prevMessages = List(prevMessageCount) { PreviousMessageEntry.decode(buffer) } val hasUnsignedContent = buffer.readBoolean() - val unsignedContent = if (hasUnsignedContent) buffer.readNetworkNBTCompound() else null // ? + val unsignedContent = if (hasUnsignedContent) buffer.readTextComponent() else null // ? val filterTypeId = buffer.readVarInt() val filterType = ChatFilterType.fromID(filterTypeId) @@ -101,10 +101,10 @@ public data class ClientboundPlayerChatMessagePacket( } else null val chatType = buffer.readVarInt() - val senderName = buffer.readNetworkNBTCompound() // ? + val senderName = buffer.readTextComponent() // ? val hasTargetName = buffer.readBoolean() - val targetName = if (hasTargetName) buffer.readNetworkNBTCompound() else null // ? + val targetName = if (hasTargetName) buffer.readTextComponent() else null // ? return ClientboundPlayerChatMessagePacket( globalIndex, sender, index, messageSignature, message, diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerCombatDeathPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerCombatDeathPacket.kt index 5e51e88..339225b 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerCombatDeathPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundPlayerCombatDeathPacket.kt @@ -7,19 +7,19 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket +import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundPlayerCombatDeathPacket(val playerId: Int, val message: NBTCompound) : MinecraftPacket { +public data class ClientboundPlayerCombatDeathPacket(val playerId: Int, val message: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundPlayerCombatDeathPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundPlayerCombatDeathPacket { val playerId = buffer.readVarInt() - val message = buffer.readNetworkNBTCompound() + val message = buffer.readTextComponent() return ClientboundPlayerCombatDeathPacket(playerId, message) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundServerDataPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundServerDataPacket.kt index f41bc11..d381768 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundServerDataPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundServerDataPacket.kt @@ -7,19 +7,19 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.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.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundServerDataPacket(val motd: NBTCompound, val icon: ByteArray?) : MinecraftPacket { +public data class ClientboundServerDataPacket(val motd: TextComponent, val icon: ByteArray?) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundServerDataPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundServerDataPacket { - val motd = buffer.readNetworkNBTCompound() + val motd = buffer.readTextComponent() val icon = buffer.readOptional { val length = readVarInt(); readBytes(length) } return ClientboundServerDataPacket(motd, icon) } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetActionBarTextPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetActionBarTextPacket.kt index 2fefe27..6be31f1 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetActionBarTextPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetActionBarTextPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundSetActionBarTextPacket(val text: NBTCompound) : MinecraftPacket { +public data class ClientboundSetActionBarTextPacket(val text: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetActionBarTextPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundSetActionBarTextPacket { - return ClientboundSetActionBarTextPacket(buffer.readNetworkNBTCompound()) + return ClientboundSetActionBarTextPacket(buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetSubtitleTextPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetSubtitleTextPacket.kt index 07f0d4b..e18e3ee 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetSubtitleTextPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetSubtitleTextPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundSetSubtitleTextPacket(val subTitleText: NBTCompound) : MinecraftPacket { +public data class ClientboundSetSubtitleTextPacket(val subTitleText: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetSubtitleTextPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundSetSubtitleTextPacket { - return ClientboundSetSubtitleTextPacket(buffer.readNetworkNBTCompound()) + return ClientboundSetSubtitleTextPacket(buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTabListHeaderAndFooterPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTabListHeaderAndFooterPacket.kt index 584954d..2465414 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTabListHeaderAndFooterPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTabListHeaderAndFooterPacket.kt @@ -7,19 +7,19 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundSetTabListHeaderAndFooterPacket(val header: NBTCompound, val footer: NBTCompound) : +public data class ClientboundSetTabListHeaderAndFooterPacket(val header: TextComponent, val footer: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTabListHeaderAndFooterPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundSetTabListHeaderAndFooterPacket { - val header = buffer.readNetworkNBTCompound() - val footer = buffer.readNetworkNBTCompound() + val header = buffer.readTextComponent() + val footer = buffer.readTextComponent() return ClientboundSetTabListHeaderAndFooterPacket(header, footer) } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTitleTextPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTitleTextPacket.kt index e90e791..70ed3d4 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTitleTextPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSetTitleTextPacket.kt @@ -7,17 +7,17 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundSetTitleTextPacket(val text: NBTCompound) : MinecraftPacket { +public data class ClientboundSetTitleTextPacket(val text: TextComponent) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTitleTextPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundSetTitleTextPacket { - return ClientboundSetTitleTextPacket(buffer.readNetworkNBTCompound()) + return ClientboundSetTitleTextPacket(buffer.readTextComponent()) } } } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSystemChatMessagePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSystemChatMessagePacket.kt index 49a13d8..a4256ca 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSystemChatMessagePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundSystemChatMessagePacket.kt @@ -7,18 +7,18 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.PacketCodec -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer -public data class ClientboundSystemChatMessagePacket(val content: NBTCompound, val overlay: Boolean) : +public data class ClientboundSystemChatMessagePacket(val content: TextComponent, val overlay: Boolean) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundSystemChatMessagePacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundSystemChatMessagePacket { - val content = buffer.readNetworkNBTCompound() + val content = buffer.readTextComponent() val overlay = buffer.readBoolean() return ClientboundSystemChatMessagePacket(content, overlay) } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundTestInstanceBlockStatusPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundTestInstanceBlockStatusPacket.kt index 35e5775..3107e77 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundTestInstanceBlockStatusPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundTestInstanceBlockStatusPacket.kt @@ -7,15 +7,15 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.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.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent +import cn.rtast.libmc.stream.BytesBuffer public data class ClientboundTestInstanceBlockStatusPacket( - val status: NBTCompound, + val status: TextComponent, val hasSize: Boolean, val sizeX: Double?, val sizeY: Double?, @@ -24,7 +24,7 @@ public data class ClientboundTestInstanceBlockStatusPacket( internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ClientboundTestInstanceBlockStatusPacket) {} override suspend fun decode(buffer: BytesBuffer): ClientboundTestInstanceBlockStatusPacket { - val status = buffer.readNetworkNBTCompound() + val status = buffer.readTextComponent() val hasSize = buffer.readBoolean() val sizeX = buffer.readOptional { readDouble() } // ? val sizeY = buffer.readOptional { readDouble() } // ? diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateObjectivePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateObjectivePacket.kt index 78aa87e..e204fb0 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateObjectivePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateObjectivePacket.kt @@ -7,16 +7,17 @@ package cn.rtast.libmc.protocol.packet.play.clientbound -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.nbt.NBTTag import cn.rtast.libmc.packet.MinecraftPacket +import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readVarInt -import cn.rtast.libmc.nbt.NBTTag +import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent import cn.rtast.libmc.protocol.protocol.game.scoreboard.ObjectivePayload import cn.rtast.libmc.protocol.protocol.game.scoreboard.ObjectiveRenderType import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.stream.BytesBuffer public data class ClientboundUpdateObjectivePacket( val objectiveName: String, @@ -31,14 +32,14 @@ public data class ClientboundUpdateObjectivePacket( val payload = when (mode.toInt()) { 1 -> ObjectivePayload.Remove 0, 2 -> { - val displayName = buffer.readNetworkNBTCompound() + val displayName = buffer.readTextComponent() val renderType = ObjectiveRenderType.fromID(buffer.readVarInt()) val hasNumberFormat = buffer.readBoolean() val numberFormat = if (hasNumberFormat) { when (val type = buffer.readVarInt()) { 0 -> ScoreNumberFormat.Blank 1 -> ScoreNumberFormat.Styled(styling = buffer.readNetworkNBTCompound().element as NBTTag.CompoundTag) // fix me - 2 -> ScoreNumberFormat.Fixed(content = buffer.readNetworkNBTCompound()) // ? + 2 -> ScoreNumberFormat.Fixed(content = buffer.readTextComponent()) // ? else -> error("Unknown ScoreNumberFormat type: $type") } } else null diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateScorePacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateScorePacket.kt index 563d072..772b77d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateScorePacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/clientbound/ClientboundUpdateScorePacket.kt @@ -7,22 +7,23 @@ package cn.rtast.libmc.protocol.packet.play.clientbound +import cn.rtast.libmc.nbt.NBTTag 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.readVarInt -import cn.rtast.libmc.stream.BytesBuffer -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.nbt.NBTTag +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.stream.BytesBuffer public data class ClientboundUpdateScorePacket( val entityName: String, val objectiveName: String, val value: Int, - val displayName: NBTCompound?, + val displayName: TextComponent?, val numberFormat: ScoreNumberFormat?, ) : MinecraftPacket { internal companion object Codec : PacketCodec { @@ -31,12 +32,12 @@ public data class ClientboundUpdateScorePacket( val entityName = buffer.readMcString() val objectiveName = buffer.readMcString() val value = buffer.readVarInt() - val displayName = buffer.readOptional { readNetworkNBTCompound() } + val displayName = buffer.readOptional { readTextComponent() } val numberFormat = buffer.readOptional { when (val type = readVarInt()) { 0 -> ScoreNumberFormat.Blank 1 -> ScoreNumberFormat.Styled(styling = readNetworkNBTCompound().element as NBTTag.CompoundTag) // fix me - 2 -> ScoreNumberFormat.Fixed(content = readNetworkNBTCompound()) + 2 -> ScoreNumberFormat.Fixed(content = readTextComponent()) else -> error("Unknown ScoreNumberFormat type: $type") } } diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundChangeDifficultyPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundChangeDifficultyPacket.kt index df38eca..3dd9c41 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundChangeDifficultyPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundChangeDifficultyPacket.kt @@ -10,7 +10,7 @@ package cn.rtast.libmc.protocol.packet.play.serverbound import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket -import cn.rtast.libmc.protocol.registry.GameDifficulty +import cn.rtast.libmc.protocol.protocol.game.registry.GameDifficulty public data class ServerboundChangeDifficultyPacket(val newDifficulty: GameDifficulty) : MinecraftPacket { internal companion object Codec : PacketCodec { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundClientActionPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundClientActionPacket.kt index 2394d0c..3abee1d 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundClientActionPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundClientActionPacket.kt @@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.primitives.writeVarInt -import cn.rtast.libmc.protocol.registry.ClientAction +import cn.rtast.libmc.protocol.protocol.game.registry.ClientAction public data class ServerboundClientActionPacket(val action: ClientAction) : MinecraftPacket { internal companion object Codec : PacketCodec { diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundTestInstanceBlockActionPacket.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundTestInstanceBlockActionPacket.kt index 5825fff..031cb21 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundTestInstanceBlockActionPacket.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/packet/play/serverbound/ServerboundTestInstanceBlockActionPacket.kt @@ -14,6 +14,8 @@ import cn.rtast.libmc.primitives.writeVarInt import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.Identifier import cn.rtast.libmc.protocol.protocol.game.block.* +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent +import cn.rtast.libmc.protocol.protocol.game.chat.writeTextComponent import cn.rtast.libmc.protocol.protocol.game.writeIdentifier import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound @@ -27,7 +29,7 @@ public data class ServerboundTestInstanceBlockActionPacket( val rotation: TestInstanceRotation, val ignoreEntities: Boolean, val status: TestInstanceStatus, - val errorMessage: NBTCompound?, + val errorMessage: TextComponent?, ) : MinecraftPacket { internal companion object Codec : PacketCodec { override suspend fun encode(buffer: BytesBuffer, value: ServerboundTestInstanceBlockActionPacket) { @@ -42,7 +44,7 @@ public data class ServerboundTestInstanceBlockActionPacket( buffer.writeBoolean(value.ignoreEntities) buffer.writeVarInt(value.status.id) buffer.writeBoolean(value.errorMessage != null) - if (value.errorMessage != null) buffer.writeNetworkNBTCompound(value.errorMessage) // TODO to fix + if (value.errorMessage != null) buffer.writeTextComponent(value.errorMessage) // TODO to fix } override suspend fun decode(buffer: BytesBuffer): ServerboundTestInstanceBlockActionPacket = diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt index 331fe61..e51bfcb 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/GameProtocols.kt @@ -76,7 +76,7 @@ internal object GameProtocols { register(0x0d, ClientboundChunksBiomesPacket) register(0x0e, ClientboundClearTitlesPacket) register(0x0f, ClientboundCommandSuggestionsPacket) -// register(0x10, ClientboundCommandsPacket) + register(0x10, ClientboundCommandsPacket) register(0x11, ClientboundContainerClosePacket) // register(0x12, ClientboundContainerSetContentPacket) register(0x13, ClientboundContainerSetDataPacket) diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/bossbar/BossBarAction.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/bossbar/BossBarAction.kt index 2664b94..ec68b42 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/bossbar/BossBarAction.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/bossbar/BossBarAction.kt @@ -7,11 +7,11 @@ package cn.rtast.libmc.protocol.protocol.game.bossbar -import cn.rtast.libmc.nbt.NBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent public sealed interface BossBarAction { public data class Add( - val title: NBTCompound, + val title: TextComponent, val health: Float, val color: BossBarColor, val division: BossBarDivision, @@ -20,7 +20,7 @@ public sealed interface BossBarAction { public object Remove : BossBarAction public data class UpdateHealth(val health: Float) : BossBarAction - public data class UpdateTitle(val title: NBTCompound) : BossBarAction + public data class UpdateTitle(val title: TextComponent) : BossBarAction public data class UpdateStyle(val color: BossBarColor, val division: BossBarDivision) : BossBarAction public data class UpdateFlags(val flags: BossBarFlags) : BossBarAction diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/ChatTypeDecoration.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/ChatTypeDecoration.kt index 54dd2b3..b1f6be8 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/ChatTypeDecoration.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/ChatTypeDecoration.kt @@ -17,7 +17,7 @@ import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound public data class ChatTypeDecoration( val translationKey: String, val parameters: List, - val style: NBTCompound, + val style: NBTCompound, // TODO TextComponent or Raw NBT Compound ) { public enum class ChatTypeParameter(public val id: Int) { Sender(0), Target(1), Content(2); diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/TextComponent.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/TextComponent.kt new file mode 100644 index 0000000..871b1dc --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/chat/TextComponent.kt @@ -0,0 +1,622 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/7 + */ + +package cn.rtast.libmc.protocol.protocol.game.chat + +import cn.rtast.libmc.nbt.NBTCompound +import cn.rtast.libmc.nbt.NBTTag +import cn.rtast.libmc.nbt.NBTType +import cn.rtast.libmc.protocol.protocol.game.Identifier +import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound +import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound +import cn.rtast.libmc.stream.BytesBuffer +import kotlin.uuid.Uuid + +public data class TextComponent( + val content: Content, + val style: Style = Style.EMPTY, + val extra: List = emptyList(), + val clickEvent: ClickEvent? = null, + val hoverEvent: HoverEvent? = null, + val insertion: String? = null, +) { + public sealed interface Content { + public val type: String + + public data class PlainText(val text: String) : Content { + override val type: String = "text" + } + + public data class Translatable( + val key: String, + val fallback: String? = null, + val args: List = emptyList(), + ) : Content { + override val type: String = "translatable" + } + + public data class Score(val name: String, val objective: String) : Content { + override val type: String = "score" + } + + public data class Selector(val selector: String, val separator: TextComponent? = null) : Content { + override val type: String = "selector" + } + + public data class Keybind(val keybind: String) : Content { + override val type: String = "keybind" + } + + public data class Nbt( + val path: String, + val interpret: Boolean = false, + val plain: Boolean = false, + val separator: TextComponent? = null, + val source: NbtSource, + ) : Content { + override val type: String = "nbt" + + public sealed interface NbtSource { + public data class Entity(val selector: String) : NbtSource + public data class Block(val coordinates: String) : NbtSource + public data class Storage(val id: Identifier) : NbtSource + } + } + + public sealed interface ObjectContent : Content { + override val type: String get() = "object" + + public data class Atlas( + val sprite: Identifier, + val atlas: Identifier = Identifier.of("minecraft", "blocks"), + ) : ObjectContent + + public data class Player( + val profile: PlayerProfile, + val hat: Boolean = true, + ) : ObjectContent + } + } + + public data class Style( + val color: TextColor? = null, + val font: Identifier? = null, + val bold: Boolean? = null, + val italic: Boolean? = null, + val underlined: Boolean? = null, + val strikethrough: Boolean? = null, + val obfuscated: Boolean? = null, + val shadowColor: ShadowColor? = null, + ) { + public companion object { + public val EMPTY: Style = Style() + } + } + + public sealed interface PlayerProfile { + public data class Name(val name: String) : PlayerProfile + public data class FullProfile( + val name: String? = null, + val id: Uuid? = null, + val properties: List = emptyList(), + ) : PlayerProfile { + public data class Property( + val name: String, + val value: String, + val signature: String? = null, + ) + } + } + + public sealed interface TextColor { + public data class Named(val name: String) : TextColor + public data class Hex(val hex: String) : TextColor + } + + public sealed interface ShadowColor { + public data class ArgbInt(val argb: Long) : ShadowColor + public data class RgbaFloat( + val red: Float, + val green: Float, + val blue: Float, + val alpha: Float, + ) : ShadowColor + } + + public sealed interface ClickEvent { + public val action: ClickEventAction + + public enum class ClickEventAction(public val text: String) { + OpenURL("open_url"), + OpenFile("open_file"), + RunCommand("run_command"), + SuggestCommand("suggest_command"), + ChangePage("change_page"), + CopyToClipboard("copy_to_clipboard"), + ShowDialog("show_dialog"), + Custom("custom"); + + public companion object { + public fun fromText(text: String): ClickEventAction = + entries.first { it.text == text } + } + } + + public data class OpenUrl(val url: String) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.OpenURL + } + + public data class OpenFile(val path: String) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.OpenFile + } + + public data class RunCommand(val command: String) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.RunCommand + } + + public data class SuggestCommand(val command: String) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.SuggestCommand + } + + public data class ChangePage(val page: Int) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.ChangePage + } + + public data class CopyToClipboard(val value: String) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.CopyToClipboard + } + + public data class ShowDialog(val dialog: DialogPayload) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.ShowDialog + + public sealed interface DialogPayload { + public data class Id(val id: Identifier) : DialogPayload + public data class Definition(val compound: NBTTag.CompoundTag) : DialogPayload + } + } + + public data class Custom(val id: Identifier, val payload: String? = null) : ClickEvent { + override val action: ClickEventAction = ClickEventAction.Custom + } + } + + public sealed interface HoverEvent { + public val action: HoverEventAction + + public enum class HoverEventAction(public val text: String) { + ShowText("show_text"), + ShowItem("show_item"), + ShowEntity("show_entity"); + + public companion object { + public fun fromText(text: String): HoverEventAction = + entries.first { it.text == text } + } + } + + public data class ShowText(val component: TextComponent) : HoverEvent { + override val action: HoverEventAction = HoverEventAction.ShowText + } + + public data class ShowItem( + val id: Identifier, + val count: Int = 1, + val components: NBTTag.CompoundTag? = null, + ) : HoverEvent { + override val action: HoverEventAction = HoverEventAction.ShowItem + } + + public data class ShowEntity( + val id: Identifier, + val uuid: UUIDRepresentation, + val name: TextComponent? = null, + ) : HoverEvent { + override val action: HoverEventAction = HoverEventAction.ShowEntity + + public sealed interface UUIDRepresentation { + public data class StringFormat(val uuid: Uuid) : UUIDRepresentation + public data class ArrayFormat(val i1: Int, val i2: Int, val i3: Int, val i4: Int) : UUIDRepresentation + } + } + } + + public companion object { + public fun of(text: String): TextComponent = TextComponent(content = Content.PlainText(text)) + } +} + +public fun NBTTag.toTextComponent(): TextComponent { + return when (this) { + is NBTTag.StringTag -> TextComponent.of(this.value) + is NBTTag.ListTag -> { + val tags = this.value + if (tags.isEmpty()) return TextComponent.of("") + val head = tags.first().toTextComponent() + val tail = tags.drop(1).map { it.toTextComponent() } + head.copy(extra = head.extra + tail) + } + + is NBTTag.CompoundTag -> { + val content = parseContentFromNbt(this) + val style = parseStyleFromNbt(this) + val extra = this.getListOrNull("extra")?.map { it.toTextComponent() } ?: emptyList() + val clickEvent = this.getCompoundOrNull("click_event")?.let { parseClickEventFromNbt(it) } + val hoverEvent = this.getCompoundOrNull("hover_event")?.let { parseHoverEventFromNbt(it) } + val insertion = this.getStringOrNull("insertion") + + TextComponent( + content = content, + style = style, + extra = extra, + clickEvent = clickEvent, + hoverEvent = hoverEvent, + insertion = insertion + ) + } + + else -> TextComponent.of(this.toString()) + } +} + +private fun parseContentFromNbt(tag: NBTTag.CompoundTag): TextComponent.Content { + val type = tag.getStringOrNull("type") + return when { + type == "text" || (type == null && tag.containsKey("text")) -> + TextComponent.Content.PlainText(tag.getString("text")) + + type == "translatable" || (type == null && tag.containsKey("translate")) -> { + TextComponent.Content.Translatable( + key = tag.getString("translate"), + fallback = tag.getStringOrNull("fallback"), + args = tag.getListOrNull("with")?.map { it.toTextComponent() } ?: emptyList() + ) + } + + type == "score" || (type == null && tag.containsKey("score")) -> { + val scoreObj = tag.getCompound("score") + TextComponent.Content.Score( + name = scoreObj.getString("name"), + objective = scoreObj.getString("objective") + ) + } + + type == "selector" || (type == null && tag.containsKey("selector")) -> { + TextComponent.Content.Selector( + selector = tag.getString("selector"), + separator = tag.getOrNull("separator")?.toTextComponent() + ) + } + + type == "keybind" || (type == null && tag.containsKey("keybind")) -> { + TextComponent.Content.Keybind(tag.getString("keybind")) + } + + type == "nbt" || (type == null && tag.containsKey("nbt")) -> { + val path = tag.getString("nbt") + val interpret = tag.getBooleanOrNull("interpret") ?: false + val plain = tag.getBooleanOrNull("plain") ?: false + val separator = tag.getOrNull("separator")?.toTextComponent() + + val source = when { + tag.containsKey("entity") -> TextComponent.Content.Nbt.NbtSource.Entity(tag.getString("entity")) + tag.containsKey("block") -> TextComponent.Content.Nbt.NbtSource.Block(tag.getString("block")) + tag.containsKey("storage") -> TextComponent.Content.Nbt.NbtSource.Storage(Identifier.of(tag.getString("storage"))) + else -> throw IllegalArgumentException("Missing NBT source (entity, block, or storage)") + } + + TextComponent.Content.Nbt(path, interpret, plain, separator, source) + } + + type == "object" || (type == null && (tag.containsKey("atlas") || tag.containsKey("sprite") || tag.containsKey("player"))) -> { + val objectType = tag.getStringOrNull("object") ?: if (tag.containsKey("player")) "player" else "atlas" + if (objectType == "player") { + val hat = tag.getBooleanOrNull("hat") ?: true + val playerTag = tag.getOrNull("player") + val profile = if (playerTag is NBTTag.StringTag) { + TextComponent.PlayerProfile.Name(playerTag.value) + } else { + val pComp = playerTag as NBTTag.CompoundTag + TextComponent.PlayerProfile.FullProfile( + name = pComp.getStringOrNull("name"), + id = pComp.getStringOrNull("id")?.let { Uuid.parse(it) } + ) + } + TextComponent.Content.ObjectContent.Player(profile, hat) + } else { + TextComponent.Content.ObjectContent.Atlas( + sprite = Identifier.of(tag.getString("sprite")), + atlas = tag.getStringOrNull("atlas")?.let { Identifier.of(it) } ?: Identifier.of( + "minecraft", + "blocks" + ) + ) + } + } + + else -> TextComponent.Content.PlainText("") + } +} + +private fun parseStyleFromNbt(tag: NBTTag.CompoundTag): TextComponent.Style { + val color = tag.getStringOrNull("color")?.let { + if (it.startsWith("#")) TextComponent.TextColor.Hex(it) else TextComponent.TextColor.Named(it) + } + val font = tag.getStringOrNull("font")?.let { Identifier.of(it) } + val shadowColor = when (val sc = tag.getOrNull("shadow_color")) { + is NBTTag.IntTag -> TextComponent.ShadowColor.ArgbInt(sc.value.toLong()) + is NBTTag.ListTag -> { + val floats = sc.value.filterIsInstance().map { it.value } + if (floats.size == 4) { + TextComponent.ShadowColor.RgbaFloat(floats[0], floats[1], floats[2], floats[3]) + } else null + } + + else -> null + } + + return TextComponent.Style( + color = color, + font = font, + bold = tag.getBooleanOrNull("bold"), + italic = tag.getBooleanOrNull("italic"), + underlined = tag.getBooleanOrNull("underlined"), + strikethrough = tag.getBooleanOrNull("strikethrough"), + obfuscated = tag.getBooleanOrNull("obfuscated"), + shadowColor = shadowColor + ) +} + +private fun parseClickEventFromNbt(tag: NBTTag.CompoundTag): TextComponent.ClickEvent { + val action = TextComponent.ClickEvent.ClickEventAction.fromText(tag.getString("action")) + return when (action) { + TextComponent.ClickEvent.ClickEventAction.OpenURL -> TextComponent.ClickEvent.OpenUrl(tag.getString("url")) + TextComponent.ClickEvent.ClickEventAction.OpenFile -> TextComponent.ClickEvent.OpenFile(tag.getString("path")) + TextComponent.ClickEvent.ClickEventAction.RunCommand -> TextComponent.ClickEvent.RunCommand(tag.getString("command")) + TextComponent.ClickEvent.ClickEventAction.SuggestCommand -> TextComponent.ClickEvent.SuggestCommand( + tag.getString( + "command" + ) + ) + + TextComponent.ClickEvent.ClickEventAction.ChangePage -> TextComponent.ClickEvent.ChangePage(tag.getInt("page")) + TextComponent.ClickEvent.ClickEventAction.CopyToClipboard -> TextComponent.ClickEvent.CopyToClipboard( + tag.getString( + "value" + ) + ) + + TextComponent.ClickEvent.ClickEventAction.ShowDialog -> { + val dialogTag = tag.getOrNull("dialog") + val payload = if (dialogTag is NBTTag.StringTag) { + TextComponent.ClickEvent.ShowDialog.DialogPayload.Id(Identifier.of(dialogTag.value)) + } else { + TextComponent.ClickEvent.ShowDialog.DialogPayload.Definition(dialogTag as NBTTag.CompoundTag) + } + TextComponent.ClickEvent.ShowDialog(payload) + } + + TextComponent.ClickEvent.ClickEventAction.Custom -> TextComponent.ClickEvent.Custom( + id = Identifier.of(tag.getString("id")), + payload = tag.getStringOrNull("payload") + ) + } +} + +private fun parseHoverEventFromNbt(tag: NBTTag.CompoundTag): TextComponent.HoverEvent { + val action = TextComponent.HoverEvent.HoverEventAction.fromText(tag.getString("action")) + return when (action) { + TextComponent.HoverEvent.HoverEventAction.ShowText -> { + TextComponent.HoverEvent.ShowText(tag.getCompound("value").toTextComponent()) + } + + TextComponent.HoverEvent.HoverEventAction.ShowItem -> { + TextComponent.HoverEvent.ShowItem( + id = Identifier.of(tag.getString("id")), + count = tag.getIntOrNull("count") ?: 1, + components = tag.getCompoundOrNull("components") + ) + } + + TextComponent.HoverEvent.HoverEventAction.ShowEntity -> { + val uuidTag = tag.getOrNull("uuid") + val uuid = if (uuidTag is NBTTag.StringTag) { + TextComponent.HoverEvent.ShowEntity.UUIDRepresentation.StringFormat(Uuid.parse(uuidTag.value)) + } else { + val list = (uuidTag as NBTTag.IntArrayTag).value + TextComponent.HoverEvent.ShowEntity.UUIDRepresentation.ArrayFormat(list[0], list[1], list[2], list[3]) + } + TextComponent.HoverEvent.ShowEntity( + id = Identifier.of(tag.getString("id")), + uuid = uuid, + name = tag.getOrNull("name")?.toTextComponent() + ) + } + } +} + +public fun TextComponent.toNbt(): NBTTag.CompoundTag { + val map = mutableMapOf() + when (val c = this.content) { + is TextComponent.Content.PlainText -> { + map["type"] = NBTTag.StringTag("text") + map["text"] = NBTTag.StringTag(c.text) + } + + is TextComponent.Content.Translatable -> { + map["type"] = NBTTag.StringTag("translatable") + map["translate"] = NBTTag.StringTag(c.key) + c.fallback?.let { map["fallback"] = NBTTag.StringTag(it) } + if (c.args.isNotEmpty()) { + map["with"] = NBTTag.ListTag(NBTType.List, c.args.map { it.toNbt() as NBTTag }.toMutableList()) + } + } + + is TextComponent.Content.Score -> { + map["type"] = NBTTag.StringTag("score") + map["score"] = NBTTag.CompoundTag( + mutableMapOf( + "name" to NBTTag.StringTag(c.name), + "objective" to NBTTag.StringTag(c.objective) + ) + ) + } + + is TextComponent.Content.Selector -> { + map["type"] = NBTTag.StringTag("selector") + map["selector"] = NBTTag.StringTag(c.selector) + c.separator?.let { map["separator"] = it.toNbt() } + } + + is TextComponent.Content.Keybind -> { + map["type"] = NBTTag.StringTag("keybind") + map["keybind"] = NBTTag.StringTag(c.keybind) + } + + is TextComponent.Content.Nbt -> { + map["type"] = NBTTag.StringTag("nbt") + map["nbt"] = NBTTag.StringTag(c.path) + map["interpret"] = NBTTag.ByteTag(if (c.interpret) 1 else 0) + map["plain"] = NBTTag.ByteTag(if (c.plain) 1 else 0) + c.separator?.let { map["separator"] = it.toNbt() } + when (val s = c.source) { + is TextComponent.Content.Nbt.NbtSource.Entity -> map["entity"] = NBTTag.StringTag(s.selector) + is TextComponent.Content.Nbt.NbtSource.Block -> map["block"] = NBTTag.StringTag(s.coordinates) + is TextComponent.Content.Nbt.NbtSource.Storage -> map["storage"] = NBTTag.StringTag(s.id.toString()) + } + } + + is TextComponent.Content.ObjectContent.Atlas -> { + map["type"] = NBTTag.StringTag("object") + map["object"] = NBTTag.StringTag("atlas") + map["sprite"] = NBTTag.StringTag(c.sprite.toString()) + map["atlas"] = NBTTag.StringTag(c.atlas.toString()) + } + + is TextComponent.Content.ObjectContent.Player -> { + map["type"] = NBTTag.StringTag("object") + map["object"] = NBTTag.StringTag("player") + map["hat"] = NBTTag.ByteTag(if (c.hat) 1 else 0) + when (val p = c.profile) { + is TextComponent.PlayerProfile.Name -> map["player"] = NBTTag.StringTag(p.name) + is TextComponent.PlayerProfile.FullProfile -> { + val pMap = mutableMapOf() + p.name?.let { pMap["name"] = NBTTag.StringTag(it) } + p.id?.let { pMap["id"] = NBTTag.StringTag(it.toString()) } + map["player"] = NBTTag.CompoundTag(pMap) + } + } + } + } + + this.style.color?.let { + map["color"] = NBTTag.StringTag( + when (it) { + is TextComponent.TextColor.Named -> it.name + is TextComponent.TextColor.Hex -> it.hex + } + ) + } + this.style.font?.let { map["font"] = NBTTag.StringTag(it.toString()) } + this.style.bold?.let { map["bold"] = NBTTag.ByteTag(if (it) 1 else 0) } + this.style.italic?.let { map["italic"] = NBTTag.ByteTag(if (it) 1 else 0) } + this.style.underlined?.let { map["underlined"] = NBTTag.ByteTag(if (it) 1 else 0) } + this.style.strikethrough?.let { map["strikethrough"] = NBTTag.ByteTag(if (it) 1 else 0) } + this.style.obfuscated?.let { map["obfuscated"] = NBTTag.ByteTag(if (it) 1 else 0) } + this.style.shadowColor?.let { + when (it) { + is TextComponent.ShadowColor.ArgbInt -> map["shadow_color"] = NBTTag.IntTag(it.argb.toInt()) + is TextComponent.ShadowColor.RgbaFloat -> map["shadow_color"] = NBTTag.ListTag( + NBTType.List, mutableListOf( + NBTTag.FloatTag(it.red), + NBTTag.FloatTag(it.green), + NBTTag.FloatTag(it.blue), + NBTTag.FloatTag(it.alpha) + ) + ) + } + } + + if (this.extra.isNotEmpty()) map["extra"] = + NBTTag.ListTag(NBTType.List, this.extra.map { it.toNbt() as NBTTag }.toMutableList()) + this.insertion?.let { map["insertion"] = NBTTag.StringTag(it) } + this.clickEvent?.let { map["click_event"] = encodeClickEventToNbt(it) } + this.hoverEvent?.let { map["hover_event"] = encodeHoverEventToNbt(it) } + + return NBTTag.CompoundTag(map) +} + +private fun encodeClickEventToNbt(event: TextComponent.ClickEvent): NBTTag.CompoundTag { + val map = mutableMapOf("action" to NBTTag.StringTag(event.action.text)) + when (event) { + is TextComponent.ClickEvent.OpenUrl -> map["url"] = NBTTag.StringTag(event.url) + is TextComponent.ClickEvent.OpenFile -> map["path"] = NBTTag.StringTag(event.path) + is TextComponent.ClickEvent.RunCommand -> map["command"] = NBTTag.StringTag(event.command) + is TextComponent.ClickEvent.SuggestCommand -> map["command"] = NBTTag.StringTag(event.command) + is TextComponent.ClickEvent.ChangePage -> map["page"] = NBTTag.IntTag(event.page) + is TextComponent.ClickEvent.CopyToClipboard -> map["value"] = NBTTag.StringTag(event.value) + is TextComponent.ClickEvent.ShowDialog -> { + map["dialog"] = when (val d = event.dialog) { + is TextComponent.ClickEvent.ShowDialog.DialogPayload.Id -> NBTTag.StringTag(d.id.toString()) + is TextComponent.ClickEvent.ShowDialog.DialogPayload.Definition -> d.compound + } + } + + is TextComponent.ClickEvent.Custom -> { + map["id"] = NBTTag.StringTag(event.id.toString()) + event.payload?.let { map["payload"] = NBTTag.StringTag(it) } + } + } + return NBTTag.CompoundTag(map) +} + +private fun encodeHoverEventToNbt(event: TextComponent.HoverEvent): NBTTag.CompoundTag { + val map = mutableMapOf("action" to NBTTag.StringTag(event.action.text)) + when (event) { + is TextComponent.HoverEvent.ShowText -> map["value"] = event.component.toNbt() + is TextComponent.HoverEvent.ShowItem -> { + map["id"] = NBTTag.StringTag(event.id.toString()) + map["count"] = NBTTag.IntTag(event.count) + event.components?.let { map["components"] = it } + } + + is TextComponent.HoverEvent.ShowEntity -> { + map["id"] = NBTTag.StringTag(event.id.toString()) + map["uuid"] = when (val u = event.uuid) { + is TextComponent.HoverEvent.ShowEntity.UUIDRepresentation.StringFormat -> NBTTag.StringTag(u.uuid.toString()) + is TextComponent.HoverEvent.ShowEntity.UUIDRepresentation.ArrayFormat -> NBTTag.IntArrayTag( + intArrayOf(u.i1, u.i2, u.i3, u.i4) + ) + } + event.name?.let { map["name"] = it.toNbt() } + } + } + return NBTTag.CompoundTag(map) +} + +private fun NBTTag.CompoundTag.getOrNull(key: String): NBTTag? = this.value[key] +private fun NBTTag.CompoundTag.containsKey(key: String): Boolean = this.value.containsKey(key) +private fun NBTTag.CompoundTag.getString(key: String): String = (this.value[key] as NBTTag.StringTag).value +private fun NBTTag.CompoundTag.getStringOrNull(key: String): String? = (this.value[key] as? NBTTag.StringTag)?.value +private fun NBTTag.CompoundTag.getInt(key: String): Int = (this.value[key] as NBTTag.IntTag).value +private fun NBTTag.CompoundTag.getIntOrNull(key: String): Int? = (this.value[key] as? NBTTag.IntTag)?.value +private fun NBTTag.CompoundTag.getBooleanOrNull(key: String): Boolean? = when (val t = this.value[key]) { + is NBTTag.ByteTag -> t.value != 0.toByte() + else -> null +} + +private fun NBTTag.CompoundTag.getCompound(key: String): NBTTag.CompoundTag = this.value[key] as NBTTag.CompoundTag +private fun NBTTag.CompoundTag.getCompoundOrNull(key: String): NBTTag.CompoundTag? = + this.value[key] as? NBTTag.CompoundTag + +private fun NBTTag.CompoundTag.getListOrNull(key: String): List? = (this.value[key] as? NBTTag.ListTag)?.value + +internal suspend fun BytesBuffer.readTextComponent(): TextComponent = + this.readNetworkNBTCompound().element.toTextComponent() + +internal suspend fun BytesBuffer.writeTextComponent(component: TextComponent) { + val nbt = component.toNbt() + this.writeNetworkNBTCompound(NBTCompound("", nbt)) +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandArgumentProperties.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandArgumentProperties.kt new file mode 100644 index 0000000..197f69f --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandArgumentProperties.kt @@ -0,0 +1,42 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/7 + */ + + +package cn.rtast.libmc.protocol.protocol.game.command + +import cn.rtast.libmc.protocol.protocol.game.Identifier + +public sealed interface CommandArgumentProperties { + public object Empty : CommandArgumentProperties + + public data class IntProp(val min: Int = Int.MIN_VALUE, val max: Int = Int.MAX_VALUE) : + CommandArgumentProperties + + public data class LongProp(val min: Long = Long.MIN_VALUE, val max: Long = Long.MAX_VALUE) : + CommandArgumentProperties + + public data class FloatProp(val min: Float = Float.MIN_VALUE, val max: Float = Float.MAX_VALUE) : + CommandArgumentProperties + + public data class DoubleProp(val min: Double = Double.MIN_VALUE, val max: Double = Double.MAX_VALUE) : + CommandArgumentProperties + + public data class StringProp(val behavior: StringBehavior) : CommandArgumentProperties { + public enum class StringBehavior(public val id: Int) { + SingleWord(0), QuotablePhrase(1), GreedyPhrase(2); + + public companion object { + public fun fromId(id: Int): StringBehavior = entries.first { it.id == id } + } + } + } + + public data class Entity(val singleOnly: Boolean, val playersOnly: Boolean) : CommandArgumentProperties + + public data class ScoreHolder(val allowMultiple: Boolean) : CommandArgumentProperties + public data class Time(val min: Int) : CommandArgumentProperties + public data class Registry(val registry: Identifier) : CommandArgumentProperties +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandNode.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandNode.kt new file mode 100644 index 0000000..e6b9533 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandNode.kt @@ -0,0 +1,233 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/7 + */ + +package cn.rtast.libmc.protocol.protocol.game.command + +import cn.rtast.libmc.packet.PacketCodec +import cn.rtast.libmc.primitives.readMcString +import cn.rtast.libmc.primitives.readVarInt +import cn.rtast.libmc.primitives.writeMcString +import cn.rtast.libmc.primitives.writeVarInt +import cn.rtast.libmc.protocol.protocol.game.readIdentifier +import cn.rtast.libmc.protocol.protocol.game.writeIdentifier +import cn.rtast.libmc.stream.BytesBuffer + +public data class CommandNode( + val type: NodeType, + val isExecutable: Boolean, + val isRestricted: Boolean, + val childrenIndexes: List, + val redirectNodeIndex: Int?, + val name: String?, + val parserId: Int?, + val properties: CommandArgumentProperties?, + val suggestionsType: CommandSuggestionsType?, +) { + public enum class NodeType(public val id: Int) { + Root(0), Literal(1), Argument(2); + + public companion object { + public const val NODE_TYPE_MASK: Int = 0x03 + public const val IS_EXECUTABLE_MASK: Int = 0x04 + public const val HAS_REDIRECT_MASK: Int = 0x08 + public const val HAS_SUGGESTIONS_TYPE_MASK: Int = 0x10 + public const val IS_RESTRICTED_MASK: Int = 0x20 + + public fun fromID(id: Int): NodeType = entries.first { it.id == id } + } + } + + internal companion object Codec : PacketCodec { + private const val PARSER_INTEGER = 3 + private const val PARSER_LONG = 4 + private const val PARSER_FLOAT = 1 + private const val PARSER_DOUBLE = 2 + private const val PARSER_STRING = 5 + private const val PARSER_ENTITY = 6 + private const val PARSER_SCORE_HOLDER = 31 + private const val PARSER_TIME = 43 + private const val PARSER_RESOURCE_OR_TAG = 44 + private const val PARSER_RESOURCE_OR_TAG_KEY = 45 + private const val PARSER_RESOURCE = 46 + private const val PARSER_RESOURCE_KEY = 47 + private const val PARSER_RESOURCE_SELECTOR = 48 + + override suspend fun encode(buffer: BytesBuffer, value: CommandNode) { + var flags = value.type.id and NodeType.NODE_TYPE_MASK + if (value.isExecutable) flags = flags or NodeType.IS_EXECUTABLE_MASK + if (value.redirectNodeIndex != null) flags = flags or NodeType.HAS_REDIRECT_MASK + if (value.suggestionsType != null) flags = flags or NodeType.HAS_SUGGESTIONS_TYPE_MASK + if (value.isRestricted) flags = flags or NodeType.IS_RESTRICTED_MASK + buffer.writeByte(flags.toByte()) + buffer.writeVarInt(value.childrenIndexes.size) + value.childrenIndexes.forEach { buffer.writeVarInt(it) } + if (value.redirectNodeIndex != null) buffer.writeVarInt(value.redirectNodeIndex) + if (value.type == NodeType.Literal || value.type == NodeType.Argument) buffer.writeMcString( + value.name ?: "" + ) + + if (value.type == NodeType.Argument) { + val parserId = value.parserId ?: 0 + buffer.writeVarInt(parserId) + value.properties?.let { buffer.encodeProperties(it) } ?: buffer.encodeProperties( + CommandArgumentProperties.Empty + ) + } + if (value.suggestionsType != null) buffer.writeIdentifier(value.suggestionsType.id) + } + + override suspend fun decode(buffer: BytesBuffer): CommandNode { + val flags = buffer.readByte().toInt() and 0xFF + val nodeType = NodeType.fromID(flags and NodeType.NODE_TYPE_MASK) + val isExecutable = (flags and NodeType.IS_EXECUTABLE_MASK) != 0 + val hasRedirect = (flags and NodeType.HAS_REDIRECT_MASK) != 0 + val hasSuggestionsType = (flags and NodeType.HAS_SUGGESTIONS_TYPE_MASK) != 0 + val isRestricted = (flags and NodeType.IS_RESTRICTED_MASK) != 0 + val childrenCount = buffer.readVarInt() + val childrenIndexes = ArrayList(childrenCount) + repeat(childrenCount) { childrenIndexes.add(buffer.readVarInt()) } + val redirectNodeIndex = if (hasRedirect) buffer.readVarInt() else null + val name = + if (nodeType == NodeType.Literal || nodeType == NodeType.Argument) buffer.readMcString() else null + var parserId: Int? = null + var properties: CommandArgumentProperties? = null + if (nodeType == NodeType.Argument) { + val pId = buffer.readVarInt() + parserId = pId + properties = buffer.decodeProperties(pId) + } + val suggestionsType = + if (hasSuggestionsType) CommandSuggestionsType.fromIdentifier(buffer.readIdentifier()) else null + return CommandNode( + type = nodeType, + isExecutable = isExecutable, + isRestricted = isRestricted, + childrenIndexes = childrenIndexes, + redirectNodeIndex = redirectNodeIndex, + name = name, + parserId = parserId, + properties = properties, + suggestionsType = suggestionsType + ) + } + + private suspend fun BytesBuffer.decodeProperties(parserId: Int): CommandArgumentProperties { + return when (parserId) { + PARSER_INTEGER -> { + val flags = readByte().toInt() + val min = if ((flags and 0x01) != 0) readInt() else Int.MIN_VALUE + val max = if ((flags and 0x02) != 0) readInt() else Int.MAX_VALUE + CommandArgumentProperties.IntProp(min, max) + } + + PARSER_LONG -> { + val flags = readByte().toInt() + val min = if ((flags and 0x01) != 0) readLong() else Long.MIN_VALUE + val max = if ((flags and 0x02) != 0) readLong() else Long.MAX_VALUE + CommandArgumentProperties.LongProp(min, max) + } + + PARSER_FLOAT -> { + val flags = readByte().toInt() + val min = if ((flags and 0x01) != 0) readFloat() else -Float.MAX_VALUE + val max = if ((flags and 0x02) != 0) readFloat() else Float.MAX_VALUE + CommandArgumentProperties.FloatProp(min, max) + } + + PARSER_DOUBLE -> { + val flags = readByte().toInt() + val min = if ((flags and 0x01) != 0) readDouble() else -Double.MAX_VALUE + val max = if ((flags and 0x02) != 0) readDouble() else Double.MAX_VALUE + CommandArgumentProperties.DoubleProp(min, max) + } + + PARSER_STRING -> { + val behaviorId = readVarInt() + CommandArgumentProperties.StringProp( + CommandArgumentProperties.StringProp.StringBehavior.fromId(behaviorId) + ) + } + + PARSER_ENTITY -> { + val flags = readByte().toInt() + CommandArgumentProperties.Entity( + singleOnly = (flags and 0x01) != 0, + playersOnly = (flags and 0x02) != 0 + ) + } + + PARSER_SCORE_HOLDER -> { + val flags = readByte().toInt() + CommandArgumentProperties.ScoreHolder(allowMultiple = (flags and 0x01) != 0) + } + + PARSER_TIME -> CommandArgumentProperties.Time(min = readInt()) + PARSER_RESOURCE_OR_TAG, PARSER_RESOURCE_OR_TAG_KEY, PARSER_RESOURCE, PARSER_RESOURCE_KEY, PARSER_RESOURCE_SELECTOR -> { + CommandArgumentProperties.Registry(readIdentifier()) + } + + else -> CommandArgumentProperties.Empty + } + } + + private suspend fun BytesBuffer.encodeProperties(properties: CommandArgumentProperties) { + when (properties) { + is CommandArgumentProperties.IntProp -> { + var flags = 0 + if (properties.min != Int.MIN_VALUE) flags = flags or 0x01 + if (properties.max != Int.MAX_VALUE) flags = flags or 0x02 + writeByte(flags.toByte()) + if ((flags and 0x01) != 0) writeInt(properties.min) + if ((flags and 0x02) != 0) writeInt(properties.max) + } + + is CommandArgumentProperties.LongProp -> { + var flags = 0 + if (properties.min != Long.MIN_VALUE) flags = flags or 0x01 + if (properties.max != Long.MAX_VALUE) flags = flags or 0x02 + writeByte(flags.toByte()) + if ((flags and 0x01) != 0) writeLong(properties.min) + if ((flags and 0x02) != 0) writeLong(properties.max) + } + + is CommandArgumentProperties.FloatProp -> { + var flags = 0 + if (properties.min != -Float.MAX_VALUE) flags = flags or 0x01 + if (properties.max != Float.MAX_VALUE) flags = flags or 0x02 + writeByte(flags.toByte()) + if ((flags and 0x01) != 0) writeFloat(properties.min) + if ((flags and 0x02) != 0) writeFloat(properties.max) + } + + is CommandArgumentProperties.DoubleProp -> { + var flags = 0 + if (properties.min != -Double.MAX_VALUE) flags = flags or 0x01 + if (properties.max != Double.MAX_VALUE) flags = flags or 0x02 + writeByte(flags.toByte()) + if ((flags and 0x01) != 0) writeDouble(properties.min) + if ((flags and 0x02) != 0) writeDouble(properties.max) + } + + is CommandArgumentProperties.StringProp -> writeVarInt(properties.behavior.id) + is CommandArgumentProperties.Entity -> { + var flags = 0 + if (properties.singleOnly) flags = flags or 0x01 + if (properties.playersOnly) flags = flags or 0x02 + writeByte(flags.toByte()) + } + + is CommandArgumentProperties.ScoreHolder -> { + val flags = if (properties.allowMultiple) 0x01 else 0x00 + writeByte(flags.toByte()) + } + + is CommandArgumentProperties.Time -> writeInt(properties.min) + is CommandArgumentProperties.Registry -> writeIdentifier(properties.registry) + CommandArgumentProperties.Empty -> {} + } + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandSuggestionsType.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandSuggestionsType.kt new file mode 100644 index 0000000..f62dfe7 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/command/CommandSuggestionsType.kt @@ -0,0 +1,22 @@ +/* + * Copyright © 2026 RTAkland + * Author: RTAkland + * Date: 2026/9/7 + */ + + +package cn.rtast.libmc.protocol.protocol.game.command + +import cn.rtast.libmc.protocol.protocol.game.Identifier + +public enum class CommandSuggestionsType(public val id: Identifier) { + AskServer(Identifier.of("minecraft:ask_server")), + AllRecipes(Identifier.of("minecraft:all_recipes")), + AvailableSounds(Identifier.of("minecraft:available_sounds")), + SummonableEntities(Identifier.of("minecraft:summonable_entities")); + + public companion object { + public fun fromIdentifier(id: Identifier?): CommandSuggestionsType? = + entries.firstOrNull { it.id.raw == id?.raw } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/ClientAction.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/ClientAction.kt similarity index 91% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/ClientAction.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/ClientAction.kt index e72d6c8..c884f23 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/ClientAction.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/ClientAction.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.registry +package cn.rtast.libmc.protocol.protocol.game.registry public enum class ClientAction(public val actionID: Int) { /** diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GameDifficulty.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/GameDifficulty.kt similarity index 87% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GameDifficulty.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/GameDifficulty.kt index 5dff327..0f55926 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/GameDifficulty.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/GameDifficulty.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.registry +package cn.rtast.libmc.protocol.protocol.game.registry public enum class GameDifficulty(public val id: Byte) { Peaceful(0), diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/BuiltinServerLinkType.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/BuiltinServerLinkType.kt similarity index 87% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/BuiltinServerLinkType.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/BuiltinServerLinkType.kt index 0a7395b..6173c78 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/BuiltinServerLinkType.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/BuiltinServerLinkType.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.registry.report +package cn.rtast.libmc.protocol.protocol.game.registry.report public enum class BuiltinServerLinkType(public val id: Int) { BUG_REPORT(0), diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/ServerLink.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/ServerLink.kt similarity index 75% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/ServerLink.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/ServerLink.kt index cdbc168..5840b83 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/registry/report/ServerLink.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/registry/report/ServerLink.kt @@ -5,17 +5,17 @@ */ -package cn.rtast.libmc.protocol.registry.report +package cn.rtast.libmc.protocol.protocol.game.registry.report import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.writeMcString import cn.rtast.libmc.primitives.writeVarInt +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.stream.BytesBuffer -import cn.rtast.libmc.nbt.NBTCompound -import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound -import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound public data class ServerLink(val label: ServerLinkLabel, val url: String) { internal companion object Codec : PacketCodec { @@ -28,7 +28,7 @@ public data class ServerLink(val label: ServerLinkLabel, val url: String) { is ServerLinkLabel.Custom -> { buffer.writeBoolean(false) - buffer.writeNetworkNBTCompound(value.label.text) + buffer.writeTextComponent(value.label.text) } } buffer.writeMcString(value.url) @@ -37,7 +37,7 @@ public data class ServerLink(val label: ServerLinkLabel, val url: String) { override suspend fun decode(buffer: BytesBuffer): ServerLink { val isBuiltin = buffer.readBoolean() val label = if (isBuiltin) ServerLinkLabel.Builtin(BuiltinServerLinkType.fromID(buffer.readVarInt())) - else ServerLinkLabel.Custom(buffer.readNetworkNBTCompound()) + else ServerLinkLabel.Custom(buffer.readTextComponent()) val url = buffer.readMcString() return ServerLink(label, url) } @@ -46,5 +46,5 @@ public data class ServerLink(val label: ServerLinkLabel, val url: String) { public sealed interface ServerLinkLabel { public data class Builtin(val type: BuiltinServerLinkType) : ServerLinkLabel - public data class Custom(val text: NBTCompound) : ServerLinkLabel + public data class Custom(val text: TextComponent) : ServerLinkLabel } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ObjectivePayload.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ObjectivePayload.kt index da0635e..a43e8cf 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ObjectivePayload.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ObjectivePayload.kt @@ -7,12 +7,12 @@ package cn.rtast.libmc.protocol.protocol.game.scoreboard -import cn.rtast.libmc.nbt.NBTCompound +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent public sealed interface ObjectivePayload { public object Remove : ObjectivePayload public data class Upsert( - val displayName: NBTCompound, + val displayName: TextComponent, val renderType: ObjectiveRenderType, val defaultNumberFormat: ScoreNumberFormat?, ) : ObjectivePayload diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ScoreNumberFormat.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ScoreNumberFormat.kt index e43e007..0fb26da 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ScoreNumberFormat.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/scoreboard/ScoreNumberFormat.kt @@ -7,11 +7,11 @@ package cn.rtast.libmc.protocol.protocol.game.scoreboard -import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.nbt.NBTTag +import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent public sealed interface ScoreNumberFormat { public object Blank : ScoreNumberFormat public data class Styled(val styling: NBTTag.CompoundTag) : ScoreNumberFormat - public data class Fixed(val content: NBTCompound) : ScoreNumberFormat + public data class Fixed(val content: TextComponent) : ScoreNumberFormat } \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/GameProfile.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/GameProfile.kt similarity index 97% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/GameProfile.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/GameProfile.kt index 8650f9a..894bcbd 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/GameProfile.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/GameProfile.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.session +package cn.rtast.libmc.protocol.protocol.game.session import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.primitives.readMcString diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/TransactionIdManager.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/TransactionIdManager.kt similarity index 94% rename from libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/TransactionIdManager.kt rename to libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/TransactionIdManager.kt index 802e5cd..f14548f 100644 --- a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/session/TransactionIdManager.kt +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/util/TransactionIdManager.kt @@ -5,7 +5,7 @@ */ -package cn.rtast.libmc.protocol.session +package cn.rtast.libmc.protocol.util import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock diff --git a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt index b03fcb3..3742377 100644 --- a/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt +++ b/libmc-protocol/src/jvmTest/kotlin/test/TestClientTestInJvm.kt @@ -7,18 +7,17 @@ package test -import cn.rtast.libmc.crypto.AuthenticationProvider -import cn.rtast.libmc.crypto.RSA1024Encryptor -import cn.rtast.libmc.crypto.Sha1Hasher import cn.rtast.libmc.protocol.client.createMinecraftClient import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext +import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundCommandsPacket +import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundDisconnectPlayPacket +import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundDisguisedChatMessagePacket +import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundSystemChatMessagePacket import cn.rtast.libmc.protocol.util.generateOfflineUuid import kotlinx.coroutines.launch import org.junit.Test import java.io.File import java.math.BigInteger -import java.net.HttpURLConnection -import java.net.URL import java.security.KeyFactory import java.security.MessageDigest import java.security.spec.X509EncodedKeySpec @@ -64,24 +63,9 @@ class TestClientTestInJvm { accessToken, crypto = DefaultProtocolContext ) -// { -// rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) } -// sha1Hasher = -// Sha1Hasher { serverId, secretKey, publicKey -> minecraftServerIdHash(serverId, secretKey, publicKey) } -// cipherFactory = { key -> JvmAesCipher(key) } -// authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> -// val connection = URL(url).openConnection() as HttpURLConnection -// connection.requestMethod = "POST" -// connection.doOutput = true -// connection.setRequestProperty("Content-Type", "application/json") -// connection.getOutputStream() -// .use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) } -// connection.disconnect() -// } -// } // cli.onPacket { println(it) } // cli.onPacket { println(it) } - cli.on { packet, direction -> println("${direction} -> $packet") } + cli.on { packet, direction -> println("$direction -> $packet") } cli.launch { cli.connect() } while (true) { } @@ -95,29 +79,10 @@ class TestClientTestInJvm { "11", generateOfflineUuid("11"), null, - ) { - rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) } - sha1Hasher = - Sha1Hasher { serverId, secretKey, publicKey -> - minecraftServerIdHash( - serverId, - secretKey, - publicKey - ) - } - cipherFactory = { key -> JvmAesCipher(key) } -// authProvider = AuthenticationProvider { url, accessToken, uuid, serverIdHash -> -// val connection = URL(url).openConnection() as HttpURLConnection -// connection.requestMethod = "POST" -// connection.doOutput = true -// connection.setRequestProperty("Content-Type", "application/json") -// connection.getOutputStream() -// .use { it.write("{\"accessToken\":\"$accessToken\", \"selectedProfile\":\"$uuid\", \"serverId\":\"$serverIdHash\"}".encodeToByteArray()) } -// connection.disconnect() -// } - } -// cli.on { println(it) } -// cli.on { println(it) } + crypto = DefaultProtocolContext + ) +// cli.on { packet, direction -> println("$direction -> $packet") } + cli.onPacket { println(it.content.content) } cli.launch { cli.connect() } while (true) { }