--- a/net/minecraft/item/crafting/Ingredient.java
+++ b/net/minecraft/item/crafting/Ingredient.java
@@ -28,15 +28,24 @@
 import net.minecraft.util.registry.Registry;
 
 public class Ingredient implements Predicate<ItemStack> {
+   //Because Mojang caches things... we need to invalidate them.. so... here we go..
+   private static final java.util.Set<Ingredient> INSTANCES = java.util.Collections.newSetFromMap(new java.util.WeakHashMap<Ingredient, Boolean>());
+   public static void invalidateAll() {
+      INSTANCES.stream().filter(e -> e != null).forEach(i -> i.invalidate());
+   }
+
    public static final Ingredient field_193370_a = new Ingredient(Stream.empty());
    private final Ingredient.IItemList[] field_199807_b;
    private ItemStack[] field_193371_b;
    private IntList field_194140_c;
+   private final boolean isSimple;
 
    protected Ingredient(Stream<? extends Ingredient.IItemList> p_i49381_1_) {
       this.field_199807_b = p_i49381_1_.toArray((p_209360_0_) -> {
          return new Ingredient.IItemList[p_209360_0_];
       });
+      this.isSimple = !net.minecraftforge.fml.ModLoader.isDataGenRunning() && !Arrays.stream(field_199807_b).anyMatch(list -> list.func_199799_a().stream().anyMatch(stack -> stack.func_77973_b().func_77645_m()));
+      Ingredient.INSTANCES.add(this);
    }
 
    public ItemStack[] func_193365_a() {
@@ -91,6 +100,10 @@
 
    public final void func_199564_a(PacketBuffer p_199564_1_) {
       this.func_199806_d();
+      if (!this.isVanilla()) {
+         net.minecraftforge.common.crafting.CraftingHelper.write(p_199564_1_, this);
+         return;
+      }
       p_199564_1_.func_150787_b(this.field_193371_b.length);
 
       for(int i = 0; i < this.field_193371_b.length; ++i) {
@@ -117,6 +130,25 @@
       return this.field_199807_b.length == 0 && (this.field_193371_b == null || this.field_193371_b.length == 0) && (this.field_194140_c == null || this.field_194140_c.isEmpty());
    }
 
+   protected void invalidate() {
+      this.field_193371_b = null;
+      this.field_194140_c = null;
+   }
+
+   public boolean isSimple() {
+      return isSimple || this == field_193370_a;
+   }
+
+   private final boolean isVanilla = this.getClass() == Ingredient.class;
+   public final boolean isVanilla() {
+       return isVanilla;
+   }
+
+   public net.minecraftforge.common.crafting.IIngredientSerializer<? extends Ingredient> getSerializer() {
+      if (!isVanilla()) throw new IllegalStateException("Modders must implement Ingredient.getSerializer in their custom Ingredients: " + this);
+      return net.minecraftforge.common.crafting.VanillaIngredientSerializer.INSTANCE;
+   }
+
    public static Ingredient func_209357_a(Stream<? extends Ingredient.IItemList> p_209357_0_) {
       Ingredient ingredient = new Ingredient(p_209357_0_);
       return ingredient.field_199807_b.length == 0 ? field_193370_a : ingredient;
@@ -144,6 +176,9 @@
 
    public static Ingredient func_199566_b(PacketBuffer p_199566_0_) {
       int i = p_199566_0_.func_150792_a();
+      if (i == -1) {
+         return net.minecraftforge.common.crafting.CraftingHelper.getIngredient(p_199566_0_.func_192575_l(), p_199566_0_);
+      }
       return func_209357_a(Stream.generate(() -> {
          return new Ingredient.SingleItemList(p_199566_0_.func_150791_c());
       }).limit((long)i));
@@ -151,6 +186,8 @@
 
    public static Ingredient func_199802_a(@Nullable JsonElement p_199802_0_) {
       if (p_199802_0_ != null && !p_199802_0_.isJsonNull()) {
+         Ingredient ret = net.minecraftforge.common.crafting.CraftingHelper.getIngredient(p_199802_0_);
+         if (ret != null) return ret;
          if (p_199802_0_.isJsonObject()) {
             return func_209357_a(Stream.of(func_199803_a(p_199802_0_.getAsJsonObject())));
          } else if (p_199802_0_.isJsonArray()) {
@@ -192,6 +229,12 @@
       }
    }
 
+   //Merges several vanilla Ingredients together. As a quirk of how the json is structured, we can't tell if its a single Ingredient type or multiple so we split per item and re-merge here.
+   //Only public for internal use, so we can access a private field in here.
+   public static Ingredient merge(Collection<Ingredient> parts) {
+      return func_209357_a(parts.stream().flatMap(i -> Arrays.stream(i.field_199807_b)));
+   }
+
    public interface IItemList {
       Collection<ItemStack> func_199799_a();
 
@@ -230,6 +273,9 @@
             list.add(new ItemStack(item));
          }
 
+         if (list.size() == 0 && !net.minecraftforge.common.ForgeConfig.SERVER.treatEmptyTagsAsAir.get()) {
+            list.add(new ItemStack(net.minecraft.block.Blocks.field_180401_cv).func_200302_a(new net.minecraft.util.text.StringTextComponent("Empty Tag: " + TagCollectionManager.func_232928_e_().func_232925_b_().func_232975_b_(this.field_199800_a).toString())));
+         }
          return list;
       }
 
