--- a/net/minecraft/world/inventory/CraftingMenu.java
+++ b/net/minecraft/world/inventory/CraftingMenu.java
@@ -13,10 +13,15 @@
 import net.minecraft.world.item.crafting.CraftingRecipe;
 import net.minecraft.world.item.crafting.RecipeHolder;
 import net.minecraft.world.item.crafting.RecipeType;
+import net.minecraft.world.item.crafting.RepairItemRecipe;
 import net.minecraft.world.level.Level;
 import net.minecraft.world.level.block.Blocks;
+import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
+// CraftBukkit end
 
 public class CraftingMenu extends RecipeBookMenu<CraftingInput, CraftingRecipe> {
+
     public static final int RESULT_SLOT = 0;
     private static final int CRAFT_SLOT_START = 1;
     private static final int CRAFT_SLOT_END = 10;
@@ -24,11 +29,14 @@
     private static final int INV_SLOT_END = 37;
     private static final int USE_ROW_SLOT_START = 37;
     private static final int USE_ROW_SLOT_END = 46;
-    public final CraftingContainer craftSlots = new TransientCraftingContainer(this, 3, 3);
-    public final ResultContainer resultSlots = new ResultContainer();
+    public final TransientCraftingContainer craftSlots; // CraftBukkit
+    public final ResultContainer resultSlots;
     public final ContainerLevelAccess access;
     private final Player player;
     private boolean placingRecipe;
+    // CraftBukkit start
+    private CraftInventoryView bukkitEntity = null;
+    // CraftBukkit end
 
     public CraftingMenu(int syncId, Inventory playerInventory) {
         this(syncId, playerInventory, ContainerLevelAccess.NULL);
@@ -36,64 +44,73 @@
 
     public CraftingMenu(int syncId, Inventory playerInventory, ContainerLevelAccess context) {
         super(MenuType.CRAFTING, syncId);
+        // CraftBukkit start - Switched order of IInventory construction and stored player
+        this.resultSlots = new ResultContainer();
+        this.craftSlots = new TransientCraftingContainer(this, 3, 3, playerInventory.player); // CraftBukkit - pass player
+        this.craftSlots.resultInventory = this.resultSlots;
+        // CraftBukkit end
         this.access = context;
         this.player = playerInventory.player;
         this.addSlot(new ResultSlot(playerInventory.player, this.craftSlots, this.resultSlots, 0, 124, 35));
 
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 3; j++) {
-                this.addSlot(new Slot(this.craftSlots, j + i * 3, 30 + j * 18, 17 + i * 18));
+        int j;
+        int k;
+
+        for (j = 0; j < 3; ++j) {
+            for (k = 0; k < 3; ++k) {
+                this.addSlot(new Slot(this.craftSlots, k + j * 3, 30 + k * 18, 17 + j * 18));
             }
         }
 
-        for (int k = 0; k < 3; k++) {
-            for (int l = 0; l < 9; l++) {
-                this.addSlot(new Slot(playerInventory, l + k * 9 + 9, 8 + l * 18, 84 + k * 18));
+        for (j = 0; j < 3; ++j) {
+            for (k = 0; k < 9; ++k) {
+                this.addSlot(new Slot(playerInventory, k + j * 9 + 9, 8 + k * 18, 84 + j * 18));
             }
         }
 
-        for (int m = 0; m < 9; m++) {
-            this.addSlot(new Slot(playerInventory, m, 8 + m * 18, 142));
+        for (j = 0; j < 9; ++j) {
+            this.addSlot(new Slot(playerInventory, j, 8 + j * 18, 142));
         }
+
     }
 
-    protected static void slotChangedCraftingGrid(
-        AbstractContainerMenu handler,
-        Level world,
-        Player player,
-        CraftingContainer craftingInventory,
-        ResultContainer resultInventory,
-        @Nullable RecipeHolder<CraftingRecipe> recipe
-    ) {
+    protected static void slotChangedCraftingGrid(AbstractContainerMenu handler, Level world, Player player, CraftingContainer craftingInventory, ResultContainer resultInventory, @Nullable RecipeHolder<CraftingRecipe> recipe) {
         if (!world.isClientSide) {
-            CraftingInput craftingInput = craftingInventory.asCraftInput();
-            ServerPlayer serverPlayer = (ServerPlayer)player;
-            ItemStack itemStack = ItemStack.EMPTY;
-            Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer()
-                .getRecipeManager()
-                .getRecipeFor(RecipeType.CRAFTING, craftingInput, world, recipe);
+            CraftingInput craftinginput = craftingInventory.asCraftInput();
+            ServerPlayer entityplayer = (ServerPlayer) player;
+            ItemStack itemstack = ItemStack.EMPTY;
+            if (recipe == null) recipe = craftingInventory.getCurrentRecipe(); // Paper - Perf: Improve mass crafting; check last recipe used first
+            Optional<RecipeHolder<CraftingRecipe>> optional = world.getServer().getRecipeManager().getRecipeFor(RecipeType.CRAFTING, craftinginput, world, recipe);
+            craftingInventory.setCurrentRecipe(optional.orElse(null)); // CraftBukkit
+
             if (optional.isPresent()) {
-                RecipeHolder<CraftingRecipe> recipeHolder = optional.get();
-                CraftingRecipe craftingRecipe = recipeHolder.value();
-                if (resultInventory.setRecipeUsed(world, serverPlayer, recipeHolder)) {
-                    ItemStack itemStack2 = craftingRecipe.assemble(craftingInput, world.registryAccess());
-                    if (itemStack2.isItemEnabled(world.enabledFeatures())) {
-                        itemStack = itemStack2;
+                RecipeHolder<CraftingRecipe> recipeholder1 = (RecipeHolder) optional.get();
+                CraftingRecipe recipecrafting = (CraftingRecipe) recipeholder1.value();
+
+                if (resultInventory.setRecipeUsed(world, entityplayer, recipeholder1)) {
+                    ItemStack itemstack1 = recipecrafting.assemble(craftinginput, world.registryAccess());
+
+                    if (itemstack1.isItemEnabled(world.enabledFeatures())) {
+                        itemstack = itemstack1;
                     }
                 }
             }
+            itemstack = org.bukkit.craftbukkit.event.CraftEventFactory.callPreCraftEvent(craftingInventory, resultInventory, itemstack, handler.getBukkitView(), optional.map(RecipeHolder::value).orElse(null) instanceof RepairItemRecipe); // CraftBukkit
 
-            resultInventory.setItem(0, itemStack);
-            handler.setRemoteSlot(0, itemStack);
-            serverPlayer.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), 0, itemStack));
+            resultInventory.setItem(0, itemstack);
+            handler.setRemoteSlot(0, itemstack);
+            entityplayer.connection.send(new ClientboundContainerSetSlotPacket(handler.containerId, handler.incrementStateId(), 0, itemstack));
         }
     }
 
     @Override
     public void slotsChanged(Container inventory) {
         if (!this.placingRecipe) {
-            this.access.execute((world, pos) -> slotChangedCraftingGrid(this, world, this.player, this.craftSlots, this.resultSlots, null));
+            this.access.execute((world, blockposition) -> {
+                CraftingMenu.slotChangedCraftingGrid(this, world, this.player, this.craftSlots, this.resultSlots, (RecipeHolder) null);
+            });
         }
+
     }
 
     @Override
@@ -104,7 +121,9 @@
     @Override
     public void finishPlacingRecipe(RecipeHolder<CraftingRecipe> recipe) {
         this.placingRecipe = false;
-        this.access.execute((world, pos) -> slotChangedCraftingGrid(this, world, this.player, this.craftSlots, this.resultSlots, recipe));
+        this.access.execute((world, blockposition) -> {
+            CraftingMenu.slotChangedCraftingGrid(this, world, this.player, this.craftSlots, this.resultSlots, recipe);
+        });
     }
 
     @Override
@@ -120,65 +139,72 @@
 
     @Override
     public boolean recipeMatches(RecipeHolder<CraftingRecipe> recipe) {
-        return recipe.value().matches(this.craftSlots.asCraftInput(), this.player.level());
+        return ((CraftingRecipe) recipe.value()).matches(this.craftSlots.asCraftInput(), this.player.level());
     }
 
     @Override
     public void removed(Player player) {
         super.removed(player);
-        this.access.execute((world, pos) -> this.clearContainer(player, this.craftSlots));
+        this.access.execute((world, blockposition) -> {
+            this.clearContainer(player, this.craftSlots);
+        });
     }
 
     @Override
     public boolean stillValid(Player player) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return stillValid(this.access, player, Blocks.CRAFTING_TABLE);
     }
 
     @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 itemstack = ItemStack.EMPTY;
+        Slot slot1 = (Slot) this.slots.get(slot);
+
+        if (slot1 != null && slot1.hasItem()) {
+            ItemStack itemstack1 = slot1.getItem();
+
+            itemstack = itemstack1.copy();
             if (slot == 0) {
-                this.access.execute((world, pos) -> itemStack2.getItem().onCraftedBy(itemStack2, world, player));
-                if (!this.moveItemStackTo(itemStack2, 10, 46, true)) {
+                this.access.execute((world, blockposition) -> {
+                    itemstack1.getItem().onCraftedBy(itemstack1, world, player);
+                });
+                if (!this.moveItemStackTo(itemstack1, 10, 46, true)) {
                     return ItemStack.EMPTY;
                 }
 
-                slot2.onQuickCraft(itemStack2, itemStack);
+                slot1.onQuickCraft(itemstack1, itemstack);
             } else if (slot >= 10 && slot < 46) {
-                if (!this.moveItemStackTo(itemStack2, 1, 10, false)) {
+                if (!this.moveItemStackTo(itemstack1, 1, 10, false)) {
                     if (slot < 37) {
-                        if (!this.moveItemStackTo(itemStack2, 37, 46, false)) {
+                        if (!this.moveItemStackTo(itemstack1, 37, 46, false)) {
                             return ItemStack.EMPTY;
                         }
-                    } else if (!this.moveItemStackTo(itemStack2, 10, 37, false)) {
+                    } else if (!this.moveItemStackTo(itemstack1, 10, 37, false)) {
                         return ItemStack.EMPTY;
                     }
                 }
-            } else if (!this.moveItemStackTo(itemStack2, 10, 46, false)) {
+            } else if (!this.moveItemStackTo(itemstack1, 10, 46, false)) {
                 return ItemStack.EMPTY;
             }
 
-            if (itemStack2.isEmpty()) {
-                slot2.setByPlayer(ItemStack.EMPTY);
+            if (itemstack1.isEmpty()) {
+                slot1.setByPlayer(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);
             if (slot == 0) {
-                player.drop(itemStack2, false);
+                player.drop(itemstack1, false);
             }
         }
 
-        return itemStack;
+        return itemstack;
     }
 
     @Override
@@ -215,4 +241,17 @@
     public boolean shouldMoveToInventory(int index) {
         return index != this.getResultSlotIndex();
     }
+
+    // CraftBukkit start
+    @Override
+    public CraftInventoryView getBukkitView() {
+        if (this.bukkitEntity != null) {
+            return this.bukkitEntity;
+        }
+
+        CraftInventoryCrafting inventory = new CraftInventoryCrafting(this.craftSlots, this.resultSlots);
+        this.bukkitEntity = new CraftInventoryView(this.player.getBukkitEntity(), inventory, this);
+        return this.bukkitEntity;
+    }
+    // CraftBukkit end
 }
