diff --git a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/AttributeSerializer.kt b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/AttributeSerializer.kt index 24656f1..b39fe4e 100644 --- a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/AttributeSerializer.kt +++ b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/AttributeSerializer.kt @@ -1,6 +1,6 @@ package com.mineinabyss.idofront.serialization -import io.papermc.paper.component.item.ItemAttributeModifiers +import io.papermc.paper.datacomponent.item.ItemAttributeModifiers import kotlinx.serialization.EncodeDefault import kotlinx.serialization.EncodeDefault.Mode.NEVER import kotlinx.serialization.KSerializer @@ -9,12 +9,10 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -import net.kyori.adventure.key.Key import org.bukkit.NamespacedKey import org.bukkit.attribute.Attribute import org.bukkit.attribute.AttributeModifier import org.bukkit.inventory.EquipmentSlotGroup -import java.util.* @Serializable @SerialName("SerializableAttribute") diff --git a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableDataTypes.kt b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableDataTypes.kt index 9d6026e..d9cf971 100644 --- a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableDataTypes.kt +++ b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableDataTypes.kt @@ -1,11 +1,12 @@ package com.mineinabyss.idofront.serialization +import com.destroystokyo.paper.profile.PlayerProfile import io.papermc.paper.block.BlockPredicate -import io.papermc.paper.component.DataComponentType -import io.papermc.paper.component.DataComponentTypes -import io.papermc.paper.component.item.* -import io.papermc.paper.component.item.MapDecorations.DecorationEntry -import io.papermc.paper.component.item.Tool.Rule +import io.papermc.paper.datacomponent.item.MapDecorations.DecorationEntry +import io.papermc.paper.datacomponent.item.Tool.Rule +import io.papermc.paper.datacomponent.DataComponentType +import io.papermc.paper.datacomponent.DataComponentTypes +import io.papermc.paper.datacomponent.item.* import io.papermc.paper.registry.RegistryAccess import io.papermc.paper.registry.RegistryKey import io.papermc.paper.registry.TypedKey @@ -18,6 +19,7 @@ import net.kyori.adventure.util.TriState import org.bukkit.Color import org.bukkit.Registry import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ItemType import org.bukkit.inventory.meta.trim.ArmorTrim import org.bukkit.map.MapCursor import org.bukkit.potion.PotionEffect @@ -38,12 +40,12 @@ object SerializableDataTypes { @Serializable data class Unbreakable(val shownInTooltip: Boolean = true) : DataType { - constructor(unbreakable: io.papermc.paper.component.item.Unbreakable) : this(unbreakable.showInTooltip()) + constructor(unbreakable: io.papermc.paper.datacomponent.item.Unbreakable) : this(unbreakable.showInTooltip()) override fun setDataType(itemStack: ItemStack) { itemStack.setData( DataComponentTypes.UNBREAKABLE, - io.papermc.paper.component.item.Unbreakable.unbreakable(shownInTooltip) + io.papermc.paper.datacomponent.item.Unbreakable.unbreakable(shownInTooltip) ) } } @@ -55,7 +57,7 @@ object SerializableDataTypes { val customEffects: List<@Serializable(PotionEffectSerializer::class) PotionEffect> = emptyList() ) : DataType { - constructor(potionContents: io.papermc.paper.component.item.PotionContents) : this( + constructor(potionContents: io.papermc.paper.datacomponent.item.PotionContents) : this( potionContents.potion(), potionContents.customColor(), potionContents.customEffects() @@ -64,8 +66,8 @@ object SerializableDataTypes { override fun setDataType(itemStack: ItemStack) { itemStack.setData( DataComponentTypes.POTION_CONTENTS, - io.papermc.paper.component.item.PotionContents.potionContents().potion(potionType).customColor(color) - .addAll(customEffects).build() + io.papermc.paper.datacomponent.item.PotionContents.potionContents().potion(potionType).customColor(color) + .addCustomEffects(customEffects).build() ) } } @@ -108,13 +110,13 @@ object SerializableDataTypes { @JvmInline value class ChargedProjectiles(private val projectiles: List) : DataType { constructor(vararg projectiles: ItemStack) : this(projectiles.map { it.toSerializable() }) - constructor(projectiles: io.papermc.paper.component.item.ChargedProjectiles) : this( + constructor(projectiles: io.papermc.paper.datacomponent.item.ChargedProjectiles) : this( projectiles.projectiles().map { it.toSerializable() }) override fun setDataType(itemStack: ItemStack) { itemStack.setData( DataComponentTypes.CHARGED_PROJECTILES, - io.papermc.paper.component.item.ChargedProjectiles.chargedProjectiles(projectiles.mapNotNull { it.toItemStackOrNull() }) + io.papermc.paper.datacomponent.item.ChargedProjectiles.chargedProjectiles(projectiles.mapNotNull { it.toItemStackOrNull() }) ) } } @@ -172,7 +174,7 @@ object SerializableDataTypes { @Serializable data class MapDecoration(val type: MapCursor.Type, val x: Double, val z: Double, val rotation: Float) { - constructor(entry: DecorationEntry) : this(entry.type(), entry.x(), entry.z(), entry.rotation()) + constructor(entry: MapDecorations.DecorationEntry) : this(entry.type(), entry.x(), entry.z(), entry.rotation()) val paperDecoration: DecorationEntry = DecorationEntry.of(type, x, z, rotation) companion object { @@ -187,7 +189,7 @@ object SerializableDataTypes { val defaultMiningSpeed: Float, val damagePerBlock: Int ) : BlockTags(), DataType { - constructor(tool: io.papermc.paper.component.item.Tool) : this( + constructor(tool: io.papermc.paper.datacomponent.item.Tool) : this( tool.rules().map(::Rule), tool.defaultMiningSpeed(), tool.damagePerBlock() @@ -195,7 +197,7 @@ object SerializableDataTypes { override fun setDataType(itemStack: ItemStack) { itemStack.setData( - DataComponentTypes.TOOL, io.papermc.paper.component.item.Tool.tool() + DataComponentTypes.TOOL, io.papermc.paper.datacomponent.item.Tool.tool() .damagePerBlock(damagePerBlock) .defaultMiningSpeed(defaultMiningSpeed) .addRules(rules.toPaperRules()) @@ -209,8 +211,8 @@ object SerializableDataTypes { val speed: Float? = null, val correctForDrops: TriState ) { - constructor(rule: io.papermc.paper.component.item.Tool.Rule) : this( - rule.blockTypes().map { it.key() }, + constructor(rule: io.papermc.paper.datacomponent.item.Tool.Rule) : this( + rule.blocks().map { it.key() }, rule.speed(), rule.correctForDrops() ) @@ -222,8 +224,7 @@ object SerializableDataTypes { val showInToolTip: Boolean = true, val modifiers: List ) : BlockTags(), DataType { - constructor(adventurePredicate: ItemAdventurePredicate) : - this(adventurePredicate.showInTooltip(), adventurePredicate.modifiers().map(::BlockPredicate)) + constructor(predicate: ItemAdventurePredicate) : this(predicate.showInTooltip(), predicate.predicates().map(::BlockPredicate)) override fun setDataType(itemStack: ItemStack) { itemStack.setData( @@ -243,8 +244,7 @@ object SerializableDataTypes { val showInToolTip: Boolean = true, val modifiers: List ) : BlockTags(), DataType { - constructor(adventurePredicate: ItemAdventurePredicate) : - this(adventurePredicate.showInTooltip(), adventurePredicate.modifiers().map(::BlockPredicate)) + constructor(predicate: ItemAdventurePredicate) : this(predicate.showInTooltip(), predicate.predicates().map(::BlockPredicate)) override fun setDataType(itemStack: ItemStack) { itemStack.setData( @@ -320,7 +320,7 @@ object SerializableDataTypes { val usingConvertsTo: SerializableItemStack? = null ) : DataType { - constructor(foodProperties: io.papermc.paper.component.item.FoodProperties) : this( + constructor(foodProperties: io.papermc.paper.datacomponent.item.FoodProperties) : this( foodProperties.nutrition(), foodProperties.saturation(), foodProperties.eatSeconds(), @@ -331,9 +331,9 @@ object SerializableDataTypes { override fun setDataType(itemStack: ItemStack) { itemStack.setData( - DataComponentTypes.FOOD, io.papermc.paper.component.item.FoodProperties.food() + DataComponentTypes.FOOD, io.papermc.paper.datacomponent.item.FoodProperties.food() .nutrition(nutrition).saturation(saturation).eatSeconds(eatSeconds).canAlwaysEat(canAlwaysEat) - .addAllEffects(effects.map { it.paperPossibleEffect }) + .addEffects(effects.map { it.paperPossibleEffect }) .usingConvertsTo(usingConvertsTo?.toItemStackOrNull()) ) } @@ -344,30 +344,16 @@ object SerializableDataTypes { val probability: Float = 1.0f ) { - val paperPossibleEffect: io.papermc.paper.component.item.FoodProperties.PossibleEffect = - io.papermc.paper.component.item.FoodProperties.PossibleEffect.of(effect, probability) + val paperPossibleEffect: io.papermc.paper.datacomponent.item.FoodProperties.PossibleEffect = + io.papermc.paper.datacomponent.item.FoodProperties.PossibleEffect.of(effect, probability) - constructor(possibleEffect: io.papermc.paper.component.item.FoodProperties.PossibleEffect) : this( + constructor(possibleEffect: io.papermc.paper.datacomponent.item.FoodProperties.PossibleEffect) : this( possibleEffect.effect(), possibleEffect.probability() ) } } - @Serializable - @JvmInline - value class CustomModelData(private val customModelData: Int) : DataType { - constructor(customModelData: io.papermc.paper.component.item.CustomModelData) : this(customModelData.data()) - - override fun setDataType(itemStack: ItemStack) { - itemStack.setData( - DataComponentTypes.CUSTOM_MODEL_DATA, - io.papermc.paper.component.item.CustomModelData.customModelData().customModelData(customModelData) - .build() - ) - } - } - @Serializable data class DyedColor( val color: @Serializable(ColorSerializer::class) Color, @@ -385,10 +371,10 @@ object SerializableDataTypes { value class MapColor( private val color: @Serializable(ColorSerializer::class) Color, ) : DataType { - constructor(mapItemColor: MapItemColor) : this(mapItemColor.mapColor()) + constructor(mapItemColor: MapItemColor) : this(mapItemColor.color()) override fun setDataType(itemStack: ItemStack) { - itemStack.setData(DataComponentTypes.MAP_COLOR, MapItemColor.mapItemColor().mapColor(color).build()) + itemStack.setData(DataComponentTypes.MAP_COLOR, MapItemColor.mapItemColor().color(color).build()) } } @@ -423,7 +409,7 @@ object SerializableDataTypes { val jukeboxSong: @Serializable(KeySerializer::class) Key, val showInToolTip: Boolean = true ) : DataType { - constructor(jukeboxPlayable: io.papermc.paper.component.item.JukeboxPlayable) : + constructor(jukeboxPlayable: io.papermc.paper.datacomponent.item.JukeboxPlayable) : this(jukeboxPlayable.jukeboxSong().key(), jukeboxPlayable.showInTooltip()) override fun setDataType(itemStack: ItemStack) { @@ -431,8 +417,7 @@ object SerializableDataTypes { val jukeboxSong = jukeboxRegistry.get(jukeboxSong) ?: return itemStack.setData( DataComponentTypes.JUKEBOX_PLAYABLE, - io.papermc.paper.component.item.JukeboxPlayable.jukeboxPlayable().showInTooltip(showInToolTip) - .jukeboxSong(jukeboxSong) + io.papermc.paper.datacomponent.item.JukeboxPlayable.jukeboxPlayable(jukeboxSong).showInTooltip(showInToolTip) ) } } @@ -454,6 +439,23 @@ object SerializableDataTypes { } } + + @Serializable + data class PotDecorations( + val backItem: ItemType? = null, + val frontItem: ItemType? = null, + val leftItem: ItemType? = null, + val rightItem: ItemType? = null, + ) : DataType { + constructor(potDecorations: io.papermc.paper.datacomponent.item.PotDecorations) : + this(potDecorations.back(), potDecorations.front(), potDecorations.left(), potDecorations.right()) + + override fun setDataType(itemStack: ItemStack) { + itemStack.setData(DataComponentTypes.POT_DECORATIONS, io.papermc.paper.datacomponent.item.PotDecorations.potDecorations(backItem, leftItem, rightItem, frontItem)) + } + } + + @Serializable object FireResistant diff --git a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableItemStack.kt b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableItemStack.kt index f4b003a..c8761fd 100644 --- a/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableItemStack.kt +++ b/idofront-serializers/src/main/kotlin/com/mineinabyss/idofront/serialization/SerializableItemStack.kt @@ -10,11 +10,10 @@ import com.mineinabyss.idofront.textcomponents.miniMsg import com.mineinabyss.idofront.textcomponents.serialize import dev.lone.itemsadder.api.CustomStack import io.lumine.mythiccrucible.MythicCrucible -import io.papermc.paper.component.DataComponentTypes -import io.papermc.paper.component.item.ItemLore -import io.papermc.paper.component.item.MapDecorations -import io.papermc.paper.component.item.MapID -import io.papermc.paper.component.item.MapPostProcessing +import io.papermc.paper.datacomponent.DataComponentTypes +import io.papermc.paper.datacomponent.item.ItemLore +import io.papermc.paper.datacomponent.item.MapDecorations +import io.papermc.paper.item.MapPostProcessing import io.th0rgal.oraxen.OraxenPlugin import io.th0rgal.oraxen.api.OraxenItems import kotlinx.serialization.Contextual @@ -45,16 +44,12 @@ typealias SerializableItemStack = @Serializable(with = SerializableItemStackSeri /** * A wrapper for [ItemStack] that uses [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization). * Allows for easy-to-use serialization to JSON (or YAML with kaml). - * - * Currently missing many things spigot's item serialization contains, but way cleaner to use! - * - * See [MiniMessage docs](https://docs.adventure.kyori.net/minimessage/format.html) for formatting info! */ @Serializable data class BaseSerializableItemStack( @EncodeDefault(NEVER) val type: @Serializable(with = MaterialByNameSerializer::class) Material? = null, @EncodeDefault(NEVER) val amount: Int? = null, - @EncodeDefault(NEVER) val customModelData: SerializableDataTypes.CustomModelData? = null, + @EncodeDefault(NEVER) val customModelData: Int? = null, @EncodeDefault(NEVER) @SerialName("itemName") private val _itemName: String? = null, // This is private as we only want to use itemName in configs @EncodeDefault(NEVER) @SerialName("customName") private val _customName: String? = null, @@ -64,7 +59,7 @@ data class BaseSerializableItemStack( @EncodeDefault(NEVER) val maxDamage: Int? = null, @EncodeDefault(NEVER) val enchantments: SerializableDataTypes.Enchantments? = null, @EncodeDefault(NEVER) val storedEnchantments: SerializableDataTypes.StoredEnchantments? = null, - @EncodeDefault(NEVER) val potionType: SerializableDataTypes.PotionContents? = null, + @EncodeDefault(NEVER) val potionContents: SerializableDataTypes.PotionContents? = null, @EncodeDefault(NEVER) val attributeModifiers: SerializableDataTypes.AttributeModifiers? = null, @EncodeDefault(NEVER) val food: SerializableDataTypes.FoodProperties? = null, @EncodeDefault(NEVER) val tool: SerializableDataTypes.Tool? = null, @@ -91,6 +86,12 @@ data class BaseSerializableItemStack( @EncodeDefault(NEVER) val mapId: Int? = null, @EncodeDefault(NEVER) val mapPostProcessing: MapPostProcessing? = null, + // Block-specific DataTypes + @EncodeDefault(NEVER) val lock: String? = null, + @EncodeDefault(NEVER) val noteBlockSound: @Serializable(KeySerializer::class) Key? = null, + @EncodeDefault(NEVER) val profile: SerializableDataTypes.PotDecorations? = null, + @EncodeDefault(NEVER) val potDecorations: SerializableDataTypes.PotDecorations? = null, + @EncodeDefault(NEVER) val prefab: String? = null, @EncodeDefault(NEVER) val tag: String? = null, @@ -171,19 +172,21 @@ data class BaseSerializableItemStack( SerializableDataTypes.setData(applyTo, DataComponentTypes.ITEM_NAME, itemName) SerializableDataTypes.setData(applyTo, DataComponentTypes.CUSTOM_NAME, customName) SerializableDataTypes.setData(applyTo, DataComponentTypes.LORE, lore?.let { ItemLore.lore(lore) }) - customModelData?.setDataType(applyTo) enchantments?.setDataType(applyTo) storedEnchantments?.setDataType(applyTo) - potionType?.setDataType(applyTo) + potionContents?.setDataType(applyTo) attributeModifiers?.setDataType(applyTo) + SerializableDataTypes.setData(applyTo, DataComponentTypes.CUSTOM_MODEL_DATA, customModelData) SerializableDataTypes.setData(applyTo, DataComponentTypes.REPAIR_COST, repairCost) SerializableDataTypes.setData(applyTo, DataComponentTypes.DAMAGE, damage) SerializableDataTypes.setData(applyTo, DataComponentTypes.MAX_DAMAGE, maxDamage) SerializableDataTypes.setData(applyTo, DataComponentTypes.MAX_STACK_SIZE, maxStackSize) SerializableDataTypes.setData(applyTo, DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, enchantmentGlintOverride) - //SerializableDataTypes.setData(applyTo, DataComponentTypes.RECIPES, recipes) + SerializableDataTypes.setData(applyTo, DataComponentTypes.MAP_ID, mapId) + SerializableDataTypes.setData(applyTo, DataComponentTypes.RECIPES, recipes) + unbreakable?.setDataType(applyTo) food?.setDataType(applyTo) tool?.setDataType(applyTo) @@ -195,10 +198,9 @@ data class BaseSerializableItemStack( bundleContents?.setDataType(applyTo) writableBook?.setDataType(applyTo) writtenBook?.setDataType(applyTo) + mapColor?.setDataType(applyTo) - mapId?.let { applyTo.setData(DataComponentTypes.MAP_ID, MapID.mapId().mapId(mapId).build()) } mapPostProcessing?.let { applyTo.setData(DataComponentTypes.MAP_POST_PROCESSING, it) } - mapColor?.setDataType(applyTo) mapDecorations?.let { applyTo.setData( DataComponentTypes.MAP_DECORATIONS, @@ -206,6 +208,10 @@ data class BaseSerializableItemStack( ) } + SerializableDataTypes.setData(applyTo, DataComponentTypes.LOCK, lock) + SerializableDataTypes.setData(applyTo, DataComponentTypes.NOTE_BLOCK_SOUND, noteBlockSound) + potDecorations?.setDataType(applyTo) + SerializableDataTypes.setData(applyTo, DataComponentTypes.FIRE_RESISTANT, fireResistant) SerializableDataTypes.setData(applyTo, DataComponentTypes.HIDE_TOOLTIP, hideTooltip) SerializableDataTypes.setData(applyTo, DataComponentTypes.HIDE_ADDITIONAL_TOOLTIP, hideAdditionalTooltip) @@ -242,9 +248,8 @@ fun ItemStack.toSerializable(): SerializableItemStack = with(itemMeta) { amount = amount.takeIf { it != 1 }, itemName = getData(DataComponentTypes.ITEM_NAME), customName = getData(DataComponentTypes.CUSTOM_NAME), - customModelData = getData(DataComponentTypes.CUSTOM_MODEL_DATA)?.let(SerializableDataTypes::CustomModelData), + customModelData = getData(DataComponentTypes.CUSTOM_MODEL_DATA), - unbreakable = getData(DataComponentTypes.UNBREAKABLE)?.let(SerializableDataTypes::Unbreakable), lore = getData(DataComponentTypes.LORE)?.lines(), damage = getData(DataComponentTypes.DAMAGE), maxDamage = getData(DataComponentTypes.MAX_DAMAGE), @@ -252,12 +257,14 @@ fun ItemStack.toSerializable(): SerializableItemStack = with(itemMeta) { rarity = getData(DataComponentTypes.RARITY), enchantmentGlintOverride = getData(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE), repairCost = getData(DataComponentTypes.REPAIR_COST), - //recipes = getData(DataComponentTypes.RECIPES), + mapId = getData(DataComponentTypes.MAP_ID), + recipes = getData(DataComponentTypes.RECIPES), + unbreakable = getData(DataComponentTypes.UNBREAKABLE)?.let(SerializableDataTypes::Unbreakable), enchantments = getData(DataComponentTypes.ENCHANTMENTS)?.let(SerializableDataTypes::Enchantments), storedEnchantments = getData(DataComponentTypes.STORED_ENCHANTMENTS)?.let(SerializableDataTypes::StoredEnchantments), attributeModifiers = getData(DataComponentTypes.ATTRIBUTE_MODIFIERS)?.let(SerializableDataTypes::AttributeModifiers), - potionType = getData(DataComponentTypes.POTION_CONTENTS)?.let(SerializableDataTypes::PotionContents), + potionContents = getData(DataComponentTypes.POTION_CONTENTS)?.let(SerializableDataTypes::PotionContents), dyedColor = getData(DataComponentTypes.DYED_COLOR)?.let(SerializableDataTypes::DyedColor), food = getData(DataComponentTypes.FOOD)?.let(SerializableDataTypes::FoodProperties), tool = getData(DataComponentTypes.TOOL)?.let(SerializableDataTypes::Tool), @@ -269,11 +276,14 @@ fun ItemStack.toSerializable(): SerializableItemStack = with(itemMeta) { bundleContents = getData(DataComponentTypes.BUNDLE_CONTENTS)?.let(SerializableDataTypes::BundleContent), writableBook = getData(DataComponentTypes.WRITABLE_BOOK_CONTENT)?.let(SerializableDataTypes::WritableBook), writtenBook = getData(DataComponentTypes.WRITTEN_BOOK_CONTENT)?.let(SerializableDataTypes::WrittenBook), - - mapId = getData(DataComponentTypes.MAP_ID)?.id(), mapColor = getData(DataComponentTypes.MAP_COLOR)?.let(SerializableDataTypes::MapColor), + mapPostProcessing = getData(DataComponentTypes.MAP_POST_PROCESSING), - mapDecorations = getData(DataComponentTypes.MAP_DECORATIONS)?.let { it.decorations.map { e -> SerializableDataTypes.MapDecoration(e.value) } }, + mapDecorations = getData(DataComponentTypes.MAP_DECORATIONS)?.let { it.decorations.map { e -> SerializableDataTypes.MapDecoration(e.value) } }, + + lock = getData(DataComponentTypes.LOCK), + noteBlockSound = getData(DataComponentTypes.NOTE_BLOCK_SOUND), + potDecorations = getData(DataComponentTypes.POT_DECORATIONS)?.let(SerializableDataTypes::PotDecorations), fireResistant = SerializableDataTypes.FireResistant.takeIf { hasData(DataComponentTypes.FIRE_RESISTANT) }, hideTooltip = SerializableDataTypes.HideToolTip.takeIf { hasData(DataComponentTypes.HIDE_TOOLTIP) }, @@ -281,7 +291,7 @@ fun ItemStack.toSerializable(): SerializableItemStack = with(itemMeta) { creativeSlotLock = SerializableDataTypes.CreativeSlotLock.takeIf { hasData(DataComponentTypes.CREATIVE_SLOT_LOCK) }, intangibleProjectile = SerializableDataTypes.IntangibleProjectile.takeIf { hasData(DataComponentTypes.INTANGIBLE_PROJECTILE) }, - ) //TODO perhaps this should encode prefab too? + ) } private fun String.getSubRecipeIDs(): MutableList {