--- a/net/minecraft/world/inventory/MerchantMenu.java
+++ b/net/minecraft/world/inventory/MerchantMenu.java
@@ -12,8 +12,10 @@
 import net.minecraft.world.item.trading.Merchant;
 import net.minecraft.world.item.trading.MerchantOffer;
 import net.minecraft.world.item.trading.MerchantOffers;
+import org.bukkit.craftbukkit.inventory.CraftInventoryView; // CraftBukkit
 
 public class MerchantMenu extends AbstractContainerMenu {
+
     protected static final int PAYMENT1_SLOT = 0;
     protected static final int PAYMENT2_SLOT = 1;
     protected static final int RESULT_SLOT = 2;
@@ -31,6 +33,19 @@
     private boolean showProgressBar;
     private boolean canRestock;
 
+    // CraftBukkit start
+    private CraftInventoryView bukkitEntity = null;
+    private Inventory player;
+
+    @Override
+    public CraftInventoryView getBukkitView() {
+        if (this.bukkitEntity == null) {
+            this.bukkitEntity = new CraftInventoryView(this.player.player.getBukkitEntity(), new org.bukkit.craftbukkit.inventory.CraftInventoryMerchant(this.trader, this.tradeContainer), this);
+        }
+        return this.bukkitEntity;
+    }
+    // CraftBukkit end
+
     public MerchantMenu(int syncId, Inventory playerInventory) {
         this(syncId, playerInventory, new ClientSideMerchant(playerInventory.player));
     }
@@ -42,16 +57,20 @@
         this.addSlot(new Slot(this.tradeContainer, 0, 136, 37));
         this.addSlot(new Slot(this.tradeContainer, 1, 162, 37));
         this.addSlot(new MerchantResultSlot(playerInventory.player, merchant, this.tradeContainer, 2, 220, 37));
+        this.player = playerInventory; // CraftBukkit - save player
 
-        for (int i = 0; i < 3; i++) {
-            for (int j = 0; j < 9; j++) {
-                this.addSlot(new Slot(playerInventory, j + i * 9 + 9, 108 + 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, 108 + k * 18, 84 + j * 18));
             }
         }
 
-        for (int k = 0; k < 9; k++) {
-            this.addSlot(new Slot(playerInventory, k, 108 + k * 18, 142));
+        for (j = 0; j < 9; ++j) {
+            this.addSlot(new Slot(playerInventory, j, 108 + j * 18, 142));
         }
+
     }
 
     public void setShowProgressBar(boolean leveled) {
@@ -108,121 +127,154 @@
 
     @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 == 2) {
-                if (!this.moveItemStackTo(itemStack2, 3, 39, true)) {
+                if (!this.moveItemStackTo(itemstack1, 3, 39, true, true)) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent
                     return ItemStack.EMPTY;
                 }
 
-                slot2.onQuickCraft(itemStack2, itemStack);
-                this.playTradeSound();
+                //  slot1.onQuickCraft(itemstack1, itemstack); // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved to after the non-check moveItemStackTo call
+                // this.playTradeSound();
             } else if (slot != 0 && slot != 1) {
                 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, 3, 39, false)) {
+            } else if (!this.moveItemStackTo(itemstack1, 3, 39, false)) {
                 return ItemStack.EMPTY;
             }
 
-            if (itemStack2.isEmpty()) {
-                slot2.setByPlayer(ItemStack.EMPTY);
+            if (slot != 2) { // Paper - Add PlayerTradeEvent and PlayerPurchaseEvent; moved down for slot 2
+            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);
+            } // Paper start - Add PlayerTradeEvent and PlayerPurchaseEvent; handle slot 2
+            if (slot == 2) { // is merchant result slot
+                slot1.onTake(player, itemstack1);
+                if (itemstack1.isEmpty()) {
+                    slot1.set(ItemStack.EMPTY);
+                    return ItemStack.EMPTY;
+                }
+
+                this.moveItemStackTo(itemstack1, 3, 39, true, false); // This should always succeed because it's checked above
+
+                slot1.onQuickCraft(itemstack1, itemstack);
+                this.playTradeSound();
+                slot1.set(ItemStack.EMPTY); // itemstack1 should ALWAYS be empty
+            }
+            // Paper end - Add PlayerTradeEvent and PlayerPurchaseEvent
         }
 
