Support TextComponent AST parser and command binary graph AST parser

This commit is contained in:
2026-09-07 18:21:46 +08:00
parent c0f9679090
commit b16ea33ee6
46 files changed
+1114 -205

No files matched your search

+8 -9
View File
@@ -21,17 +21,16 @@ A lightweight minecraft client-side protocol library and related library, includ
## Implementation details ## Implementation details
- [x] **Online Mode Authentication & Encryption**: See [Implement Encryption](docs/implement-encryption.md) - [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 - [x] **Structured `TextComponent` Parser**: TextComponent AST decoder (Some packets fallback to Raw NBTCompound)
`NBTCompound`). - [x] **Command Tree Parser (0x10)**: Full binary graph decoder for brigadier nodes, argument types, and suggestions
- [ ] **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 - [ ] **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 - [ ] **Chunk & World Data (0x2D)**: Level Chunk Data with Light decoder (Bitsets, Paletted Containers, Direct/Indirect
Palettes). Palettes)
- [ ] **Light Engine Update (0x30)**: Sky & Block light nibble array parser. - [ ] **Light Engine Update (0x30)**: Sky & Block light nibble array parser
- [ ] **Explosion Event Decoder (0x24)**: Knockback vectors and destroyed block offsets array. - [ ] **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. - [ ] **Debug Packets Parsing (0x1A - 0x1E)**: Debug subs, block/entity states, and game performance sample events
- [ ] **Particle Parsing** - [ ] **Particle Parsing**
- [ ] **Slot Data Parsing** - [ ] **Slot Data Parsing**
@@ -11,7 +11,7 @@ public data class ProtocolContext(
val rsaEncryptor: RSA1024Encryptor, val rsaEncryptor: RSA1024Encryptor,
val sha1Hasher: Sha1Hasher, val sha1Hasher: Sha1Hasher,
val cipherFactory: (sharedKey: ByteArray) -> NetworkCipher, val cipherFactory: (sharedKey: ByteArray) -> NetworkCipher,
val authProvider: AuthenticationProvider, val authProvider: AuthenticationProvider?,
) )
public class ProtocolContextBuilder(private val onlineMode: Boolean) { 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"), rsaEncryptor = if (::rsaEncryptor.isInitialized) rsaEncryptor else error("rsaEncryptor is required"),
sha1Hasher = if (::sha1Hasher.isInitialized) sha1Hasher else error("sha1Hasher is required"), sha1Hasher = if (::sha1Hasher.isInitialized) sha1Hasher else error("sha1Hasher is required"),
cipherFactory = if (::cipherFactory.isInitialized) cipherFactory else error("cipherFactory is required"), cipherFactory = if (::cipherFactory.isInitialized) cipherFactory else error("cipherFactory is required"),
authProvider = if (::authProvider.isInitialized) if (onlineMode) authProvider authProvider = if (onlineMode) {
else error("authProvider is required") else error("authProvider is required") if (::authProvider.isInitialized) authProvider else error("authProvider is required in online mode")
} else if (::authProvider.isInitialized) authProvider else null
) )
} }
@@ -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.packet.login.serverbound.ServerboundLoginStartPacket
import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent
import cn.rtast.libmc.protocol.protocol.state.ProtocolState 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 cn.rtast.libmc.protocol.util.generateOfflineUuid
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -35,7 +35,7 @@ import cn.rtast.libmc.protocol.util.generateRandom16Bytes
*/ */
public class InternalPacketDispatcher( public class InternalPacketDispatcher(
private val client: MinecraftClient, private val client: MinecraftClient,
private val authProvider: AuthenticationProvider, private val authProvider: AuthenticationProvider?,
) { ) {
public suspend fun handleIncomingPackets(packet: MinecraftPacket) { public suspend fun handleIncomingPackets(packet: MinecraftPacket) {
when (packet) { when (packet) {
@@ -51,7 +51,7 @@ public class InternalPacketDispatcher(
val sharedSecret = generateRandom16Bytes() val sharedSecret = generateRandom16Bytes()
if (client.isOnlineMode) { if (client.isOnlineMode) {
val serverHash = client.serverIdHasher.hash(packet.serverId, sharedSecret, packet.publicKey) val serverHash = client.serverIdHasher.hash(packet.serverId, sharedSecret, packet.publicKey)
authProvider.joinServer( authProvider!!.joinServer(
"https://sessionserver.mojang.com/session/minecraft/join", "https://sessionserver.mojang.com/session/minecraft/join",
client.accessToken!!, client.accessToken!!,
client.uuid.toString().replace("-", ""), client.uuid.toString().replace("-", ""),
@@ -12,9 +12,9 @@ import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readUuid import cn.rtast.libmc.primitives.readUuid
import cn.rtast.libmc.primitives.readVarInt 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.stream.BytesBuffer
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import kotlin.uuid.Uuid import kotlin.uuid.Uuid
public data class ClientboundAddResourcePackPacket( public data class ClientboundAddResourcePackPacket(
@@ -22,7 +22,7 @@ public data class ClientboundAddResourcePackPacket(
val url: String, val url: String,
val hash: String, val hash: String,
val forced: Boolean, val forced: Boolean,
val prompt: NBTCompound, val prompt: TextComponent,
) : MinecraftPacket { ) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundAddResourcePackPacket> { internal companion object Codec : PacketCodec<ClientboundAddResourcePackPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundAddResourcePackPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundAddResourcePackPacket) {}
@@ -32,7 +32,7 @@ public data class ClientboundAddResourcePackPacket(
val hash = buffer.readMcString() val hash = buffer.readMcString()
val forced = buffer.readBoolean() val forced = buffer.readBoolean()
buffer.readVarInt() // ? buffer.readVarInt() // ?
val prompt = buffer.readNetworkNBTCompound() // ? val prompt = buffer.readTextComponent() // ?
return ClientboundAddResourcePackPacket(uuid, url, hash, forced, prompt) return ClientboundAddResourcePackPacket(uuid, url, hash, forced, prompt)
} }
} }
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.configuration.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundDisconnectConfigurationPacket(val reason: NBTCompound) : MinecraftPacket { public data class ClientboundDisconnectConfigurationPacket(val reason: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> { internal companion object Codec : PacketCodec<ClientboundDisconnectConfigurationPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectConfigurationPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectConfigurationPacket {
return ClientboundDisconnectConfigurationPacket(reason = buffer.readNetworkNBTCompound()) return ClientboundDisconnectConfigurationPacket(reason = buffer.readTextComponent())
} }
} }
} }
@@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt 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<ServerLink>) : MinecraftPacket { public data class ClientboundServerLinksPacket(val links: List<ServerLink>) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundServerLinksPacket> { internal companion object Codec : PacketCodec<ClientboundServerLinksPacket> {
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.login.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundDisconnectLoginPacket(val reason: NBTCompound) : MinecraftPacket { public data class ClientboundDisconnectLoginPacket(val reason: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> { internal companion object Codec : PacketCodec<ClientboundDisconnectLoginPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectLoginPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectLoginPacket {
return ClientboundDisconnectLoginPacket(reason = buffer.readNetworkNBTCompound()) return ClientboundDisconnectLoginPacket(reason = buffer.readTextComponent())
} }
} }
} }
@@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readUuid 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 import kotlin.uuid.Uuid
public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) : public data class ClientboundLoginSuccessPacket(val gameProfile: GameProfile, val sessionId: Uuid) :
@@ -7,7 +7,6 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readUuid 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.BossBarColor
import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarDivision import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarDivision
import cn.rtast.libmc.protocol.protocol.game.bossbar.BossBarFlags 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 import kotlin.uuid.Uuid
public data class ClientboundBossEventPacket(val uuid: Uuid, val action: BossBarAction) : MinecraftPacket { 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 uuid = buffer.readUuid()
val action = when (val actionId = buffer.readVarInt()) { val action = when (val actionId = buffer.readVarInt()) {
BossBarAction.ADD_ID -> { BossBarAction.ADD_ID -> {
val title = buffer.readNetworkNBTCompound() val title = buffer.readTextComponent()
val health = buffer.readFloat() val health = buffer.readFloat()
val color = BossBarColor.fromID(buffer.readVarInt()) val color = BossBarColor.fromID(buffer.readVarInt())
val division = BossBarDivision.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.REMOVE_ID -> BossBarAction.Remove
BossBarAction.UPDATE_TITLE_ID -> BossBarAction.UpdateTitle(buffer.readNetworkNBTCompound()) BossBarAction.UPDATE_TITLE_ID -> BossBarAction.UpdateTitle(buffer.readTextComponent())
BossBarAction.UPDATE_STYLE_ID -> { BossBarAction.UPDATE_STYLE_ID -> {
val color = BossBarColor.fromID(buffer.readVarInt()) val color = BossBarColor.fromID(buffer.readVarInt())
val division = BossBarDivision.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.UpdateFlags(BossBarFlags.fromBitmask(buffer.readByte().toInt() and 0xFF))
} }
BossBarAction.UPDATE_HEALTH_ID -> BossBarAction.UpdateHealth(buffer.readFloat())
else -> throw IllegalArgumentException("Unknown action $actionId") else -> throw IllegalArgumentException("Unknown action $actionId")
} }
return ClientboundBossEventPacket(uuid, action) return ClientboundBossEventPacket(uuid, action)
@@ -10,7 +10,7 @@ package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec 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) : public data class ClientboundChangeDifficultyPacket(val difficulty: GameDifficulty, val locked: Boolean) :
MinecraftPacket { MinecraftPacket {
@@ -11,11 +11,9 @@ import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readVarInt 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.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( public data class ClientboundCommandSuggestionsPacket(
val id: Int, val id: Int,
@@ -23,18 +21,13 @@ public data class ClientboundCommandSuggestionsPacket(
val length: Int, val length: Int,
val matches: List<CommandSuggestionMatch>, val matches: List<CommandSuggestionMatch>,
) : MinecraftPacket { ) : 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<CommandSuggestionMatch> { internal companion object Codec : PacketCodec<CommandSuggestionMatch> {
override suspend fun encode(buffer: BytesBuffer, value: CommandSuggestionMatch) { 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 decode(buffer: BytesBuffer): CommandSuggestionMatch { override suspend fun decode(buffer: BytesBuffer): CommandSuggestionMatch {
val match = buffer.readMcString() val match = buffer.readMcString()
val hasTooltip = buffer.readBoolean() val hasTooltip = buffer.readBoolean()
val tooltip = if (hasTooltip) buffer.readNetworkNBTCompound() else null val tooltip = if (hasTooltip) buffer.readTextComponent() else null
return CommandSuggestionMatch(match, tooltip) return CommandSuggestionMatch(match, tooltip)
} }
} }
@@ -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<CommandNode>, val rootIndex: Int) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundCommandsPacket> {
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)
}
}
}
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundDisconnectPlayPacket(val reason: NBTCompound) : MinecraftPacket { public data class ClientboundDisconnectPlayPacket(val reason: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> { internal companion object Codec : PacketCodec<ClientboundDisconnectPlayPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisconnectPlayPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundDisconnectPlayPacket {
return ClientboundDisconnectPlayPacket(reason = buffer.readNetworkNBTCompound()) return ClientboundDisconnectPlayPacket(reason = buffer.readTextComponent())
} }
} }
} }
@@ -12,25 +12,25 @@ import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.IdOrX import cn.rtast.libmc.primitives.IdOrX
import cn.rtast.libmc.primitives.readIdOrX import cn.rtast.libmc.primitives.readIdOrX
import cn.rtast.libmc.primitives.readOptional 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.InlineChatType
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.chat.readInlineChatType import cn.rtast.libmc.protocol.protocol.game.chat.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( public data class ClientboundDisguisedChatMessagePacket(
val message: NBTCompound, val message: TextComponent,
val chatType: IdOrX<InlineChatType>, val chatType: IdOrX<InlineChatType>,
val senderName: NBTCompound, val senderName: TextComponent,
val targetName: NBTCompound?, val targetName: TextComponent?,
) : MinecraftPacket { ) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundDisguisedChatMessagePacket> { internal companion object Codec : PacketCodec<ClientboundDisguisedChatMessagePacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisguisedChatMessagePacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundDisguisedChatMessagePacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundDisguisedChatMessagePacket { override suspend fun decode(buffer: BytesBuffer): ClientboundDisguisedChatMessagePacket {
val message = buffer.readNetworkNBTCompound() val message = buffer.readTextComponent()
val chatType = buffer.readIdOrX { readInlineChatType() } val chatType = buffer.readIdOrX { readInlineChatType() }
val senderName = buffer.readNetworkNBTCompound() val senderName = buffer.readTextComponent()
val targetName = buffer.readOptional { readNetworkNBTCompound() } val targetName = buffer.readOptional { readTextComponent() }
return ClientboundDisguisedChatMessagePacket(message, chatType, senderName, targetName) return ClientboundDisguisedChatMessagePacket(message, chatType, senderName, targetName)
} }
} }
@@ -7,24 +7,24 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundOpenScreenPacket( public data class ClientboundOpenScreenPacket(
val windowId: Int, val windowId: Int,
val windowType: Int, val windowType: Int,
val title: NBTCompound, val title: TextComponent,
) : MinecraftPacket { ) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundOpenScreenPacket> { internal companion object Codec : PacketCodec<ClientboundOpenScreenPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundOpenScreenPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundOpenScreenPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundOpenScreenPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundOpenScreenPacket {
val windowId = buffer.readVarInt() val windowId = buffer.readVarInt()
val windowType = buffer.readVarInt() val windowType = buffer.readVarInt()
val title = buffer.readNetworkNBTCompound() val title = buffer.readTextComponent()
return ClientboundOpenScreenPacket(windowId, windowType, title) return ClientboundOpenScreenPacket(windowId, windowType, title)
} }
} }
@@ -7,14 +7,14 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readUuid import cn.rtast.libmc.primitives.readUuid
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 import kotlin.uuid.Uuid
/** /**
@@ -29,12 +29,12 @@ public data class ClientboundPlayerChatMessagePacket(
val timestamp: Long, val timestamp: Long,
val salt: Long, val salt: Long,
val previousMessages: List<PreviousMessageEntry>, val previousMessages: List<PreviousMessageEntry>,
val unsignedContent: NBTCompound?, val unsignedContent: TextComponent?,
val filterType: ChatFilterType, val filterType: ChatFilterType,
val filterMaskBits: LongArray?, val filterMaskBits: LongArray?,
val chatType: Int, val chatType: Int,
val senderName: NBTCompound, val senderName: TextComponent,
val targetName: NBTCompound?, val targetName: TextComponent?,
) : MinecraftPacket { ) : MinecraftPacket {
public enum class ChatFilterType(public val id: Int) { public enum class ChatFilterType(public val id: Int) {
PASS_THROUGH(0), PASS_THROUGH(0),
@@ -90,7 +90,7 @@ public data class ClientboundPlayerChatMessagePacket(
val prevMessages = List(prevMessageCount) { PreviousMessageEntry.decode(buffer) } val prevMessages = List(prevMessageCount) { PreviousMessageEntry.decode(buffer) }
val hasUnsignedContent = buffer.readBoolean() 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 filterTypeId = buffer.readVarInt()
val filterType = ChatFilterType.fromID(filterTypeId) val filterType = ChatFilterType.fromID(filterTypeId)
@@ -101,10 +101,10 @@ public data class ClientboundPlayerChatMessagePacket(
} else null } else null
val chatType = buffer.readVarInt() val chatType = buffer.readVarInt()
val senderName = buffer.readNetworkNBTCompound() // ? val senderName = buffer.readTextComponent() // ?
val hasTargetName = buffer.readBoolean() val hasTargetName = buffer.readBoolean()
val targetName = if (hasTargetName) buffer.readNetworkNBTCompound() else null // ? val targetName = if (hasTargetName) buffer.readTextComponent() else null // ?
return ClientboundPlayerChatMessagePacket( return ClientboundPlayerChatMessagePacket(
globalIndex, sender, index, globalIndex, sender, index,
messageSignature, message, messageSignature, message,
@@ -7,19 +7,19 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundPlayerCombatDeathPacket(val playerId: Int, val message: NBTCompound) : MinecraftPacket { public data class ClientboundPlayerCombatDeathPacket(val playerId: Int, val message: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundPlayerCombatDeathPacket> { internal companion object Codec : PacketCodec<ClientboundPlayerCombatDeathPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundPlayerCombatDeathPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundPlayerCombatDeathPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundPlayerCombatDeathPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundPlayerCombatDeathPacket {
val playerId = buffer.readVarInt() val playerId = buffer.readVarInt()
val message = buffer.readNetworkNBTCompound() val message = buffer.readTextComponent()
return ClientboundPlayerCombatDeathPacket(playerId, message) return ClientboundPlayerCombatDeathPacket(playerId, message)
} }
} }
@@ -7,19 +7,19 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readOptional import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundServerDataPacket(val motd: NBTCompound, val icon: ByteArray?) : MinecraftPacket { public data class ClientboundServerDataPacket(val motd: TextComponent, val icon: ByteArray?) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundServerDataPacket> { internal companion object Codec : PacketCodec<ClientboundServerDataPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundServerDataPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundServerDataPacket) {}
override suspend fun decode(buffer: BytesBuffer): 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) } val icon = buffer.readOptional { val length = readVarInt(); readBytes(length) }
return ClientboundServerDataPacket(motd, icon) return ClientboundServerDataPacket(motd, icon)
} }
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.packet.PacketCodec
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 ClientboundSetActionBarTextPacket(val text: NBTCompound) : MinecraftPacket { public data class ClientboundSetActionBarTextPacket(val text: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetActionBarTextPacket> { internal companion object Codec : PacketCodec<ClientboundSetActionBarTextPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetActionBarTextPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetActionBarTextPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSetActionBarTextPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundSetActionBarTextPacket {
return ClientboundSetActionBarTextPacket(buffer.readNetworkNBTCompound()) return ClientboundSetActionBarTextPacket(buffer.readTextComponent())
} }
} }
} }
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.packet.PacketCodec
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 ClientboundSetSubtitleTextPacket(val subTitleText: NBTCompound) : MinecraftPacket { public data class ClientboundSetSubtitleTextPacket(val subTitleText: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetSubtitleTextPacket> { internal companion object Codec : PacketCodec<ClientboundSetSubtitleTextPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetSubtitleTextPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetSubtitleTextPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSetSubtitleTextPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundSetSubtitleTextPacket {
return ClientboundSetSubtitleTextPacket(buffer.readNetworkNBTCompound()) return ClientboundSetSubtitleTextPacket(buffer.readTextComponent())
} }
} }
} }
@@ -7,19 +7,19 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.packet.PacketCodec
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 ClientboundSetTabListHeaderAndFooterPacket(val header: NBTCompound, val footer: NBTCompound) : public data class ClientboundSetTabListHeaderAndFooterPacket(val header: TextComponent, val footer: TextComponent) :
MinecraftPacket { MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetTabListHeaderAndFooterPacket> { internal companion object Codec : PacketCodec<ClientboundSetTabListHeaderAndFooterPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTabListHeaderAndFooterPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTabListHeaderAndFooterPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSetTabListHeaderAndFooterPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundSetTabListHeaderAndFooterPacket {
val header = buffer.readNetworkNBTCompound() val header = buffer.readTextComponent()
val footer = buffer.readNetworkNBTCompound() val footer = buffer.readTextComponent()
return ClientboundSetTabListHeaderAndFooterPacket(header, footer) return ClientboundSetTabListHeaderAndFooterPacket(header, footer)
} }
} }
@@ -7,17 +7,17 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.packet.PacketCodec
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 ClientboundSetTitleTextPacket(val text: NBTCompound) : MinecraftPacket { public data class ClientboundSetTitleTextPacket(val text: TextComponent) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetTitleTextPacket> { internal companion object Codec : PacketCodec<ClientboundSetTitleTextPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTitleTextPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundSetTitleTextPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSetTitleTextPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundSetTitleTextPacket {
return ClientboundSetTitleTextPacket(buffer.readNetworkNBTCompound()) return ClientboundSetTitleTextPacket(buffer.readTextComponent())
} }
} }
} }
@@ -7,18 +7,18 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundSystemChatMessagePacket(val content: NBTCompound, val overlay: Boolean) : public data class ClientboundSystemChatMessagePacket(val content: TextComponent, val overlay: Boolean) :
MinecraftPacket { MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSystemChatMessagePacket> { internal companion object Codec : PacketCodec<ClientboundSystemChatMessagePacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundSystemChatMessagePacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundSystemChatMessagePacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundSystemChatMessagePacket { override suspend fun decode(buffer: BytesBuffer): ClientboundSystemChatMessagePacket {
val content = buffer.readNetworkNBTCompound() val content = buffer.readTextComponent()
val overlay = buffer.readBoolean() val overlay = buffer.readBoolean()
return ClientboundSystemChatMessagePacket(content, overlay) return ClientboundSystemChatMessagePacket(content, overlay)
} }
@@ -7,15 +7,15 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readOptional import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
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 ClientboundTestInstanceBlockStatusPacket( public data class ClientboundTestInstanceBlockStatusPacket(
val status: NBTCompound, val status: TextComponent,
val hasSize: Boolean, val hasSize: Boolean,
val sizeX: Double?, val sizeX: Double?,
val sizeY: Double?, val sizeY: Double?,
@@ -24,7 +24,7 @@ public data class ClientboundTestInstanceBlockStatusPacket(
internal companion object Codec : PacketCodec<ClientboundTestInstanceBlockStatusPacket> { internal companion object Codec : PacketCodec<ClientboundTestInstanceBlockStatusPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ClientboundTestInstanceBlockStatusPacket) {} override suspend fun encode(buffer: BytesBuffer, value: ClientboundTestInstanceBlockStatusPacket) {}
override suspend fun decode(buffer: BytesBuffer): ClientboundTestInstanceBlockStatusPacket { override suspend fun decode(buffer: BytesBuffer): ClientboundTestInstanceBlockStatusPacket {
val status = buffer.readNetworkNBTCompound() val status = buffer.readTextComponent()
val hasSize = buffer.readBoolean() val hasSize = buffer.readBoolean()
val sizeX = buffer.readOptional { readDouble() } // ? val sizeX = buffer.readOptional { readDouble() } // ?
val sizeY = buffer.readOptional { readDouble() } // ? val sizeY = buffer.readOptional { readDouble() } // ?
@@ -7,16 +7,17 @@
package cn.rtast.libmc.protocol.packet.play.clientbound package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.nbt.NBTTag
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readVarInt 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.ObjectivePayload
import cn.rtast.libmc.protocol.protocol.game.scoreboard.ObjectiveRenderType import cn.rtast.libmc.protocol.protocol.game.scoreboard.ObjectiveRenderType
import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import cn.rtast.libmc.stream.BytesBuffer
public data class ClientboundUpdateObjectivePacket( public data class ClientboundUpdateObjectivePacket(
val objectiveName: String, val objectiveName: String,
@@ -31,14 +32,14 @@ public data class ClientboundUpdateObjectivePacket(
val payload = when (mode.toInt()) { val payload = when (mode.toInt()) {
1 -> ObjectivePayload.Remove 1 -> ObjectivePayload.Remove
0, 2 -> { 0, 2 -> {
val displayName = buffer.readNetworkNBTCompound() val displayName = buffer.readTextComponent()
val renderType = ObjectiveRenderType.fromID(buffer.readVarInt()) val renderType = ObjectiveRenderType.fromID(buffer.readVarInt())
val hasNumberFormat = buffer.readBoolean() val hasNumberFormat = buffer.readBoolean()
val numberFormat = if (hasNumberFormat) { val numberFormat = if (hasNumberFormat) {
when (val type = buffer.readVarInt()) { when (val type = buffer.readVarInt()) {
0 -> ScoreNumberFormat.Blank 0 -> ScoreNumberFormat.Blank
1 -> ScoreNumberFormat.Styled(styling = buffer.readNetworkNBTCompound().element as NBTTag.CompoundTag) // fix me 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 -> error("Unknown ScoreNumberFormat type: $type")
} }
} else null } else null
@@ -7,22 +7,23 @@
package cn.rtast.libmc.protocol.packet.play.clientbound 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.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readOptional import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.nbt.NBTTag
import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat import cn.rtast.libmc.protocol.protocol.game.scoreboard.ScoreNumberFormat
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
import cn.rtast.libmc.stream.BytesBuffer
public data class ClientboundUpdateScorePacket( public data class ClientboundUpdateScorePacket(
val entityName: String, val entityName: String,
val objectiveName: String, val objectiveName: String,
val value: Int, val value: Int,
val displayName: NBTCompound?, val displayName: TextComponent?,
val numberFormat: ScoreNumberFormat?, val numberFormat: ScoreNumberFormat?,
) : MinecraftPacket { ) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundUpdateScorePacket> { internal companion object Codec : PacketCodec<ClientboundUpdateScorePacket> {
@@ -31,12 +32,12 @@ public data class ClientboundUpdateScorePacket(
val entityName = buffer.readMcString() val entityName = buffer.readMcString()
val objectiveName = buffer.readMcString() val objectiveName = buffer.readMcString()
val value = buffer.readVarInt() val value = buffer.readVarInt()
val displayName = buffer.readOptional { readNetworkNBTCompound() } val displayName = buffer.readOptional { readTextComponent() }
val numberFormat = buffer.readOptional { val numberFormat = buffer.readOptional {
when (val type = readVarInt()) { when (val type = readVarInt()) {
0 -> ScoreNumberFormat.Blank 0 -> ScoreNumberFormat.Blank
1 -> ScoreNumberFormat.Styled(styling = readNetworkNBTCompound().element as NBTTag.CompoundTag) // fix me 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") else -> error("Unknown ScoreNumberFormat type: $type")
} }
} }
@@ -10,7 +10,7 @@ package cn.rtast.libmc.protocol.packet.play.serverbound
import cn.rtast.libmc.stream.BytesBuffer import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket 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 { public data class ServerboundChangeDifficultyPacket(val newDifficulty: GameDifficulty) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundChangeDifficultyPacket> { internal companion object Codec : PacketCodec<ServerboundChangeDifficultyPacket> {
@@ -11,7 +11,7 @@ import cn.rtast.libmc.stream.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.primitives.writeVarInt 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 { public data class ServerboundClientActionPacket(val action: ClientAction) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundClientActionPacket> { internal companion object Codec : PacketCodec<ServerboundClientActionPacket> {
@@ -14,6 +14,8 @@ import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.nbt.NBTCompound import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.game.Identifier import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.block.* 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.game.writeIdentifier
import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound import cn.rtast.libmc.protocol.protocol.util.writeNetworkNBTCompound
@@ -27,7 +29,7 @@ public data class ServerboundTestInstanceBlockActionPacket(
val rotation: TestInstanceRotation, val rotation: TestInstanceRotation,
val ignoreEntities: Boolean, val ignoreEntities: Boolean,
val status: TestInstanceStatus, val status: TestInstanceStatus,
val errorMessage: NBTCompound?, val errorMessage: TextComponent?,
) : MinecraftPacket { ) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundTestInstanceBlockActionPacket> { internal companion object Codec : PacketCodec<ServerboundTestInstanceBlockActionPacket> {
override suspend fun encode(buffer: BytesBuffer, value: ServerboundTestInstanceBlockActionPacket) { override suspend fun encode(buffer: BytesBuffer, value: ServerboundTestInstanceBlockActionPacket) {
@@ -42,7 +44,7 @@ public data class ServerboundTestInstanceBlockActionPacket(
buffer.writeBoolean(value.ignoreEntities) buffer.writeBoolean(value.ignoreEntities)
buffer.writeVarInt(value.status.id) buffer.writeVarInt(value.status.id)
buffer.writeBoolean(value.errorMessage != null) 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 = override suspend fun decode(buffer: BytesBuffer): ServerboundTestInstanceBlockActionPacket =
@@ -76,7 +76,7 @@ internal object GameProtocols {
register(0x0d, ClientboundChunksBiomesPacket) register(0x0d, ClientboundChunksBiomesPacket)
register(0x0e, ClientboundClearTitlesPacket) register(0x0e, ClientboundClearTitlesPacket)
register(0x0f, ClientboundCommandSuggestionsPacket) register(0x0f, ClientboundCommandSuggestionsPacket)
// register(0x10, ClientboundCommandsPacket) register(0x10, ClientboundCommandsPacket)
register(0x11, ClientboundContainerClosePacket) register(0x11, ClientboundContainerClosePacket)
// register(0x12, ClientboundContainerSetContentPacket) // register(0x12, ClientboundContainerSetContentPacket)
register(0x13, ClientboundContainerSetDataPacket) register(0x13, ClientboundContainerSetDataPacket)
@@ -7,11 +7,11 @@
package cn.rtast.libmc.protocol.protocol.game.bossbar 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 sealed interface BossBarAction {
public data class Add( public data class Add(
val title: NBTCompound, val title: TextComponent,
val health: Float, val health: Float,
val color: BossBarColor, val color: BossBarColor,
val division: BossBarDivision, val division: BossBarDivision,
@@ -20,7 +20,7 @@ public sealed interface BossBarAction {
public object Remove : BossBarAction public object Remove : BossBarAction
public data class UpdateHealth(val health: Float) : 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 UpdateStyle(val color: BossBarColor, val division: BossBarDivision) : BossBarAction
public data class UpdateFlags(val flags: BossBarFlags) : BossBarAction public data class UpdateFlags(val flags: BossBarFlags) : BossBarAction
@@ -17,7 +17,7 @@ import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class ChatTypeDecoration( public data class ChatTypeDecoration(
val translationKey: String, val translationKey: String,
val parameters: List<ChatTypeParameter>, val parameters: List<ChatTypeParameter>,
val style: NBTCompound, val style: NBTCompound, // TODO TextComponent or Raw NBT Compound
) { ) {
public enum class ChatTypeParameter(public val id: Int) { public enum class ChatTypeParameter(public val id: Int) {
Sender(0), Target(1), Content(2); Sender(0), Target(1), Content(2);
@@ -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<TextComponent> = 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<TextComponent> = 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<Property> = 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<NBTTag.FloatTag>().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<String, NBTTag>()
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<String, NBTTag>()
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<String, NBTTag>("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<String, NBTTag>("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<NBTTag>? = (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))
}
@@ -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
}
@@ -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<Int>,
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<CommandNode> {
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<Int>(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 -> {}
}
}
}
}
@@ -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 }
}
}
@@ -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) { public enum class ClientAction(public val actionID: Int) {
/** /**
@@ -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) { public enum class GameDifficulty(public val id: Byte) {
Peaceful(0), Peaceful(0),
@@ -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) { public enum class BuiltinServerLinkType(public val id: Int) {
BUG_REPORT(0), BUG_REPORT(0),
@@ -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.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readVarInt import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeMcString import cn.rtast.libmc.primitives.writeMcString
import cn.rtast.libmc.primitives.writeVarInt 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.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) { public data class ServerLink(val label: ServerLinkLabel, val url: String) {
internal companion object Codec : PacketCodec<ServerLink> { internal companion object Codec : PacketCodec<ServerLink> {
@@ -28,7 +28,7 @@ public data class ServerLink(val label: ServerLinkLabel, val url: String) {
is ServerLinkLabel.Custom -> { is ServerLinkLabel.Custom -> {
buffer.writeBoolean(false) buffer.writeBoolean(false)
buffer.writeNetworkNBTCompound(value.label.text) buffer.writeTextComponent(value.label.text)
} }
} }
buffer.writeMcString(value.url) 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 { override suspend fun decode(buffer: BytesBuffer): ServerLink {
val isBuiltin = buffer.readBoolean() val isBuiltin = buffer.readBoolean()
val label = if (isBuiltin) ServerLinkLabel.Builtin(BuiltinServerLinkType.fromID(buffer.readVarInt())) val label = if (isBuiltin) ServerLinkLabel.Builtin(BuiltinServerLinkType.fromID(buffer.readVarInt()))
else ServerLinkLabel.Custom(buffer.readNetworkNBTCompound()) else ServerLinkLabel.Custom(buffer.readTextComponent())
val url = buffer.readMcString() val url = buffer.readMcString()
return ServerLink(label, url) return ServerLink(label, url)
} }
@@ -46,5 +46,5 @@ public data class ServerLink(val label: ServerLinkLabel, val url: String) {
public sealed interface ServerLinkLabel { public sealed interface ServerLinkLabel {
public data class Builtin(val type: BuiltinServerLinkType) : 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
} }
@@ -7,12 +7,12 @@
package cn.rtast.libmc.protocol.protocol.game.scoreboard 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 sealed interface ObjectivePayload {
public object Remove : ObjectivePayload public object Remove : ObjectivePayload
public data class Upsert( public data class Upsert(
val displayName: NBTCompound, val displayName: TextComponent,
val renderType: ObjectiveRenderType, val renderType: ObjectiveRenderType,
val defaultNumberFormat: ScoreNumberFormat?, val defaultNumberFormat: ScoreNumberFormat?,
) : ObjectivePayload ) : ObjectivePayload
@@ -7,11 +7,11 @@
package cn.rtast.libmc.protocol.protocol.game.scoreboard package cn.rtast.libmc.protocol.protocol.game.scoreboard
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.nbt.NBTTag import cn.rtast.libmc.nbt.NBTTag
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public sealed interface ScoreNumberFormat { public sealed interface ScoreNumberFormat {
public object Blank : ScoreNumberFormat public object Blank : ScoreNumberFormat
public data class Styled(val styling: NBTTag.CompoundTag) : 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
} }
@@ -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.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString import cn.rtast.libmc.primitives.readMcString
@@ -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.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@@ -7,18 +7,17 @@
package test 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.client.createMinecraftClient
import cn.rtast.libmc.protocol.crypto.DefaultProtocolContext 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 cn.rtast.libmc.protocol.util.generateOfflineUuid
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.junit.Test import org.junit.Test
import java.io.File import java.io.File
import java.math.BigInteger import java.math.BigInteger
import java.net.HttpURLConnection
import java.net.URL
import java.security.KeyFactory import java.security.KeyFactory
import java.security.MessageDigest import java.security.MessageDigest
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
@@ -64,24 +63,9 @@ class TestClientTestInJvm {
accessToken, accessToken,
crypto = DefaultProtocolContext 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<ClientboundSystemChatMessagePacket> { println(it) } // cli.onPacket<ClientboundSystemChatMessagePacket> { println(it) }
// cli.onPacket<ClientboundLoginSuccessPacket> { println(it) } // cli.onPacket<ClientboundLoginSuccessPacket> { println(it) }
cli.on { packet, direction -> println("${direction} -> $packet") } cli.on { packet, direction -> println("$direction -> $packet") }
cli.launch { cli.connect() } cli.launch { cli.connect() }
while (true) { while (true) {
} }
@@ -95,29 +79,10 @@ class TestClientTestInJvm {
"11", "11",
generateOfflineUuid("11"), generateOfflineUuid("11"),
null, null,
) { crypto = DefaultProtocolContext
rsaEncryptor = RSA1024Encryptor { key, data -> encrypt(key, data) } )
sha1Hasher = // cli.on { packet, direction -> println("$direction -> $packet") }
Sha1Hasher { serverId, secretKey, publicKey -> cli.onPacket<ClientboundSystemChatMessagePacket> { println(it.content.content) }
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<ClientboundSystemChatMessagePacket> { println(it) }
// cli.on<ClientboundLoginSuccessPacket> { println(it) }
cli.launch { cli.connect() } cli.launch { cli.connect() }
while (true) { while (true) {
} }