item builder DSL framework completed and seems to work. custom tag and model generation don't work yet. don't uncomment the tag generation code as it causes a stack overflow crash ⚠️

This commit is contained in:
2026-07-11 22:21:17 +01:00
parent 5b57baafb1
commit fe04526040
23 changed files with 524 additions and 166 deletions
+6 -6
View File
@@ -1,10 +1,10 @@
package dev.zxq5
import dev.zxq5.items.ItemImplementation
import dev.zxq5.items.ItemTagAdder
import dev.zxq5.items.template.GenericItemSet
import dev.zxq5.items.template.ItemTagAdder
import dev.zxq5.items.ModItems
import dev.zxq5.items.Witherite
//import dev.zxq5.items.Witherite
import dev.zxq5.items.sets.Witherite
//import dev.zxq5.items.sets.Witherite
import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.datagen.v1.FabricPackOutput
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider
@@ -51,7 +51,7 @@ class ModRecipeProvider(
return object : RecipeProvider(registries, exporter) {
override fun buildRecipes() {
print("GENERATING ALL RECIPES")
ItemImplementation.all.forEach {
GenericItemSet.all.forEach {
print("GENERATING RECIPES FOR" + it.items.toString())
it.generateRecipes(registries, exporter)
}
@@ -66,6 +66,6 @@ class ModItemTagProvider(output: FabricPackOutput, registriesFuture: Completable
FabricTagsProvider.ItemTagsProvider(output, registriesFuture) {
override fun addTags(provider: HolderLookup.Provider) {
val adder = ItemTagAdder(this)
ItemImplementation.all.forEach { it.generateItemTags(adder) }
GenericItemSet.all.forEach { it.generateItemTags(adder) }
}
}
@@ -1,8 +1,9 @@
package dev.zxq5.items
import dev.zxq5.Fantasysmp
import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.core.GenericItemSet
import dev.zxq5.items.template.GenericItemSet
import dev.zxq5.items.sets.Dragonite
import dev.zxq5.items.sets.Witherite
import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTab
import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries
@@ -18,7 +19,7 @@ object ModItems {
fun <T : Item> register(
name: String,
itemFactory: (Item.Properties) -> T,
settings: Item.Properties
settings: Item.Properties = Item.Properties(),
): T {
val itemKey = ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, name))
val item = itemFactory(settings.setId(itemKey))
@@ -40,7 +41,7 @@ object ModItems {
}
.build()
private val eagerLoad = listOf<GenericGearSet>(
private val eagerLoad = listOf<GenericItemSet>(
Witherite,
Dragonite,
)
@@ -1,20 +1,29 @@
package dev.zxq5.items.sets
import dev.zxq5.items.core.ArmorSet
import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.template.ArmorSet
import dev.zxq5.items.template.GenericGearSet
import dev.zxq5.items.ModItems.register
import dev.zxq5.items.template.registerItem
import dev.zxq5.items.template.provideDelegate
import net.minecraft.core.component.DataComponents
import net.minecraft.core.particles.ParticleTypes
import net.minecraft.core.particles.PowerParticleOption
import net.minecraft.server.level.ServerLevel
import net.minecraft.sounds.SoundEvents
import net.minecraft.sounds.SoundSource
import net.minecraft.util.Unit
import net.minecraft.world.InteractionHand
import net.minecraft.world.InteractionResult
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.item.ToolMaterial
import net.minecraft.world.item.component.UseCooldown
import net.minecraft.world.item.equipment.*
import net.minecraft.world.level.Level
import kotlin.getValue
object Dragonite: GenericGearSet() {
@@ -22,60 +31,55 @@ object Dragonite: GenericGearSet() {
override val materialDefinition
get() = super.materialDefinition.copy(assetId = materialKey)
val SWORD = register("dragonite_sword", ::DragoniteSword,
Item.Properties()
.enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
.sword(ToolMaterial.NETHERITE, 3.0F, -2.4F)
)
val HELMET = register(
"dragonite_helmet",
::Item,
Item.Properties()
.enchantable(armorMaterial.enchantmentValue)
.humanoidArmor(armorMaterial , ArmorType.HELMET)
.durability(ArmorType.HELMET.getDurability(armorMaterial.durability))
)
val SCALE by registerItem("dragon_scale", ::Item)
val INGOT by registerItem("dragonite_ingot", ::Item) {
shapedRecipe("SBS", "CXC", "SBS", key=mapOf<Char, Item>(
'S' to Items.SHULKER_SHELL,
'X' to SCALE,
'B' to Items.DRAGON_BREATH,
'C' to Items.CHORUS_FRUIT
))
}
val CHESTPLATE = register(
"dragonite_chestplate",
::Item,
Item.Properties()
.enchantable(armorMaterial.enchantmentValue)
.humanoidArmor(armorMaterial , ArmorType.CHESTPLATE)
.component(DataComponents.GLIDER, Unit.INSTANCE)
.durability(ArmorType.CHESTPLATE.getDurability(armorMaterial.durability))
)
val SWORD by registerItem("dragonite_sword", ::DragoniteSword) {
properties {
enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
.sword(ToolMaterial.NETHERITE, 3F, -2.4F)
}
smithingUpgrade(Items.END_CRYSTAL, Items.NETHERITE_SWORD, INGOT)
}
val LEGGINGS = register(
"dragonite_leggings",
::Item,
Item.Properties()
.enchantable(armorMaterial.enchantmentValue)
.humanoidArmor(armorMaterial , ArmorType.LEGGINGS)
.durability(ArmorType.LEGGINGS.getDurability(armorMaterial.durability))
)
val HELMET by registerItem("dragonite_helmet", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.HELMET)
}
smithingUpgrade(Items.DRAGON_HEAD, Items.NETHERITE_HELMET, INGOT)
}
val BOOTS = register(
"dragonite_boots",
::Item,
Item.Properties()
.enchantable(armorMaterial.enchantmentValue)
.humanoidArmor(armorMaterial , ArmorType.BOOTS)
.durability(ArmorType.BOOTS.getDurability(armorMaterial.durability))
)
val CHESTPLATE by registerItem("dragonite_chestplate", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.CHESTPLATE)
.component(DataComponents.GLIDER, Unit.INSTANCE)
.component(DataComponents.UNBREAKABLE, Unit.INSTANCE)
}
smithingUpgrade(Items.ELYTRA, Items.NETHERITE_CHESTPLATE, INGOT)
}
val INGOT = register(
"dragonite_ingot",
::Item,
Item.Properties()
)
val LEGGINGS by registerItem("dragonite_leggings", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.LEGGINGS)
}
smithingUpgrade(Items.CRYING_OBSIDIAN, Items.NETHERITE_LEGGINGS, INGOT)
}
val SCALE = register(
"dragon_scale",
::Item,
Item.Properties()
)
val BOOTS by registerItem("dragonite_boots", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.BOOTS)
}
smithingUpgrade(Items.CRYING_OBSIDIAN, Items.NETHERITE_BOOTS, INGOT)
}
override val armorSet: ArmorSet = ArmorSet(HELMET, CHESTPLATE, LEGGINGS, BOOTS)
override val items = listOf(SWORD, HELMET, CHESTPLATE, LEGGINGS, BOOTS, SCALE, INGOT)
@@ -95,13 +99,41 @@ class DragoniteSword(properties: Properties) : Item(properties) {
level.playSound(null, user.x, user.y, user.z,
SoundEvents.ENDER_DRAGON_FLAP, SoundSource.PLAYERS, 1.0f, 1.0f)
// TODO("fix this broken ahh code. WHY TF DOES FABRIC HAVE NO DOCUMENTATION FOR BASICALLY ANYTHING.")
// (level as? ServerLevel)?.sendParticles(
// ParticleTypes.DRAGON_BREATH as ParticleOptions,
// user.x, user.y, user.z,
// 100, 0.5, 0.5, 0.5, 0.5
// )
(level as? ServerLevel)?.sendParticles(
PowerParticleOption.create(ParticleTypes.DRAGON_BREATH, 1.0f),
user.x, user.y, user.z,
100, 0.5, 0.5, 0.5, 0.5
)
return InteractionResult.SUCCESS
}
override fun getAttackDamageBonus(victim: Entity, damage: Float, damageSource: DamageSource): Float {
val superBonus = super.getAttackDamageBonus(victim, damage, damageSource) // 0f from vanilla, but respect it anyway
val attacker = damageSource.entity as? Player ?: return superBonus
if (Dragonite.fullSetEquippedBy(attacker)) return superBonus
if (attacker.level().isClientSide) return superBonus
if (!attacker.isFallFlying) return superBonus
val speed = attacker.deltaMovement.length()
if (speed < MIN_DIVE_SPEED) return superBonus
val clampedSpeed = speed.coerceAtMost(MAX_DIVE_SPEED)
val t = (clampedSpeed - MIN_DIVE_SPEED) / (MAX_DIVE_SPEED - MIN_DIVE_SPEED)
val multiplier = 1.0f + (t * MAX_BONUS_MULTIPLIER).toFloat()
// damage is the current total (base + crit + whatever ran before this hook).
// We want final = damage * multiplier, and this method adds its return value on top of damage,
// so the bonus we need to return is exactly (damage * multiplier) - damage.
val extraBonus = damage * (multiplier - 1f)
println("base: $damage bonus: ${superBonus + extraBonus}")
return superBonus + extraBonus
}
companion object {
const val MIN_DIVE_SPEED = 0.3
const val MAX_DIVE_SPEED = 1.1
const val MAX_BONUS_MULTIPLIER = 2.0f
}
}
@@ -1,8 +1,9 @@
package dev.zxq5.items.sets
import dev.zxq5.items.core.ArmorSet
import dev.zxq5.items.core.GenericGearSet
import dev.zxq5.items.core.gearItem
import dev.zxq5.items.template.ArmorSet
import dev.zxq5.items.template.GenericGearSet
import dev.zxq5.items.template.registerItem
import dev.zxq5.items.template.provideDelegate
import net.minecraft.core.component.DataComponents
import net.minecraft.sounds.SoundEvents
import net.minecraft.world.InteractionHand
@@ -29,7 +30,7 @@ object Witherite: GenericGearSet() {
override val materialDefinition
get() = super.materialDefinition.copy(assetId = materialKey)
val SHIELD by gearItem("witherite_shield", ::Item) {
val SHIELD by registerItem("witherite_shield", ::Item) {
properties {
component(DataComponents.BLOCKS_ATTACKS, BlocksAttacks(
0.25f, // 5 tick warmup
@@ -48,9 +49,10 @@ object Witherite: GenericGearSet() {
Optional.empty() // default disable sound
))
}
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_SWORD, Items.WITHER_SKELETON_SKULL)
}
val SWORD by gearItem("witherite_sword", ::WitheriteSword) {
val SWORD by registerItem("witherite_sword", ::WitheriteSword) {
properties {
enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
@@ -59,7 +61,7 @@ object Witherite: GenericGearSet() {
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_SWORD, Items.WITHER_SKELETON_SKULL)
}
val AXE by gearItem("witherite_axe", ::WitheriteWeapon) {
val AXE by registerItem("witherite_axe", ::WitheriteWeapon) {
properties {
enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
@@ -68,28 +70,28 @@ object Witherite: GenericGearSet() {
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_AXE, Items.WITHER_SKELETON_SKULL)
}
val HELMET by gearItem("witherite_helmet", ::Item) {
val HELMET by registerItem("witherite_helmet", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.HELMET)
}
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_HELMET, Items.WITHER_SKELETON_SKULL)
}
val CHESTPLATE by gearItem("witherite_chestplate", ::Item) {
val CHESTPLATE by registerItem("witherite_chestplate", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.CHESTPLATE)
}
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_CHESTPLATE, Items.WITHER_SKELETON_SKULL)
}
val LEGGINGS by gearItem("witherite_leggings", ::Item) {
val LEGGINGS by registerItem("witherite_leggings", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.LEGGINGS)
}
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_LEGGINGS, Items.WITHER_SKELETON_SKULL)
}
val BOOTS by gearItem("witherite_boots", ::Item) {
val BOOTS by registerItem("witherite_boots", ::Item) {
properties {
humanoidArmor(armorMaterial, ArmorType.BOOTS)
}
@@ -1,24 +1,16 @@
package dev.zxq5.items.core
package dev.zxq5.items.template
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.core.Holder
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.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
abstract class GenericGearSet: GenericItemSet() {
@@ -36,58 +28,64 @@ abstract class GenericGearSet: GenericItemSet() {
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,
)
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,
)
}
@@ -1,8 +1,24 @@
package dev.zxq5.items.core
package dev.zxq5.items.template
import dev.zxq5.items.ModItems
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.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.tags.TagKey
import net.minecraft.world.item.Item
import net.minecraft.world.item.crafting.Ingredient
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.reflect.KProperty
abstract class GenericItemSet {
// list of all items part of the class
@@ -10,10 +26,49 @@ abstract class GenericItemSet {
abstract fun onLoad()
open fun generateRecipes(registries: HolderLookup.Provider, exporter: RecipeOutput) {}
private val itemRecipes = mutableMapOf<Item, List<RecipeSpec>>()
private val itemTags = mutableMapOf<Item, List<TagKey<Item>>>()
private val itemModels = mutableMapOf<Item, ModelSpec>()
val modelSpecs: Map<Item, ModelSpec> get() = itemModels
internal fun registerDatagen(item: Item, recipes: List<RecipeSpec>, tags: List<TagKey<Item>>, model: ModelSpec) {
itemRecipes[item] = recipes
itemTags[item] = tags
itemModels[item] = model
}
open fun generateItemTags(tags: ItemTagAdder) {}
// open fun generateItemModels(models: ItemModelGenerators) {}
open fun generateItemTags(tags: ItemTagAdder) {
itemTags.forEach { (item, tagList) -> tagList.forEach { tags.add(it, item) } }
}
open 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)
}
}
}
}
}
init {
register(this)
@@ -26,4 +81,70 @@ abstract class GenericItemSet {
fun register(impl: GenericItemSet) = _all.add(impl)
}
}
}
class CustomItemBuilder<T : Item>(
private val owner: GenericItemSet,
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
}
}
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 // handwritten JSON already exists, skip auto-gen
}
fun <T : Item> GenericItemSet.registerItem(
name: String,
itemFactory: (Item.Properties) -> T,
block: CustomItemBuilder<T>.() -> Unit = {},
): CustomItemBuilder<T> = CustomItemBuilder(this, name, itemFactory).apply(block)
operator fun <T : Item> CustomItemBuilder<T>.provideDelegate(
thisRef: Any?, property: KProperty<*>
): Lazy<T> = lazy { build() }
operator fun <T : Item> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value