Slot Data

This commit is contained in:
2026-09-09 17:43:29 +08:00
parent 54409d9e8a
commit 0d22e85d88
15 files changed
+494 -5

No files matched your search

@@ -16,12 +16,10 @@ public sealed interface IdSet {
public fun BytesBuffer.readIdSet(): IdSet { public fun BytesBuffer.readIdSet(): IdSet {
val type = this.readVarInt() val type = this.readVarInt()
return if (type == 0) { return if (type == 0) IdSet.Tag(tagName = this.readMcString()) else {
IdSet.Tag(tagName = this.readMcString())
} else {
val count = type - 1 val count = type - 1
val ids = ArrayList<Int>(count) val ids = ArrayList<Int>(count)
(0 until count).forEach { _ -> ids.add(this.readVarInt()) } repeat(count) { ids.add(this.readVarInt()) }
IdSet.Entries(ids) IdSet.Entries(ids)
} }
} }
@@ -42,7 +42,6 @@ public fun BytesBuffer.writePrefixedStringArray(value: List<String>) {
} }
public inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List<T> { public inline fun <T> BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List<T> {
val count = this.readVarInt() val count = this.readVarInt()
require(count in 0..4096) { require(count in 0..4096) {
@@ -58,3 +57,13 @@ public inline fun <T> BytesBuffer.writePrefixed(list: List<T>, writer: BytesBuff
this.writeVarInt(list.size) this.writeVarInt(list.size)
for (item in list) this.writer(item) for (item in list) this.writer(item)
} }
public fun <T> BytesBuffer.readPrefixOptional(reader: BytesBuffer.() -> T): T? {
val hasValue = readBoolean()
return if (hasValue) reader() else null
}
public fun <T> BytesBuffer.writePrefixedOptional(value: T?, writer: BytesBuffer.(T) -> Unit) {
writeBoolean(value != null)
if (value != null) writer(value)
}
@@ -0,0 +1,35 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.block
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.IdSet
import cn.rtast.libmc.primitives.readIdSet
import cn.rtast.libmc.primitives.readPrefixOptional
import cn.rtast.libmc.primitives.readPrefixed
import cn.rtast.libmc.protocol.protocol.game.component.DataProperty
import cn.rtast.libmc.protocol.protocol.game.component.ExactDataComponentMatcher
import cn.rtast.libmc.protocol.protocol.game.component.PartialDataComponentMatcher
import cn.rtast.libmc.protocol.protocol.game.component.readDataProperty
import cn.rtast.libmc.protocol.protocol.util.readNetworkNBTCompound
public data class BlockPredicate(
val blocks: IdSet?,
val properties: List<DataProperty>?,
val nbt: NBTCompound?,
val dataComponents: List<ExactDataComponentMatcher>,
val partialDataComponentPredicates: List<PartialDataComponentMatcher>,
)
internal fun BytesBuffer.readBlockPredicate(): BlockPredicate {
val blocks = readPrefixOptional { readIdSet() }
val properties = readPrefixOptional { readPrefixed { readDataProperty() } }
val nbt = readPrefixOptional { readNetworkNBTCompound() }
val dataComponents = readPrefixed { }
}
@@ -0,0 +1,46 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.component
import cn.rtast.libmc.nbt.NBTCompound
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.chat.TextComponent
import cn.rtast.libmc.protocol.protocol.game.enchantement.ItemEnchantment
public sealed interface DataComponent {
public data class CustomData(val nbt: NBTCompound) : DataComponent
public data class MaxStackSize(val size: Int) : DataComponent
public data class MaxDamage(val damage: Int) : DataComponent
public data class Damage(val damage: Int) : DataComponent
public object Unbreakable : DataComponent
public data class CustomName(val name: TextComponent) : DataComponent
public data class ItemName(val name: TextComponent) : DataComponent
public data class ItemModel(val model: Identifier) : DataComponent
public data class Lore(val line: List<TextComponent>) : DataComponent
public data class Rarity(val rarity: ItemRarity) : DataComponent {
public enum class ItemRarity(public val id: Int) {
Common(0), Uncommon(1), Rare(2), Epic(3);
public companion object {
public fun fromID(id: Int): ItemRarity = entries.first { it.id == id }
}
}
}
public data class Enchantments(val enchantments: List<ItemEnchantment>) : DataComponent
public data class Food(val nutrition: Int, val saturationModifier: Float, val canAlwaysEat: Boolean) : DataComponent
public data class Unknown(val typeId: Int, val rawBytes: ByteArray) : DataComponent {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Unknown) return false
return typeId == other.typeId && rawBytes.contentEquals(other.rawBytes)
}
override fun hashCode(): Int = 31 * typeId + rawBytes.contentHashCode()
}
}
@@ -0,0 +1,39 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.component
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readMcString
import cn.rtast.libmc.primitives.readOptional
import cn.rtast.libmc.primitives.writeMcString
import cn.rtast.libmc.primitives.writeOptional
public data class DataProperty(
val name: String,
val isExactMatch: Boolean,
val exactValue: String?,
val minValue: String?,
val maxValue: String?,
)
internal fun BytesBuffer.readDataProperty(): DataProperty {
val name = readMcString()
val exactMatch = readBoolean()
val exactValue = readOptional { readMcString() }
val minValue = readOptional { readMcString() }
val maxValue = readOptional { readMcString() }
return DataProperty(name, exactMatch, exactValue, minValue, maxValue)
}
internal fun BytesBuffer.writeDataProperty(property: DataProperty) {
writeMcString(property.name)
writeBoolean(property.isExactMatch)
writeOptional(property.exactValue) { writeMcString(it) }
writeOptional(property.minValue) { writeMcString(it) }
writeOptional(property.maxValue) { writeMcString(it) }
}
@@ -0,0 +1,10 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.component
public data class ExactDataComponentMatcher(val type: Int, val value: DataComponent)
@@ -0,0 +1,26 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.component
import cn.rtast.libmc.nbt.NBTCompound
public data class PartialDataComponentMatcher(
val type: PartialDataComponentMatcherType,
val predicate: NBTCompound,
) {
public enum class PartialDataComponentMatcherType(public val id: Int) {
Damage(0), Enchantments(1), StoredEnchantment(2), PotionContents(3),
CustomData(4), Container(5), BundleContents(6), FireworkExplosion(7),
Fireworks(8), WritableBookContents(9), WrittenBookContents(10),
AttributeModifiers(12), Trim(12), JukeboxPlayable(13);
public companion object {
public fun fromID(id: Int): PartialDataComponentMatcherType = entries.first { it.id == id }
}
}
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.enchantement
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
public data class ItemEnchantment(val enchantmentId: Int, val level: Int)
internal fun BytesBuffer.readItemEnchantment(): ItemEnchantment {
val id = readVarInt()
val level = readVarInt()
return ItemEnchantment(id, level)
}
internal fun BytesBuffer.writeItemEnchantment(enchantment: ItemEnchantment) {
writeVarInt(enchantment.enchantmentId)
writeVarInt(enchantment.level)
}
@@ -0,0 +1,25 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.inventory.slot
import cn.rtast.libmc.protocol.protocol.game.component.DataComponent
public data class Slot(
val count: Int,
val itemId: Int = 0,
val componentsToAdd: List<TypedDataComponent> = emptyList(),
val componentsToRemove: List<Int> = emptyList(),
) {
public data class TypedDataComponent(val typeId: Int, val data: DataComponent)
public val isEmpty: Boolean get() = count <= 0
public companion object {
public val EMPTY: Slot = Slot(count = 0)
}
}
@@ -0,0 +1,117 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.inventory.slot
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.readVarInt
import cn.rtast.libmc.primitives.writeVarInt
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
public sealed interface SlotDisplay {
public object Empty : SlotDisplay
public object AnyFuel : SlotDisplay
public object WithAnyPotion : SlotDisplay
public data class OnlyWithComponent(val base: SlotDisplay, val componentTypeId: Int) : SlotDisplay
public data class Item(val itemTypeId: Int) : SlotDisplay
public data class ItemStack(val itemStack: Slot) : SlotDisplay
public data class Tag(val tagName: Identifier) : SlotDisplay
public data class Dyed(val dye: SlotDisplay, val target: SlotDisplay) : SlotDisplay
public data class SmithingTrim(val base: SlotDisplay, val material: SlotDisplay, val pattern: Int) : SlotDisplay
public data class WithRemainder(val ingredient: SlotDisplay, val remainder: SlotDisplay) : SlotDisplay
public data class Composite(val options: List<SlotDisplay>) : SlotDisplay
}
internal fun BytesBuffer.readSlotDisplay(): SlotDisplay {
return when (val typeId = this.readVarInt()) {
0 -> SlotDisplay.Empty
1 -> SlotDisplay.AnyFuel
2 -> SlotDisplay.WithAnyPotion
3 -> SlotDisplay.OnlyWithComponent(this.readSlotDisplay(), this.readVarInt())
4 -> SlotDisplay.Item(this.readVarInt())
5 -> SlotDisplay.ItemStack(this.readSlot())
6 -> SlotDisplay.Tag(this.readIdentifier())
7 -> SlotDisplay.Dyed(dye = this.readSlotDisplay(), target = this.readSlotDisplay())
8 -> SlotDisplay.SmithingTrim(this.readSlotDisplay(), this.readSlotDisplay(), this.readVarInt())
9 -> SlotDisplay.WithRemainder(this.readSlotDisplay(), this.readSlotDisplay())
10 -> {
val count = this.readVarInt()
require(count >= 0) { "Invalid Composite SlotDisplay size: $count" }
val options = List(count) { this.readSlotDisplay() }
SlotDisplay.Composite(options)
}
else -> error("Unknown SlotDisplay type ID: $typeId")
}
}
internal fun BytesBuffer.writeSlotDisplay(slotDisplay: SlotDisplay) {
when (slotDisplay) {
is SlotDisplay.Empty -> {
this.writeVarInt(0)
}
is SlotDisplay.AnyFuel -> {
this.writeVarInt(1)
}
is SlotDisplay.WithAnyPotion -> {
this.writeVarInt(2)
}
is SlotDisplay.OnlyWithComponent -> {
this.writeVarInt(3)
this.writeSlotDisplay(slotDisplay.base)
this.writeVarInt(slotDisplay.componentTypeId)
}
is SlotDisplay.Item -> {
this.writeVarInt(4)
this.writeVarInt(slotDisplay.itemTypeId)
}
is SlotDisplay.ItemStack -> {
this.writeVarInt(5)
this.writeSlot(slotDisplay.itemStack)
}
is SlotDisplay.Tag -> {
this.writeVarInt(6)
this.writeIdentifier(slotDisplay.tagName)
}
is SlotDisplay.Dyed -> {
this.writeVarInt(7)
this.writeSlotDisplay(slotDisplay.dye)
this.writeSlotDisplay(slotDisplay.target)
}
is SlotDisplay.SmithingTrim -> {
this.writeVarInt(8)
this.writeSlotDisplay(slotDisplay.base)
this.writeSlotDisplay(slotDisplay.material)
this.writeVarInt(slotDisplay.pattern)
}
is SlotDisplay.WithRemainder -> {
this.writeVarInt(9)
this.writeSlotDisplay(slotDisplay.ingredient)
this.writeSlotDisplay(slotDisplay.remainder)
}
is SlotDisplay.Composite -> {
this.writeVarInt(10)
this.writeVarInt(slotDisplay.options.size)
for (option in slotDisplay.options) {
this.writeSlotDisplay(option)
}
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.item
public enum class DyeColor(public val id: Int) {
WHITE(0), ORANGE(1), MAGENTA(2), LIGHT_BLUE(3),
YELLOW(4), LIME(5), PINK(6), GRAY(7), LIGHT_GRAY(8),
CYAN(9), PURPLE(10), BLUE(11), BROWN(12), GREEN(13),
RED(14), BLACK(15);
public companion object {
private val CACHED_ID = ArrayList<DyeColor>(entries.size).apply {
for (color in entries) this[color.id] = color
}
public fun fromID(id: Int): DyeColor = CACHED_ID.first { it.id == id }
}
}
@@ -0,0 +1,45 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.item
public data class FireworkExplosion(
val shape: FireworkExplosionShape,
val colors: IntArray,
val fadeColors: IntArray,
val hasTrail: Boolean,
val hasTwinkle: Boolean,
) {
public enum class FireworkExplosionShape(public val id: Int) {
SmallBall(0), LargeBall(1), Star(2), Creeper(3), Burst(4);
public companion object {
public fun fromID(id: Int): FireworkExplosionShape = entries.first { it.id == id }
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as FireworkExplosion
if (hasTrail != other.hasTrail) return false
if (hasTwinkle != other.hasTwinkle) return false
if (shape != other.shape) return false
if (!colors.contentEquals(other.colors)) return false
if (!fadeColors.contentEquals(other.fadeColors)) return false
return true
}
override fun hashCode(): Int {
var result = hasTrail.hashCode()
result = 31 * result + hasTwinkle.hashCode()
result = 31 * result + shape.hashCode()
result = 31 * result + colors.contentHashCode()
result = 31 * result + fadeColors.contentHashCode()
return result
}
}
@@ -0,0 +1,16 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.player.skin
public enum class SkinModelType(public val id: Int) {
WIDE(0), SLIM(1);
public companion object {
public fun fromID(id: Int): SkinModelType = entries.first { it.id == id }
}
}
@@ -0,0 +1,75 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/9
*/
package cn.rtast.libmc.protocol.protocol.game.session
import cn.rtast.libmc.network.BytesBuffer
import cn.rtast.libmc.primitives.*
import cn.rtast.libmc.protocol.protocol.game.Identifier
import cn.rtast.libmc.protocol.protocol.game.player.skin.SkinModelType
import cn.rtast.libmc.protocol.protocol.game.readIdentifier
import cn.rtast.libmc.protocol.protocol.game.writeIdentifier
import kotlin.uuid.Uuid
public data class ResolvableGameProfile(
val profile: UnpackedProfile,
val body: Identifier?,
val cape: Identifier?,
val elytra: Identifier?,
val model: SkinModelType?,
) {
public sealed interface UnpackedProfile {
public data class PartialProfile(
val username: String?,
val uuid: Uuid?,
val properties: GameProfile.Property,
) : UnpackedProfile
public data class CompleteProfile(val profile: GameProfile) : UnpackedProfile
}
}
internal fun BytesBuffer.readResolvableProfile(): ResolvableGameProfile {
val unpackedProfile = when (val type = readVarInt()) {
0 -> {
val username = readPrefixOptional { readMcString() }
val uuid = readPrefixOptional { readUuid() }
val properties = GameProfile.Property.decode(this)
ResolvableGameProfile.UnpackedProfile.PartialProfile(username, uuid, properties)
}
1 -> ResolvableGameProfile.UnpackedProfile.CompleteProfile(GameProfile.decode(this))
else -> error("Unknown profile type $type")
}
val body = readPrefixOptional { readIdentifier() }
val cape = readPrefixOptional { readIdentifier() }
val elytra = readPrefixOptional { readIdentifier() }
val model = readPrefixOptional { SkinModelType.fromID(readVarInt()) }
return ResolvableGameProfile(unpackedProfile, body, cape, elytra, model)
}
internal fun BytesBuffer.writeResolvableProfile(profile: ResolvableGameProfile) {
when (profile.profile) {
is ResolvableGameProfile.UnpackedProfile.CompleteProfile -> {
writeVarInt(1)
writePrefixedOptional(profile.profile.profile) {
GameProfile.encode(this, it)
}
}
is ResolvableGameProfile.UnpackedProfile.PartialProfile -> {
writeVarInt(0)
writePrefixedOptional(profile.profile.username) { writeMcString(it) }
writePrefixedOptional(profile.profile.uuid) { writeUuid(it) }
writePrefixedOptional(profile.profile.properties) { GameProfile.Property.encode(this, it) }
}
}
writePrefixedOptional(profile.body) { writeIdentifier(it) }
writePrefixedOptional(profile.cape) { writeIdentifier(it) }
writePrefixedOptional(profile.elytra) { writeIdentifier(it) }
writePrefixedOptional(profile.model) { writeVarInt(it.id) }
}