Posted July 10, 20214 yr I have been trying to use the forge:ingots tag (as an Ingredient) in a custom recipe to create a 'sheet metal' item. Unfortunately, I found that this tag includes brick and nether brick as well as the metal ingots. I could just use the individual forge:ingots/gold, forge:ingots/iron, etc. tags - but this means that new ingots added to the forge:ingots tag won't automatically work with my recipe. As a workaround, I tried to create a forge:ingots/metals tag, using the following code: List<Item> metalIngots = Tags.Items.INGOTS.getValues(); metalIngots.removeAll(Tags.Items.INGOTS_BRICK.getValues()); metalIngots.removeAll(Tags.Items.INGOTS_NETHER_BRICK.getValues()); tag(ModTags.Items.INGOTS_METAL).add(metalIngots.toArray(new Item[0])); Where ModTags.Items.INGOTS_METAL is the tag I created. Unfortunately, it comes up with this error on runData: java.lang.IllegalStateException: Tag forge:ingots used before it was bound The error seems to arise from the first line (Tags.Items.INGOTS.getValues()) Here is the stack trace: Spoiler Caused by: java.lang.IllegalStateException: Tag forge:ingots used before it was bound at net.minecraft.tags.TagRegistry$NamedTag.resolve(TagRegistry.java:131) at net.minecraft.tags.TagRegistry$NamedTag.getValues(TagRegistry.java:146) at my.mod.data.ModItemTagsProvider.addTags(ModItemTagsProvider.java:31) at net.minecraft.data.TagsProvider.run(TagsProvider.java:59) at net.minecraft.data.DataGenerator.run(DataGenerator.java:44) at net.minecraftforge.fml.event.lifecycle.GatherDataEvent$DataGeneratorConfig.lambda$runAll$0(GatherDataEvent.java:111) at cpw.mods.modlauncher.api.LamdbaExceptionUtils.lambda$rethrowConsumer$0(LamdbaExceptionUtils.java:34) at java.util.HashMap$Values.forEach(HashMap.java:981) at net.minecraftforge.fml.event.lifecycle.GatherDataEvent$DataGeneratorConfig.runAll(GatherDataEvent.java:107) at net.minecraftforge.fml.DatagenModLoader.begin(DatagenModLoader.java:61) at net.minecraft.data.Main.main(Main.java:43) ... 11 more I also tried a similar approach directly in my RecipeProvider class to generate an Ingredient there - but the same issue arose. Is there any way of creating an in Ingredient that includes all metal (non-brick) ingots?
July 10, 20214 yr Create your own tag. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
July 10, 20214 yr Author 40 minutes ago, Draco18s said: Create your own tag. Sorry, that's what I was doing? If you mean create my own complete tag, I tried mod:metal_ingots, but this raised the exact same error. Or, if you mean just putting iron, gold, and netherite in the same tag - other mods won't automatically integrate if they add their ingots to the forge:ingots tag.
July 11, 20214 yr 4 hours ago, DoctorC said: java.lang.IllegalStateException: Tag forge:ingots used before it was bound Show more of your code. Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable. If you think this is the case, JUST REPORT ME. Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice. Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked. DO NOT PM ME WITH PROBLEMS. No help will be given.
July 11, 20214 yr Author 1 hour ago, Draco18s said: Show more of your code. I think this is what you mean? ModItemTagsProvider.java package my.mod.data; import my.mod.Main; import my.mod.setup.ModTags; import net.minecraft.data.BlockTagsProvider; import net.minecraft.data.DataGenerator; import net.minecraft.data.ItemTagsProvider; import net.minecraft.item.Item; import net.minecraftforge.common.Tags; import net.minecraftforge.common.data.ExistingFileHelper; import java.util.List; public class ModItemTagsProvider extends ItemTagsProvider { public ModItemTagsProvider(DataGenerator dataGenerator, BlockTagsProvider blockTagsProvider, ExistingFileHelper existingFileHelper) { super(dataGenerator, blockTagsProvider, Main.MOD_ID, existingFileHelper); } @Override protected void addTags() { List<Item> metalIngots = Tags.Items.INGOTS.getValues(); metalIngots.removeAll(Tags.Items.INGOTS_BRICK.getValues()); metalIngots.removeAll(Tags.Items.INGOTS_NETHER_BRICK.getValues()); tag(ModTags.Items.INGOTS_METAL).add(metalIngots.toArray(new Item[0])); } } DataGenerators.java package my.mod.data; import my.mod.Main; import my.mod.data.ModBlockTagsProvider; import my.mod.data.ModItemTagsProvider; import net.minecraft.data.DataGenerator; import net.minecraftforge.common.data.ExistingFileHelper; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.fml.event.lifecycle.GatherDataEvent; @EventBusSubscriber(modid = Main.MOD_ID, bus = EventBusSubscriber.Bus.MOD) public class DataGenerators { @SubscribeEvent public static void gatherData(GatherDataEvent event) { DataGenerator gen = event.getGenerator(); ExistingFileHelper fileHelper = event.getExistingFileHelper(); ModBlockTagsProvider blockTags = new ModBlockTagsProvider(gen, fileHelper); gen.addProvider(blockTags); gen.addProvider(new ModItemTagsProvider(gen, blockTags, fileHelper)); } } ModTags.java package my.mod.setup; import my.mod.Main; import net.minecraft.tags.ITag; import net.minecraft.util.ResourceLocation; import net.minecraft.item.Item; public class ModTags { public static final class Items { public static final ITag.INamedTag<Item> INGOTS_METAL = mod("metal_ingots"); private static ITag.INamedTag<Item> mod(String path) { return ItemTags.createOptional(new ResourceLocation(Main.MOD_ID, path)); } } }
July 11, 20214 yr Author 2 hours ago, diesieben07 said: I don't think you can use tags like that in data generation. I would just check at runtime if the item is in the ingots tag but not in the brick or nether brick tags. This way it is dynamic as well, should mods add to these tags. Okay, I guess that's the only option. I'd like to have multiple recipes, so is there a good way to remove the bricks for the specific recipe instead of completely removing any support for them as an input Ingredient?
July 11, 20214 yr Author 11 hours ago, diesieben07 said: I am not sure I understand your question. Sorry. You said that 'I would just check at runtime if the item is in the ingots tag but not in the brick or nether brick tags'. How would I go about doing this?
July 11, 20214 yr Author 18 minutes ago, InspectorCaracal said: What does your custom recipe look like? It uses a custom recipe builder, but it's pretty similar to the furnace builders: PressingRecipeBuilder.builder(Ingredient.of(ModTags.Items.INGOTS_METAL), ModItems.SHEET_METAL.get()) .unlockedBy("has_item", has(ModTags.Items.INGOTS_METAL)) .save(consumer, mod("sheet_metal_pressing"))
July 12, 20214 yr Author 1 hour ago, diesieben07 said: Write a custom Ingredient. You can register a custom serializer using CraftingHelper.register in FMLCommonSetupEvent#enqueueWork. Sorry, I'm still not sure what you mean? I have a RegistryObject<IRecipeSerializer<PressingRecipe>> which is used for my custom recipe type. I have tried creating an Ingredient from a list of ItemStacks, generated using Tags.Items.INGOTS and removing the brick and nether brick tags (similar to as seen above) - but this produced the same error as in my original post.
July 12, 20214 yr Author 38 minutes ago, diesieben07 said: You need to create a class that extends Ingredient. You could call it "this but not the other" or something like this. In this Ingredient you take in two other ingredients and you test that the first one matches, but not the other one. Then you use this Ingredient in your recipe JSON with "ingot tag" as the ingredient that should match and "brick or nether brick" (you can use "forge:compound" for this) as the ingredient that should not match. Okay, I implemented this (without the forge:compound, no idea how to do this): package my.mod.crafting; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import java.util.Arrays; import java.util.stream.Stream; public class IngredientWithout extends Ingredient { protected IngredientWithout(Stream<? extends IItemList> p_i49381_1_) { super(p_i49381_1_); } public static IngredientWithout of(Ingredient of, Ingredient without) { Stream<ItemStack> withoutItems = Arrays.stream(of.getItems()).filter(without); IngredientWithout ing = new IngredientWithout(withoutItems.filter((item) -> !item.isEmpty()).map(SingleItemList::new)); return ing.getItems().length == 0 ? new IngredientWithout(Stream.empty()) : ing; } } But it produced the same issue: Spoiler Caused by: java.lang.IllegalStateException: Tag forge:ingots used before it was bound at net.minecraft.tags.TagRegistry$NamedTag.resolve(TagRegistry.java:131) at net.minecraft.tags.TagRegistry$NamedTag.getValues(TagRegistry.java:146) at net.minecraft.item.crafting.Ingredient$TagList.getItems(Ingredient.java:272) at net.minecraft.item.crafting.Ingredient.lambda$dissolve$5(Ingredient.java:59) at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:269) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:546) at java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) at java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:505) at net.minecraft.item.crafting.Ingredient.dissolve(Ingredient.java:60) at net.minecraft.item.crafting.Ingredient.getItems(Ingredient.java:52) at my.mod.crafting.IngredientWithout.of(IngredientWithout.java:15) at my.mod.data.ModRecipeProvider.buildShapelessRecipes(ModRecipeProvider.java:52) at net.minecraft.data.RecipeProvider.run(RecipeProvider.java:50) at net.minecraft.data.DataGenerator.run(DataGenerator.java:44) at net.minecraftforge.fml.event.lifecycle.GatherDataEvent$DataGeneratorConfig.lambda$runAll$0(GatherDataEvent.java:111) at cpw.mods.modlauncher.api.LamdbaExceptionUtils.lambda$rethrowConsumer$0(LamdbaExceptionUtils.java:34) at java.util.HashMap$Values.forEach(HashMap.java:981) at net.minecraftforge.fml.event.lifecycle.GatherDataEvent$DataGeneratorConfig.runAll(GatherDataEvent.java:107) at net.minecraftforge.fml.DatagenModLoader.begin(DatagenModLoader.java:61) at net.minecraft.data.Main.main(Main.java:43) ... 11 more Edited July 12, 20214 yr by DoctorC
July 12, 20214 yr Author Okay, so I've tried to implement this: IngredientWithout.java package my.mod.crafting; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import net.minecraft.tags.ITag; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.Stream; public class IngredientWithout extends Ingredient { private final Ingredient of; private final Ingredient without; protected IngredientWithout(@Nonnull ITag<Item> of, ITag<Item> without) { super(Stream.of(new Ingredient.TagList(of))); this.of = Ingredient.of(of); this.without = Ingredient.of(without); } protected IngredientWithout(@Nonnull Ingredient of, Ingredient without) { super(Arrays.stream(of.getItems()).filter((item) -> !item.isEmpty()).map(SingleItemList::new)); this.of = of; this.without = without; } public static IngredientWithout of(ITag<Item> of, ITag<Item> without) { return new IngredientWithout(of, without); } public static IngredientWithout of(Ingredient of, Ingredient without) { return new IngredientWithout(of, without); } public static IngredientWithout fromJson(JsonElement jsonElement) { return new IngredientWithout(Ingredient.fromJson(jsonElement.getAsJsonObject().get("of")), Ingredient.fromJson(jsonElement.getAsJsonObject().get("without"))); } public Ingredient of() { return this.of; } public Ingredient without() { return this.without; } @Override public ItemStack[] getItems() { return Arrays.stream(this.of.getItems()).filter( item -> Arrays.asList(this.without.getItems()).contains(item)) .toArray(ItemStack[]::new); } @Override public boolean test(@Nullable ItemStack p_test_1_) { if (super.test(p_test_1_)) { for (ItemStack itemStack : this.of.getItems()) { if (!this.without.test(itemStack)) return true; } } return false; } @Override public JsonElement toJson() { JsonObject jsonObject = new JsonObject(); jsonObject.add("of", this.of.toJson()); jsonObject.add("without", this.without.toJson()); return jsonObject; } } I already have a Serialiser (as the recipe is for a custom Tile Entity), and have successfully implemented fromJson, but I'm not sure how to do fromNetwork and toNetwork as there are two ingredients: PressingRecipe.java package my.mod.crafting.recipe; import com.google.gson.JsonObject; import my.mod.crafting.IngredientWithout; import my.mod.setup.ModRecipes; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.Ingredient; import net.minecraft.item.crafting.SingleItemRecipe; import net.minecraft.network.PacketBuffer; import net.minecraft.util.JSONUtils; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistryEntry; import javax.annotation.Nullable; public class PressingRecipe extends SingleItemRecipe { public PressingRecipe(ResourceLocation recipeId, Ingredient ingredient, ItemStack result) { super(ModRecipes.Types.PRESSING, ModRecipes.Serialisers.PRESSING.get(), recipeId, "", ingredient, result); } @Override public boolean matches(IInventory inv, World world) { return this.ingredient.test(inv.getItem(0)); } public static class Serialiser extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<PressingRecipe> { @Override public PressingRecipe fromJson(ResourceLocation recipeId, JsonObject json) { ResourceLocation itemId = new ResourceLocation(JSONUtils.getAsString(json, "result")); int count = JSONUtils.getAsInt(json, "count", 1); ItemStack result = new ItemStack(ForgeRegistries.ITEMS.getValue(itemId), count); if (json.has("ingredientWithout")) { IngredientWithout ingredient = IngredientWithout.fromJson(json.get("ingredientWithout")); return new PressingRecipe(recipeId, ingredient, result); } else { Ingredient ingredient = Ingredient.fromJson(json.get("ingredient")); return new PressingRecipe(recipeId, ingredient, result); } } @Nullable @Override public PressingRecipe fromNetwork(ResourceLocation recipeId, PacketBuffer buffer) { Ingredient of = Ingredient.fromNetwork(buffer); Ingredient without = Ingredient.fromNetwork(buffer); ItemStack result = buffer.readItem(); return new PressingRecipe(recipeId, IngredientWithout.of(of, without), result); } @Override public void toNetwork(PacketBuffer buffer, PressingRecipe recipe) { if (recipe.ingredient instanceof IngredientWithout) { ((IngredientWithout) recipe.ingredient).of().toNetwork(buffer); ((IngredientWithout) recipe.ingredient).without().toNetwork(buffer); } else { recipe.ingredient.toNetwork(buffer); } buffer.writeItem(recipe.result); } } } And this is how I implemented the recipe: PressingRecipeBuilder.builder(IngredientWithout.of(Tags.Items.INGOTS, Tags.Items.INGOTS_BRICK), ModItems.SHEET_METAL.get()) .unlockedBy("has_item", has(Tags.Items.INGOTS)) .save(consumer, mod("sheet_metal_pressing")); Unfortunately, anything from the forge:ingots tag seems to still work. This has also messed up my JEI integration (the input is blank in the JEI screen).
July 13, 20214 yr Might be wrong, but I think in here you should be comparing items instead of itemstacks. 3 hours ago, DoctorC said: @Override public ItemStack[] getItems() { return Arrays.stream(this.of.getItems()).filter( item -> Arrays.asList(this.without.getItems()).contains(item)) .toArray(ItemStack[]::new); } And in fact this returns an array that contains all of the "without" items, which I think it's the opposite of your target? Because you then @Override public boolean test(@Nullable ItemStack p_test_1_) { if (super.test(p_test_1_)) { for (ItemStack itemStack : this.of.getItems()) { if (!this.without.test(itemStack)) return true; } } return false; } check if the stack is not in "without", where does not make much sense.
July 13, 20214 yr Author 3 hours ago, poopoodice said: Might be wrong, but I think in here you should be comparing items instead of itemstacks. And in fact this returns an array that contains all of the "without" items, which I think it's the opposite of your target? Because you then @Override public boolean test(@Nullable ItemStack p_test_1_) { if (super.test(p_test_1_)) { for (ItemStack itemStack : this.of.getItems()) { if (!this.without.test(itemStack)) return true; } } return false; } check if the stack is not in "without", where does not make much sense. Yep, thanks - I've fixed this. 44 minutes ago, diesieben07 said: You probably need to teach JEI about your custom ingredient. Now that I have fixed up the getItems() method, JEI has all the ingots except brick - so that respect is working great! 46 minutes ago, diesieben07 said: Your recipe shouldn't have to care about your special ingredient at all. Just treat it as a regular ingredient... I've done this - but unfortunately the recipe still works with brick. Here are my new files: IngredientWithout.java: package my.mod.crafting; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.Ingredient; import net.minecraft.tags.ITag; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Arrays; import java.util.stream.Collectors; import java.util.stream.Stream; public class IngredientWithout extends Ingredient { private final Ingredient of; private final Ingredient without; protected IngredientWithout(@Nonnull ITag<Item> of, ITag<Item> without) { super(Stream.of(new Ingredient.TagList(of))); this.of = Ingredient.of(of); this.without = Ingredient.of(without); } protected IngredientWithout(@Nonnull Ingredient of, Ingredient without) { super(Arrays.stream(of.getItems()).filter((item) -> !item.isEmpty()).map(SingleItemList::new)); this.of = of; this.without = without; } public static IngredientWithout of(ITag<Item> of, ITag<Item> without) { return new IngredientWithout(of, without); } public static IngredientWithout fromJson(JsonElement jsonElement) { return new IngredientWithout(Ingredient.fromJson(jsonElement.getAsJsonObject().get("of")), Ingredient.fromJson(jsonElement.getAsJsonObject().get("without"))); } @Override public ItemStack[] getItems() { return Arrays.stream(this.of.getItems()).filter( ofItemStack -> !Arrays.stream(this.without.getItems()) .map(ItemStack::getItem) .collect(Collectors.toList()).contains(ofItemStack.getItem())) .toArray(ItemStack[]::new); } @Override public boolean test(@Nullable ItemStack p_test_1_) { if (super.test(p_test_1_)) { for (ItemStack itemStack : this.of.getItems()) { if (!this.without.test(itemStack)) return true; } } return false; } @Override public JsonElement toJson() { JsonObject jsonObject = new JsonObject(); jsonObject.add("of", this.of.toJson()); jsonObject.add("without", this.without.toJson()); return jsonObject; } } PressingRecipe.java: package my.mod.crafting.recipe; import com.google.gson.JsonObject; import my.mod.crafting.IngredientWithout; import my.mod.setup.ModRecipes; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; import net.minecraft.item.crafting.IRecipeSerializer; import net.minecraft.item.crafting.Ingredient; import net.minecraft.item.crafting.SingleItemRecipe; import net.minecraft.network.PacketBuffer; import net.minecraft.util.JSONUtils; import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistryEntry; import javax.annotation.Nullable; public class PressingRecipe extends SingleItemRecipe { public PressingRecipe(ResourceLocation recipeId, Ingredient ingredient, ItemStack result) { super(ModRecipes.Types.PRESSING, ModRecipes.Serialisers.PRESSING.get(), recipeId, "", ingredient, result); } @Override public boolean matches(IInventory inv, World world) { return this.ingredient.test(inv.getItem(0)); } public static class Serialiser extends ForgeRegistryEntry<IRecipeSerializer<?>> implements IRecipeSerializer<PressingRecipe> { @Override public PressingRecipe fromJson(ResourceLocation recipeId, JsonObject json) { ResourceLocation itemId = new ResourceLocation(JSONUtils.getAsString(json, "result")); int count = JSONUtils.getAsInt(json, "count", 1); ItemStack result = new ItemStack(ForgeRegistries.ITEMS.getValue(itemId), count); if (json.has("ingredientWithout")) { IngredientWithout ingredient = IngredientWithout.fromJson(json.get("ingredientWithout")); return new PressingRecipe(recipeId, ingredient, result); } else { Ingredient ingredient = Ingredient.fromJson(json.get("ingredient")); return new PressingRecipe(recipeId, ingredient, result); } } @Nullable @Override public PressingRecipe fromNetwork(ResourceLocation recipeId, PacketBuffer buffer) { Ingredient ingredient = Ingredient.fromNetwork(buffer); ItemStack result = buffer.readItem(); return new PressingRecipe(recipeId, ingredient, result); } @Override public void toNetwork(PacketBuffer buffer, PressingRecipe recipe) { recipe.ingredient.toNetwork(buffer); buffer.writeItem(recipe.result); } } }
July 13, 20214 yr Author 37 minutes ago, diesieben07 said: Your getItems implementation is wrong, because ItemStack does not implement equals, as such the contains call will never work. You need to use ItemStack.matches. Your test implementation is just... strange. What is wrong with just return of.test(stack) && !without.test(stack) ? Your recipe serializer still has special checks for your custom ingredient. You do not need to do this. Ingredient.fromJson handles custom ingredients already. It works! I'm not sure about your final point about Ingredient.fromJson working already, as I used a different JSON key ('ingredientWithout' instead of 'ingredient') - so I didn't change this. Sorry to bother you again but what did you mean by 'forge:compound' earlier as a method of removing both bricks and nether bricks. I can easily do this with a custom tag, just wondering if that would be an easier way? Thanks!
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.