-        return itemStack;
+        return itemstack;
     }
 
     private void playTradeSound() {
-        if (!this.trader.isClientSide()) {
-            Entity entity = (Entity)this.trader;
-            entity.level()
-                .playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
+        if (!this.trader.isClientSide() && this.trader instanceof Entity) { // CraftBukkit - SPIGOT-5035
+            Entity entity = (Entity) this.trader;
+
+            entity.level().playLocalSound(entity.getX(), entity.getY(), entity.getZ(), this.trader.getNotifyTradeSound(), SoundSource.NEUTRAL, 1.0F, 1.0F, false);
         }
+
     }
 
     @Override
     public void removed(Player player) {
         super.removed(player);
-        this.trader.setTradingPlayer(null);
+        this.trader.setTradingPlayer((Player) null);
         if (!this.trader.isClientSide()) {
-            if (!player.isAlive() || player instanceof ServerPlayer && ((ServerPlayer)player).hasDisconnected()) {
-                ItemStack itemStack = this.tradeContainer.removeItemNoUpdate(0);
-                if (!itemStack.isEmpty()) {
-                    player.drop(itemStack, false);
+            if (player.isAlive() && (!(player instanceof ServerPlayer) || !((ServerPlayer) player).hasDisconnected())) {
+                if (player instanceof ServerPlayer) {
+                    player.getInventory().placeItemBackInInventory(this.tradeContainer.removeItemNoUpdate(0));
+                    player.getInventory().placeItemBackInInventory(this.tradeContainer.removeItemNoUpdate(1));
                 }
+            } else {
+                ItemStack itemstack = this.tradeContainer.removeItemNoUpdate(0);
 
-                itemStack = this.tradeContainer.removeItemNoUpdate(1);
-                if (!itemStack.isEmpty()) {
-                    player.drop(itemStack, false);
+                if (!itemstack.isEmpty()) {
+                    player.drop(itemstack, false);
+                }
+
+                itemstack = this.tradeContainer.removeItemNoUpdate(1);
+                if (!itemstack.isEmpty()) {
+                    player.drop(itemstack, false);
                 }
-            } else if (player instanceof ServerPlayer) {
-                player.getInventory().placeItemBackInInventory(this.tradeContainer.removeItemNoUpdate(0));
-                player.getInventory().placeItemBackInInventory(this.tradeContainer.removeItemNoUpdate(1));
             }
+
         }
     }
 
     public void tryMoveItems(int recipeIndex) {
         if (recipeIndex >= 0 && this.getOffers().size() > recipeIndex) {
-            ItemStack itemStack = this.tradeContainer.getItem(0);
-            if (!itemStack.isEmpty()) {
-                if (!this.moveItemStackTo(itemStack, 3, 39, true)) {
+            ItemStack itemstack = this.tradeContainer.getItem(0);
+
+            if (!itemstack.isEmpty()) {
+                if (!this.moveItemStackTo(itemstack, 3, 39, true)) {
                     return;
                 }
 
-                this.tradeContainer.setItem(0, itemStack);
+                this.tradeContainer.setItem(0, itemstack);
             }
 
-            ItemStack itemStack2 = this.tradeContainer.getItem(1);
-            if (!itemStack2.isEmpty()) {
-                if (!this.moveItemStackTo(itemStack2, 3, 39, true)) {
+            ItemStack itemstack1 = this.tradeContainer.getItem(1);
+
+            if (!itemstack1.isEmpty()) {
+                if (!this.moveItemStackTo(itemstack1, 3, 39, true)) {
                     return;
                 }
 
-                this.tradeContainer.setItem(1, itemStack2);
+                this.tradeContainer.setItem(1, itemstack1);
             }
 
             if (this.tradeContainer.getItem(0).isEmpty() && this.tradeContainer.getItem(1).isEmpty()) {
-                MerchantOffer merchantOffer = this.getOffers().get(recipeIndex);
-                this.moveFromInventoryToPaymentSlot(0, merchantOffer.getItemCostA());
-                merchantOffer.getItemCostB().ifPresent(item -> this.moveFromInventoryToPaymentSlot(1, item));
+                MerchantOffer merchantrecipe = (MerchantOffer) this.getOffers().get(recipeIndex);
+
+                this.moveFromInventoryToPaymentSlot(0, merchantrecipe.getItemCostA());
+                merchantrecipe.getItemCostB().ifPresent((itemcost) -> {
+                    this.moveFromInventoryToPaymentSlot(1, itemcost);
+                });
             }
+
         }
     }
 
     private void moveFromInventoryToPaymentSlot(int slot, ItemCost stack) {
-        for (int i = 3; i < 39; i++) {
-            ItemStack itemStack = this.slots.get(i).getItem();
-            if (!itemStack.isEmpty() && stack.test(itemStack)) {
-                ItemStack itemStack2 = this.tradeContainer.getItem(slot);
-                if (itemStack2.isEmpty() || ItemStack.isSameItemSameComponents(itemStack, itemStack2)) {
-                    int j = itemStack.getMaxStackSize();
-                    int k = Math.min(j - itemStack2.getCount(), itemStack.getCount());
-                    ItemStack itemStack3 = itemStack.copyWithCount(itemStack2.getCount() + k);
-                    itemStack.shrink(k);
-                    this.tradeContainer.setItem(slot, itemStack3);
-                    if (itemStack3.getCount() >= j) {
+        for (int j = 3; j < 39; ++j) {
+            ItemStack itemstack = ((Slot) this.slots.get(j)).getItem();
+
+            if (!itemstack.isEmpty() && stack.test(itemstack)) {
+                ItemStack itemstack1 = this.tradeContainer.getItem(slot);
+
+                if (itemstack1.isEmpty() || ItemStack.isSameItemSameComponents(itemstack, itemstack1)) {
+                    int k = itemstack.getMaxStackSize();
+                    int l = Math.min(k - itemstack1.getCount(), itemstack.getCount());
+                    ItemStack itemstack2 = itemstack.copyWithCount(itemstack1.getCount() + l);
+
+                    itemstack.shrink(l);
+                    this.tradeContainer.setItem(slot, itemstack2);
+                    if (itemstack2.getCount() >= k) {
                         break;
                     }
                 }
             }
         }
+
     }
 
     public void setOffers(MerchantOffers offers) {
