diff --git a/src/generated_test/resources/.cache/cache b/src/generated_test/resources/.cache/cache index 30377b61fe..38a055837e 100644 --- a/src/generated_test/resources/.cache/cache +++ b/src/generated_test/resources/.cache/cache @@ -90,9 +90,23 @@ ba49dc1068fd9c93a458f4267896ea4476187411 assets/tag_based_tool_types/models/item 15c4780e8ea8e13179a35f045ac7975c76f4471d assets/tag_based_tool_types/models/item/test_tool.json 806659ba3e9c12797df0fe7211a222795834fdfd data/data_gen_test/advancements/conditional.json 0001aa55877ddee50e3180db7337e926797d7e4d data/data_gen_test/advancements/obtain_dirt.json +9142f390548b2aba862f29e05ac44452b6083d31 data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_custom_types.json +ed9a05f407f40fcf73bf16a488540f33e69d79df data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_only_vanilla.json +a9b940a9341ba1e6836f6cccde57fd84ac6cefde data/data_gen_test/advancements/recipes/building_blocks/intersection_ingredient.json 76e760c8e8a1bbd3fb5538bc2d773c117016c0ce data/data_gen_test/advancements/recipes/conditional2.json +eb5a3c9b0eece693b02c1d13095e395b702a0f9d data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_item_set.json +5b8b202b81157845e9065a0a92cfd777d0f6dc97 data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_single_item.json +139a68aa4991369581091633c15ead8c212a70ce data/data_gen_test/advancements/recipes/tools/difference_ingredient.json +b9f392d257ce4622bd7daa98d601f461b78934c9 data/data_gen_test/advancements/recipes/tools/exact_nbt_ingredient.json +094ed894c5a3d86df27b40cca803ae842f783db6 data/data_gen_test/recipes/compound_ingredient_custom_types.json +4b6e057c4ca229e22f57c2fb929173b6ed7dc187 data/data_gen_test/recipes/compound_ingredient_only_vanilla.json ed4cbf1a3a2f5d8969f6346fdc9acdbe81d0c919 data/data_gen_test/recipes/conditional.json ca9827bff0a49957e01cf3887ef5131ad8710320 data/data_gen_test/recipes/conditional2.json +82ef22fdcd68a56bfd89fcd65a7d3472761c2b67 data/data_gen_test/recipes/contains_nbt_ingredient_item_set.json +9befa598e96e2ed26ce13290164373e4a37808a7 data/data_gen_test/recipes/contains_nbt_ingredient_single_item.json +d39a07652a4e5b7d38e94a00ceb3ae54298ed169 data/data_gen_test/recipes/difference_ingredient.json +1af57f3033a3ac89842f210d55ac39a7b3e43c5b data/data_gen_test/recipes/exact_nbt_ingredient.json +d769b2a8ef432cd2de02b81ceac7f747aa1f7e8d data/data_gen_test/recipes/intersection_ingredient.json 8831e79c8d15b1bc00eb29d354bb22b64d9ddcdd data/data_gen_test/tags/blocks/test.json d65c425e740dc833f29d16606a1171825876be0d data/data_gen_test/tags/blocks/thing/one.json 1f70ed4ddc878bada5e43d3c44a34157806beda8 data/data_gen_test/tags/blocks/thing/three.json diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_custom_types.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_custom_types.json new file mode 100644 index 0000000000..2055e861be --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_custom_types.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:compound_ingredient_custom_types" + ] + }, + "criteria": { + "has_planks": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:crimson_planks" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:compound_ingredient_custom_types" + } + } + }, + "requirements": [ + [ + "has_planks", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_only_vanilla.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_only_vanilla.json new file mode 100644 index 0000000000..5c7c90f706 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/compound_ingredient_only_vanilla.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:compound_ingredient_only_vanilla" + ] + }, + "criteria": { + "has_planks": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:crimson_planks" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:compound_ingredient_only_vanilla" + } + } + }, + "requirements": [ + [ + "has_planks", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/intersection_ingredient.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/intersection_ingredient.json new file mode 100644 index 0000000000..9e51ddbb93 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/building_blocks/intersection_ingredient.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:intersection_ingredient" + ] + }, + "criteria": { + "has_planks": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:crimson_planks" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:intersection_ingredient" + } + } + }, + "requirements": [ + [ + "has_planks", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_item_set.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_item_set.json new file mode 100644 index 0000000000..ba30ea8fd3 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_item_set.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:contains_nbt_ingredient_item_set" + ] + }, + "criteria": { + "has_pickaxe": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:wooden_pickaxe" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:contains_nbt_ingredient_item_set" + } + } + }, + "requirements": [ + [ + "has_pickaxe", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_single_item.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_single_item.json new file mode 100644 index 0000000000..3d4b403480 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/contains_nbt_ingredient_single_item.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:contains_nbt_ingredient_single_item" + ] + }, + "criteria": { + "has_pickaxe": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:stone_pickaxe" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:contains_nbt_ingredient_single_item" + } + } + }, + "requirements": [ + [ + "has_pickaxe", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/difference_ingredient.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/difference_ingredient.json new file mode 100644 index 0000000000..3fb4661b53 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/difference_ingredient.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:difference_ingredient" + ] + }, + "criteria": { + "has_fence": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:crimson_fence" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:difference_ingredient" + } + } + }, + "requirements": [ + [ + "has_fence", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/exact_nbt_ingredient.json b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/exact_nbt_ingredient.json new file mode 100644 index 0000000000..8845092d90 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/advancements/recipes/tools/exact_nbt_ingredient.json @@ -0,0 +1,34 @@ +{ + "parent": "minecraft:recipes/root", + "rewards": { + "recipes": [ + "data_gen_test:exact_nbt_ingredient" + ] + }, + "criteria": { + "has_pickaxe": { + "trigger": "minecraft:inventory_changed", + "conditions": { + "items": [ + { + "items": [ + "minecraft:iron_pickaxe" + ] + } + ] + } + }, + "has_the_recipe": { + "trigger": "minecraft:recipe_unlocked", + "conditions": { + "recipe": "data_gen_test:exact_nbt_ingredient" + } + } + }, + "requirements": [ + [ + "has_pickaxe", + "has_the_recipe" + ] + ] +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_custom_types.json b/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_custom_types.json new file mode 100644 index 0000000000..ce7425a3b4 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_custom_types.json @@ -0,0 +1,28 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "#", + "#" + ], + "key": { + "#": [ + { + "type": "forge:nbt", + "item": "minecraft:iron_pickaxe", + "count": 1, + "nbt": "{Damage:3}" + }, + [ + { + "tag": "minecraft:planks" + }, + { + "tag": "minecraft:logs" + } + ] + ] + }, + "result": { + "item": "minecraft:gold_block" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_only_vanilla.json b/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_only_vanilla.json new file mode 100644 index 0000000000..5173707f13 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/compound_ingredient_only_vanilla.json @@ -0,0 +1,23 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "###", + " # " + ], + "key": { + "#": [ + { + "tag": "minecraft:planks" + }, + { + "tag": "minecraft:logs" + }, + { + "item": "minecraft:bedrock" + } + ] + }, + "result": { + "item": "minecraft:dirt" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_item_set.json b/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_item_set.json new file mode 100644 index 0000000000..e7e6e93431 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_item_set.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "type": "forge:partial_nbt", + "items": [ + "minecraft:iron_pickaxe", + "minecraft:stone_pickaxe", + "minecraft:wooden_pickaxe" + ], + "nbt": "{display:{Name:\u0027{\"text\":\"Diamond Pickaxe\"}\u0027}}" + } + ], + "result": { + "item": "minecraft:diamond_pickaxe" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_single_item.json b/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_single_item.json new file mode 100644 index 0000000000..861116b77a --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/contains_nbt_ingredient_single_item.json @@ -0,0 +1,13 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "type": "forge:partial_nbt", + "item": "minecraft:stone_pickaxe", + "nbt": "{Damage:3}" + } + ], + "result": { + "item": "minecraft:iron_pickaxe" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/difference_ingredient.json b/src/generated_test/resources/data/data_gen_test/recipes/difference_ingredient.json new file mode 100644 index 0000000000..129b69c57c --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/difference_ingredient.json @@ -0,0 +1,22 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + " # ", + "###", + " # " + ], + "key": { + "#": { + "type": "forge:difference", + "base": { + "tag": "minecraft:fences" + }, + "subtracted": { + "tag": "minecraft:non_flammable_wood" + } + } + }, + "result": { + "item": "minecraft:flint_and_steel" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/exact_nbt_ingredient.json b/src/generated_test/resources/data/data_gen_test/recipes/exact_nbt_ingredient.json new file mode 100644 index 0000000000..d2f533c2e3 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/exact_nbt_ingredient.json @@ -0,0 +1,14 @@ +{ + "type": "minecraft:crafting_shapeless", + "ingredients": [ + { + "type": "forge:nbt", + "item": "minecraft:iron_pickaxe", + "count": 1, + "nbt": "{Damage:3}" + } + ], + "result": { + "item": "minecraft:golden_pickaxe" + } +} \ No newline at end of file diff --git a/src/generated_test/resources/data/data_gen_test/recipes/intersection_ingredient.json b/src/generated_test/resources/data/data_gen_test/recipes/intersection_ingredient.json new file mode 100644 index 0000000000..ab8f45fa75 --- /dev/null +++ b/src/generated_test/resources/data/data_gen_test/recipes/intersection_ingredient.json @@ -0,0 +1,24 @@ +{ + "type": "minecraft:crafting_shaped", + "pattern": [ + "###", + "###", + " # " + ], + "key": { + "#": { + "type": "forge:intersection", + "children": [ + { + "tag": "minecraft:planks" + }, + { + "tag": "minecraft:non_flammable_wood" + } + ] + } + }, + "result": { + "item": "minecraft:netherrack" + } +} \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/common/ForgeMod.java b/src/main/java/net/minecraftforge/common/ForgeMod.java index 51a118e70d..4ec5fabc3e 100644 --- a/src/main/java/net/minecraftforge/common/ForgeMod.java +++ b/src/main/java/net/minecraftforge/common/ForgeMod.java @@ -5,11 +5,9 @@ package net.minecraftforge.common; -import com.google.common.collect.Multimap; import net.minecraft.commands.synchronization.EmptyArgumentSerializer; import net.minecraft.commands.synchronization.ArgumentTypes; import net.minecraft.commands.synchronization.ArgumentSerializer; -import net.minecraft.nbt.ListTag; import net.minecraft.world.entity.ai.attributes.Attribute; import net.minecraft.world.entity.ai.attributes.RangedAttribute; import net.minecraft.world.item.crafting.Ingredient; @@ -17,17 +15,17 @@ import net.minecraft.world.level.material.Fluid; import net.minecraft.world.item.Items; import net.minecraft.sounds.SoundEvent; import net.minecraft.core.Registry; -import net.minecraft.world.level.storage.WorldData; -import net.minecraft.world.level.storage.LevelStorageSource; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.ForgeHooksClient; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; +import net.minecraftforge.common.crafting.PartialNBTIngredient; +import net.minecraftforge.common.crafting.DifferenceIngredient; +import net.minecraftforge.common.crafting.IntersectionIngredient; import net.minecraftforge.common.data.ExistingFileHelper; import net.minecraftforge.common.data.ForgeFluidTagsProvider; import net.minecraftforge.common.loot.CanToolPerformAction; import net.minecraftforge.common.loot.GlobalLootModifierSerializer; import net.minecraftforge.common.loot.LootTableIdCondition; -import net.minecraftforge.common.util.MavenVersionStringHelper; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fluids.FluidAttributes; @@ -53,7 +51,6 @@ import org.apache.logging.log4j.Logger; import net.minecraft.data.DataGenerator; import net.minecraft.world.item.crafting.Recipe; import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.nbt.CompoundTag; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.common.crafting.CompoundIngredient; import net.minecraftforge.common.crafting.ConditionalRecipe; @@ -256,6 +253,9 @@ public class ForgeMod CraftingHelper.register(new ResourceLocation("forge", "compound"), CompoundIngredient.Serializer.INSTANCE); CraftingHelper.register(new ResourceLocation("forge", "nbt"), NBTIngredient.Serializer.INSTANCE); + CraftingHelper.register(new ResourceLocation("forge", "partial_nbt"), PartialNBTIngredient.Serializer.INSTANCE); + CraftingHelper.register(new ResourceLocation("forge", "difference"), DifferenceIngredient.Serializer.INSTANCE); + CraftingHelper.register(new ResourceLocation("forge", "intersection"), IntersectionIngredient.Serializer.INSTANCE); CraftingHelper.register(new ResourceLocation("minecraft", "item"), VanillaIngredientSerializer.INSTANCE); event.getRegistry().register(new ConditionalRecipe.Serializer>().setRegistryName(new ResourceLocation("forge", "conditional"))); diff --git a/src/main/java/net/minecraftforge/common/crafting/AbstractIngredient.java b/src/main/java/net/minecraftforge/common/crafting/AbstractIngredient.java new file mode 100644 index 0000000000..e242850e74 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/crafting/AbstractIngredient.java @@ -0,0 +1,104 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.crafting; + +import com.google.gson.JsonElement; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; + +import javax.annotation.Nullable; +import java.util.stream.Stream; + +/** + * Extension of {@link Ingredient} which makes most methods custom ingredients need to implement abstract, and removes the static constructors + * Mods are encouraged to extend this class for their custom ingredients + */ +public abstract class AbstractIngredient extends Ingredient +{ + /** Empty constructor, for the sake of dynamic ingredients */ + protected AbstractIngredient() + { + super(Stream.of()); + } + + /** Value constructor, for ingredients that have some vanilla representation */ + protected AbstractIngredient(Stream values) + { + super(values); + } + + @Override + public abstract boolean isSimple(); + + @Override + public abstract IIngredientSerializer getSerializer(); + + @Override + public abstract JsonElement toJson(); + + + /* Hide vanilla ingredient static constructors to reduce errors with constructing custom ingredients */ + + /** @deprecated use {@link Ingredient#fromValues(Stream)} */ + @Deprecated + public static Ingredient fromValues(Stream values) + { + throw new UnsupportedOperationException("Use Ingredient.fromValues()"); + } + + /** @deprecated use {@link Ingredient#of()} */ + @Deprecated + public static Ingredient of() + { + throw new UnsupportedOperationException("Use Ingredient.of()"); + } + + /** @deprecated use {@link Ingredient#of(ItemLike...)} (Stream)} */ + @Deprecated + public static Ingredient of(ItemLike... items) + { + throw new UnsupportedOperationException("Use Ingredient.of()"); + } + + /** @deprecated use {@link Ingredient#of(ItemStack...)} (Stream)} */ + @Deprecated + public static Ingredient of(ItemStack... stacks) + { + throw new UnsupportedOperationException("Use Ingredient.of()"); + } + + /** @deprecated use {@link Ingredient#of(Stream)} (Stream)} */ + @Deprecated + public static Ingredient of(Stream stacks) + { + throw new UnsupportedOperationException("Use Ingredient.of()"); + } + + /** @deprecated use {@link Ingredient#of(TagKey)} (Stream)} */ + @Deprecated + public static Ingredient of(TagKey tag) + { + throw new UnsupportedOperationException("Use Ingredient.of()"); + } + + /** @deprecated use {@link Ingredient#fromNetwork(FriendlyByteBuf)} */ + @Deprecated + public static Ingredient fromNetwork(FriendlyByteBuf buffer) + { + throw new UnsupportedOperationException("Use Ingredient.fromNetwork()"); + } + + /** @deprecated use {@link Ingredient#fromJson(JsonElement)} (Stream)} */ + @Deprecated + public static Ingredient fromJson(@Nullable JsonElement json) + { + throw new UnsupportedOperationException("Use Ingredient.fromJson()"); + } +} diff --git a/src/main/java/net/minecraftforge/common/crafting/CompoundIngredient.java b/src/main/java/net/minecraftforge/common/crafting/CompoundIngredient.java index ff763d1baa..ef03f7cea7 100644 --- a/src/main/java/net/minecraftforge/common/crafting/CompoundIngredient.java +++ b/src/main/java/net/minecraftforge/common/crafting/CompoundIngredient.java @@ -5,6 +5,7 @@ package net.minecraftforge.common.crafting; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -14,6 +15,7 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -27,7 +29,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.network.FriendlyByteBuf; -public class CompoundIngredient extends Ingredient +/** Ingredient that matches if any of the child ingredients match */ +public class CompoundIngredient extends AbstractIngredient { private List children; private ItemStack[] stacks; @@ -36,11 +39,35 @@ public class CompoundIngredient extends Ingredient protected CompoundIngredient(List children) { - super(Stream.of()); this.children = Collections.unmodifiableList(children); this.isSimple = children.stream().allMatch(Ingredient::isSimple); } + /** Creates a compound ingredient from the given list of ingredients */ + public static Ingredient of(Ingredient... children) + { + // if 0 or 1 ingredient, can save effort + if (children.length == 0) + throw new IllegalArgumentException("Cannot create a compound ingredient with no children, use Ingredient.of() to create an empty ingredient"); + if (children.length == 1) + return children[0]; + + // need to merge vanilla ingredients, as otherwise the JSON produced by this ingredient could be invalid + List vanillaIngredients = new ArrayList<>(); + List allIngredients = new ArrayList<>(); + for (Ingredient child : children) { + if (child.getSerializer() == VanillaIngredientSerializer.INSTANCE) + vanillaIngredients.add(child); + else + allIngredients.add(child); + } + if (!vanillaIngredients.isEmpty()) + allIngredients.add(merge(vanillaIngredients)); + if (allIngredients.size() == 1) + return allIngredients.get(0); + return new CompoundIngredient(allIngredients); + } + @Override @Nonnull public ItemStack[] getItems() diff --git a/src/main/java/net/minecraftforge/common/crafting/CraftingHelper.java b/src/main/java/net/minecraftforge/common/crafting/CraftingHelper.java index 913dd34822..4b2e9c80bf 100644 --- a/src/main/java/net/minecraftforge/common/crafting/CraftingHelper.java +++ b/src/main/java/net/minecraftforge/common/crafting/CraftingHelper.java @@ -8,6 +8,7 @@ package net.minecraftforge.common.crafting; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import javax.annotation.Nullable; @@ -147,48 +148,52 @@ public class CraftingHelper return getItemStack(json, readNBT, false); } - public static ItemStack getItemStack(JsonObject json, boolean readNBT, boolean disallowsAirInRecipe) + public static Item getItem(String itemName, boolean disallowsAirInRecipe) { - String itemName = GsonHelper.getAsString(json, "item"); ResourceLocation itemKey = new ResourceLocation(itemName); - if (!ForgeRegistries.ITEMS.containsKey(itemKey)) throw new JsonSyntaxException("Unknown item '" + itemName + "'"); Item item = ForgeRegistries.ITEMS.getValue(itemKey); - if (disallowsAirInRecipe && item == Items.AIR) throw new JsonSyntaxException("Invalid item: " + itemName); + return Objects.requireNonNull(item); + } + public static CompoundTag getNBT(JsonElement element) + { + try + { + if (element.isJsonObject()) + return TagParser.parseTag(GSON.toJson(element)); + else + return TagParser.parseTag(GsonHelper.convertToString(element, "nbt")); + } + catch (CommandSyntaxException e) + { + throw new JsonSyntaxException("Invalid NBT Entry: " + e); + } + } + + public static ItemStack getItemStack(JsonObject json, boolean readNBT, boolean disallowsAirInRecipe) + { + String itemName = GsonHelper.getAsString(json, "item"); + Item item = getItem(itemName, disallowsAirInRecipe); if (readNBT && json.has("nbt")) { - // Lets hope this works? Needs test - try + CompoundTag nbt = getNBT(json.get("nbt")); + CompoundTag tmp = new CompoundTag(); + if (nbt.contains("ForgeCaps")) { - JsonElement element = json.get("nbt"); - CompoundTag nbt; - if(element.isJsonObject()) - nbt = TagParser.parseTag(GSON.toJson(element)); - else - nbt = TagParser.parseTag(GsonHelper.convertToString(element, "nbt")); - - CompoundTag tmp = new CompoundTag(); - if (nbt.contains("ForgeCaps")) - { - tmp.put("ForgeCaps", nbt.get("ForgeCaps")); - nbt.remove("ForgeCaps"); - } - - tmp.put("tag", nbt); - tmp.putString("id", itemName); - tmp.putInt("Count", GsonHelper.getAsInt(json, "count", 1)); - - return ItemStack.of(tmp); - } - catch (CommandSyntaxException e) - { - throw new JsonSyntaxException("Invalid NBT Entry: " + e.toString()); + tmp.put("ForgeCaps", nbt.get("ForgeCaps")); + nbt.remove("ForgeCaps"); } + + tmp.put("tag", nbt); + tmp.putString("id", itemName); + tmp.putInt("Count", GsonHelper.getAsInt(json, "count", 1)); + + return ItemStack.of(tmp); } return new ItemStack(item, GsonHelper.getAsInt(json, "count", 1)); diff --git a/src/main/java/net/minecraftforge/common/crafting/DifferenceIngredient.java b/src/main/java/net/minecraftforge/common/crafting/DifferenceIngredient.java new file mode 100644 index 0000000000..49f808116a --- /dev/null +++ b/src/main/java/net/minecraftforge/common/crafting/DifferenceIngredient.java @@ -0,0 +1,143 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.crafting; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntComparators; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; + +import javax.annotation.Nullable; +import java.util.Arrays; + +/** Ingredient that matches everything from the first ingredient that is not included in the second ingredient */ +public class DifferenceIngredient extends AbstractIngredient +{ + private final Ingredient base; + private final Ingredient subtracted; + private ItemStack[] filteredMatchingStacks; + private IntList packedMatchingStacks; + + protected DifferenceIngredient(Ingredient base, Ingredient subtracted) + { + this.base = base; + this.subtracted = subtracted; + } + + /** + * Gets the difference from the two ingredients + * @param base Ingredient the item must match + * @param subtracted Ingredient the item must not match + * @return Ingredient that {@code base} anything in base that is not in {@code subtracted} + */ + public static DifferenceIngredient of(Ingredient base, Ingredient subtracted) + { + return new DifferenceIngredient(base, subtracted); + } + + @Override + public boolean test(@Nullable ItemStack stack) + { + if (stack == null || stack.isEmpty()) + return false; + return base.test(stack) && !subtracted.test(stack); + } + + @Override + public ItemStack[] getItems() + { + if (this.filteredMatchingStacks == null) + this.filteredMatchingStacks = Arrays.stream(base.getItems()) + .filter(stack -> !subtracted.test(stack)) + .toArray(ItemStack[]::new); + return filteredMatchingStacks; + } + + @Override + public boolean isEmpty() + { + return getItems().length == 0; + } + + @Override + public boolean isSimple() + { + return base.isSimple() && subtracted.isSimple(); + } + + @Override + protected void invalidate() + { + super.invalidate(); + this.filteredMatchingStacks = null; + this.packedMatchingStacks = null; + } + + @Override + public IntList getStackingIds() + { + if (this.packedMatchingStacks == null || checkInvalidation()) + { + markValid(); + ItemStack[] matchingStacks = getItems(); + this.packedMatchingStacks = new IntArrayList(matchingStacks.length); + for (ItemStack stack : matchingStacks) + this.packedMatchingStacks.add(StackedContents.getStackingIndex(stack)); + + this.packedMatchingStacks.sort(IntComparators.NATURAL_COMPARATOR); + } + return packedMatchingStacks; + } + + @Override + public JsonElement toJson() + { + JsonObject json = new JsonObject(); + json.addProperty("type", CraftingHelper.getID(Serializer.INSTANCE).toString()); + json.add("base", base.toJson()); + json.add("subtracted", subtracted.toJson()); + return json; + } + + @Override + public IIngredientSerializer getSerializer() + { + return Serializer.INSTANCE; + } + + public static class Serializer implements IIngredientSerializer + { + public static final IIngredientSerializer INSTANCE = new Serializer(); + + @Override + public DifferenceIngredient parse(JsonObject json) + { + Ingredient base = Ingredient.fromJson(json.get("base")); + Ingredient without = Ingredient.fromJson(json.get("subtracted")); + return new DifferenceIngredient(base, without); + } + + @Override + public DifferenceIngredient parse(FriendlyByteBuf buffer) + { + Ingredient base = Ingredient.fromNetwork(buffer); + Ingredient without = Ingredient.fromNetwork(buffer); + return new DifferenceIngredient(base, without); + } + + @Override + public void write(FriendlyByteBuf buffer, DifferenceIngredient ingredient) + { + ingredient.base.toNetwork(buffer); + ingredient.subtracted.toNetwork(buffer); + } + } +} diff --git a/src/main/java/net/minecraftforge/common/crafting/IntersectionIngredient.java b/src/main/java/net/minecraftforge/common/crafting/IntersectionIngredient.java new file mode 100644 index 0000000000..60d7fabd64 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/crafting/IntersectionIngredient.java @@ -0,0 +1,172 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.crafting; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntComparators; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** Ingredient that matches if all child ingredients match */ +public class IntersectionIngredient extends AbstractIngredient +{ + private final List children; + private final boolean isSimple; + private ItemStack[] intersectedMatchingStacks = null; + private IntList packedMatchingStacks = null; + protected IntersectionIngredient(List children) + { + if (children.size() < 2) + throw new IllegalArgumentException("Cannot create an IntersectionIngredient with one or no children"); + this.children = Collections.unmodifiableList(children); + this.isSimple = children.stream().allMatch(Ingredient::isSimple); + } + + /** + * Gets an intersection ingredient + * @param ingredients List of ingredients to match + * @return Ingredient that only matches if all the passed ingredients match + */ + public static Ingredient of(Ingredient... ingredients) + { + if (ingredients.length == 0) + throw new IllegalArgumentException("Cannot create an IntersectionIngredient with no children, use Ingredient.of() to create an empty ingredient"); + if (ingredients.length == 1) + return ingredients[0]; + + return new IntersectionIngredient(Arrays.asList(ingredients)); + } + + @Override + public boolean test(@Nullable ItemStack stack) + { + if (stack == null || stack.isEmpty()) + return false; + + for (Ingredient ingredient : children) + if (!ingredient.test(stack)) + return false; + + return true; + } + + @Override + public ItemStack[] getItems() + { + if (this.intersectedMatchingStacks == null) + { + this.intersectedMatchingStacks = Arrays + .stream(children.get(0).getItems()) + .filter(stack -> + { + // the first ingredient is treated as a base, filtered by the second onwards + for (int i = 1; i < children.size(); i++) + if (!children.get(i).test(stack)) + return false; + return true; + }) + .toArray(ItemStack[]::new); + } + return intersectedMatchingStacks; + } + + @Override + public boolean isEmpty() + { + return getItems().length == 0; + } + + @Override + public boolean isSimple() + { + return isSimple; + } + + @Override + protected void invalidate() + { + super.invalidate(); + this.intersectedMatchingStacks = null; + this.packedMatchingStacks = null; + } + + @Override + public IntList getStackingIds() + { + if (this.packedMatchingStacks == null || checkInvalidation()) + { + markValid(); + ItemStack[] matchingStacks = getItems(); + this.packedMatchingStacks = new IntArrayList(matchingStacks.length); + for(ItemStack stack : matchingStacks) + this.packedMatchingStacks.add(StackedContents.getStackingIndex(stack)); + this.packedMatchingStacks.sort(IntComparators.NATURAL_COMPARATOR); + } + return packedMatchingStacks; + } + + @Override + public JsonElement toJson() + { + JsonObject json = new JsonObject(); + json.addProperty("type", CraftingHelper.getID(Serializer.INSTANCE).toString()); + JsonArray array = new JsonArray(); + for (Ingredient ingredient : children) + array.add(ingredient.toJson()); + + json.add("children", array); + return json; + } + + @Override + public IIngredientSerializer getSerializer() + { + return Serializer.INSTANCE; + } + + public static class Serializer implements IIngredientSerializer + { + public static final IIngredientSerializer INSTANCE = new Serializer(); + + @Override + public IntersectionIngredient parse(JsonObject json) + { + JsonArray children = GsonHelper.getAsJsonArray(json, "children"); + if (children.size() < 2) + throw new JsonSyntaxException("Must have at least two children for an intersection ingredient"); + return new IntersectionIngredient(IntStream.range(0, children.size()).mapToObj(i -> Ingredient.fromJson(children.get(i))).toList()); + } + + @Override + public IntersectionIngredient parse(FriendlyByteBuf buffer) + { + return new IntersectionIngredient(Stream.generate(() -> Ingredient.fromNetwork(buffer)).limit(buffer.readVarInt()).toList()); + } + + @Override + public void write(FriendlyByteBuf buffer, IntersectionIngredient intersection) + { + buffer.writeVarInt(intersection.children.size()); + for (Ingredient ingredient : intersection.children) + ingredient.toNetwork(buffer); + } + } +} diff --git a/src/main/java/net/minecraftforge/common/crafting/NBTIngredient.java b/src/main/java/net/minecraftforge/common/crafting/NBTIngredient.java index 7ba99aad17..c9c2201689 100644 --- a/src/main/java/net/minecraftforge/common/crafting/NBTIngredient.java +++ b/src/main/java/net/minecraftforge/common/crafting/NBTIngredient.java @@ -16,7 +16,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.network.FriendlyByteBuf; -public class NBTIngredient extends Ingredient +/** Ingredient that matches the given stack, performing an exact NBT match. Use {@link PartialNBTIngredient} if you need partial match. */ +public class NBTIngredient extends AbstractIngredient { private final ItemStack stack; protected NBTIngredient(ItemStack stack) @@ -25,6 +26,12 @@ public class NBTIngredient extends Ingredient this.stack = stack; } + /** Creates a new ingredient matching the given stack and tag */ + public static NBTIngredient of(ItemStack stack) + { + return new NBTIngredient(stack); + } + @Override public boolean test(@Nullable ItemStack input) { diff --git a/src/main/java/net/minecraftforge/common/crafting/PartialNBTIngredient.java b/src/main/java/net/minecraftforge/common/crafting/PartialNBTIngredient.java new file mode 100644 index 0000000000..a0a51ed180 --- /dev/null +++ b/src/main/java/net/minecraftforge/common/crafting/PartialNBTIngredient.java @@ -0,0 +1,157 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.common.crafting; + +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; +import net.minecraft.advancements.critereon.NbtPredicate; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.level.ItemLike; +import net.minecraftforge.registries.ForgeRegistries; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** Ingredient that matches the given items, performing a partial NBT match. Use {@link NBTIngredient} if you want exact match on NBT */ +public class PartialNBTIngredient extends AbstractIngredient +{ + private final Set items; + private final CompoundTag nbt; + private final NbtPredicate predicate; + protected PartialNBTIngredient(Set items, CompoundTag nbt) + { + super(items.stream().map(item -> + { + ItemStack stack = new ItemStack(item); + // copy NBT to prevent the stack from modifying the original, as capabilities or vanilla item durability will modify the tag + stack.setTag(nbt.copy()); + return new Ingredient.ItemValue(stack); + })); + if (items.isEmpty()) + { + throw new IllegalArgumentException("Cannot create a PartialNBTIngredient with no items"); + } + this.items = Collections.unmodifiableSet(items); + this.nbt = nbt; + this.predicate = new NbtPredicate(nbt); + } + + /** Creates a new ingredient matching any item from the list, containing the given NBT */ + public static PartialNBTIngredient of(CompoundTag nbt, ItemLike... items) + { + return new PartialNBTIngredient(Arrays.stream(items).map(ItemLike::asItem).collect(Collectors.toSet()), nbt); + } + + /** Creates a new ingredient matching the given item, containing the given NBT */ + public static PartialNBTIngredient of(ItemLike item, CompoundTag nbt) + { + return new PartialNBTIngredient(Set.of(item.asItem()), nbt); + } + + @Override + public boolean test(@Nullable ItemStack input) + { + if (input == null) + return false; + return items.contains(input.getItem()) && predicate.matches(input.getShareTag()); + } + + @Override + public boolean isSimple() + { + return false; + } + + @Override + public IIngredientSerializer getSerializer() + { + return Serializer.INSTANCE; + } + + @Override + public JsonElement toJson() + { + JsonObject json = new JsonObject(); + json.addProperty("type", CraftingHelper.getID(Serializer.INSTANCE).toString()); + if (items.size() == 1) + { + json.addProperty("item", items.iterator().next().getRegistryName().toString()); + } + else + { + JsonArray items = new JsonArray(); + // ensure the order of items in the set is deterministic when saved to JSON + this.items.stream().map(Item::getRegistryName).sorted().forEach(name -> items.add(name.toString())); + json.add("items", items); + } + json.addProperty("nbt", nbt.toString()); + return json; + } + + public static class Serializer implements IIngredientSerializer + { + public static final Serializer INSTANCE = new Serializer(); + + @Override + public PartialNBTIngredient parse(JsonObject json) + { + // parse items + Set items; + if (json.has("item")) + items = Set.of(CraftingHelper.getItem(GsonHelper.getAsString(json, "item"), true)); + else if (json.has("items")) + { + ImmutableSet.Builder builder = ImmutableSet.builder(); + JsonArray itemArray = GsonHelper.getAsJsonArray(json, "items"); + for (int i = 0; i < itemArray.size(); i++) + { + builder.add(CraftingHelper.getItem(GsonHelper.convertToString(itemArray.get(i), "items[" + i + ']'), true)); + } + items = builder.build(); + } + else + throw new JsonSyntaxException("Must set either 'item' or 'items'"); + + // parse NBT + if (!json.has("nbt")) + throw new JsonSyntaxException("Missing nbt, expected to find a String or JsonObject"); + CompoundTag nbt = CraftingHelper.getNBT(json.get("nbt")); + + return new PartialNBTIngredient(items, nbt); + } + + @Override + public PartialNBTIngredient parse(FriendlyByteBuf buffer) + { + Set items = Stream.generate(() -> buffer.readRegistryIdUnsafe(ForgeRegistries.ITEMS)).limit(buffer.readVarInt()).collect(Collectors.toSet()); + CompoundTag nbt = buffer.readNbt(); + return new PartialNBTIngredient(items, Objects.requireNonNull(nbt)); + } + + @Override + public void write(FriendlyByteBuf buffer, PartialNBTIngredient ingredient) + { + buffer.writeVarInt(ingredient.items.size()); + for (Item item : ingredient.items) + buffer.writeRegistryIdUnsafe(ForgeRegistries.ITEMS, item); + buffer.writeNbt(ingredient.nbt); + } + } +} diff --git a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java index da1eb5bef8..fb0794a927 100644 --- a/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java +++ b/src/test/java/net/minecraftforge/debug/DataGeneratorTest.java @@ -27,14 +27,25 @@ import java.util.stream.Stream; import com.google.gson.JsonNull; import com.google.gson.JsonPrimitive; +import net.minecraft.Util; import net.minecraft.advancements.critereon.InventoryChangeTrigger; import net.minecraft.data.advancements.AdvancementProvider; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.sounds.SoundEvents; +import net.minecraft.tags.ItemTags; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; import net.minecraft.world.level.block.ButtonBlock; import net.minecraft.world.level.block.PressurePlateBlock; import net.minecraft.world.level.block.StandingSignBlock; import net.minecraft.world.level.block.WallSignBlock; +import net.minecraftforge.common.crafting.CompoundIngredient; +import net.minecraftforge.common.crafting.PartialNBTIngredient; +import net.minecraftforge.common.crafting.DifferenceIngredient; +import net.minecraftforge.common.crafting.IntersectionIngredient; +import net.minecraftforge.common.crafting.NBTIngredient; import net.minecraftforge.common.data.SoundDefinition; import net.minecraftforge.common.data.SoundDefinitionsProvider; import org.apache.commons.lang3.tuple.Triple; @@ -157,6 +168,7 @@ public class DataGeneratorTest @Override protected void buildCraftingRecipes(Consumer consumer) { + // conditional recipe ResourceLocation ID = new ResourceLocation("data_gen_test", "conditional"); ConditionalRecipe.builder() @@ -222,6 +234,78 @@ public class DataGeneratorTest ) .generateAdvancement() .build(consumer, new ResourceLocation("data_gen_test", "conditional2")); + + // ingredient tests + // strict NBT match - should match an unnamed iron pickaxe that lost 3 durability + Ingredient nbtIngredient = NBTIngredient.of(Util.make(() -> + { + ItemStack stack = new ItemStack(Items.IRON_PICKAXE); + stack.setDamageValue(3); + return stack; + })); + ShapelessRecipeBuilder.shapeless(Items.GOLDEN_PICKAXE) + .requires(nbtIngredient) + .unlockedBy("has_pickaxe", has(Items.IRON_PICKAXE)) + .save(consumer, new ResourceLocation("data_gen_test", "exact_nbt_ingredient")); + + // ingredient tests + // contains NBT match - should match a stone pickaxe that lost 3 durability, regardless of setting its name + ShapelessRecipeBuilder.shapeless(Items.IRON_PICKAXE) + .requires(PartialNBTIngredient.of(Items.STONE_PICKAXE, Util.make(() -> + { + CompoundTag nbt = new CompoundTag(); + nbt.putInt(ItemStack.TAG_DAMAGE, 3); + return nbt; + }))) + .unlockedBy("has_pickaxe", has(Items.STONE_PICKAXE)) + .save(consumer, new ResourceLocation("data_gen_test", "contains_nbt_ingredient_single_item")); + + // contains NBT match - should match a wood, stone, or iron pickaxe that was named "Diamond Pickaxe", regardless of damage + ShapelessRecipeBuilder.shapeless(Items.DIAMOND_PICKAXE) + .requires(PartialNBTIngredient.of(Util.make(() -> + { + CompoundTag nbt = new CompoundTag(); + CompoundTag display = new CompoundTag(); + display.putString(ItemStack.TAG_DISPLAY_NAME, "{\"text\":\"Diamond Pickaxe\"}"); + nbt.put(ItemStack.TAG_DISPLAY, display); + return nbt; + }), Items.WOODEN_PICKAXE, Items.STONE_PICKAXE, Items.IRON_PICKAXE)) + .unlockedBy("has_pickaxe", has(Items.WOODEN_PICKAXE)) + .save(consumer, new ResourceLocation("data_gen_test", "contains_nbt_ingredient_item_set")); + + // intersection - should match all non-flammable planks + ShapedRecipeBuilder.shaped(Blocks.NETHERRACK) + .pattern("###") + .pattern("###") + .pattern(" # ") + .define('#', IntersectionIngredient.of(Ingredient.of(ItemTags.PLANKS), Ingredient.of(ItemTags.NON_FLAMMABLE_WOOD))) + .unlockedBy("has_planks", has(Items.CRIMSON_PLANKS)) + .save(consumer, new ResourceLocation("data_gen_test", "intersection_ingredient")); + + // difference - should match all flammable fences + ShapedRecipeBuilder.shaped(Items.FLINT_AND_STEEL) + .pattern(" # ") + .pattern("###") + .pattern(" # ") + .define('#', DifferenceIngredient.of(Ingredient.of(ItemTags.FENCES), Ingredient.of(ItemTags.NON_FLAMMABLE_WOOD))) + .unlockedBy("has_fence", has(Items.CRIMSON_FENCE)) + .save(consumer, new ResourceLocation("data_gen_test", "difference_ingredient")); + + // compound - should match planks, logs, or bedrock + ShapedRecipeBuilder.shaped(Blocks.DIRT) + .pattern("###") + .pattern(" # ") + .define('#', CompoundIngredient.of(Ingredient.of(ItemTags.PLANKS), Ingredient.of(ItemTags.LOGS), Ingredient.of(Blocks.BEDROCK))) + .unlockedBy("has_planks", has(Items.CRIMSON_PLANKS)) + .save(consumer, new ResourceLocation("data_gen_test", "compound_ingredient_only_vanilla")); + + // compound - should match planks, logs, or a stone pickaxe with 3 damage + ShapedRecipeBuilder.shaped(Blocks.GOLD_BLOCK) + .pattern("#") + .pattern("#") + .define('#', CompoundIngredient.of(Ingredient.of(ItemTags.PLANKS), Ingredient.of(ItemTags.LOGS), nbtIngredient)) + .unlockedBy("has_planks", has(Items.CRIMSON_PLANKS)) + .save(consumer, new ResourceLocation("data_gen_test", "compound_ingredient_custom_types")); } }