--- a/net/minecraft/server/ContainerEnchantTable.java
+++ b/net/minecraft/server/ContainerEnchantTable.java
@@ -3,6 +3,21 @@
 import java.util.List;
 import java.util.Random;
 
+// CraftBukkit start
+import java.util.Collections;
+import java.util.Map;
+import org.bukkit.Location;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.inventory.CraftInventoryEnchanting;
+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+import org.bukkit.enchantments.EnchantmentOffer;
+import org.bukkit.event.enchantment.EnchantItemEvent;
+import org.bukkit.event.enchantment.PrepareItemEnchantEvent;
+import org.bukkit.entity.Player;
+// CraftBukkit end
+
 public class ContainerEnchantTable extends Container {
 
     public IInventory enchantSlots = new InventorySubcontainer(new ChatComponentText("Enchant"), 2) {
@@ -14,6 +29,13 @@
             super.update();
             ContainerEnchantTable.this.a((IInventory) this);
         }
+
+        // CraftBukkit start
+        @Override
+        public Location getLocation() {
+            return new org.bukkit.Location(world.getWorld(), position.getX(), position.getY(), position.getZ());
+        }
+        // CraftBukkit end
     };
     public World world;
     private final BlockPosition position;
@@ -22,6 +44,10 @@
     public int[] costs = new int[3];
     public int[] h = new int[] { -1, -1, -1};
     public int[] i = new int[] { -1, -1, -1};
+    // CraftBukkit start
+    private CraftInventoryView bukkitEntity = null;
+    private Player player;
+    // CraftBukkit end
 
     public ContainerEnchantTable(PlayerInventory playerinventory, World world, BlockPosition blockposition) {
         this.world = world;
@@ -54,6 +80,9 @@
             this.a(new Slot(playerinventory, i, 8 + i * 18, 142));
         }
 
