--- a/net/minecraft/world/item/crafting/Ingredient.java
+++ b/net/minecraft/world/item/crafting/Ingredient.java
@@ -11,6 +11,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;
@@ -27,19 +28,26 @@
 import net.minecraft.world.level.ItemLike;
 
 public final class Ingredient implements Predicate<ItemStack> {
+
     public static final Ingredient EMPTY = new Ingredient(Stream.empty());
-    public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ItemStack.LIST_STREAM_CODEC
-        .map(list -> fromValues(list.stream().map(Ingredient.ItemValue::new)), ingredient -> Arrays.asList(ingredient.getItems()));
+    public static final StreamCodec<RegistryFriendlyByteBuf, Ingredient> CONTENTS_STREAM_CODEC = ItemStack.LIST_STREAM_CODEC.map((list) -> {
+        return Ingredient.fromValues(list.stream().map(Ingredient.ItemValue::new));
+    }, (recipeitemstack) -> {
+        return Arrays.asList(recipeitemstack.getItems());
+    });
     private final Ingredient.Value[] values;
     @Nullable
     public ItemStack[] itemStacks;
     @Nullable
     private IntList stackingIds;
-    public static final Codec<Ingredient> CODEC = codec(true);
-    public static final Codec<Ingredient> CODEC_NONEMPTY = codec(false);
+    public boolean exact; // CraftBukkit
+    public static final Codec<Ingredient> CODEC = Ingredient.codec(true);
+    public static final Codec<Ingredient> CODEC_NONEMPTY = Ingredient.codec(false);
 
     public Ingredient(Stream<? extends Ingredient.Value> entries) {
-        this.values = entries.toArray(Ingredient.Value[]::new);
+        this.values = (Ingredient.Value[]) entries.toArray((i) -> {
+            return new Ingredient.Value[i];
+        });
     }
 
     private Ingredient(Ingredient.Value[] entries) {
@@ -48,21 +56,38 @@
 
     public ItemStack[] getItems() {
         if (this.itemStacks == null) {
-            this.itemStacks = Arrays.stream(this.values).flatMap(entry -> entry.getItems().stream()).distinct().toArray(ItemStack[]::new);
+            this.itemStacks = (ItemStack[]) Arrays.stream(this.values).flatMap((recipeitemstack_provider) -> {
+                return recipeitemstack_provider.getItems().stream();
+            }).distinct().toArray((i) -> {
+                return new ItemStack[i];
+            });
         }
 
         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.isSameItemSameComponents(itemstack, itemstack1)) {
+                        return true;
+                    }
+
+                    continue;
+                }
+                // CraftBukkit end
+                if (itemstack1.is(itemstack.getItem())) {
                     return true;
                 }
             }
@@ -73,11 +98,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);
@@ -90,92 +120,105 @@
         return this.values.length == 0;
     }
 
-    @Override
     public boolean equals(Object object) {
-        return object instanceof Ingredient ingredient && Arrays.equals((Object[])this.values, (Object[])ingredient.values);
+        if (object instanceof Ingredient recipeitemstack) {
+            return Arrays.equals(this.values, recipeitemstack.values);
+        } else {
+            return false;
+        }
     }
 
     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 -> !stack.isEmpty()).map(Ingredient.ItemValue::new));
+        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)));
     }
 
     private static Codec<Ingredient> codec(boolean allowEmpty) {
-        Codec<Ingredient.Value[]> codec = Codec.list(Ingredient.Value.CODEC)
-            .comapFlatMap(
-                entries -> !allowEmpty && entries.size() < 1
-                        ? DataResult.error(() -> "Item array cannot be empty, at least one item must be defined")
-                        : DataResult.success(entries.toArray(new Ingredient.Value[0])),
-                List::of
-            );
-        return Codec.either(codec, Ingredient.Value.CODEC)
-            .flatComapMap(
-                either -> either.map(Ingredient::new, entry -> new Ingredient(new Ingredient.Value[]{entry})),
-                ingredient -> {
-                    if (ingredient.values.length == 1) {
-                        return DataResult.success(Either.right(ingredient.values[0]));
-                    } else {
-                        return ingredient.values.length == 0 && !allowEmpty
-                            ? DataResult.error(() -> "Item array cannot be empty, at least one item must be defined")
-                            : DataResult.success(Either.left(ingredient.values));
-                    }
-                }
-            );
+        Codec<Ingredient.Value[]> codec = Codec.list(Ingredient.Value.CODEC).comapFlatMap((list) -> {
+            return !allowEmpty && list.size() < 1 ? DataResult.error(() -> {
+                return "Item array cannot be empty, at least one item must be defined";
+            }) : DataResult.success((Ingredient.Value[]) list.toArray(new Ingredient.Value[0]));
+        }, List::of);
+
+        return Codec.either(codec, Ingredient.Value.CODEC).flatComapMap((either) -> {
+            return (Ingredient) either.map(Ingredient::new, (recipeitemstack_provider) -> {
+                return new Ingredient(new Ingredient.Value[]{recipeitemstack_provider});
+            });
+        }, (recipeitemstack) -> {
+            return recipeitemstack.values.length == 1 ? DataResult.success(Either.right(recipeitemstack.values[0])) : (recipeitemstack.values.length == 0 && !allowEmpty ? DataResult.error(() -> {
+                return "Item array cannot be empty, at least one item must be defined";
+            }) : DataResult.success(Either.left(recipeitemstack.values)));
+        });
     }
 
