custom items now use new builder DSL syntax, implementing first new set begins tmr
build / build (push) Failing after 3m16s

This commit is contained in:
2026-07-08 00:58:10 +01:00
parent 5f6350943e
commit 5b57baafb1
6 changed files with 192 additions and 361 deletions
@@ -0,0 +1,54 @@
package dev.zxq5.items
import dev.zxq5.Fantasysmp
import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.core.GenericItemSet
import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTab
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.Identifier
import net.minecraft.resources.ResourceKey
import net.minecraft.world.item.CreativeModeTab
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
object ModItems {
fun <T : Item> register(
name: String,
itemFactory: (Item.Properties) -> T,
settings: Item.Properties
): T {
val itemKey = ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, name))
val item = itemFactory(settings.setId(itemKey))
Registry.register(BuiltInRegistries.ITEM, itemKey, item)
return item
}
val CUSTOM_CREATIVE_TAB_KEY: ResourceKey<CreativeModeTab> = ResourceKey.create(
BuiltInRegistries.CREATIVE_MODE_TAB.key(), Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, "creative_tab")
)
val CUSTOM_CREATIVE_TAB: CreativeModeTab = FabricCreativeModeTab.builder()
.icon { ItemStack(Witherite.SWORD) }
.title(Component.translatable("Fantasysmp"))
.displayItems { _, output ->
GenericItemSet.all.forEach {
it.items.forEach(output::accept)
}
}
.build()
private val eagerLoad = listOf<GenericGearSet>(
Witherite,
Dragonite,
)
fun onLoad() {
// ensure that implementations such as creative tab are loaded and initialised
eagerLoad
GenericItemSet.all.forEach { it.onLoad() }
Registry.register(BuiltInRegistries.CREATIVE_MODE_TAB, CUSTOM_CREATIVE_TAB_KEY, CUSTOM_CREATIVE_TAB);
}
}
-290
View File
@@ -1,290 +0,0 @@
package dev.zxq5.items
import dev.zxq5.Fantasysmp
import dev.zxq5.util.id
import dev.zxq5.util.resourceKey
import kotlinx.serialization.descriptors.StructureKind
import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTab
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagsProvider
import net.minecraft.advancements.criterion.InventoryChangeTrigger
import net.minecraft.advancements.criterion.ItemPredicate
import net.minecraft.core.Holder
import net.minecraft.core.HolderGetter
import net.minecraft.core.HolderLookup
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.data.recipes.RecipeCategory
import net.minecraft.data.recipes.RecipeOutput
import net.minecraft.data.recipes.RecipeProvider
import net.minecraft.data.recipes.ShapedRecipeBuilder
import net.minecraft.data.recipes.SmithingTransformRecipeBuilder
import net.minecraft.network.chat.Component
import net.minecraft.resources.Identifier
import net.minecraft.resources.ResourceKey
import net.minecraft.sounds.SoundEvent
import net.minecraft.tags.TagKey
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.*
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.equipment.ArmorMaterial
import net.minecraft.world.item.equipment.ArmorMaterials
import net.minecraft.world.item.equipment.ArmorType
import net.minecraft.world.item.equipment.EquipmentAsset
import kotlin.reflect.KProperty
abstract class ItemImplementation {
// list of all items part of the class
abstract val items: List<Item>
abstract fun onLoad()
open fun generateRecipes(registries: HolderLookup.Provider, exporter: RecipeOutput) {}
open fun generateItemTags(tags: ItemTagAdder) {}
// open fun generateItemModels(models: ItemModelGenerators) {}
init {
register(this)
}
companion object {
private val _all = mutableListOf<ItemImplementation>()
val all: List<ItemImplementation> get() = _all
fun register(impl: ItemImplementation) = _all.add(impl)
}
}
abstract class GenericGearSet: ItemImplementation() {
abstract val materialName: String
abstract val armorSet: ArmorSet
open val materialKey: ResourceKey<EquipmentAsset>
get() = materialName.resourceKey()
open val materialDefinition: GearMaterialDef
get() = GearMaterialDef.NETHERITE
val armorMaterial: ArmorMaterial by lazy {
materialDefinition.toArmorMaterial()
}
private val itemRecipes = mutableMapOf<Item, List<RecipeSpec>>()
private val itemTags = mutableMapOf<Item, List<TagKey<Item>>>()
private val itemModels = mutableMapOf<Item, ModelSpec>()
internal fun registerDatagen(item: Item, recipes: List<RecipeSpec>, tags: List<TagKey<Item>>, model: ModelSpec) {
itemRecipes[item] = recipes
itemTags[item] = tags
itemModels[item] = model
}
val modelSpecs: Map<Item, ModelSpec> get() = itemModels
override fun generateRecipes(registries: HolderLookup.Provider, exporter: RecipeOutput) {
val itemLookup: HolderGetter<Item> = registries.lookupOrThrow(Registries.ITEM)
itemRecipes.forEach { (item, specs) ->
specs.forEach { spec ->
when (spec) {
is RecipeSpec.Smithing -> SmithingTransformRecipeBuilder.smithing(
Ingredient.of(spec.template), Ingredient.of(spec.base), Ingredient.of(spec.addition),
RecipeCategory.COMBAT, item
).unlocks("has_addition", InventoryChangeTrigger.TriggerInstance.hasItems(spec.addition))
.save(exporter, BuiltInRegistries.ITEM.getKey(item).path)
is RecipeSpec.Shaped -> {
val builder = ShapedRecipeBuilder.shaped(itemLookup, RecipeCategory.MISC, item)
spec.pattern.forEach { builder.pattern(it) }
spec.key.forEach { (char, ingredientItem) ->
builder.define(char, ingredientItem)
}
builder
.unlockedBy("has_ingredient", RecipeProvider.inventoryTrigger(
ItemPredicate.Builder().of(itemLookup, *spec.key.values.toTypedArray() )
))
.save(exporter)
}
}
}
}
}
override fun generateItemTags(tags: ItemTagAdder) {
itemTags.forEach { (item, tagList) -> tagList.forEach { tags.add(it, item) } }
}
fun fullSetEquippedBy(p: Player) =
p.getItemBySlot(EquipmentSlot.HEAD).item == armorSet.helmet &&
p.getItemBySlot(EquipmentSlot.CHEST).item == armorSet.chestplate &&
p.getItemBySlot(EquipmentSlot.LEGS).item == armorSet.leggings &&
p.getItemBySlot(EquipmentSlot.FEET).item == armorSet.boots
}
data class ArmorSet(
val helmet: Item,
val chestplate: Item,
val leggings: Item,
val boots: Item,
)
object ModItems {
fun <T : Item> register(
name: String,
itemFactory: (Item.Properties) -> T,
settings: Item.Properties
): T {
val itemKey = ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, name))
val item = itemFactory(settings.setId(itemKey))
Registry.register(BuiltInRegistries.ITEM, itemKey, item)
return item
}
val CUSTOM_CREATIVE_TAB_KEY: ResourceKey<CreativeModeTab> = ResourceKey.create(
BuiltInRegistries.CREATIVE_MODE_TAB.key(), Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, "creative_tab")
)
val CUSTOM_CREATIVE_TAB: CreativeModeTab = FabricCreativeModeTab.builder()
.icon { ItemStack(Witherite.SWORD) }
.title(Component.translatable("Fantasysmp"))
.displayItems { _, output ->
ItemImplementation.all.forEach {
it.items.forEach(output::accept)
}
}
.build()
private val eagerLoad = listOf<GenericGearSet>(
Witherite,
Dragonite,
)
fun onLoad() {
// ensure that implementations such as creative tab are loaded and initialised
eagerLoad
ItemImplementation.all.forEach { it.onLoad() }
Registry.register(BuiltInRegistries.CREATIVE_MODE_TAB, CUSTOM_CREATIVE_TAB_KEY, CUSTOM_CREATIVE_TAB);
}
}
data class GearMaterialDef(
val durability: Int = ArmorMaterials.NETHERITE.durability,
val defence: Map<ArmorType, Int> = ArmorMaterials.NETHERITE.defense,
val enchantability: Int = ArmorMaterials.NETHERITE.enchantmentValue,
val equipSound: Holder<SoundEvent> = ArmorMaterials.NETHERITE.equipSound,
val toughness: Float = ArmorMaterials.NETHERITE.toughness,
val knockbackResistance: Float = ArmorMaterials.NETHERITE.knockbackResistance,
val repairIngredient: TagKey<Item> = ArmorMaterials.NETHERITE.repairIngredient,
val assetId: ResourceKey<EquipmentAsset> = ArmorMaterials.NETHERITE.assetId,
) {
companion object {
val NETHERITE = GearMaterialDef(
durability = ArmorMaterials.NETHERITE.durability,
defence = ArmorMaterials.NETHERITE.defense,
enchantability = ArmorMaterials.NETHERITE.enchantmentValue,
equipSound = ArmorMaterials.NETHERITE.equipSound,
toughness = ArmorMaterials.NETHERITE.toughness,
knockbackResistance = ArmorMaterials.NETHERITE.knockbackResistance,
repairIngredient = ArmorMaterials.NETHERITE.repairIngredient,
assetId = ArmorMaterials.NETHERITE.assetId,
)
val DIAMOND = GearMaterialDef(
durability = ArmorMaterials.DIAMOND.durability,
defence = ArmorMaterials.DIAMOND.defense,
enchantability = ArmorMaterials.DIAMOND.enchantmentValue,
equipSound = ArmorMaterials.DIAMOND.equipSound,
toughness = ArmorMaterials.DIAMOND.toughness,
knockbackResistance = ArmorMaterials.DIAMOND.knockbackResistance,
repairIngredient = ArmorMaterials.DIAMOND.repairIngredient,
assetId = ArmorMaterials.DIAMOND.assetId,
)
}
}
fun GearMaterialDef.toArmorMaterial(): ArmorMaterial {
return ArmorMaterial(
durability,
defence,
enchantability,
equipSound,
toughness,
knockbackResistance,
repairIngredient,
assetId,
)
}
class ItemTagAdder(private val provider: FabricTagsProvider.ItemTagsProvider) {
fun add(tag: TagKey<Item>, vararg items: Item) {
provider.apply { items.forEach { add(tag, it) } }
}
}
sealed interface RecipeSpec {
data class Smithing(val template: Item, val base: Item, val addition: Item) : RecipeSpec
data class Shaped(val pattern: List<String>, val key: Map<Char, Item>) : RecipeSpec
}
sealed interface ModelSpec {
data object Generated : ModelSpec // plain flat 2D item icon
data object Handheld : ModelSpec // flat icon, held like a tool
data object None : ModelSpec // hand-written JSON already exists, skip auto-gen
}
class GearItemBuilder<T : Item>(
private val owner: GenericGearSet,
val name: String,
private val itemFactory: (Item.Properties) -> T,
) {
private var properties = Item.Properties()
private val recipes = mutableListOf<RecipeSpec>()
private val tags = mutableListOf<TagKey<Item>>()
private var modelSpec: ModelSpec = ModelSpec.None
fun model(spec: ModelSpec) {
modelSpec = spec
}
fun properties(block: Item.Properties.() -> Item.Properties) {
properties = properties.block()
}
fun smithingUpgrade(template: Item, base: Item, addition: Item) {
recipes += RecipeSpec.Smithing(template, base, addition)
}
fun shapedRecipe(vararg pattern: String, key: Map<Char, Item>) {
recipes += RecipeSpec.Shaped(pattern.toList(), key)
}
fun tags(vararg tag: TagKey<Item>) {
tags += tag
}
internal fun build(): T {
val item = ModItems.register(name, itemFactory, properties)
owner.registerDatagen(item, recipes, tags, modelSpec)
return item
}
}
fun <T : Item> GenericGearSet.gearItem(
name: String,
itemFactory: (Item.Properties) -> T,
block: GearItemBuilder<T>.() -> Unit = {},
): GearItemBuilder<T> = GearItemBuilder(this, name, itemFactory).apply(block)
operator fun <T : Item> GearItemBuilder<T>.provideDelegate(
thisRef: Any?, property: KProperty<*>
): Lazy<T> = lazy { build() }
operator fun <T : Item> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value
@@ -1,16 +1,11 @@
package dev.zxq5.items package dev.zxq5.items.sets
import dev.zxq5.Fantasysmp import dev.zxq5.items.core.ArmorSet
import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.ModItems.register import dev.zxq5.items.ModItems.register
import net.minecraft.core.component.DataComponents import net.minecraft.core.component.DataComponents
import net.minecraft.core.particles.ParticleOptions
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.resources.Identifier
import net.minecraft.resources.ResourceKey
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource import net.minecraft.sounds.SoundSource
import net.minecraft.tags.ItemTags
import net.minecraft.util.Unit import net.minecraft.util.Unit
import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult import net.minecraft.world.InteractionResult
@@ -1,11 +1,10 @@
package dev.zxq5.items package dev.zxq5.items.sets
import dev.zxq5.items.ModItems.register import dev.zxq5.items.core.ArmorSet
import dev.zxq5.util.mcItemTag import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.core.gearItem
import net.minecraft.core.component.DataComponents import net.minecraft.core.component.DataComponents
import net.minecraft.sounds.SoundEvents import net.minecraft.sounds.SoundEvents
import net.minecraft.tags.ItemTags
import net.minecraft.tags.TagKey
import net.minecraft.world.InteractionHand import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult import net.minecraft.world.InteractionResult
import net.minecraft.world.effect.MobEffectInstance import net.minecraft.world.effect.MobEffectInstance
@@ -20,7 +19,6 @@ import net.minecraft.world.item.Items
import net.minecraft.world.item.ToolMaterial import net.minecraft.world.item.ToolMaterial
import net.minecraft.world.item.component.BlocksAttacks import net.minecraft.world.item.component.BlocksAttacks
import net.minecraft.world.item.component.UseCooldown import net.minecraft.world.item.component.UseCooldown
import net.minecraft.world.item.equipment.ArmorMaterial
import net.minecraft.world.item.equipment.ArmorType import net.minecraft.world.item.equipment.ArmorType
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import java.util.* import java.util.*
@@ -31,9 +29,9 @@ object Witherite: GenericGearSet() {
override val materialDefinition override val materialDefinition
get() = super.materialDefinition.copy(assetId = materialKey) get() = super.materialDefinition.copy(assetId = materialKey)
val SHIELD = register("witherite_shield", ::Item, val SHIELD by gearItem("witherite_shield", ::Item) {
Item.Properties() properties {
.component(DataComponents.BLOCKS_ATTACKS, BlocksAttacks( component(DataComponents.BLOCKS_ATTACKS, BlocksAttacks(
0.25f, // 5 tick warmup 0.25f, // 5 tick warmup
1.0f, // standard disable cooldown 1.0f, // standard disable cooldown
listOf( listOf(
@@ -49,19 +47,8 @@ object Witherite: GenericGearSet() {
Optional.empty(), // default block sound Optional.empty(), // default block sound
Optional.empty() // default disable sound Optional.empty() // default disable sound
)) ))
) }
}
// val SWORD = register("witherite_sword", ::WitheriteSword,
// Item.Properties()
// .enchantable(armorMaterial.enchantmentValue)
// .component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
// .sword(ToolMaterial.NETHERITE, 3F, -2.4F)
// )
// val AXE = register("witherite_axe", ::WitheriteWeapon,
// Item.Properties()
// .enchantable(armorMaterial.enchantmentValue)
// .axe(ToolMaterial.NETHERITE, 5F, -3F))
val SWORD by gearItem("witherite_sword", ::WitheriteSword) { val SWORD by gearItem("witherite_sword", ::WitheriteSword) {
properties { properties {
@@ -79,9 +66,6 @@ object Witherite: GenericGearSet() {
.axe(ToolMaterial.NETHERITE, 3F, -3F) .axe(ToolMaterial.NETHERITE, 3F, -3F)
} }
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_AXE, Items.WITHER_SKELETON_SKULL) smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_AXE, Items.WITHER_SKELETON_SKULL)
tags(
mcItemTag("axes")
)
} }
val HELMET by gearItem("witherite_helmet", ::Item) { val HELMET by gearItem("witherite_helmet", ::Item) {
@@ -112,46 +96,12 @@ object Witherite: GenericGearSet() {
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_BOOTS, Items.WITHER_SKELETON_SKULL) smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_BOOTS, Items.WITHER_SKELETON_SKULL)
} }
//
// val HELMET = register(
// "witherite_helmet",
// ::Item,
// Item.Properties().humanoidArmor(armorMaterial , ArmorType.HELMET)
// .enchantable(armorMaterial.enchantmentValue)
// .durability(ArmorType.HELMET.getDurability(armorMaterial.durability))
// )
//
// val CHESTPLATE = register(
// "witherite_chestplate",
// ::Item,
//
// Item.Properties()
// .enchantable(armorMaterial.enchantmentValue)
// .humanoidArmor(armorMaterial , ArmorType.CHESTPLATE)
// .durability(ArmorType.CHESTPLATE.getDurability(armorMaterial.durability))
// )
//
// val LEGGINGS = register(
// "witherite_leggings",
// ::Item,
// Item.Properties()
// .enchantable(armorMaterial.enchantmentValue)
// .humanoidArmor(armorMaterial , ArmorType.LEGGINGS)
// .durability(ArmorType.LEGGINGS.getDurability(armorMaterial.durability))
// )
//
// val BOOTS = register(
// "witherite_boots",
// ::Item,
// Item.Properties()
// .enchantable(armorMaterial.enchantmentValue)
// .humanoidArmor(armorMaterial , ArmorType.BOOTS)
// .durability(ArmorType.BOOTS.getDurability(armorMaterial.durability))
// )
override val armorSet: ArmorSet = ArmorSet(HELMET, CHESTPLATE, LEGGINGS, BOOTS) override val armorSet: ArmorSet = ArmorSet(HELMET, CHESTPLATE, LEGGINGS, BOOTS)
override val items = listOf(SWORD, AXE, SHIELD, HELMET, CHESTPLATE, LEGGINGS, BOOTS) override val items = listOf(SWORD, AXE, SHIELD, HELMET, CHESTPLATE, LEGGINGS, BOOTS)
override fun onLoad() {}
override fun onLoad() {
// empty function - this must be called to initialise the above properties ^
}
} }
open class WitheriteWeapon(properties: Properties): Item(properties) { open class WitheriteWeapon(properties: Properties): Item(properties) {
@@ -0,0 +1,93 @@
package dev.zxq5.items.core
import dev.zxq5.util.resourceKey
import net.minecraft.advancements.criterion.InventoryChangeTrigger
import net.minecraft.advancements.criterion.ItemPredicate
import net.minecraft.core.HolderGetter
import net.minecraft.core.HolderLookup
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.data.recipes.RecipeCategory
import net.minecraft.data.recipes.RecipeOutput
import net.minecraft.data.recipes.RecipeProvider
import net.minecraft.data.recipes.ShapedRecipeBuilder
import net.minecraft.data.recipes.SmithingTransformRecipeBuilder
import net.minecraft.resources.ResourceKey
import net.minecraft.tags.TagKey
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.equipment.ArmorMaterial
import net.minecraft.world.item.equipment.EquipmentAsset
abstract class GenericGearSet: GenericItemSet() {
abstract val materialName: String
abstract val armorSet: ArmorSet
open val materialKey: ResourceKey<EquipmentAsset>
get() = materialName.resourceKey()
open val materialDefinition: GearMaterialDef
get() = GearMaterialDef.NETHERITE
val armorMaterial: ArmorMaterial by lazy {
materialDefinition.toArmorMaterial()
}
private val itemRecipes = mutableMapOf<Item, List<RecipeSpec>>()
private val itemTags = mutableMapOf<Item, List<TagKey<Item>>>()
private val itemModels = mutableMapOf<Item, ModelSpec>()
internal fun registerDatagen(item: Item, recipes: List<RecipeSpec>, tags: List<TagKey<Item>>, model: ModelSpec) {
itemRecipes[item] = recipes
itemTags[item] = tags
itemModels[item] = model
}
val modelSpecs: Map<Item, ModelSpec> get() = itemModels
override fun generateRecipes(registries: HolderLookup.Provider, exporter: RecipeOutput) {
val itemLookup: HolderGetter<Item> = registries.lookupOrThrow(Registries.ITEM)
itemRecipes.forEach { (item, specs) ->
specs.forEach { spec ->
when (spec) {
is RecipeSpec.Smithing -> SmithingTransformRecipeBuilder.smithing(
Ingredient.of(spec.template), Ingredient.of(spec.base), Ingredient.of(spec.addition),
RecipeCategory.COMBAT, item
).unlocks("has_addition", InventoryChangeTrigger.TriggerInstance.hasItems(spec.addition))
.save(exporter, BuiltInRegistries.ITEM.getKey(item).path)
is RecipeSpec.Shaped -> {
val builder = ShapedRecipeBuilder.shaped(itemLookup, RecipeCategory.MISC, item)
spec.pattern.forEach { builder.pattern(it) }
spec.key.forEach { (char, ingredientItem) ->
builder.define(char, ingredientItem)
}
builder
.unlockedBy("has_ingredient", RecipeProvider.inventoryTrigger(
ItemPredicate.Builder().of(itemLookup, *spec.key.values.toTypedArray() )
))
.save(exporter)
}
}
}
}
}
override fun generateItemTags(tags: ItemTagAdder) {
itemTags.forEach { (item, tagList) -> tagList.forEach { tags.add(it, item) } }
}
fun fullSetEquippedBy(p: Player) =
p.getItemBySlot(EquipmentSlot.HEAD).item == armorSet.helmet &&
p.getItemBySlot(EquipmentSlot.CHEST).item == armorSet.chestplate &&
p.getItemBySlot(EquipmentSlot.LEGS).item == armorSet.leggings &&
p.getItemBySlot(EquipmentSlot.FEET).item == armorSet.boots
}
@@ -0,0 +1,29 @@
package dev.zxq5.items.core
import net.minecraft.core.HolderLookup
import net.minecraft.data.recipes.RecipeOutput
import net.minecraft.world.item.Item
abstract class GenericItemSet {
// list of all items part of the class
abstract val items: List<Item>
abstract fun onLoad()
open fun generateRecipes(registries: HolderLookup.Provider, exporter: RecipeOutput) {}
open fun generateItemTags(tags: ItemTagAdder) {}
// open fun generateItemModels(models: ItemModelGenerators) {}
init {
register(this)
}
companion object {
private val _all = mutableListOf<GenericItemSet>()
val all: List<GenericItemSet> get() = _all
fun register(impl: GenericItemSet) = _all.add(impl)
}
}