+        // CraftBukkit start
+        player = (Player) playerinventory.player.getBukkitEntity();
+        // CraftBukkit end
     }
 
     protected void c(ICrafting icrafting) {
@@ -90,7 +119,7 @@
             ItemStack itemstack = iinventory.getItem(0);
             int i;
 
-            if (!itemstack.isEmpty() && itemstack.canEnchant()) {
+            if (!itemstack.isEmpty()) { // CraftBukkit - relax condition
                 if (!this.world.isClientSide) {
                     i = 0;
 
@@ -146,12 +175,47 @@
                             if (list != null && !list.isEmpty()) {
                                 WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) list.get(this.l.nextInt(list.size()));
 
-                                this.h[j] = Enchantment.enchantments.a((Object) weightedrandomenchant.enchantment);
+                                this.h[j] = Enchantment.enchantments.a(weightedrandomenchant.enchantment); // CraftBukkit - decompile error
                                 this.i[j] = weightedrandomenchant.level;
                             }
                         }
                     }
 
+                    // CraftBukkit start
+                    CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
+                    org.bukkit.enchantments.EnchantmentOffer[] offers = new EnchantmentOffer[3];
+                    for (j = 0; j < 3; ++j) {
+                        org.bukkit.enchantments.Enchantment enchantment = (this.h[j] >= 0) ? org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(Enchantment.getId(Enchantment.enchantments.getId(this.h[j])))) : null;
+                        offers[j] = (enchantment != null) ? new EnchantmentOffer(enchantment, this.i[j], this.costs[j]) : null;
+                    }
+
+                    PrepareItemEnchantEvent event = new PrepareItemEnchantEvent(player, this.getBukkitView(), this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, offers, i);
+                    event.setCancelled(!itemstack.canEnchant());
+                    this.world.getServer().getPluginManager().callEvent(event);
+
+                    if (event.isCancelled()) {
+                        for (j = 0; j < 3; ++j) {
+                            this.costs[j] = 0;
+                            this.h[j] = -1;
+                            this.i[j] = -1;
+                        }
+                        return;
+                    }
+
+                    for (j = 0; j < 3; j++) {
+                        EnchantmentOffer offer = event.getOffers()[j];
+                        if (offer != null) {
+                            this.costs[j] = offer.getCost();
+                            this.h[j] = Enchantment.enchantments.a(Enchantment.fromId(CraftNamespacedKey.toMinecraft(offer.getEnchantment().getKey())));
+                            this.i[j] = offer.getEnchantmentLevel();
+                        } else {
+                            this.costs[j] = 0;
+                            this.h[j] = -1;
+                            this.i[j] = -1;
+                        }
+                    }
+                    // CraftBukkit end
+
                     this.b();
                 }
             } else {
@@ -176,25 +240,53 @@
             if (!this.world.isClientSide) {
                 List list = this.a(itemstack, i, this.costs[i]);
 
-                if (!list.isEmpty()) {
-                    entityhuman.enchantDone(itemstack, j);
+                // CraftBukkit start
+                if (true || !list.isEmpty()) {
+                    // entityhuman.enchantDone(itemstack, j); // Moved down
                     boolean flag = itemstack.getItem() == Items.BOOK;
+                    Map<org.bukkit.enchantments.Enchantment, Integer> enchants = new java.util.HashMap<org.bukkit.enchantments.Enchantment, Integer>();
+                    for (Object obj : list) {
+                        WeightedRandomEnchant instance = (WeightedRandomEnchant) obj;
+                        enchants.put(org.bukkit.enchantments.Enchantment.getByKey(CraftNamespacedKey.fromMinecraft(Enchantment.getId(instance.enchantment))), instance.level);
+                    }
+                    CraftItemStack item = CraftItemStack.asCraftMirror(itemstack);
+
+                    EnchantItemEvent event = new EnchantItemEvent((Player) entityhuman.getBukkitEntity(), this.getBukkitView(), this.world.getWorld().getBlockAt(position.getX(), position.getY(), position.getZ()), item, this.costs[i], enchants, i);
+                    this.world.getServer().getPluginManager().callEvent(event);
+
+                    int level = event.getExpLevelCost();
+                    if (event.isCancelled() || (level > entityhuman.expLevel && !entityhuman.abilities.canInstantlyBuild) || event.getEnchantsToAdd().isEmpty()) {
+                        return false;
+                    }
 
                     if (flag) {
                         itemstack = new ItemStack(Items.ENCHANTED_BOOK);
                         this.enchantSlots.setItem(0, itemstack);
                     }
 
-                    for (int k = 0; k < list.size(); ++k) {
-                        WeightedRandomEnchant weightedrandomenchant = (WeightedRandomEnchant) list.get(k);
+                    for (Map.Entry<org.bukkit.enchantments.Enchantment, Integer> entry : event.getEnchantsToAdd().entrySet()) {
+                        try {
+                            if (flag) {
+                                NamespacedKey enchantId = entry.getKey().getKey();
+                                Enchantment nms = Enchantment.fromId(CraftNamespacedKey.toMinecraft(enchantId));
+                                if (nms == null) {
+                                    continue;
+                                }
 
-                        if (flag) {
-                            ItemEnchantedBook.a(itemstack, weightedrandomenchant);
-                        } else {
-                            itemstack.addEnchantment(weightedrandomenchant.enchantment, weightedrandomenchant.level);
+                                WeightedRandomEnchant weightedrandomenchant = new WeightedRandomEnchant(nms, entry.getValue());
+                                ItemEnchantedBook.a(itemstack, weightedrandomenchant);
+                            } else {
+                                item.addUnsafeEnchantment(entry.getKey(), entry.getValue());
+                            }
+                        } catch (IllegalArgumentException e) {
+                            /* Just swallow invalid enchantments */
                         }
                     }
 
+                    entityhuman.enchantDone(itemstack, j);
+                    // CraftBukkit end
+
+                    // CraftBukkit - TODO: let plugins change this
                     if (!entityhuman.abilities.canInstantlyBuild) {
                         itemstack1.subtract(j);
                         if (itemstack1.isEmpty()) {
@@ -233,12 +325,18 @@
 
     public void b(EntityHuman entityhuman) {
         super.b(entityhuman);
+        // CraftBukkit Start - If an enchantable was opened from a null location, set the world to the player's world, preventing a crash
+        if (this.world == null) {
+            this.world = entityhuman.getWorld();
+        }
+        // CraftBukkit end
         if (!this.world.isClientSide) {
             this.a(entityhuman, entityhuman.world, this.enchantSlots);
         }
     }
 
     public boolean canUse(EntityHuman entityhuman) {
+        if (!this.checkReachable) return true; // CraftBukkit
         return this.world.getType(this.position).getBlock() != Blocks.ENCHANTING_TABLE ? false : entityhuman.d((double) this.position.getX() + 0.5D, (double) this.position.getY() + 0.5D, (double) this.position.getZ() + 0.5D) <= 64.0D;
     }
 
@@ -291,4 +389,17 @@
 
         return itemstack;
     }
+
+    // CraftBukkit start
+    @Override
+    public CraftInventoryView getBukkitView() {
+        if (bukkitEntity != null) {
+            return bukkitEntity;
+        }
+
+        CraftInventoryEnchanting inventory = new CraftInventoryEnchanting(this.enchantSlots);
+        bukkitEntity = new CraftInventoryView(this.player, inventory, this);
+        return bukkitEntity;
+    }
+    // CraftBukkit end
 }