--- a/net/minecraft/world/inventory/GrindstoneMenu.java
+++ b/net/minecraft/world/inventory/GrindstoneMenu.java
@@ -1,13 +1,14 @@
 package net.minecraft.world.inventory;
 
+import java.util.Iterator;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.stream.Collectors;
 import net.minecraft.server.level.ServerLevel;
 import net.minecraft.world.Container;
 import net.minecraft.world.SimpleContainer;
 import net.minecraft.world.entity.ExperienceOrb;
 import net.minecraft.world.entity.player.Inventory;
-import net.minecraft.world.entity.player.Player;
 import net.minecraft.world.item.Item;
 import net.minecraft.world.item.ItemStack;
 import net.minecraft.world.item.Items;
@@ -16,8 +17,30 @@
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.Blocks;
 import net.minecraft.world.phys.Vec3;
+// CraftBukkit start
+import org.bukkit.Location;
+import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventoryGrindstone;
+import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftInventoryView;
+import org.bukkit.entity.Player;
+// CraftBukkit end
 
 public class GrindstoneMenu extends AbstractContainerMenu {
+
+    // CraftBukkit start
+    private CraftInventoryView bukkitEntity = null;
+    private Player player;
+
+    @Override
+    public CraftInventoryView getBukkitView() {
+        if (this.bukkitEntity != null) {
+            return this.bukkitEntity;
+        }
+
+        CraftInventoryGrindstone inventory = new CraftInventoryGrindstone(this.repairSlots, this.resultSlots);
+        this.bukkitEntity = new CraftInventoryView(this.player, inventory, this);
+        return this.bukkitEntity;
+    }
+    // CraftBukkit end
     public static final int MAX_NAME_LENGTH = 35;
     public static final int INPUT_SLOT = 0;
     public static final int ADDITIONAL_SLOT = 1;
@@ -26,14 +49,8 @@
     private static final int INV_SLOT_END = 30;
     private static final int USE_ROW_SLOT_START = 30;
     private static final int USE_ROW_SLOT_END = 39;
-    private final Container resultSlots = new ResultContainer();
-    final Container repairSlots = new SimpleContainer(2) {
-        @Override
-        public void setChanged() {
-            super.setChanged();
-            GrindstoneMenu.this.slotsChanged(this);
-        }
-    };
+    private final Container resultSlots;
+    final Container repairSlots;
     private final ContainerLevelAccess access;
 
     public GrindstoneMenu(int syncId, Inventory playerInventory) {
@@ -42,6 +59,21 @@
 
     public GrindstoneMenu(int syncId, Inventory playerInventory, final ContainerLevelAccess context) {
         super(MenuType.GRINDSTONE, syncId);
+        this.resultSlots = new ResultContainer();
+        this.repairSlots = new SimpleContainer(2) {
+            @Override
+            public void setChanged() {
+                super.setChanged();
+                GrindstoneMenu.this.slotsChanged(this);
+            }
+
+            // CraftBukkit start
+            @Override
+            public Location getLocation() {
+                return context.getLocation();
+            }
+            // CraftBukkit end
+        };
         this.access = context;
         this.addSlot(new Slot(this.repairSlots, 0, 49, 19) {
             @Override
@@ -62,56 +94,64 @@
             }
 
             @Override
-            public void onTake(Player player, ItemStack stack) {
-                context.execute((world, pos) -> {
+            public void onTake(net.minecraft.world.entity.player.Player player, ItemStack stack) {
+                context.execute((world, blockposition) -> {
                     if (world instanceof ServerLevel) {
-                        ExperienceOrb.award((ServerLevel)world, Vec3.atCenterOf(pos), this.getExperienceAmount(world));
+                        ExperienceOrb.award((ServerLevel) world, Vec3.atCenterOf(blockposition), this.getExperienceAmount(world), org.bukkit.entity.ExperienceOrb.SpawnReason.GRINDSTONE, player); // Paper
                     }
 
-                    world.levelEvent(1042, pos, 0);
+                    world.levelEvent(1042, blockposition, 0);
                 });
                 GrindstoneMenu.this.repairSlots.setItem(0, ItemStack.EMPTY);
                 GrindstoneMenu.this.repairSlots.setItem(1, ItemStack.EMPTY);
             }
 
             private int getExperienceAmount(Level world) {
-                int i = 0;
-                i += this.getExperienceFromItem(GrindstoneMenu.this.repairSlots.getItem(0));
-                i += this.getExperienceFromItem(GrindstoneMenu.this.repairSlots.getItem(1));
-                if (i > 0) {
-                    int j = (int)Math.ceil((double)i / 2.0D);
-                    return j + world.random.nextInt(j);
+                byte b0 = 0;
+                int j = b0 + this.getExperienceFromItem(GrindstoneMenu.this.repairSlots.getItem(0));
+
+                j += this.getExperienceFromItem(GrindstoneMenu.this.repairSlots.getItem(1));
+                if (j > 0) {
+                    int k = (int) Math.ceil((double) j / 2.0D);
+
+                    return k + world.random.nextInt(k);
                 } else {
                     return 0;
                 }
             }
 
             private int getExperienceFromItem(ItemStack stack) {
-                int i = 0;
+                int j = 0;
                 Map<Enchantment, Integer> map = EnchantmentHelper.getEnchantments(stack);
+                Iterator iterator = map.entrySet().iterator();
+
+                while (iterator.hasNext()) {
+                    Entry<Enchantment, Integer> entry = (Entry) iterator.next();
+                    Enchantment enchantment = (Enchantment) entry.getKey();
+                    Integer integer = (Integer) entry.getValue();
 
-                for(Map.Entry<Enchantment, Integer> entry : map.entrySet()) {
-                    Enchantment enchantment = entry.getKey();
-                    Integer integer = entry.getValue();
                     if (!enchantment.isCurse()) {
-                        i += enchantment.getMinCost(integer);
+                        j += enchantment.getMinCost(integer);
                     }
                 }
 
-                return i;
+                return j;
             }
         });
 
-        for(int i = 0; i < 3; ++i) {
-            for(int j = 0; j < 9; ++j) {
-                this.addSlot(new Slot(playerInventory, j + i * 9 + 9, 8 + j * 18, 84 + i * 18));
+        int j;
+
+        for (j = 0; j < 3; ++j) {
+            for (int k = 0; k < 9; ++k) {
+                this.addSlot(new Slot(playerInventory, k + j * 9 + 9, 8 + k * 18, 84 + j * 18));
             }
         }
 
-        for(int k = 0; k < 9; ++k) {
-            this.addSlot(new Slot(playerInventory, k, 8 + k * 18, 142));
+        for (j = 0; j < 9; ++j) {
+            this.addSlot(new Slot(playerInventory, j, 8 + j * 18, 142));
         }
 
+        this.player = (Player) playerInventory.player.getBukkitEntity(); // CraftBukkit
     }
 
     @Override
@@ -119,163 +159,178 @@
         super.slotsChanged(inventory);
         if (inventory == this.repairSlots) {
             this.createResult();
+            org.bukkit.craftbukkit.v1_19_R2.event.CraftEventFactory.callPrepareResultEvent(this, RESULT_SLOT); // Paper
         }
 
     }
 
     private void createResult() {
-        ItemStack itemStack = this.repairSlots.getItem(0);
-        ItemStack itemStack2 = this.repairSlots.getItem(1);
-        boolean bl = !itemStack.isEmpty() || !itemStack2.isEmpty();
-        boolean bl2 = !itemStack.isEmpty() && !itemStack2.isEmpty();
-        if (!bl) {
-            this.resultSlots.setItem(0, ItemStack.EMPTY);
-        } else {
-            boolean bl3 = !itemStack.isEmpty() && !itemStack.is(Items.ENCHANTED_BOOK) && !itemStack.isEnchanted() || !itemStack2.isEmpty() && !itemStack2.is(Items.ENCHANTED_BOOK) && !itemStack2.isEnchanted();
-            if (itemStack.getCount() > 1 || itemStack2.getCount() > 1 || !bl2 && bl3) {
+        ItemStack itemstack = this.repairSlots.getItem(0);
+        ItemStack itemstack1 = this.repairSlots.getItem(1);
+        boolean flag = !itemstack.isEmpty() || !itemstack1.isEmpty();
+        boolean flag1 = !itemstack.isEmpty() && !itemstack1.isEmpty();
+
+        if (flag) {
+            boolean flag2 = !itemstack.isEmpty() && !itemstack.is(Items.ENCHANTED_BOOK) && !itemstack.isEnchanted() || !itemstack1.isEmpty() && !itemstack1.is(Items.ENCHANTED_BOOK) && !itemstack1.isEnchanted();
+
+            if (itemstack.getCount() > 1 || itemstack1.getCount() > 1 || !flag1 && flag2) {
                 this.resultSlots.setItem(0, ItemStack.EMPTY);
                 this.broadcastChanges();
                 return;
             }
 
-            int i = 1;
-            int m;
-            ItemStack itemStack3;
-            if (bl2) {
-                if (!itemStack.is(itemStack2.getItem())) {
+            byte b0 = 1;
+            int i;
+            ItemStack itemstack2;
+
+            if (flag1) {
+                if (!itemstack.is(itemstack1.getItem())) {
                     this.resultSlots.setItem(0, ItemStack.EMPTY);
                     this.broadcastChanges();
                     return;
                 }
 
-                Item item = itemStack.getItem();
-                int j = item.getMaxDamage() - itemStack.getDamageValue();
-                int k = item.getMaxDamage() - itemStack2.getDamageValue();
+                Item item = itemstack.getItem();
+                int j = item.getMaxDamage() - itemstack.getDamageValue();
+                int k = item.getMaxDamage() - itemstack1.getDamageValue();
                 int l = j + k + item.getMaxDamage() * 5 / 100;
-                m = Math.max(item.getMaxDamage() - l, 0);
-                itemStack3 = this.mergeEnchants(itemStack, itemStack2);
-                if (!itemStack3.isDamageableItem()) {
-                    if (!ItemStack.matches(itemStack, itemStack2)) {
+
+                i = Math.max(item.getMaxDamage() - l, 0);
+                itemstack2 = this.mergeEnchants(itemstack, itemstack1);
+                if (!itemstack2.isDamageableItem()) {
+                    if (!ItemStack.matches(itemstack, itemstack1) || (itemstack2.getMaxStackSize() == 1 && !io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowGrindstoneOverstacking)) { // Paper - add max stack size check & config value
                         this.resultSlots.setItem(0, ItemStack.EMPTY);
                         this.broadcastChanges();
                         return;
                     }
 
-                    i = 2;
+                    b0 = 2; // Paper - the problem line for above change, causing over-stacking
                 }
             } else {
-                boolean bl4 = !itemStack.isEmpty();
-                m = bl4 ? itemStack.getDamageValue() : itemStack2.getDamageValue();
-                itemStack3 = bl4 ? itemStack : itemStack2;
+                boolean flag3 = !itemstack.isEmpty();
+
+                i = flag3 ? itemstack.getDamageValue() : itemstack1.getDamageValue();
+                itemstack2 = flag3 ? itemstack : itemstack1;
             }
 
-            this.resultSlots.setItem(0, this.removeNonCurses(itemStack3, m, i));
+            this.resultSlots.setItem(0, this.removeNonCurses(itemstack2, i, b0));
+        } else {
+            this.resultSlots.setItem(0, ItemStack.EMPTY);
         }
 
         this.broadcastChanges();
     }
 
     private ItemStack mergeEnchants(ItemStack target, ItemStack source) {
-        ItemStack itemStack = target.copy();
+        ItemStack itemstack2 = target.copy();
         Map<Enchantment, Integer> map = EnchantmentHelper.getEnchantments(source);
+        Iterator iterator = map.entrySet().iterator();
 
-        for(Map.Entry<Enchantment, Integer> entry : map.entrySet()) {
-            Enchantment enchantment = entry.getKey();
-            if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemStack) == 0) {
-                itemStack.enchant(enchantment, entry.getValue());
+        while (iterator.hasNext()) {
+            Entry<Enchantment, Integer> entry = (Entry) iterator.next();
+            Enchantment enchantment = (Enchantment) entry.getKey();
+
+            if (!enchantment.isCurse() || EnchantmentHelper.getItemEnchantmentLevel(enchantment, itemstack2) == 0) {
+                itemstack2.enchant(enchantment, (Integer) entry.getValue());
             }
         }
 
-        return itemStack;
+        return itemstack2;
     }
 
     private ItemStack removeNonCurses(ItemStack item, int damage, int amount) {
-        ItemStack itemStack = item.copy();
-        itemStack.removeTagKey("Enchantments");
-        itemStack.removeTagKey("StoredEnchantments");
+        ItemStack itemstack1 = item.copy();
+
+        itemstack1.removeTagKey("Enchantments");
+        itemstack1.removeTagKey("StoredEnchantments");
         if (damage > 0) {
-            itemStack.setDamageValue(damage);
+            itemstack1.setDamageValue(damage);
         } else {
-            itemStack.removeTagKey("Damage");
+            itemstack1.removeTagKey("Damage");
         }
 
-        itemStack.setCount(amount);
-        Map<Enchantment, Integer> map = EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> {
-            return entry.getKey().isCurse();
-        }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
-        EnchantmentHelper.setEnchantments(map, itemStack);
-        itemStack.setRepairCost(0);
-        if (itemStack.is(Items.ENCHANTED_BOOK) && map.size() == 0) {
-            itemStack = new ItemStack(Items.BOOK);
+        itemstack1.setCount(amount);
+        Map<Enchantment, Integer> map = (Map) EnchantmentHelper.getEnchantments(item).entrySet().stream().filter((entry) -> {
+            return ((Enchantment) entry.getKey()).isCurse();
+        }).collect(Collectors.toMap(Entry::getKey, Entry::getValue));
+
+        EnchantmentHelper.setEnchantments(map, itemstack1);
+        itemstack1.setRepairCost(0);
+        if (itemstack1.is(Items.ENCHANTED_BOOK) && map.size() == 0) {
+            itemstack1 = new ItemStack(Items.BOOK);
             if (item.hasCustomHoverName()) {
-                itemStack.setHoverName(item.getHoverName());
+                itemstack1.setHoverName(item.getHoverName());
             }
         }
 
-        for(int i = 0; i < map.size(); ++i) {
-            itemStack.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemStack.getBaseRepairCost()));
+        for (int k = 0; k < map.size(); ++k) {
+            itemstack1.setRepairCost(AnvilMenu.calculateIncreasedRepairCost(itemstack1.getBaseRepairCost()));
         }
 
-        return itemStack;
+        return itemstack1;
     }
 
     @Override
-    public void removed(Player player) {
+    public void removed(net.minecraft.world.entity.player.Player player) {
         super.removed(player);
-        this.access.execute((world, pos) -> {
+        this.access.execute((world, blockposition) -> {
             this.clearContainer(player, this.repairSlots);
         });
     }
 
     @Override
-    public boolean stillValid(Player player) {
+    public boolean stillValid(net.minecraft.world.entity.player.Player player) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return stillValid(this.access, player, Blocks.GRINDSTONE);
     }
 
     @Override
-    public ItemStack quickMoveStack(Player player, int slot) {
-        ItemStack itemStack = ItemStack.EMPTY;
-        Slot slot2 = this.slots.get(slot);
-        if (slot2 != null && slot2.hasItem()) {
-            ItemStack itemStack2 = slot2.getItem();
-            itemStack = itemStack2.copy();
-            ItemStack itemStack3 = this.repairSlots.getItem(0);
-            ItemStack itemStack4 = this.repairSlots.getItem(1);
+    public ItemStack quickMoveStack(net.minecraft.world.entity.player.Player player, int slot) {
+        ItemStack itemstack = ItemStack.EMPTY;
+        Slot slot1 = (Slot) this.slots.get(slot);
+
+        if (slot1 != null && slot1.hasItem()) {
+            ItemStack itemstack1 = slot1.getItem();
+
+            itemstack = itemstack1.copy();
+            ItemStack itemstack2 = this.repairSlots.getItem(0);
+            ItemStack itemstack3 = this.repairSlots.getItem(1);
+
             if (slot == 2) {
-                if (!this.moveItemStackTo(itemStack2, 3, 39, true)) {
+                if (!this.moveItemStackTo(itemstack1, 3, 39, true)) {
                     return ItemStack.EMPTY;
                 }
 
-                slot2.onQuickCraft(itemStack2, itemStack);
+                slot1.onQuickCraft(itemstack1, itemstack);
             } else if (slot != 0 && slot != 1) {
-                if (!itemStack3.isEmpty() && !itemStack4.isEmpty()) {
+                if (!itemstack2.isEmpty() && !itemstack3.isEmpty()) {
                     if (slot >= 3 && slot < 30) {
-                        if (!this.moveItemStackTo(itemStack2, 30, 39, false)) {
+                        if (!this.moveItemStackTo(itemstack1, 30, 39, false)) {
                             return ItemStack.EMPTY;
                         }
-                    } else if (slot >= 30 && slot < 39 && !this.moveItemStackTo(itemStack2, 3, 30, false)) {
+                    } else if (slot >= 30 && slot < 39 && !this.moveItemStackTo(itemstack1, 3, 30, false)) {
                         return ItemStack.EMPTY;
                     }
-                } else if (!this.moveItemStackTo(itemStack2, 0, 2, false)) {
+                } else if (!this.moveItemStackTo(itemstack1, 0, 2, false)) {
                     return ItemStack.EMPTY;
                 }
-            } else if (!this.moveItemStackTo(itemStack2, 3, 39, false)) {
+            } else if (!this.moveItemStackTo(itemstack1, 3, 39, false)) {
                 return ItemStack.EMPTY;
             }
 
-            if (itemStack2.isEmpty()) {
-                slot2.set(ItemStack.EMPTY);
+            if (itemstack1.isEmpty()) {
+                slot1.set(ItemStack.EMPTY);
             } else {
-                slot2.setChanged();
+                slot1.setChanged();
             }
 
-            if (itemStack2.getCount() == itemStack.getCount()) {
+            if (itemstack1.getCount() == itemstack.getCount()) {
                 return ItemStack.EMPTY;
             }
 
-            slot2.onTake(player, itemStack2);
+            slot1.onTake(player, itemstack1);
         }
 
-        return itemStack;
+        return itemstack;
     }
 }
