diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/IdSet.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/IdSet.kt index b20d36f..1117758 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/IdSet.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/IdSet.kt @@ -16,12 +16,10 @@ public sealed interface IdSet { public fun BytesBuffer.readIdSet(): IdSet { val type = this.readVarInt() - return if (type == 0) { - IdSet.Tag(tagName = this.readMcString()) - } else { + return if (type == 0) IdSet.Tag(tagName = this.readMcString()) else { val count = type - 1 val ids = ArrayList(count) - (0 until count).forEach { _ -> ids.add(this.readVarInt()) } + repeat(count) { ids.add(this.readVarInt()) } IdSet.Entries(ids) } } diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/Prefixed.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/Prefixed.kt index dad7b38..f7a96f7 100644 --- a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/Prefixed.kt +++ b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/Prefixed.kt @@ -42,7 +42,6 @@ public fun BytesBuffer.writePrefixedStringArray(value: List) { } - public inline fun BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): List { val count = this.readVarInt() require(count in 0..4096) { @@ -57,4 +56,14 @@ public inline fun BytesBuffer.readPrefixed(reader: BytesBuffer.() -> T): Lis public inline fun BytesBuffer.writePrefixed(list: List, writer: BytesBuffer.(T) -> Unit) { this.writeVarInt(list.size) for (item in list) this.writer(item) +} + +public fun BytesBuffer.readPrefixOptional(reader: BytesBuffer.() -> T): T? { + val hasValue = readBoolean() + return if (hasValue) reader() else null +} + +public fun BytesBuffer.writePrefixedOptional(value: T?, writer: BytesBuffer.(T) -> Unit) { + writeBoolean(value != null) + if (value != null) writer(value) } \ No newline at end of file diff --git a/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/MinecraftString.kt b/libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/String.kt similarity index 100% rename from libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/MinecraftString.kt rename to libmc-common/src/commonMain/kotlin/cn/rtast/libmc/primitives/String.kt diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/block/BlockPredicate.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/block/BlockPredicate.kt new file mode 100644 index 0000000..0129fc4 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/block/BlockPredicate.kt @@ -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?, + val nbt: NBTCompound?, + val dataComponents: List, + val partialDataComponentPredicates: List, +) + +internal fun BytesBuffer.readBlockPredicate(): BlockPredicate { + val blocks = readPrefixOptional { readIdSet() } + val properties = readPrefixOptional { readPrefixed { readDataProperty() } } + val nbt = readPrefixOptional { readNetworkNBTCompound() } + val dataComponents = readPrefixed { } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataComponent.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataComponent.kt new file mode 100644 index 0000000..b98ce69 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataComponent.kt @@ -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) : 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) : 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() + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataProperty.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataProperty.kt new file mode 100644 index 0000000..8e3e8d2 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/DataProperty.kt @@ -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) } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/ExactDataComponentMatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/ExactDataComponentMatcher.kt new file mode 100644 index 0000000..a9677d4 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/ExactDataComponentMatcher.kt @@ -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) \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/PartialDataComponentMatcher.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/PartialDataComponentMatcher.kt new file mode 100644 index 0000000..d02bf82 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/component/PartialDataComponentMatcher.kt @@ -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 } + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/enchantement/ItemEnchantment.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/enchantement/ItemEnchantment.kt new file mode 100644 index 0000000..21749d0 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/enchantement/ItemEnchantment.kt @@ -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) +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/Slot.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/Slot.kt new file mode 100644 index 0000000..d223d38 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/Slot.kt @@ -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 = emptyList(), + val componentsToRemove: List = 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) + } +} diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/SlotDisplay.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/SlotDisplay.kt new file mode 100644 index 0000000..3ba61ce --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/inventory/slot/SlotDisplay.kt @@ -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 +} + +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) + } + } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/DyeColor.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/DyeColor.kt new file mode 100644 index 0000000..2cd5125 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/DyeColor.kt @@ -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(entries.size).apply { + for (color in entries) this[color.id] = color + } + + public fun fromID(id: Int): DyeColor = CACHED_ID.first { it.id == id } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/FireworkExplosion.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/FireworkExplosion.kt new file mode 100644 index 0000000..570bbc2 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/item/FireworkExplosion.kt @@ -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 + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/skin/SkinModelType.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/skin/SkinModelType.kt new file mode 100644 index 0000000..5802347 --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/player/skin/SkinModelType.kt @@ -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 } + } +} \ No newline at end of file diff --git a/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/ResolvableGameProfile.kt b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/ResolvableGameProfile.kt new file mode 100644 index 0000000..c9f42ca --- /dev/null +++ b/libmc-protocol/src/commonMain/kotlin/cn/rtast/libmc/protocol/protocol/game/session/ResolvableGameProfile.kt @@ -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) } +} \ No newline at end of file