-    public static record ItemValue(ItemStack item) implements Ingredient.Value {
-        static final Codec<Ingredient.ItemValue> CODEC = RecordCodecBuilder.create(
-            instance -> instance.group(ItemStack.SIMPLE_ITEM_CODEC.fieldOf("item").forGetter(entry -> entry.item)).apply(instance, Ingredient.ItemValue::new)
-        );
+    public interface Value {
 
-        @Override
-        public boolean equals(Object object) {
-            return object instanceof Ingredient.ItemValue itemValue
-                && itemValue.item.getItem().equals(this.item.getItem())
-                && itemValue.item.getCount() == this.item.getCount();
-        }
+        Codec<Ingredient.Value> CODEC = Codec.xor(Ingredient.ItemValue.CODEC, Ingredient.TagValue.CODEC).xmap((either) -> {
+            return (Ingredient.Value) either.map((recipeitemstack_stackprovider) -> {
+                return recipeitemstack_stackprovider;
+            }, (recipeitemstack_b) -> {
+                return recipeitemstack_b;
+            });
+        }, (recipeitemstack_provider) -> {
+            if (recipeitemstack_provider instanceof Ingredient.TagValue recipeitemstack_b) {
+                return Either.right(recipeitemstack_b);
+            } else if (recipeitemstack_provider instanceof Ingredient.ItemValue recipeitemstack_stackprovider) {
+                return Either.left(recipeitemstack_stackprovider);
+            } else {
+                throw new UnsupportedOperationException("This is neither an item value nor a tag value.");
+            }
+        });
 
-        @Override
-        public Collection<ItemStack> getItems() {
-            return Collections.singleton(this.item);
-        }
+        Collection<ItemStack> getItems();
     }
 
-    static record TagValue(TagKey<Item> tag) implements Ingredient.Value {
-        static final Codec<Ingredient.TagValue> CODEC = RecordCodecBuilder.create(
-            instance -> instance.group(TagKey.codec(Registries.ITEM).fieldOf("tag").forGetter(entry -> entry.tag)).apply(instance, Ingredient.TagValue::new)
-        );
+    private static record TagValue(TagKey<Item> tag) implements Ingredient.Value {
+
+        static final Codec<Ingredient.TagValue> CODEC = RecordCodecBuilder.create((instance) -> {
+            return instance.group(TagKey.codec(Registries.ITEM).fieldOf("tag").forGetter((recipeitemstack_b) -> {
+                return recipeitemstack_b.tag;
+            })).apply(instance, Ingredient.TagValue::new);
+        });
 
-        @Override
         public boolean equals(Object object) {
-            return object instanceof Ingredient.TagValue tagValue && tagValue.tag.location().equals(this.tag.location());
+            if (object instanceof Ingredient.TagValue recipeitemstack_b) {
+                return recipeitemstack_b.tag.location().equals(this.tag.location());
+            } else {
+                return false;
+            }
         }
 
         @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));
             }
 
@@ -183,18 +226,25 @@
         }
     }
 
-    public interface Value {
-        Codec<Ingredient.Value> CODEC = Codec.xor(Ingredient.ItemValue.CODEC, Ingredient.TagValue.CODEC)
-            .xmap(either -> either.map(stackEntry -> stackEntry, tagEntry -> tagEntry), entry -> {
-                if (entry instanceof Ingredient.TagValue tagValue) {
-                    return Either.right(tagValue);
-                } else if (entry instanceof Ingredient.ItemValue itemValue) {
-                    return Either.left(itemValue);
-                } else {
-                    throw new UnsupportedOperationException("This is neither an item value nor a tag value.");
-                }
-            });
+    public static record ItemValue(ItemStack item) implements Ingredient.Value {
 
-        Collection<ItemStack> getItems();
+        static final Codec<Ingredient.ItemValue> CODEC = RecordCodecBuilder.create((instance) -> {
+            return instance.group(ItemStack.SIMPLE_ITEM_CODEC.fieldOf("item").forGetter((recipeitemstack_stackprovider) -> {
+                return recipeitemstack_stackprovider.item;
+            })).apply(instance, Ingredient.ItemValue::new);
+        });
+
+        public boolean equals(Object object) {
+            if (!(object instanceof Ingredient.ItemValue recipeitemstack_stackprovider)) {
+                return false;
+            } else {
+                return recipeitemstack_stackprovider.item.getItem().equals(this.item.getItem()) && recipeitemstack_stackprovider.item.getCount() == this.item.getCount();
+            }
+        }
+
+        @Override
+        public Collection<ItemStack> getItems() {
+            return Collections.singleton(this.item);
+        }
     }
 }
