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
@@ -1,9 +1,8 @@
package dev.zxq5.client package dev.zxq5.client
import dev.zxq5.ModItemTagProvider
import dev.zxq5.ModRecipeProvider import dev.zxq5.ModRecipeProvider
import dev.zxq5.items.GenericGearSet import dev.zxq5.items.template.GenericItemSet
import dev.zxq5.items.ItemImplementation import dev.zxq5.items.template.ModelSpec
import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator
@@ -17,27 +16,27 @@ object FantasysmpDataGenerator : DataGeneratorEntrypoint {
val pack = fabricDataGenerator.createPack() val pack = fabricDataGenerator.createPack()
pack.addProvider(::ModRecipeProvider) // main-safe, can live in either sourceSet pack.addProvider(::ModRecipeProvider) // main-safe, can live in either sourceSet
// pack.addProvider(::ModItemTagProvider) // main-safe // pack.addProvider(::ModItemTagProvider) // main-safe
// pack.addProvider(::ModModelProvider) // needs client, since ItemModelGenerators is client-only pack.addProvider(::ModModelProvider) // needs client, since ItemModelGenerators is client-only
} }
} }
// client sourceSet // client sourceSet
//class ModModelProvider(output: FabricPackOutput) : FabricModelProvider(output) { class ModModelProvider(output: FabricPackOutput) : FabricModelProvider(output) {
// override fun generateBlockStateModels(blockModels: BlockModelGenerators) {} override fun generateBlockStateModels(blockModels: BlockModelGenerators) {}
//
// override fun generateItemModels(itemModels: ItemModelGenerators) { override fun generateItemModels(itemModels: ItemModelGenerators) {
// ItemImplementation.all.forEach { it.generateModels(itemModels) } GenericItemSet.all.forEach { it.generateModels(itemModels) }
// } }
//} }
// client sourceSet // client sourceSet
//fun ItemImplementation.generateModels(itemModels: ItemModelGenerators) { fun GenericItemSet.generateModels(itemModels: ItemModelGenerators) {
// if (this !is GenericGearSet) return if (this !is GenericItemSet) return
// modelSpecs.forEach { (item, spec) -> modelSpecs.forEach { (item, spec) ->
// when (spec) { when (spec) {
// ModelSpec.Generated -> itemModels.generateFlatItem(item, ModelTemplates.FLAT_ITEM) ModelSpec.Generated -> itemModels.generateFlatItem(item, ModelTemplates.FLAT_ITEM)
// ModelSpec.Handheld -> itemModels.generateFlatItem(item, ModelTemplates.FLAT_HANDHELD_ITEM) ModelSpec.Handheld -> itemModels.generateFlatItem(item, ModelTemplates.FLAT_HANDHELD_ITEM)
// ModelSpec.None -> {} // nothing to do — hand-authored JSON covers it ModelSpec.None -> {} // nothing to do — hand-authored JSON covers it
// } }
// } }
//} }
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_addition": {
"conditions": {
"items": [
{
"items": "fantasysmp:dragonite_ingot"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "fantasysmp:dragonite_boots"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_addition"
]
],
"rewards": {
"recipes": [
"fantasysmp:dragonite_boots"
]
}
}
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_addition": {
"conditions": {
"items": [
{
"items": "fantasysmp:dragonite_ingot"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "fantasysmp:dragonite_chestplate"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_addition"
]
],
"rewards": {
"recipes": [
"fantasysmp:dragonite_chestplate"
]
}
}
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_addition": {
"conditions": {
"items": [
{
"items": "fantasysmp:dragonite_ingot"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "fantasysmp:dragonite_helmet"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_addition"
]
],
"rewards": {
"recipes": [
"fantasysmp:dragonite_helmet"
]
}
}
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_addition": {
"conditions": {
"items": [
{
"items": "fantasysmp:dragonite_ingot"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "fantasysmp:dragonite_leggings"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_addition"
]
],
"rewards": {
"recipes": [
"fantasysmp:dragonite_leggings"
]
}
}
@@ -0,0 +1,32 @@
{
"parent": "minecraft:recipes/root",
"criteria": {
"has_addition": {
"conditions": {
"items": [
{
"items": "fantasysmp:dragonite_ingot"
}
]
},
"trigger": "minecraft:inventory_changed"
},
"has_the_recipe": {
"conditions": {
"recipe": "fantasysmp:dragonite_sword"
},
"trigger": "minecraft:recipe_unlocked"
}
},
"requirements": [
[
"has_the_recipe",
"has_addition"
]
],
"rewards": {
"recipes": [
"fantasysmp:dragonite_sword"
]
}
}
@@ -0,0 +1,9 @@
{
"type": "minecraft:smithing_transform",
"addition": "fantasysmp:dragonite_ingot",
"base": "minecraft:netherite_boots",
"result": {
"id": "fantasysmp:dragonite_boots"
},
"template": "minecraft:crying_obsidian"
}
@@ -0,0 +1,9 @@
{
"type": "minecraft:smithing_transform",
"addition": "fantasysmp:dragonite_ingot",
"base": "minecraft:netherite_chestplate",
"result": {
"id": "fantasysmp:dragonite_chestplate"
},
"template": "minecraft:elytra"
}
@@ -0,0 +1,9 @@
{
"type": "minecraft:smithing_transform",
"addition": "fantasysmp:dragonite_ingot",
"base": "minecraft:netherite_helmet",
"result": {
"id": "fantasysmp:dragonite_helmet"
},
"template": "minecraft:dragon_head"
}
@@ -0,0 +1,9 @@
{
"type": "minecraft:smithing_transform",
"addition": "fantasysmp:dragonite_ingot",
"base": "minecraft:netherite_leggings",
"result": {
"id": "fantasysmp:dragonite_leggings"
},
"template": "minecraft:crying_obsidian"
}
@@ -0,0 +1,9 @@
{
"type": "minecraft:smithing_transform",
"addition": "fantasysmp:dragonite_ingot",
"base": "minecraft:netherite_sword",
"result": {
"id": "fantasysmp:dragonite_sword"
},
"template": "minecraft:end_crystal"
}
+6 -6
View File
@@ -1,10 +1,10 @@
package dev.zxq5 package dev.zxq5
import dev.zxq5.items.ItemImplementation import dev.zxq5.items.template.GenericItemSet
import dev.zxq5.items.ItemTagAdder import dev.zxq5.items.template.ItemTagAdder
import dev.zxq5.items.ModItems import dev.zxq5.items.ModItems
import dev.zxq5.items.Witherite import dev.zxq5.items.sets.Witherite
//import dev.zxq5.items.Witherite //import dev.zxq5.items.sets.Witherite
import net.fabricmc.api.ModInitializer import net.fabricmc.api.ModInitializer
import net.fabricmc.fabric.api.datagen.v1.FabricPackOutput import net.fabricmc.fabric.api.datagen.v1.FabricPackOutput
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider
@@ -51,7 +51,7 @@ class ModRecipeProvider(
return object : RecipeProvider(registries, exporter) { return object : RecipeProvider(registries, exporter) {
override fun buildRecipes() { override fun buildRecipes() {
print("GENERATING ALL RECIPES") print("GENERATING ALL RECIPES")
ItemImplementation.all.forEach { GenericItemSet.all.forEach {
print("GENERATING RECIPES FOR" + it.items.toString()) print("GENERATING RECIPES FOR" + it.items.toString())
it.generateRecipes(registries, exporter) it.generateRecipes(registries, exporter)
} }
@@ -66,6 +66,6 @@ class ModItemTagProvider(output: FabricPackOutput, registriesFuture: Completable
FabricTagsProvider.ItemTagsProvider(output, registriesFuture) { FabricTagsProvider.ItemTagsProvider(output, registriesFuture) {
override fun addTags(provider: HolderLookup.Provider) { override fun addTags(provider: HolderLookup.Provider) {
val adder = ItemTagAdder(this) val adder = ItemTagAdder(this)
ItemImplementation.all.forEach { it.generateItemTags(adder) } GenericItemSet.all.forEach { it.generateItemTags(adder) }
} }
} }
@@ -1,8 +1,9 @@
package dev.zxq5.items package dev.zxq5.items
import dev.zxq5.Fantasysmp import dev.zxq5.Fantasysmp
import dev.zxq5.items.core.GenericGearSet import dev.zxq5.items.template.GenericItemSet
import dev.zxq5.items.core.GenericItemSet import dev.zxq5.items.sets.Dragonite
import dev.zxq5.items.sets.Witherite
import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTab import net.fabricmc.fabric.api.creativetab.v1.FabricCreativeModeTab
import net.minecraft.core.Registry import net.minecraft.core.Registry
import net.minecraft.core.registries.BuiltInRegistries import net.minecraft.core.registries.BuiltInRegistries
@@ -18,7 +19,7 @@ object ModItems {
fun <T : Item> register( fun <T : Item> register(
name: String, name: String,
itemFactory: (Item.Properties) -> T, itemFactory: (Item.Properties) -> T,
settings: Item.Properties settings: Item.Properties = Item.Properties(),
): T { ): T {
val itemKey = ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, name)) val itemKey = ResourceKey.create(Registries.ITEM, Identifier.fromNamespaceAndPath(Fantasysmp.MOD_ID, name))
val item = itemFactory(settings.setId(itemKey)) val item = itemFactory(settings.setId(itemKey))
@@ -40,7 +41,7 @@ object ModItems {
} }
.build() .build()
private val eagerLoad = listOf<GenericGearSet>( private val eagerLoad = listOf<GenericItemSet>(
Witherite, Witherite,
Dragonite, Dragonite,
) )
@@ -1,20 +1,29 @@
package dev.zxq5.items.sets package dev.zxq5.items.sets
import dev.zxq5.items.core.ArmorSet import dev.zxq5.items.template.ArmorSet
import dev.zxq5.items.core.GenericGearSet import dev.zxq5.items.template.GenericGearSet
import dev.zxq5.items.ModItems.register 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.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.SoundEvents
import net.minecraft.sounds.SoundSource import net.minecraft.sounds.SoundSource
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
import net.minecraft.world.damagesource.DamageSource
import net.minecraft.world.entity.Entity
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item import net.minecraft.world.item.Item
import net.minecraft.world.item.Items
import net.minecraft.world.item.ToolMaterial import net.minecraft.world.item.ToolMaterial
import net.minecraft.world.item.component.UseCooldown import net.minecraft.world.item.component.UseCooldown
import net.minecraft.world.item.equipment.* import net.minecraft.world.item.equipment.*
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import kotlin.getValue
object Dragonite: GenericGearSet() { object Dragonite: GenericGearSet() {
@@ -22,60 +31,55 @@ object Dragonite: GenericGearSet() {
override val materialDefinition override val materialDefinition
get() = super.materialDefinition.copy(assetId = materialKey) get() = super.materialDefinition.copy(assetId = materialKey)
val SWORD = register("dragonite_sword", ::DragoniteSword, val SCALE by registerItem("dragon_scale", ::Item)
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 CHESTPLATE = register( val INGOT by registerItem("dragonite_ingot", ::Item) {
"dragonite_chestplate", shapedRecipe("SBS", "CXC", "SBS", key=mapOf<Char, Item>(
::Item, 'S' to Items.SHULKER_SHELL,
Item.Properties() 'X' to SCALE,
.enchantable(armorMaterial.enchantmentValue) 'B' to Items.DRAGON_BREATH,
.humanoidArmor(armorMaterial , ArmorType.CHESTPLATE) 'C' to Items.CHORUS_FRUIT
.component(DataComponents.GLIDER, Unit.INSTANCE) ))
.durability(ArmorType.CHESTPLATE.getDurability(armorMaterial.durability)) }
)
val LEGGINGS = register( val SWORD by registerItem("dragonite_sword", ::DragoniteSword) {
"dragonite_leggings", properties {
::Item, enchantable(armorMaterial.enchantmentValue)
Item.Properties() .component(DataComponents.USE_COOLDOWN, UseCooldown(2f))
.enchantable(armorMaterial.enchantmentValue) .sword(ToolMaterial.NETHERITE, 3F, -2.4F)
.humanoidArmor(armorMaterial , ArmorType.LEGGINGS) }
.durability(ArmorType.LEGGINGS.getDurability(armorMaterial.durability)) smithingUpgrade(Items.END_CRYSTAL, Items.NETHERITE_SWORD, INGOT)
) }
val BOOTS = register( val HELMET by registerItem("dragonite_helmet", ::Item) {
"dragonite_boots", properties {
::Item, humanoidArmor(armorMaterial, ArmorType.HELMET)
Item.Properties() }
.enchantable(armorMaterial.enchantmentValue) smithingUpgrade(Items.DRAGON_HEAD, Items.NETHERITE_HELMET, INGOT)
.humanoidArmor(armorMaterial , ArmorType.BOOTS) }
.durability(ArmorType.BOOTS.getDurability(armorMaterial.durability))
)
val INGOT = register( val CHESTPLATE by registerItem("dragonite_chestplate", ::Item) {
"dragonite_ingot", properties {
::Item, humanoidArmor(armorMaterial, ArmorType.CHESTPLATE)
Item.Properties() .component(DataComponents.GLIDER, Unit.INSTANCE)
) .component(DataComponents.UNBREAKABLE, Unit.INSTANCE)
}
smithingUpgrade(Items.ELYTRA, Items.NETHERITE_CHESTPLATE, INGOT)
}
val SCALE = register( val LEGGINGS by registerItem("dragonite_leggings", ::Item) {
"dragon_scale", properties {
::Item, humanoidArmor(armorMaterial, ArmorType.LEGGINGS)
Item.Properties() }
) smithingUpgrade(Items.CRYING_OBSIDIAN, Items.NETHERITE_LEGGINGS, INGOT)
}
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 armorSet: ArmorSet = ArmorSet(HELMET, CHESTPLATE, LEGGINGS, BOOTS)
override val items = listOf(SWORD, HELMET, CHESTPLATE, LEGGINGS, BOOTS, SCALE, INGOT) 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, level.playSound(null, user.x, user.y, user.z,
SoundEvents.ENDER_DRAGON_FLAP, SoundSource.PLAYERS, 1.0f, 1.0f) 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(
// (level as? ServerLevel)?.sendParticles( PowerParticleOption.create(ParticleTypes.DRAGON_BREATH, 1.0f),
// ParticleTypes.DRAGON_BREATH as ParticleOptions, user.x, user.y, user.z,
// user.x, user.y, user.z, 100, 0.5, 0.5, 0.5, 0.5
// 100, 0.5, 0.5, 0.5, 0.5 )
// )
return InteractionResult.SUCCESS 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 package dev.zxq5.items.sets
import dev.zxq5.items.core.ArmorSet import dev.zxq5.items.template.ArmorSet
import dev.zxq5.items.core.GenericGearSet import dev.zxq5.items.template.GenericGearSet
import dev.zxq5.items.core.gearItem import dev.zxq5.items.template.registerItem
import dev.zxq5.items.template.provideDelegate
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.world.InteractionHand import net.minecraft.world.InteractionHand
@@ -29,7 +30,7 @@ object Witherite: GenericGearSet() {
override val materialDefinition override val materialDefinition
get() = super.materialDefinition.copy(assetId = materialKey) get() = super.materialDefinition.copy(assetId = materialKey)
val SHIELD by gearItem("witherite_shield", ::Item) { val SHIELD by registerItem("witherite_shield", ::Item) {
properties { properties {
component(DataComponents.BLOCKS_ATTACKS, BlocksAttacks( component(DataComponents.BLOCKS_ATTACKS, BlocksAttacks(
0.25f, // 5 tick warmup 0.25f, // 5 tick warmup
@@ -48,9 +49,10 @@ object Witherite: GenericGearSet() {
Optional.empty() // default disable sound 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 { properties {
enchantable(armorMaterial.enchantmentValue) enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f)) .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) 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 { properties {
enchantable(armorMaterial.enchantmentValue) enchantable(armorMaterial.enchantmentValue)
.component(DataComponents.USE_COOLDOWN, UseCooldown(2f)) .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) 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 { properties {
humanoidArmor(armorMaterial, ArmorType.HELMET) humanoidArmor(armorMaterial, ArmorType.HELMET)
} }
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_HELMET, Items.WITHER_SKELETON_SKULL) 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 { properties {
humanoidArmor(armorMaterial, ArmorType.CHESTPLATE) humanoidArmor(armorMaterial, ArmorType.CHESTPLATE)
} }
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_CHESTPLATE, Items.WITHER_SKELETON_SKULL) 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 { properties {
humanoidArmor(armorMaterial, ArmorType.LEGGINGS) humanoidArmor(armorMaterial, ArmorType.LEGGINGS)
} }
smithingUpgrade(Items.NETHERITE_UPGRADE_SMITHING_TEMPLATE, Items.NETHERITE_LEGGINGS, Items.WITHER_SKELETON_SKULL) 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 { properties {
humanoidArmor(armorMaterial, ArmorType.BOOTS) humanoidArmor(armorMaterial, ArmorType.BOOTS)
} }
@@ -1,24 +1,16 @@
package dev.zxq5.items.core package dev.zxq5.items.template
import dev.zxq5.util.resourceKey import dev.zxq5.util.resourceKey
import net.minecraft.advancements.criterion.InventoryChangeTrigger import net.minecraft.core.Holder
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.resources.ResourceKey
import net.minecraft.sounds.SoundEvent
import net.minecraft.tags.TagKey import net.minecraft.tags.TagKey
import net.minecraft.world.entity.EquipmentSlot import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.player.Player import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.Item 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.ArmorMaterial
import net.minecraft.world.item.equipment.ArmorMaterials
import net.minecraft.world.item.equipment.ArmorType
import net.minecraft.world.item.equipment.EquipmentAsset import net.minecraft.world.item.equipment.EquipmentAsset
abstract class GenericGearSet: GenericItemSet() { abstract class GenericGearSet: GenericItemSet() {
@@ -36,58 +28,64 @@ abstract class GenericGearSet: GenericItemSet() {
materialDefinition.toArmorMaterial() 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) = fun fullSetEquippedBy(p: Player) =
p.getItemBySlot(EquipmentSlot.HEAD).item == armorSet.helmet && p.getItemBySlot(EquipmentSlot.HEAD).item == armorSet.helmet &&
p.getItemBySlot(EquipmentSlot.CHEST).item == armorSet.chestplate && p.getItemBySlot(EquipmentSlot.CHEST).item == armorSet.chestplate &&
p.getItemBySlot(EquipmentSlot.LEGS).item == armorSet.leggings && p.getItemBySlot(EquipmentSlot.LEGS).item == armorSet.leggings &&
p.getItemBySlot(EquipmentSlot.FEET).item == armorSet.boots 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.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.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.Item
import net.minecraft.world.item.crafting.Ingredient
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.reflect.KProperty
abstract class GenericItemSet { abstract class GenericItemSet {
// list of all items part of the class // list of all items part of the class
@@ -10,10 +26,49 @@ abstract class GenericItemSet {
abstract fun onLoad() 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 generateItemTags(tags: ItemTagAdder) {
// open fun generateItemModels(models: ItemModelGenerators) {} 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 { init {
register(this) register(this)
@@ -27,3 +82,69 @@ abstract class GenericItemSet {
fun register(impl: GenericItemSet) = _all.add(impl) 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