Slot Data
This commit is contained in:
15 files changed
+494
-5
No files matched your search
@@ -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<Int>(count)
|
||||
(0 until count).forEach { _ -> ids.add(this.readVarInt()) }
|
||||
repeat(count) { ids.add(this.readVarInt()) }
|
||||
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> {
|
||||
val count = this.readVarInt()
|
||||
require(count in 0..4096) {
|
||||
@@ -58,3 +57,13 @@ public inline fun <T> BytesBuffer.writePrefixed(list: List<T>, writer: BytesBuff
|
||||
this.writeVarInt(list.size)
|
||||
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)
|
||||
}
|
||||
File renamed without changes.
+35
@@ -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 { }
|
||||
}
|
||||
+46
@@ -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()
|
||||
}
|
||||
}
|
||||
+39
@@ -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) }
|
||||
}
|
||||
+10
@@ -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)
|
||||
+26
@@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -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)
|
||||
}
|
||||
+25
@@ -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)
|
||||
}
|
||||
}
|
||||
+117
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -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 }
|
||||
}
|
||||
}
|
||||
+45
@@ -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
|
||||
}
|
||||
}
|
||||
+16
@@ -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 }
|
||||
}
|
||||
}
|
||||
+75
@@ -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) }
|
||||
}
|
||||
Reference in New Issue
Block a user