--- a/net/minecraft/world/item/crafting/Ingredient.java
+++ b/net/minecraft/world/item/crafting/Ingredient.java
@@ -12,6 +12,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.List;
 import java.util.function.Predicate;
 import java.util.stream.Stream;
@@ -30,23 +31,25 @@
 import net.minecraft.world.level.ItemLike;
 
 public final class Ingredient implements Predicate<ItemStack> {
+
     public static final Ingredient EMPTY = new Ingredient(Stream.empty());
     private final Ingredient.Value[] values;
     @Nullable
     public ItemStack[] itemStacks;
     @Nullable
     private IntList stackingIds;
+    public boolean exact; // CraftBukkit
 
     public Ingredient(Stream<? extends Ingredient.Value> entries) {
-        this.values = entries.toArray((i) -> {
+        this.values = (Ingredient.Value[]) entries.toArray((i) -> {
             return new Ingredient.Value[i];
         });
     }
 
     public ItemStack[] getItems() {
         if (this.itemStacks == null) {
-            this.itemStacks = Arrays.stream(this.values).flatMap((entry) -> {
-                return entry.getItems().stream();
+            this.itemStacks = (ItemStack[]) Arrays.stream(this.values).flatMap((recipeitemstack_provider) -> {
+                return recipeitemstack_provider.getItems().stream();
             }).distinct().toArray((i) -> {
                 return new ItemStack[i];
             });
@@ -55,15 +58,28 @@
         return this.itemStacks;
     }
 
-    @Override
-    public boolean test(@Nullable ItemStack itemStack) {
-        if (itemStack == null) {
+    public boolean test(@Nullable ItemStack itemstack) {
+        if (itemstack == null) {
             return false;
         } else if (this.isEmpty()) {
-            return itemStack.isEmpty();
+            return itemstack.isEmpty();
         } else {
-            for(ItemStack itemStack2 : this.getItems()) {
-                if (itemStack2.is(itemStack.getItem())) {
+            ItemStack[] aitemstack = this.getItems();
+            int i = aitemstack.length;
+
+            for (int j = 0; j < i; ++j) {
+                ItemStack itemstack1 = aitemstack[j];
+
+                // CraftBukkit start
+                if (this.exact) {
+                    if (itemstack1.getItem() == itemstack.getItem() && ItemStack.tagMatches(itemstack, itemstack1)) {
+                        return true;
+                    }
+
+                    continue;
+                }
+                // CraftBukkit end
+                if (itemstack1.is(itemstack.getItem())) {
                     return true;
                 }
             }
@@ -74,11 +90,16 @@
 
     public IntList getStackingIds() {
         if (this.stackingIds == null) {
-            ItemStack[] itemStacks = this.getItems();
-            this.stackingIds = new IntArrayList(itemStacks.length);
+            ItemStack[] aitemstack = this.getItems();
 
-            for(ItemStack itemStack : itemStacks) {
-                this.stackingIds.add(StackedContents.getStackingIndex(itemStack));
+            this.stackingIds = new IntArrayList(aitemstack.length);
+            ItemStack[] aitemstack1 = aitemstack;
+            int i = aitemstack.length;
+
+            for (int j = 0; j < i; ++j) {
+                ItemStack itemstack = aitemstack1[j];
+
+                this.stackingIds.add(StackedContents.getStackingIndex(itemstack));
             }
 
             this.stackingIds.sort(IntComparators.NATURAL_COMPARATOR);
@@ -95,13 +116,17 @@
         if (this.values.length == 1) {
             return this.values[0].serialize();
         } else {
-            JsonArray jsonArray = new JsonArray();
+            JsonArray jsonarray = new JsonArray();
+            Ingredient.Value[] arecipeitemstack_provider = this.values;
+            int i = arecipeitemstack_provider.length;
 
-            for(Ingredient.Value value : this.values) {
-                jsonArray.add(value.serialize());
+            for (int j = 0; j < i; ++j) {
+                Ingredient.Value recipeitemstack_provider = arecipeitemstack_provider[j];
+
+                jsonarray.add(recipeitemstack_provider.serialize());
             }
 
-            return jsonArray;
+            return jsonarray;
         }
     }
 
@@ -110,47 +135,49 @@
     }
 
     private static Ingredient fromValues(Stream<? extends Ingredient.Value> entries) {
-        Ingredient ingredient = new Ingredient(entries);
-        return ingredient.isEmpty() ? EMPTY : ingredient;
+        Ingredient recipeitemstack = new Ingredient(entries);
+
+        return recipeitemstack.isEmpty() ? Ingredient.EMPTY : recipeitemstack;
     }
 
     public static Ingredient of() {
-        return EMPTY;
+        return Ingredient.EMPTY;
     }
 
     public static Ingredient of(ItemLike... items) {
-        return of(Arrays.stream(items).map(ItemStack::new));
+        return Ingredient.of(Arrays.stream(items).map(ItemStack::new));
     }
 
     public static Ingredient of(ItemStack... stacks) {
-        return of(Arrays.stream(stacks));
+        return Ingredient.of(Arrays.stream(stacks));
     }
 
     public static Ingredient of(Stream<ItemStack> stacks) {
-        return fromValues(stacks.filter((stack) -> {
-            return !stack.isEmpty();
+        return Ingredient.fromValues(stacks.filter((itemstack) -> {
+            return !itemstack.isEmpty();
         }).map(Ingredient.ItemValue::new));
     }
 
     public static Ingredient of(TagKey<Item> tag) {
-        return fromValues(Stream.of(new Ingredient.TagValue(tag)));
+        return Ingredient.fromValues(Stream.of(new Ingredient.TagValue(tag)));
     }
 
     public static Ingredient fromNetwork(FriendlyByteBuf buf) {
-        return fromValues(buf.<ItemStack>readList(FriendlyByteBuf::readItem).stream().map(Ingredient.ItemValue::new));
+        return Ingredient.fromValues(buf.readList(FriendlyByteBuf::readItem).stream().map(Ingredient.ItemValue::new));
     }
 
     public static Ingredient fromJson(@Nullable JsonElement json) {
         if (json != null && !json.isJsonNull()) {
             if (json.isJsonObject()) {
-                return fromValues(Stream.of(valueFromJson(json.getAsJsonObject())));
+                return Ingredient.fromValues(Stream.of(Ingredient.valueFromJson(json.getAsJsonObject())));
             } else if (json.isJsonArray()) {
-                JsonArray jsonArray = json.getAsJsonArray();
-                if (jsonArray.size() == 0) {
+                JsonArray jsonarray = json.getAsJsonArray();
+
+                if (jsonarray.size() == 0) {
                     throw new JsonSyntaxException("Item array cannot be empty, at least one item must be defined");
                 } else {
-                    return fromValues(StreamSupport.stream(jsonArray.spliterator(), false).map((jsonElement) -> {
-                        return valueFromJson(GsonHelper.convertToJsonObject(jsonElement, "item"));
+                    return Ingredient.fromValues(StreamSupport.stream(jsonarray.spliterator(), false).map((jsonelement1) -> {
+                        return Ingredient.valueFromJson(GsonHelper.convertToJsonObject(jsonelement1, "item"));
                     }));
                 }
             } else {
@@ -166,37 +193,27 @@
             throw new JsonParseException("An ingredient entry is either a tag or an item, not both");
         } else if (json.has("item")) {
             Item item = ShapedRecipe.itemFromJson(json);
+
             return new Ingredient.ItemValue(new ItemStack(item));
         } else if (json.has("tag")) {
-            ResourceLocation resourceLocation = new ResourceLocation(GsonHelper.getAsString(json, "tag"));
-            TagKey<Item> tagKey = TagKey.create(Registries.ITEM, resourceLocation);
-            return new Ingredient.TagValue(tagKey);
+            ResourceLocation minecraftkey = new ResourceLocation(GsonHelper.getAsString(json, "tag"));
+            TagKey<Item> tagkey = TagKey.create(Registries.ITEM, minecraftkey);
+
+            return new Ingredient.TagValue(tagkey);
         } else {
             throw new JsonParseException("An ingredient entry needs either a tag or an item");
         }
     }
 
-    public static class ItemValue implements Ingredient.Value {
-        private final ItemStack item;
-
-        public ItemValue(ItemStack stack) {
-            this.item = stack;
-        }
+    public interface Value {
 
-        @Override
-        public Collection<ItemStack> getItems() {
-            return Collections.singleton(this.item);
-        }
+        Collection<ItemStack> getItems();
 
-        @Override
-        public JsonObject serialize() {
-            JsonObject jsonObject = new JsonObject();
-            jsonObject.addProperty("item", BuiltInRegistries.ITEM.getKey(this.item.getItem()).toString());
-            return jsonObject;
-        }
+        JsonObject serialize();
     }
 
-    static class TagValue implements Ingredient.Value {
+    private static class TagValue implements Ingredient.Value {
+
         private final TagKey<Item> tag;
 
         TagValue(TagKey<Item> tag) {
@@ -206,8 +223,11 @@
         @Override
         public Collection<ItemStack> getItems() {
             List<ItemStack> list = Lists.newArrayList();
+            Iterator iterator = BuiltInRegistries.ITEM.getTagOrEmpty(this.tag).iterator();
+
+            while (iterator.hasNext()) {
+                Holder<Item> holder = (Holder) iterator.next();
 
-            for(Holder<Item> holder : BuiltInRegistries.ITEM.getTagOrEmpty(this.tag)) {
                 list.add(new ItemStack(holder));
             }
 
@@ -216,15 +236,32 @@
 
         @Override
         public JsonObject serialize() {
-            JsonObject jsonObject = new JsonObject();
-            jsonObject.addProperty("tag", this.tag.location().toString());
-            return jsonObject;
+            JsonObject jsonobject = new JsonObject();
+
+            jsonobject.addProperty("tag", this.tag.location().toString());
+            return jsonobject;
         }
     }
 
-    public interface Value {
-        Collection<ItemStack> getItems();
+    public static class ItemValue implements Ingredient.Value {
 
-        JsonObject serialize();
+        private final ItemStack item;
+
+        public ItemValue(ItemStack stack) {
+            this.item = stack;
+        }
+
+        @Override
+        public Collection<ItemStack> getItems() {
+            return Collections.singleton(this.item);
+        }
+
+        @Override
+        public JsonObject serialize() {
+            JsonObject jsonobject = new JsonObject();
+
+            jsonobject.addProperty("item", BuiltInRegistries.ITEM.getKey(this.item.getItem()).toString());
+            return jsonobject;
+        }
     }
 }
