Fix serverbound client command;

Implemented 0x85-ClientboundUpdateRecipesPacket
0x82-ClientboundUpdateAdvancementsPacket
0x6d-ClientboundSetPlayerTeamPacket
0x6c-ClientboundSetPlayerInventorySlotPacket
0x66-ClientboundSetEquipmentPacket
0x60-ClientboundSetCursorItemPacket
0x4a-ClientboundRecipeBookAddPacket
0x3f-ClientboundPlaceGhostRecipePacket
0x34-ClientboundMerchantOffersPacket
0x30-ClientboundLightUpdatePacket
This commit is contained in:
2026-09-11 15:52:22 +08:00
parent 3cca64d9c1
commit e47a0bff04
35 files changed
+895 -87

No files matched your search

+16 -20
View File
@@ -18,18 +18,14 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
# Minecraft Protocol Library Status # Minecraft Protocol Library Status
## Implementation details ## Unimplemented
- [x] **Online Mode Authentication & Encryption/Decryption**: See [Embedded cryptography](docs/Embedded-cryptography.md)
- [x] **Structured `TextComponent` Parser**: TextComponent AST decoder
- [x] **Command Tree Parser**: Full binary graph decoder for brigadier nodes, argument types, and suggestions
- [x] **Recipe Book & Recipe Data**
- [ ] **Chunk & World Data**: Level Chunk Data with Light decoder (Paletted Containers, Direct/Indirect Palettes) - [ ] **Chunk & World Data**: Level Chunk Data with Light decoder (Paletted Containers, Direct/Indirect Palettes)
- [ ] **Light Engine Update**: Sky & Block light nibble array parser - [ ] **Light Engine Update**: Sky & Block light nibble array parser
- [ ] **Explosion Event Decoder**: Knockback vectors and destroyed block offsets array
- [ ] **Debug Packets Parsing**: Debug subs, block/entity states, and game performance sample events - [ ] **Debug Packets Parsing**: Debug subs, block/entity states, and game performance sample events
- [ ] **Particle Parsing**
- [x] **Slot Data Codec**
> Encryption was implemented
--- ---
@@ -160,7 +156,7 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x21` Disguised Chat Message (`ClientboundDisguisedChatMessagePacket`) - [x] `0x21` Disguised Chat Message (`ClientboundDisguisedChatMessagePacket`)
- [x] `0x22` Entity Event (`ClientboundEntityEventPacket`) - [x] `0x22` Entity Event (`ClientboundEntityEventPacket`)
- [x] `0x23` Teleport Entity (`ClientboundTeleportEntityPacket`) - [x] `0x23` Teleport Entity (`ClientboundTeleportEntityPacket`)
- [ ] `0x24` Explode (`ClientboundExplodePacket`) - [x] `0x24` Explode (`ClientboundExplodePacket`)
- [x] `0x25` Unload Chunk (`ClientboundUnloadChunkPacket`) - [x] `0x25` Unload Chunk (`ClientboundUnloadChunkPacket`)
- [x] `0x26` Game Event (`ClientboundGameEventPacket`) - [x] `0x26` Game Event (`ClientboundGameEventPacket`)
- [x] `0x27` Game Rule Values (`ClientboundGameRuleValuesPacket`) - [x] `0x27` Game Rule Values (`ClientboundGameRuleValuesPacket`)
@@ -171,12 +167,12 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x2C` Keep Alive (`ClientboundKeepAlivePlayPacket`) - [x] `0x2C` Keep Alive (`ClientboundKeepAlivePlayPacket`)
- [ ] `0x2D` Level Chunk Update With Light (`ClientboundLevelChunkUpdateWithLightPacket`) - [ ] `0x2D` Level Chunk Update With Light (`ClientboundLevelChunkUpdateWithLightPacket`)
- [x] `0x2E` Level Event (`ClientboundLevelEventPacket`) - [x] `0x2E` Level Event (`ClientboundLevelEventPacket`)
- [x] `0x2F` Particle (`ClientboundLevelParticlePacket`) **NOTE: Slot data are parsed as raw ByteArray** - [x] `0x2F` Particle (`ClientboundLevelParticlePacket`)
- [ ] `0x30` Light Update (`ClientboundLightUpdatePacket`) - [x] `0x30` Light Update (`ClientboundLightUpdatePacket`)
- [x] `0x31` Login Play (`ClientboundLoginPlayPacket`) - [x] `0x31` Login Play (`ClientboundLoginPlayPacket`)
- [x] `0x32` Low Disk Space Warning (`ClientboundLowDiskSpaceWarningPacket`) - [x] `0x32` Low Disk Space Warning (`ClientboundLowDiskSpaceWarningPacket`)
- [x] `0x33` Map Item Data (`ClientboundMapItemDataPacket`) - [x] `0x33` Map Item Data (`ClientboundMapItemDataPacket`)
- [ ] `0x34` Merchant Offers (`ClientboundMerchantOffersPacket`) - [x] `0x34` Merchant Offers (`ClientboundMerchantOffersPacket`)
- [x] `0x35` Update Entity Position (`ClientboundUpdateEntityPositionPacket`) - [x] `0x35` Update Entity Position (`ClientboundUpdateEntityPositionPacket`)
- [x] `0x36` Update Entity Position and Rotation (`ClientboundUpdateEntityPositionAndRotationPacket`) - [x] `0x36` Update Entity Position and Rotation (`ClientboundUpdateEntityPositionAndRotationPacket`)
- [x] `0x37` Move Minecart Along Track (`ClientboundMoveMinecartAlongTrackPacket`) - [x] `0x37` Move Minecart Along Track (`ClientboundMoveMinecartAlongTrackPacket`)
@@ -187,7 +183,7 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x3C` Open Sign Editor (`ClientboundOpenSignEditorPacket`) - [x] `0x3C` Open Sign Editor (`ClientboundOpenSignEditorPacket`)
- [x] `0x3D` Ping (`ClientboundPingPacket`) - [x] `0x3D` Ping (`ClientboundPingPacket`)
- [x] `0x3E` Pong Response (`ClientboundPongResponsePacket`) - [x] `0x3E` Pong Response (`ClientboundPongResponsePacket`)
- [ ] `0x3F` Place Ghost Recipe (`ClientboundPlaceGhostRecipePacket`) - [x] `0x3F` Place Ghost Recipe (`ClientboundPlaceGhostRecipePacket`)
- [x] `0x40` Player Abilities (`ClientboundPlayerAbilitiesPacket`) - [x] `0x40` Player Abilities (`ClientboundPlayerAbilitiesPacket`)
- [x] `0x41` Player Chat Message (`ClientboundPlayerChatMessagePacket`) - [x] `0x41` Player Chat Message (`ClientboundPlayerChatMessagePacket`)
- [x] `0x42` Player End Combat (`ClientboundPlayerEndCombatPacket`) - [x] `0x42` Player End Combat (`ClientboundPlayerEndCombatPacket`)
@@ -198,7 +194,7 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x47` Player Look At (`ClientboundPlayerLookAtPacket`) - [x] `0x47` Player Look At (`ClientboundPlayerLookAtPacket`)
- [x] `0x48` Synchronize Player Position (`ClientboundSynchronizePlayerPositionPacket`) - [x] `0x48` Synchronize Player Position (`ClientboundSynchronizePlayerPositionPacket`)
- [x] `0x49` Player Rotation (`ClientboundPlayerRotationPacket`) - [x] `0x49` Player Rotation (`ClientboundPlayerRotationPacket`)
- [ ] `0x4A` Recipe Book Add (`ClientboundRecipeBookAddPacket`) - [x] `0x4A` Recipe Book Add (`ClientboundRecipeBookAddPacket`)
- [x] `0x4B` Recipe Book Remove (`ClientboundRecipeBookRemovePacket`) - [x] `0x4B` Recipe Book Remove (`ClientboundRecipeBookRemovePacket`)
- [x] `0x4C` Recipe Book Settings (`ClientboundRecipeBookSettingsPacket`) - [x] `0x4C` Recipe Book Settings (`ClientboundRecipeBookSettingsPacket`)
- [x] `0x4D` Remove Entities (`ClientboundRemoveEntitiesPacket`) - [x] `0x4D` Remove Entities (`ClientboundRemoveEntitiesPacket`)
@@ -220,20 +216,20 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x5D` Set Camera (`ClientboundSetCameraPacket`) - [x] `0x5D` Set Camera (`ClientboundSetCameraPacket`)
- [x] `0x5E` Set Center Chunk (`ClientboundSetCenterChunkPacket`) - [x] `0x5E` Set Center Chunk (`ClientboundSetCenterChunkPacket`)
- [x] `0x5F` Set Render Distance (`ClientboundSetRenderDistancePacket`) - [x] `0x5F` Set Render Distance (`ClientboundSetRenderDistancePacket`)
- [ ] `0x60` Set Cursor Item (`ClientboundSetCursorItemPacket`) - [x] `0x60` Set Cursor Item (`ClientboundSetCursorItemPacket`)
- [x] `0x61` Set Default Spawn Position (`ClientboundSetDefaultSpawnPositionPacket`) - [x] `0x61` Set Default Spawn Position (`ClientboundSetDefaultSpawnPositionPacket`)
- [x] `0x62` Set Display Objective (`ClientboundSetDisplayObjectivePacket`) - [x] `0x62` Set Display Objective (`ClientboundSetDisplayObjectivePacket`)
- [ ] `0x63` Set Entity Metadata (`ClientboundSetEntityMetadataPacket`) - [ ] `0x63` Set Entity Metadata (`ClientboundSetEntityMetadataPacket`)
- [x] `0x64` Link Entities (`ClientboundLinkEntitiesPacket`) - [x] `0x64` Link Entities (`ClientboundLinkEntitiesPacket`)
- [x] `0x65` Set Entity Velocity (`ClientboundSetEntityVelocityPacket`) - [x] `0x65` Set Entity Velocity (`ClientboundSetEntityVelocityPacket`)
- [ ] `0x66` Set Equipment (`ClientboundSetEquipmentPacket`) - [x] `0x66` Set Equipment (`ClientboundSetEquipmentPacket`)
- [x] `0x67` Set Experience (`ClientboundSetExperiencePacket`) - [x] `0x67` Set Experience (`ClientboundSetExperiencePacket`)
- [x] `0x68` Set Health (`ClientboundSetHealthPacket`) - [x] `0x68` Set Health (`ClientboundSetHealthPacket`)
- [x] `0x69` Set Carried Item (`ClientboundSetCarriedItemPacket`) - [x] `0x69` Set Carried Item (`ClientboundSetCarriedItemPacket`)
- [x] `0x6A` Update Objective (`ClientboundUpdateObjectivePacket`) - [x] `0x6A` Update Objective (`ClientboundUpdateObjectivePacket`)
- [x] `0x6B` Set Passengers (`ClientboundSetPassengersPacket`) - [x] `0x6B` Set Passengers (`ClientboundSetPassengersPacket`)
- [ ] `0x6C` Set Player Inventory Slot (`ClientboundSetPlayerInventorySlotPacket`) - [x] `0x6C` Set Player Inventory Slot (`ClientboundSetPlayerInventorySlotPacket`)
- [ ] `0x6D` Set Player Team (`ClientboundSetPlayerTeamPacket`) - [x] `0x6D` Set Player Team (`ClientboundSetPlayerTeamPacket`)
- [x] `0x6E` Update Score (`ClientboundUpdateScorePacket`) - [x] `0x6E` Update Score (`ClientboundUpdateScorePacket`)
- [x] `0x6F` Set Simulation Distance (`ClientboundSetSimulationDistancePacket`) - [x] `0x6F` Set Simulation Distance (`ClientboundSetSimulationDistancePacket`)
- [x] `0x70` Set Subtitle Text (`ClientboundSetSubtitleTextPacket`) - [x] `0x70` Set Subtitle Text (`ClientboundSetSubtitleTextPacket`)
@@ -254,10 +250,10 @@ A lightweight minecraft client-side protocol library for Kotlin Native and JVM
- [x] `0x7F` Set Ticking State (`ClientboundSetTickingStatePacket`) - [x] `0x7F` Set Ticking State (`ClientboundSetTickingStatePacket`)
- [x] `0x80` Step Tick (`ClientboundStepTickPacket`) - [x] `0x80` Step Tick (`ClientboundStepTickPacket`)
- [x] `0x81` Transfer (`ClientboundTransferPacket`) - [x] `0x81` Transfer (`ClientboundTransferPacket`)
- [ ] `0x82` Update Advancements (`ClientboundUpdateAdvancementsPacket`) - [x] `0x82` Update Advancements (`ClientboundUpdateAdvancementsPacket`)
- [x] `0x83` Update Attributes (`ClientboundUpdateAttributesPacket`) - [x] `0x83` Update Attributes (`ClientboundUpdateAttributesPacket`)
- [x] `0x84` Entity Effect (`ClientboundEntityEffectPacket`) - [x] `0x84` Entity Effect (`ClientboundEntityEffectPacket`)
- [ ] `0x85` Update Recipes (`ClientboundUpdateRecipesPacket`) - [x] `0x85` Update Recipes (`ClientboundUpdateRecipesPacket`)
- [x] `0x86` Update Tags (`ClientboundUpdateTagsPacket`) - [x] `0x86` Update Tags (`ClientboundUpdateTagsPacket`)
- [x] `0x87` Projectile Power (`ClientboundProjectilePowerPacket`) - [x] `0x87` Projectile Power (`ClientboundProjectilePowerPacket`)
- [x] `0x88` Custom Report Details (`ClientboundCustomReportDetailsPacket`) - [x] `0x88` Custom Report Details (`ClientboundCustomReportDetailsPacket`)
@@ -0,0 +1,93 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.data.component.DataComponent
import cn.rtast.libmc.protocol.protocol.game.entity.VillagerLevel
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
import cn.rtast.libmc.protocol.registry.readDataComponent
public data class ClientboundMerchantOffersPacket(
val windowId: Int,
val trades: List<MerchantTrade>,
val villagerLevel: VillagerLevel,
val experience: Int,
val isRegularVillager: Boolean,
val canRestock: Boolean,
) : MinecraftPacket {
public data class MerchantTrade(
val inputItem1: TradeItem,
val outputItem: Slot,
val inputItem2: TradeItem?,
val tradable: Boolean,
val tradeUsed: Int,
val maxTradeUsed: Int,
val xp: Int,
val specialPrice: Int,
val priceMultiplier: Float,
val demand: Int,
)
public data class TradeItem(
val itemId: Int,
val itemCount: Int,
val components: Map<Int, DataComponent>,
)
internal companion object Codec : PacketCodec<ClientboundMerchantOffersPacket> {
private fun BytesBuffer.readTradeItem(): TradeItem {
val itemId = readVarInt()
val itemCount = readVarInt()
val componentCount = readVarInt()
val components = HashMap<Int, DataComponent>(componentCount)
repeat(componentCount) {
val typeId = readVarInt()
val componentData = readDataComponent(typeId)
components[typeId] = componentData
}
return TradeItem(itemId, itemCount, components)
}
override fun encode(buffer: BytesBuffer, value: ClientboundMerchantOffersPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundMerchantOffersPacket {
val windowId = buffer.readVarInt()
val trades = buffer.readPrefixed {
val inputItem1 = readTradeItem()
val outputItem = readSlot()
val inputItem2 = readPrefixOptional { readTradeItem() }
val tradable = !readBoolean() // reverse
val tradeUsed = readInt()
val maxTradeUsed = readInt()
val xp = readInt()
val specialPrice = readInt()
val priceMultiplier = readFloat()
val demand = readInt()
MerchantTrade(
inputItem1, outputItem, inputItem2, tradable,
tradeUsed, maxTradeUsed, xp, specialPrice,
priceMultiplier, demand,
)
}
val villagerLevel = VillagerLevel.fromLevel(buffer.readVarInt())
val experience = buffer.readVarInt()
val isRegularVillager = buffer.readBoolean()
val canRestock = buffer.readBoolean()
return ClientboundMerchantOffersPacket(
windowId, trades, villagerLevel,
experience, isRegularVillager, canRestock
)
}
}
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.recipe.RecipeDisplay
import cn.rtast.libmc.protocol.protocol.game.recipe.readRecipeDisplay
public data class ClientboundPlaceGhostRecipePacket(val windowId: Int, val recipeDisplay: RecipeDisplay) :
MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundPlaceGhostRecipePacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundPlaceGhostRecipePacket) {}
override fun decode(buffer: BytesBuffer): ClientboundPlaceGhostRecipePacket {
val windowId = buffer.readVarInt()
val recipeDisplay = buffer.readRecipeDisplay()
return ClientboundPlaceGhostRecipePacket(windowId, recipeDisplay)
}
}
}
@@ -0,0 +1,38 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readIdSet
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.recipe.RecipeEntry
import cn.rtast.libmc.protocol.protocol.game.recipe.readRecipeDisplay
public data class ClientboundRecipeBookAddPacket(val recipes: List<RecipeEntry>, val replace: Boolean) :
MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundRecipeBookAddPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundRecipeBookAddPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundRecipeBookAddPacket {
val recipes = buffer.readPrefixed {
val recipeId = readVarInt()
val display = readRecipeDisplay()
val groupId = readVarInt()
val categoryId = readVarInt()
val ingredients = readPrefixOptional { readPrefixed { readIdSet() } }
val flags = readByte()
RecipeEntry(recipeId, display, groupId, categoryId, ingredients, flags)
}
val replace = buffer.readBoolean()
return ClientboundRecipeBookAddPacket(recipes, replace)
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
public data class ClientboundSetCursorItemPacket(val carriedItem: Slot) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetCursorItemPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetCursorItemPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSetCursorItemPacket =
ClientboundSetCursorItemPacket(buffer.readSlot())
}
}
@@ -0,0 +1,37 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.inventory.equipment.EntityEquipment
import cn.rtast.libmc.protocol.protocol.game.inventory.equipment.EntityEquipmentSlot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
public data class ClientboundSetEquipmentPacket(val entityId: Int, val equipments: List<EntityEquipment>) :
MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetEquipmentPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetEquipmentPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSetEquipmentPacket {
val entityId = buffer.readVarInt()
val equipments = ArrayList<EntityEquipment>()
while (true) {
val rawByte = buffer.readByte().toInt()
val hasNext = (rawByte and 0x80) != 0
val slotId = rawByte and 0x7F
val slot = EntityEquipmentSlot.fromID(slotId)
val item = buffer.readSlot()
equipments.add(EntityEquipment(slot, item))
if (!hasNext) break
}
return ClientboundSetEquipmentPacket(entityId, equipments)
}
}
}
@@ -0,0 +1,26 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
public data class ClientboundSetPlayerInventorySlotPacket(val slot: Int, val slotData: Slot) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetPlayerInventorySlotPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetPlayerInventorySlotPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSetPlayerInventorySlotPacket {
val slot = buffer.readVarInt()
val slotData = buffer.readSlot()
return ClientboundSetPlayerInventorySlotPacket(slot, slotData)
}
}
}
@@ -0,0 +1,57 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.protocol.protocol.game.team.CollisionRule
import cn.rtast.libmc.protocol.protocol.game.team.NameTagVisibility
import cn.rtast.libmc.protocol.protocol.game.team.TeamAction
public data class ClientboundSetPlayerTeamPacket(val teamName: String, val action: TeamAction) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundSetPlayerTeamPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundSetPlayerTeamPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundSetPlayerTeamPacket {
val teamName = buffer.readMcString()
val action = when (val teamAction = buffer.readByte().toInt()) {
0 -> {
val info = buffer.decodeTeamInfo()
val entities = buffer.readPrefixed { readMcString() }
TeamAction.CreateTeam(info, entities)
}
1 -> TeamAction.RemoveTeam
2 -> TeamAction.UpdateTeamInfo(buffer.decodeTeamInfo())
3 -> TeamAction.AddEntities(buffer.readPrefixed { readMcString() })
4 -> TeamAction.RemoveEntities(buffer.readPrefixed { readMcString() })
else -> error("Unknown team action method id $teamAction")
}
return ClientboundSetPlayerTeamPacket(teamName, action)
}
private fun BytesBuffer.decodeTeamInfo(): TeamAction.TeamInfo {
val displayName = readTextComponent()
val prefix = readTextComponent()
val suffix = readTextComponent()
val nameTagVisibility = NameTagVisibility.fromID(readVarInt())
val collisionRule = CollisionRule.fromID(readVarInt())
val teamColor = readVarInt()
val friendlyFlags = readByte()
return TeamAction.TeamInfo(
displayName, prefix, suffix,
nameTagVisibility, collisionRule,
teamColor, friendlyFlags
)
}
}
}
@@ -0,0 +1,64 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.packet.play.clientbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.advancement.Advancement
import cn.rtast.libmc.protocol.protocol.game.advancement.AdvancementProgress
import cn.rtast.libmc.protocol.protocol.game.advancement.readAdvancement
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
public data class ClientboundUpdateAdvancementsPacket(
val reset: Boolean,
val advancementMapping: Map<Identifier, Advancement>,
/**
* The identifiers of the advancements that should be removed
*/
val identifiers: List<Identifier>,
val progressMapping: Map<Identifier, AdvancementProgress>,
val showAdvancements: Boolean,
) : MinecraftPacket {
internal companion object Codec : PacketCodec<ClientboundUpdateAdvancementsPacket> {
override fun encode(buffer: BytesBuffer, value: ClientboundUpdateAdvancementsPacket) {}
override fun decode(buffer: BytesBuffer): ClientboundUpdateAdvancementsPacket {
val reset = buffer.readBoolean()
val mappingCount = buffer.readVarInt()
val advancementMapping = HashMap<Identifier, Advancement>(mappingCount)
repeat(mappingCount) {
val key = buffer.readIdentifier()
val advancement = buffer.readAdvancement()
advancementMapping[key] = advancement
}
val identifiers = buffer.readPrefixed { readIdentifier() }
val progressMappingCount = buffer.readVarInt()
val progressMapping = HashMap<Identifier, AdvancementProgress>(progressMappingCount)
repeat(progressMappingCount) {
val key = buffer.readIdentifier()
val criteriaCount = buffer.readVarInt()
val criteria = HashMap<Identifier, Long?>(criteriaCount)
repeat(criteriaCount) {
val k = buffer.readIdentifier()
val date = buffer.readPrefixOptional { readLong() }
criteria[k] = date
}
progressMapping[key] = AdvancementProgress(criteria)
}
val showAdvancements = buffer.readBoolean()
return ClientboundUpdateAdvancementsPacket(
reset, advancementMapping, identifiers,
progressMapping, showAdvancements
)
}
}
}
@@ -1,25 +0,0 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/5
*/
package cn.rtast.libmc.protocol.packet.play.serverbound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.registry.ClientAction
public data class ServerboundClientActionPacket(val action: ClientAction) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundClientActionPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundClientActionPacket) {
buffer.writeVarInt(value.action.actionID)
}
override fun decode(buffer: BytesBuffer): ServerboundClientActionPacket =
throw UnsupportedOperationException()
}
}
@@ -8,12 +8,12 @@
package cn.rtast.libmc.protocol.packet.play.serverbound package cn.rtast.libmc.protocol.packet.play.serverbound
import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.network.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.writeVarInt import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.player.action.PlayerActionStatus import cn.rtast.libmc.protocol.protocol.game.registry.ClientAction
public data class ServerboundClientCommandPacket(val action: PlayerActionStatus) : MinecraftPacket { public data class ServerboundClientCommandPacket(val action: ClientAction) : MinecraftPacket {
internal companion object Codec : PacketCodec<ServerboundClientCommandPacket> { internal companion object Codec : PacketCodec<ServerboundClientCommandPacket> {
override fun encode(buffer: BytesBuffer, value: ServerboundClientCommandPacket) { override fun encode(buffer: BytesBuffer, value: ServerboundClientCommandPacket) {
buffer.writeVarInt(value.action.id) buffer.writeVarInt(value.action.id)
@@ -23,9 +23,9 @@ public value class Identifier internal constructor(public val raw: String) {
override fun toString(): String = "$namespace:$path" override fun toString(): String = "$namespace:$path"
internal companion object Codec : PacketCodec<Identifier> { public companion object Codec : PacketCodec<Identifier> {
fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path") public fun of(namespace: String, path: String): Identifier = Identifier("$namespace:$path")
fun of(full: String): Identifier = Identifier(full) public fun of(full: String): Identifier = Identifier(full)
override fun encode(buffer: BytesBuffer, value: Identifier) { override fun encode(buffer: BytesBuffer, value: Identifier) {
buffer.writeMcString(value.toString()) buffer.writeMcString(value.toString())
@@ -0,0 +1,44 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.advancement
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.chat.readTextComponent
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlot
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
public data class Advancement(
val parentId: Identifier?,
val displayData: AdvancementDisplay?,
val requirements: List<List<String>>,
val sendsTelemetryData: Boolean,
)
internal fun BytesBuffer.readAdvancement(): Advancement {
val parentId = readPrefixOptional { readIdentifier() }
val displayData = readPrefixOptional {
val title = readTextComponent()
val description = readTextComponent()
val icon = readSlot()
val frameType = AdvancementFrameType.fromID(readVarInt())
val flags = readInt()
val hasBackground = (flags and 0x01) != 0
val backgroundTexture = if (hasBackground) readIdentifier() else null
val x = readFloat()
val y = readFloat()
AdvancementDisplay(title, description, icon, frameType, flags, backgroundTexture, x, y)
}
val requirements = readPrefixed { readPrefixed { readMcString() } }
val sendsTelemetryData = readBoolean()
return Advancement(parentId, displayData, requirements, sendsTelemetryData)
}
@@ -0,0 +1,27 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.advancement
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
public data class AdvancementDisplay(
val title: TextComponent,
val description: TextComponent,
val icon: Slot,
val frameType: AdvancementFrameType,
val flags: Int,
val backgroundTexture: Identifier?,
val x: Float,
val y: Float
) {
public val hasBackgroundTexture: Boolean get() = (flags and 0x01) != 0
public val showToast: Boolean get() = (flags and 0x02) != 0
public val isHidden: Boolean get() = (flags and 0x04) != 0
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.advancement
public enum class AdvancementFrameType(public val id: Int) {
TASK(0), CHALLENGE(1), GOAL(2);
public companion object {
public fun fromID(id: Int): AdvancementFrameType = entries.first { it.id == id }
}
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.advancement
import cn.rtast.libmc.protocol.protocol.game.Identifier
public data class AdvancementProgress(val criteria: Map<Identifier, Long?>)
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.entity
public enum class VillagerLevel(public val level: Int) {
Novice(1), Apprentice(2), Journeyman(3), Expert(4), Master(5);
public companion object {
public fun fromLevel(level: Int): VillagerLevel = entries.first { it.level == level }
}
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.inventory.equipment
import cn.rtast.libmc.protocol.protocol.game.item.slot.Slot
public data class EntityEquipment(val slot: EntityEquipmentSlot, val item: Slot)
@@ -0,0 +1,23 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.inventory.equipment
public enum class EntityEquipmentSlot(public val id: Int) {
MAIN_HAND(0),
OFF_HAND(1),
BOOTS(2),
LEGGINGS(3),
CHESTPLATE(4),
HELMET(5),
BODY(6),
SADDLE(7);
public companion object {
public fun fromID(id: Int): EntityEquipmentSlot = entries.firstOrNull { it.id == id } ?: MAIN_HAND
}
}
@@ -8,7 +8,6 @@
package cn.rtast.libmc.protocol.protocol.game.item.slot package cn.rtast.libmc.protocol.protocol.game.item.slot
import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.EmptyPacketCodec
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.primitives.writePrefixed import cn.rtast.libmc.primitives.writePrefixed
@@ -18,8 +17,16 @@ import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public sealed interface SlotDisplayData { public sealed interface SlotDisplayData {
public data object Empty : SlotDisplayData, EmptyPacketCodec<Empty>(Empty) public data object Empty : SlotDisplayData, PacketCodec<Empty> {
public data object AnyFuel : SlotDisplayData, EmptyPacketCodec<Empty>(Empty) override fun encode(buffer: BytesBuffer, value: Empty) {}
override fun decode(buffer: BytesBuffer): Empty = Empty
}
public data object AnyFuel : SlotDisplayData, PacketCodec<AnyFuel> {
override fun encode(buffer: BytesBuffer, value: AnyFuel) {}
override fun decode(buffer: BytesBuffer): AnyFuel = AnyFuel
}
public data class WithAnyPotion(val base: SlotDisplay) : SlotDisplayData { public data class WithAnyPotion(val base: SlotDisplay) : SlotDisplayData {
internal companion object Codec : PacketCodec<WithAnyPotion> { internal companion object Codec : PacketCodec<WithAnyPotion> {
override fun encode(buffer: BytesBuffer, value: WithAnyPotion) { override fun encode(buffer: BytesBuffer, value: WithAnyPotion) {
@@ -65,13 +72,13 @@ public sealed interface SlotDisplayData {
} }
} }
public data class Tags(val tag: Identifier) : SlotDisplayData { public data class Tag(val tag: Identifier) : SlotDisplayData {
internal companion object Codec : PacketCodec<Tags> { internal companion object Codec : PacketCodec<Tag> {
override fun encode(buffer: BytesBuffer, value: Tags) { override fun encode(buffer: BytesBuffer, value: Tag) {
buffer.writeIdentifier(value.tag) buffer.writeIdentifier(value.tag)
} }
override fun decode(buffer: BytesBuffer): Tags = Tags(buffer.readIdentifier()) override fun decode(buffer: BytesBuffer): Tag = Tag(buffer.readIdentifier())
} }
} }
@@ -79,6 +86,7 @@ public sealed interface SlotDisplayData {
internal companion object Codec : PacketCodec<Dyed> { internal companion object Codec : PacketCodec<Dyed> {
override fun encode(buffer: BytesBuffer, value: Dyed) { override fun encode(buffer: BytesBuffer, value: Dyed) {
buffer.writeSlotDisplay(value.dye) buffer.writeSlotDisplay(value.dye)
buffer.writeSlotDisplay(value.target)
} }
override fun decode(buffer: BytesBuffer): Dyed { override fun decode(buffer: BytesBuffer): Dyed {
@@ -0,0 +1,27 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.recipe
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.registry.readRecipeDisplayData
import cn.rtast.libmc.protocol.registry.writeRecipeDisplayData
public data class RecipeDisplay(val displayType: Int, val data: RecipeDisplayData)
internal fun BytesBuffer.readRecipeDisplay(): RecipeDisplay {
val displayType = readVarInt()
val data = readRecipeDisplayData(displayType)
return RecipeDisplay(displayType, data)
}
internal fun BytesBuffer.writeRecipeDisplay(display: RecipeDisplay) {
writeVarInt(display.displayType)
writeRecipeDisplayData(display.data)
}
@@ -0,0 +1,154 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.recipe
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writePrefixed
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.item.slot.SlotDisplay
import cn.rtast.libmc.protocol.protocol.game.item.slot.readSlotDisplay
import cn.rtast.libmc.protocol.protocol.game.item.slot.writeSlotDisplay
public sealed interface RecipeDisplayData {
public val result: SlotDisplay
public val craftingStation: SlotDisplay
public data class CraftingShapeless(
val ingredientCount: Int,
val ingredients: List<SlotDisplay>,
override val result: SlotDisplay,
override val craftingStation: SlotDisplay,
) : RecipeDisplayData {
internal companion object Codec : PacketCodec<CraftingShapeless> {
override fun encode(buffer: BytesBuffer, value: CraftingShapeless) {
buffer.writePrefixed(value.ingredients) { writeSlotDisplay(it) }
buffer.writeSlotDisplay(value.result)
buffer.writeSlotDisplay(value.craftingStation)
}
override fun decode(buffer: BytesBuffer): CraftingShapeless {
val ingredientCount = buffer.readVarInt()
val ingredients = ArrayList<SlotDisplay>(ingredientCount)
repeat(ingredientCount) { ingredients.add(buffer.readSlotDisplay()) }
val result = buffer.readSlotDisplay()
val craftingStation = buffer.readSlotDisplay()
return CraftingShapeless(ingredientCount, ingredients, result, craftingStation)
}
}
}
public data class CraftingShaped(
val width: Int,
val height: Int,
val ingredientCount: Int,
val ingredients: List<SlotDisplay>,
override val result: SlotDisplay,
override val craftingStation: SlotDisplay,
) : RecipeDisplayData {
internal companion object Codec : PacketCodec<CraftingShaped> {
override fun encode(buffer: BytesBuffer, value: CraftingShaped) {
buffer.writeVarInt(value.width)
buffer.writeVarInt(value.height)
buffer.writePrefixed(value.ingredients) { writeSlotDisplay(it) }
buffer.writeSlotDisplay(value.result)
buffer.writeSlotDisplay(value.craftingStation)
}
override fun decode(buffer: BytesBuffer): CraftingShaped {
val width = buffer.readVarInt()
val height = buffer.readVarInt()
val ingredientCount = buffer.readVarInt()
val ingredients = ArrayList<SlotDisplay>(ingredientCount)
repeat(ingredientCount) { ingredients.add(buffer.readSlotDisplay()) }
val result = buffer.readSlotDisplay()
val craftingStation = buffer.readSlotDisplay()
return CraftingShaped(width, height, ingredientCount, ingredients, result, craftingStation)
}
}
}
public data class Furnace(
val ingredient: SlotDisplay,
val fuel: SlotDisplay,
override val result: SlotDisplay,
override val craftingStation: SlotDisplay,
val cookingTime: Int, // ticks
val experience: Float,
) : RecipeDisplayData {
internal companion object Codec : PacketCodec<Furnace> {
override fun encode(buffer: BytesBuffer, value: Furnace) {
buffer.writeSlotDisplay(value.ingredient)
buffer.writeSlotDisplay(value.fuel)
buffer.writeSlotDisplay(value.result)
buffer.writeSlotDisplay(value.craftingStation)
buffer.writeVarInt(value.cookingTime)
buffer.writeFloat(value.experience)
}
override fun decode(buffer: BytesBuffer): Furnace {
val ingredient = buffer.readSlotDisplay()
val fuel = buffer.readSlotDisplay()
val result = buffer.readSlotDisplay()
val craftingStation = buffer.readSlotDisplay()
val cookingTime = buffer.readVarInt()
val experience = buffer.readFloat()
return Furnace(ingredient, fuel, result, craftingStation, cookingTime, experience)
}
}
}
public data class Stonecutter(
val ingredient: SlotDisplay,
override val result: SlotDisplay,
override val craftingStation: SlotDisplay,
) : RecipeDisplayData {
internal companion object Codec : PacketCodec<Stonecutter> {
override fun encode(buffer: BytesBuffer, value: Stonecutter) {
buffer.writeSlotDisplay(value.ingredient)
buffer.writeSlotDisplay(value.result)
buffer.writeSlotDisplay(value.craftingStation)
}
override fun decode(buffer: BytesBuffer): Stonecutter {
val ingredient = buffer.readSlotDisplay()
val result = buffer.readSlotDisplay()
val craftingStation = buffer.readSlotDisplay()
return Stonecutter(ingredient, result, craftingStation)
}
}
}
public data class Smithing(
val template: SlotDisplay,
val base: SlotDisplay,
val addition: SlotDisplay,
override val result: SlotDisplay,
override val craftingStation: SlotDisplay,
) : RecipeDisplayData {
internal companion object Codec : PacketCodec<Smithing> {
override fun encode(buffer: BytesBuffer, value: Smithing) {
buffer.writeSlotDisplay(value.template)
buffer.writeSlotDisplay(value.base)
buffer.writeSlotDisplay(value.addition)
buffer.writeSlotDisplay(value.result)
buffer.writeSlotDisplay(value.craftingStation)
}
override fun decode(buffer: BytesBuffer): Smithing {
val template = buffer.readSlotDisplay()
val base = buffer.readSlotDisplay()
val addition = buffer.readSlotDisplay()
val result = buffer.readSlotDisplay()
val craftingStation = buffer.readSlotDisplay()
return Smithing(template, base, addition, result, craftingStation)
}
}
}
}
@@ -0,0 +1,22 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.recipe
import cn.rtast.libmc.primitives.IdSet
public data class RecipeEntry(
val recipeId: Int,
val display: RecipeDisplay,
val groupId: Int,
val categoryId: Int,
val ingredients: List<IdSet>?,
val flags: Byte
) {
val showNotification: Boolean get() = (flags.toInt() and 0x01) != 0
val highlightAsNew: Boolean get() = (flags.toInt() and 0x02) != 0
}
@@ -7,7 +7,7 @@
package cn.rtast.libmc.protocol.protocol.game.registry package cn.rtast.libmc.protocol.protocol.game.registry
public enum class ClientAction(public val actionID: Int) { public enum class ClientAction(public val id: Int) {
/** /**
* Sent when the client is ready to respawn after death. * Sent when the client is ready to respawn after death.
*/ */
@@ -24,6 +24,6 @@ public enum class ClientAction(public val actionID: Int) {
RequestGameRuleValues(2); RequestGameRuleValues(2);
public companion object { public companion object {
public fun fromID(id: Int): ClientAction = entries.first { it.actionID == id } public fun fromID(id: Int): ClientAction = entries.first { it.id == id }
} }
} }
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.team
public enum class CollisionRule(public val id: Int) {
ALWAYS(0),
NEVER(1),
PUSH_OTHER_TEAMS(2),
PUSH_OWN_TEAM(3);
public companion object {
public fun fromID(id: Int): CollisionRule = entries.firstOrNull { it.id == id } ?: ALWAYS
}
}
@@ -0,0 +1,19 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.team
public enum class NameTagVisibility(public val id: Int) {
ALWAYS(0),
NEVER(1),
HIDE_FOR_OTHER_TEAMS(2),
HIDE_FOR_OWN_TEAMS(3);
public companion object {
public fun fromID(id: Int): NameTagVisibility = entries.first { it.id == id }
}
}
@@ -0,0 +1,50 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.protocol.game.team
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
public sealed interface TeamAction {
public val methodId: Int
public data class CreateTeam(
val teamInfo: TeamInfo,
val entities: List<String>,
) : TeamAction {
override val methodId: Int = 0
}
public data object RemoveTeam : TeamAction {
override val methodId: Int = 1
}
public data class UpdateTeamInfo(val teamInfo: TeamInfo) : TeamAction {
override val methodId: Int = 2
}
public data class AddEntities(val entities: List<String>) : TeamAction {
override val methodId: Int = 3
}
public data class RemoveEntities(val entities: List<String>) : TeamAction {
override val methodId: Int = 4
}
public data class TeamInfo(
val displayName: TextComponent,
val prefix: TextComponent,
val suffix: TextComponent,
val nameTagVisibility: NameTagVisibility,
val collisionRule: CollisionRule,
val teamColor: Int,
val friendlyFlags: Byte,
) {
public val allowFriendlyFire: Boolean get() = (friendlyFlags.toInt() and 0x01) != 0
public val canSeeFriendlyInvisible: Boolean get() = (friendlyFlags.toInt() and 0x02) != 0
}
}
@@ -7,6 +7,7 @@
package cn.rtast.libmc.protocol.protocol.session package cn.rtast.libmc.protocol.protocol.session
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.CURRENT_MINECRAFT_PROTOCOL_VERSION import cn.rtast.libmc.protocol.client.CURRENT_MINECRAFT_PROTOCOL_VERSION
import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent import cn.rtast.libmc.protocol.protocol.state.HandshakeIntent
import kotlin.reflect.KClass import kotlin.reflect.KClass
@@ -24,6 +25,7 @@ public interface Session {
public suspend fun status(protocolVersion: Int = CURRENT_MINECRAFT_PROTOCOL_VERSION): String public suspend fun status(protocolVersion: Int = CURRENT_MINECRAFT_PROTOCOL_VERSION): String
public suspend fun disconnect() public suspend fun disconnect()
public suspend fun init() public suspend fun init()
public suspend fun sendPacket(packet: MinecraftPacket)
} }
public inline fun <reified T : SessionEvent> Session.onEvent(noinline block: suspend Session.(T) -> Unit) { public inline fun <reified T : SessionEvent> Session.onEvent(noinline block: suspend Session.(T) -> Unit) {
@@ -1,5 +1,6 @@
package cn.rtast.libmc.protocol.protocol.session package cn.rtast.libmc.protocol.protocol.session
import cn.rtast.libmc.packet.MinecraftPacket
import cn.rtast.libmc.protocol.client.MinecraftClient import cn.rtast.libmc.protocol.client.MinecraftClient
import cn.rtast.libmc.protocol.crypto.minecraftServerIdHash import cn.rtast.libmc.protocol.crypto.minecraftServerIdHash
import cn.rtast.libmc.protocol.crypto.rsaEncrypt import cn.rtast.libmc.protocol.crypto.rsaEncrypt
@@ -76,6 +77,8 @@ public class SessionImpl internal constructor() : Session {
} }
} }
public override suspend fun sendPacket(packet: MinecraftPacket): Unit = client.networkChannel.sendPacket(packet)
override fun <T : SessionEvent> _onEvent(clazz: KClass<T>, block: suspend Session.(T) -> Unit) { override fun <T : SessionEvent> _onEvent(clazz: KClass<T>, block: suspend Session.(T) -> Unit) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
this.eventListener.getOrPut(clazz) { mutableListOf() }.add { block(it as T) } this.eventListener.getOrPut(clazz) { mutableListOf() }.add { block(it as T) }
@@ -112,7 +112,7 @@ internal object GamePacketsProtocolRegistry {
register(0x31, ClientboundLoginPlayPacket) register(0x31, ClientboundLoginPlayPacket)
register(0x32, ClientboundLowDiskSpaceWarningPacket) register(0x32, ClientboundLowDiskSpaceWarningPacket)
register(0x33, ClientboundMapItemDataPacket) register(0x33, ClientboundMapItemDataPacket)
// register(0x34, ClientboundMerchantOffersPacket) register(0x34, ClientboundMerchantOffersPacket)
register(0x35, ClientboundUpdateEntityPositionPacket) register(0x35, ClientboundUpdateEntityPositionPacket)
register(0x36, ClientboundUpdateEntityPositionAndRotationPacket) register(0x36, ClientboundUpdateEntityPositionAndRotationPacket)
register(0x37, ClientboundMoveMinecartAlongTrackPacket) register(0x37, ClientboundMoveMinecartAlongTrackPacket)
@@ -123,7 +123,7 @@ internal object GamePacketsProtocolRegistry {
register(0x3C, ClientboundOpenSignEditorPacket) register(0x3C, ClientboundOpenSignEditorPacket)
register(0x3D, ClientboundPingPacket) register(0x3D, ClientboundPingPacket)
register(0x3E, ClientboundPongResponsePacket) register(0x3E, ClientboundPongResponsePacket)
// register(0x3F, ClientboundPlaceGhostRecipePacket) register(0x3F, ClientboundPlaceGhostRecipePacket)
register(0x40, ClientboundPlayerAbilitiesPacket) register(0x40, ClientboundPlayerAbilitiesPacket)
register(0x41, ClientboundPlayerChatMessagePacket) register(0x41, ClientboundPlayerChatMessagePacket)
register(0x42, ClientboundPlayerEndCombatPacket) register(0x42, ClientboundPlayerEndCombatPacket)
@@ -134,7 +134,7 @@ internal object GamePacketsProtocolRegistry {
register(0x47, ClientboundPlayerLookAtPacket) register(0x47, ClientboundPlayerLookAtPacket)
register(0x48, ClientboundSynchronizePlayerPositionPacket) register(0x48, ClientboundSynchronizePlayerPositionPacket)
register(0x49, ClientboundPlayerRotationPacket) register(0x49, ClientboundPlayerRotationPacket)
// register(0x4A, ClientboundRecipeBookAddPacket) register(0x4A, ClientboundRecipeBookAddPacket)
register(0x4B, ClientboundRecipeBookRemovePacket) register(0x4B, ClientboundRecipeBookRemovePacket)
register(0x4C, ClientboundRecipeBookSettingsPacket) register(0x4C, ClientboundRecipeBookSettingsPacket)
register(0x4D, ClientboundRemoveEntitiesPacket) register(0x4D, ClientboundRemoveEntitiesPacket)
@@ -156,20 +156,20 @@ internal object GamePacketsProtocolRegistry {
register(0x5D, ClientboundSetCameraPacket) register(0x5D, ClientboundSetCameraPacket)
register(0x5E, ClientboundSetCenterChunkPacket) register(0x5E, ClientboundSetCenterChunkPacket)
register(0x5F, ClientboundSetRenderDistancePacket) register(0x5F, ClientboundSetRenderDistancePacket)
// register(0x60, ClientboundSetCursorItemPacket) register(0x60, ClientboundSetCursorItemPacket)
register(0x61, ClientboundSetDefaultSpawnPositionPacket) register(0x61, ClientboundSetDefaultSpawnPositionPacket)
register(0x62, ClientboundSetDisplayObjectivePacket) register(0x62, ClientboundSetDisplayObjectivePacket)
// register(0x63, ClientboundSetEntityMetadataPacket) // register(0x63, ClientboundSetEntityMetadataPacket)
register(0x64, ClientboundLinkEntitiesPacket) register(0x64, ClientboundLinkEntitiesPacket)
register(0x65, ClientboundSetEntityVelocityPacket) register(0x65, ClientboundSetEntityVelocityPacket)
// register(0x66, ClientboundSetEquipmentPacket) register(0x66, ClientboundSetEquipmentPacket)
register(0x67, ClientboundSetExperiencePacket) register(0x67, ClientboundSetExperiencePacket)
register(0x68, ClientboundSetHealthPacket) register(0x68, ClientboundSetHealthPacket)
register(0x69, ClientboundSetCarriedItemPacket) register(0x69, ClientboundSetCarriedItemPacket)
register(0x6A, ClientboundUpdateObjectivePacket) register(0x6A, ClientboundUpdateObjectivePacket)
register(0x6B, ClientboundSetPassengersPacket) register(0x6B, ClientboundSetPassengersPacket)
// register(0x6C, ClientboundSetPlayerInventorySlotPacket) register(0x6C, ClientboundSetPlayerInventorySlotPacket)
// register(0x6D, ClientboundSetPlayerTeamPacket) register(0x6D, ClientboundSetPlayerTeamPacket)
register(0x6E, ClientboundUpdateScorePacket) register(0x6E, ClientboundUpdateScorePacket)
register(0x6F, ClientboundSetSimulationDistancePacket) register(0x6F, ClientboundSetSimulationDistancePacket)
register(0x70, ClientboundSetSubtitleTextPacket) register(0x70, ClientboundSetSubtitleTextPacket)
@@ -190,7 +190,7 @@ internal object GamePacketsProtocolRegistry {
register(0x7F, ClientboundSetTickingStatePacket) register(0x7F, ClientboundSetTickingStatePacket)
register(0x80, ClientboundStepTickPacket) register(0x80, ClientboundStepTickPacket)
register(0x81, ClientboundTransferPacket) register(0x81, ClientboundTransferPacket)
// register(0x82, ClientboundUpdateAdvancementsPacket) register(0x82, ClientboundUpdateAdvancementsPacket)
register(0x83, ClientboundUpdateAttributesPacket) register(0x83, ClientboundUpdateAttributesPacket)
register(0x84, ClientboundEntityEffectPacket) register(0x84, ClientboundEntityEffectPacket)
register(0x85, ClientboundUpdateRecipesPacket) register(0x85, ClientboundUpdateRecipesPacket)
@@ -0,0 +1,26 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/11
*/
package cn.rtast.libmc.protocol.registry
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.protocol.protocol.game.recipe.RecipeDisplayData
internal object RecipeDisplayDataRegistry : ArrayIndexedRegistry<RecipeDisplayData>("RecipeDisplayDataRegistry") {
init {
register(RecipeDisplayData.CraftingShapeless)
register(RecipeDisplayData.CraftingShaped)
register(RecipeDisplayData.Furnace)
register(RecipeDisplayData.Stonecutter)
register(RecipeDisplayData.Smithing)
freeze()
}
}
internal fun BytesBuffer.readRecipeDisplayData(typeId: Int) = RecipeDisplayDataRegistry.read(typeId, this)
internal fun BytesBuffer.writeRecipeDisplayData(data: RecipeDisplayData) = RecipeDisplayDataRegistry.write(this, data)
@@ -10,12 +10,10 @@ package cn.rtast.libmc.protocol.registry
import cn.rtast.libmc.network.BytesBuffer import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.packet.PacketCodec import cn.rtast.libmc.packet.PacketCodec
import cn.rtast.libmc.primitives.writeVarInt import cn.rtast.libmc.primitives.writeVarInt
import kotlin.concurrent.Volatile
import kotlin.reflect.KClass import kotlin.reflect.KClass
internal interface Registry<T : Any> { internal interface Registry<T : Any> {
fun freeze() fun freeze()
fun isFrozen(): Boolean
fun getCodec(id: Int): PacketCodec<out T>? fun getCodec(id: Int): PacketCodec<out T>?
fun getId(kClass: KClass<out T>): Int? fun getId(kClass: KClass<out T>): Int?
} }
@@ -23,13 +21,9 @@ internal interface Registry<T : Any> {
internal open class ArrayIndexedRegistry<T : Any>(private val registryName: String = "Registry") : Registry<T> { internal open class ArrayIndexedRegistry<T : Any>(private val registryName: String = "Registry") : Registry<T> {
private val codecMap = mutableMapOf<Int, PacketCodec<out T>>() private val codecMap = mutableMapOf<Int, PacketCodec<out T>>()
private val classToIdMap = mutableMapOf<KClass<out T>, Int>() private val classToIdMap = mutableMapOf<KClass<out T>, Int>()
@Volatile
private var frozen = false
private lateinit var codecArray: Array<PacketCodec<T>> private lateinit var codecArray: Array<PacketCodec<T>>
fun <U : T> register(kClass: KClass<U>, codec: PacketCodec<U>) { fun <U : T> register(kClass: KClass<U>, codec: PacketCodec<U>) {
check(!frozen) { "$registryName is frozen and cannot accept new registrations." }
val id = codecMap.size val id = codecMap.size
codecMap[id] = codec codecMap[id] = codec
classToIdMap[kClass] = id classToIdMap[kClass] = id
@@ -38,20 +32,15 @@ internal open class ArrayIndexedRegistry<T : Any>(private val registryName: Stri
inline fun <reified U : T> register(codec: PacketCodec<U>) = register(U::class, codec) inline fun <reified U : T> register(codec: PacketCodec<U>) = register(U::class, codec)
override fun freeze() { override fun freeze() {
if (frozen) return
val maxId = codecMap.keys.maxOrNull() ?: -1 val maxId = codecMap.keys.maxOrNull() ?: -1
codecArray = Array(maxId + 1) { index -> codecArray = Array(maxId + 1) { index ->
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
codecMap[index] as? PacketCodec<T> ?: error("[$registryName] Missing codec for ID: $index") codecMap[index] as? PacketCodec<T> ?: error("[$registryName] Missing codec for ID: $index")
} }
codecMap.clear() codecMap.clear()
frozen = true
} }
override fun isFrozen(): Boolean = frozen
override fun getCodec(id: Int): PacketCodec<T>? { override fun getCodec(id: Int): PacketCodec<T>? {
check(frozen) { "$registryName is not frozen yet!" }
return codecArray.getOrNull(id) return codecArray.getOrNull(id)
} }
@@ -18,7 +18,7 @@ internal object SlotDisplayDataRegistry : ArrayIndexedRegistry<SlotDisplayData>(
register(SlotDisplayData.OnlyWithComponent) register(SlotDisplayData.OnlyWithComponent)
register(SlotDisplayData.Item) register(SlotDisplayData.Item)
register(SlotDisplayData.ItemStack) register(SlotDisplayData.ItemStack)
register(SlotDisplayData.Tags) register(SlotDisplayData.Tag)
register(SlotDisplayData.Dyed) register(SlotDisplayData.Dyed)
register(SlotDisplayData.SmithingTrim) register(SlotDisplayData.SmithingTrim)
register(SlotDisplayData.WithRemainder) register(SlotDisplayData.WithRemainder)
@@ -9,13 +9,14 @@ package client
import cn.rtast.libmc.crypto.AuthenticationProvider import cn.rtast.libmc.crypto.AuthenticationProvider
import cn.rtast.libmc.protocol.client.createMinecraftClient import cn.rtast.libmc.protocol.client.createMinecraftClient
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundContainerSetSlotPacket import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundAwardStatisticsPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundExplodePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLevelParticlePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundLightUpdatePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundPlayerChatMessagePacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundSetCursorItemPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundSetHealthPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStepTickPacket import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundStepTickPacket
import cn.rtast.libmc.protocol.packet.play.clientbound.ClientboundUpdateRecipesPacket import cn.rtast.libmc.protocol.packet.play.serverbound.ServerboundClientCommandPacket
import cn.rtast.libmc.protocol.packet.status.clientbound.ClientboundStatusResponsePacket
import cn.rtast.libmc.protocol.protocol.game.registry.ClientAction
import cn.rtast.libmc.protocol.protocol.session.SessionEvent import cn.rtast.libmc.protocol.protocol.session.SessionEvent
import cn.rtast.libmc.protocol.protocol.session.onEvent import cn.rtast.libmc.protocol.protocol.session.onEvent
import cn.rtast.libmc.protocol.util.generateOfflineUuid import cn.rtast.libmc.protocol.util.generateOfflineUuid
@@ -104,7 +105,10 @@ class TestClient {
println(it) println(it)
} }
cli.onPacket<ClientboundPlayerChatMessagePacket> { chatTracker.onReceivePlayerChat(it.messageSignature) } cli.onPacket<ClientboundPlayerChatMessagePacket> { chatTracker.onReceivePlayerChat(it.messageSignature) }
cli.onPacket<ClientboundUpdateRecipesPacket> { println(it) } cli.onPacket<ClientboundSetHealthPacket> {
if (it.health <= 0) sendPacket(ServerboundClientCommandPacket(ClientAction.PerformRespawn))
}
cli.onPacket<ClientboundSetCursorItemPacket> { println(it) }
// cli.on { packet, direction -> println("$direction -> $packet") } // cli.on { packet, direction -> println("$direction -> $packet") }
cli.connect() cli.connect()
+1 -1
View File
@@ -4,7 +4,7 @@ import os
path = "./registries.json" path = "./registries.json"
with open(path, "r") as f: with open(path, "r") as f:
data = json.loads(f.read())["minecraft:particle_type"] data = json.loads(f.read())["minecraft:recipe_display"]
sorted_entries = sorted( sorted_entries = sorted(
data.get('entries', {}).items(), data.get('entries', {}).items(),