--- ../src-base/minecraft/net/minecraft/network/NetHandlerPlayServer.java
+++ ../src-work/minecraft/net/minecraft/network/NetHandlerPlayServer.java
@@ -13,6 +13,7 @@
 import java.util.Iterator;
 import java.util.Random;
 import java.util.concurrent.Callable;
+import net.minecraft.block.Block;
 import net.minecraft.block.material.Material;
 import net.minecraft.command.server.CommandBlockLogic;
 import net.minecraft.crash.CrashReport;
@@ -32,6 +33,7 @@
 import net.minecraft.inventory.ContainerMerchant;
 import net.minecraft.inventory.ContainerRepair;
 import net.minecraft.inventory.Slot;
+import net.minecraft.item.Item;
 import net.minecraft.item.ItemEditableBook;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.ItemWritableBook;
@@ -61,6 +63,7 @@
 import net.minecraft.network.play.server.S00PacketKeepAlive;
 import net.minecraft.network.play.server.S02PacketChat;
 import net.minecraft.network.play.server.S08PacketPlayerPosLook;
+import net.minecraft.network.play.server.S1CPacketEntityMetadata;
 import net.minecraft.network.play.server.S23PacketBlockChange;
 import net.minecraft.network.play.server.S2FPacketSetSlot;
 import net.minecraft.network.play.server.S32PacketConfirmTransaction;
@@ -81,19 +84,75 @@
 import net.minecraft.util.IChatComponent;
 import net.minecraft.util.IntHashMap;
 import net.minecraft.util.ReportedException;
+import net.minecraft.world.World;
 import net.minecraft.world.WorldServer;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
+import net.minecraftforge.cauldron.CauldronUtils;
 import net.minecraftforge.common.ForgeHooks;
 import net.minecraftforge.common.MinecraftForge;
-import cpw.mods.fml.common.eventhandler.Event;
 import net.minecraftforge.event.ForgeEventFactory;
 import net.minecraftforge.event.ServerChatEvent;
 import net.minecraftforge.event.entity.player.PlayerInteractEvent;
-import net.minecraftforge.event.entity.player.PlayerInteractEvent.Action;
 
+// CraftBukkit start
+import java.io.UnsupportedEncodingException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.HashSet;
+
+import net.minecraft.entity.EntityLiving;
+import net.minecraft.init.Blocks;
+import net.minecraft.network.play.server.S05PacketSpawnPosition;
+import net.minecraft.network.play.server.S09PacketHeldItemChange;
+import net.minecraft.network.play.server.S1BPacketEntityAttach;
+import net.minecraft.network.play.server.S33PacketUpdateSign;
+import net.minecraft.util.MathHelper;
+import net.minecraft.util.MovingObjectPosition;
+import net.minecraft.util.Vec3;
+
+import org.bukkit.craftbukkit.entity.CraftPlayer;
+import org.bukkit.craftbukkit.event.CraftEventFactory;
+import org.bukkit.craftbukkit.inventory.CraftInventoryView;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+import org.bukkit.craftbukkit.util.LazyPlayerSet;
+import org.bukkit.craftbukkit.util.Waitable;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.block.SignChangeEvent;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.CraftItemEvent;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCreativeEvent;
+import org.bukkit.event.inventory.InventoryType.SlotType;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+import org.bukkit.event.player.PlayerAnimationEvent;
+import org.bukkit.event.player.PlayerChatEvent;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+import org.bukkit.event.player.PlayerItemHeldEvent;
+import org.bukkit.event.player.PlayerKickEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.event.player.PlayerTeleportEvent;
+import org.bukkit.event.player.PlayerToggleFlightEvent;
+import org.bukkit.event.player.PlayerToggleSneakEvent;
+import org.bukkit.event.player.PlayerToggleSprintEvent;
+import org.bukkit.inventory.CraftingInventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.util.NumberConversions;
+// CraftBukkit end
+// Cauldron start
+import org.bukkit.craftbukkit.CraftServer;
+import org.bukkit.event.inventory.InventoryType;
+// Cauldron end
+
 public class NetHandlerPlayServer implements INetHandlerPlayServer
 {
     private static final Logger logger = LogManager.getLogger();
@@ -107,13 +166,12 @@
     private long field_147379_i;
     private static Random field_147376_j = new Random();
     private long field_147377_k;
-    private int chatSpamThresholdCount;
+    private volatile int chatSpamThresholdCount; // Cauldron - set to volatile to fix multithreaded issues
+    private static final AtomicIntegerFieldUpdater chatSpamField = AtomicIntegerFieldUpdater.newUpdater(NetHandlerPlayServer.class, CauldronUtils.deobfuscatedEnvironment() ? "chatSpamThresholdCount" : "fiel" + "d_147374_l"); // CraftBukkit - multithreaded field
     private int field_147375_m;
     private IntHashMap field_147372_n = new IntHashMap();
-    private double lastPosX;
-    private double lastPosY;
-    private double lastPosZ;
-    private boolean hasMoved = true;
+    public boolean hasMoved = true; // CraftBukkit - private -> public
+    private boolean processedDisconnect; // CraftBukkit - added
     private static final String __OBFID = "CL_00001452";
 
     public NetHandlerPlayServer(MinecraftServer p_i1530_1_, NetworkManager p_i1530_2_, EntityPlayerMP p_i1530_3_)
@@ -123,8 +181,41 @@
         p_i1530_2_.setNetHandler(this);
         this.playerEntity = p_i1530_3_;
         p_i1530_3_.playerNetServerHandler = this;
+        // CraftBukkit start
+        this.server = p_i1530_1_.server;
     }
 
+    private final org.bukkit.craftbukkit.CraftServer server;
+    private int lastTick = MinecraftServer.currentTick;
+    private int lastDropTick = MinecraftServer.currentTick;
+    private int dropCount = 0;
+    private static final int SURVIVAL_PLACE_DISTANCE_SQUARED = 6 * 6;
+    private static final int CREATIVE_PLACE_DISTANCE_SQUARED = 7 * 7;
+
+    private double lastPosX = Double.MAX_VALUE;
+    private double lastPosY = Double.MAX_VALUE;
+    private double lastPosZ = Double.MAX_VALUE;
+    private float lastPitch = Float.MAX_VALUE;
+    private float lastYaw = Float.MAX_VALUE;
+    private boolean justTeleported = false;
+
+    // For the PacketPlayOutBlockPlace hack :(
+    Long lastPacket;
+
+    // Store the last block right clicked and what type it was
+    private Item lastMaterial;
+
+    // Cauldron - rename getPlayer -> getPlayerB() to disambiguate with FML's getPlayer() method of the same name (below)
+    // Plugins calling this method will be remapped appropriately, but CraftBukkit code should be updated
+    public CraftPlayer getPlayerB()
+    {
+        return (this.playerEntity == null) ? null : (CraftPlayer) this.playerEntity.getBukkitEntity();
+    }
+
+    private final static HashSet<Integer> invalidItems = new HashSet<Integer>(java.util.Arrays.asList(8, 9, 10, 11, 26, 34, 36, 43, 51, 52, 55, 59, 60, 62, 63,
+            64, 68, 71, 74, 75, 83, 90, 92, 93, 94, 104, 105, 115, 117, 118, 119, 125, 127, 132, 140, 141, 142, 144)); // TODO: Check after every update.
+    // CraftBukkit end
+
     public void onNetworkTick()
     {
         this.field_147366_g = false;
@@ -139,10 +230,16 @@
             this.sendPacket(new S00PacketKeepAlive(this.field_147378_h));
         }
 
+        // CraftBukkit start
+        for (int spam; (spam = this.chatSpamThresholdCount) > 0 && !chatSpamField.compareAndSet(this, spam, spam - 1);) ;
+
+        /* Use thread-safe field access instead
         if (this.chatSpamThresholdCount > 0)
         {
             --this.chatSpamThresholdCount;
         }
+        */
+        // CraftBukkit end
 
         if (this.field_147375_m > 0)
         {
@@ -162,6 +259,24 @@
 
     public void kickPlayerFromServer(String p_147360_1_)
     {
+        // CraftBukkit start
+        String leaveMessage = EnumChatFormatting.YELLOW + this.playerEntity.getCommandSenderName() + " left the game.";
+        PlayerKickEvent event = new PlayerKickEvent(this.server.getPlayer(this.playerEntity), p_147360_1_, leaveMessage);
+
+        if (this.server.getServer().isServerRunning())
+        {
+            this.server.getPluginManager().callEvent(event);
+        }
+
+        if (event.isCancelled())
+        {
+            // Do not kick the player
+            return;
+        }
+
+        // Send the possibly modified leave message
+        p_147360_1_ = event.getReason();
+        // CraftBukkit end
         final ChatComponentText chatcomponenttext = new ChatComponentText(p_147360_1_);
         this.netManager.scheduleOutboundPacket(new S40PacketDisconnect(chatcomponenttext), new GenericFutureListener[] {new GenericFutureListener()
         {
@@ -170,8 +285,8 @@
             {
                 NetHandlerPlayServer.this.netManager.closeChannel(chatcomponenttext);
             }
-        }
-                                                                                                     });
+        }});
+        this.onDisconnect(chatcomponenttext); // CraftBukkit - Process quit immediately
         this.netManager.disableAutoRead();
     }
 
@@ -182,6 +297,15 @@
 
     public void processPlayer(C03PacketPlayer p_147347_1_)
     {
+        // CraftBukkit start - Check for NaN
+        if (Double.isNaN(p_147347_1_.field_149479_a) || Double.isNaN(p_147347_1_.field_149477_b) || Double.isNaN(p_147347_1_.field_149478_c)
+                || Double.isNaN(p_147347_1_.field_149475_d))
+        {
+            logger.warn(playerEntity.getCommandSenderName() + " was caught trying to crash the server with an invalid position.");
+            getPlayerB().kickPlayer("Nope!");
+            return;
+        }
+        // CraftBukkit end
         WorldServer worldserver = this.serverController.worldServerForDimension(this.playerEntity.dimension);
         this.field_147366_g = true;
 
@@ -199,8 +323,70 @@
                 }
             }
 
-            if (this.hasMoved)
+            // CraftBukkit start
+            Player player = this.getPlayerB();
+            Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location.
+            Location to = player.getLocation().clone(); // Start off the To location as the Players current location.
+
+            // If the packet contains movement information then we update the To location with the correct XYZ.
+            if (p_147347_1_.field_149480_h && !(p_147347_1_.field_149480_h && p_147347_1_.field_149477_b == -999.0D && p_147347_1_.field_149475_d == -999.0D))
             {
+                to.setX(p_147347_1_.field_149479_a);
+                to.setY(p_147347_1_.field_149477_b);
+                to.setZ(p_147347_1_.field_149478_c);
+            }
+
+            // If the packet contains look information then we update the To location with the correct Yaw & Pitch.
+            if (p_147347_1_.field_149481_i)
+            {
+                to.setYaw(p_147347_1_.field_149476_e);
+                to.setPitch(p_147347_1_.field_149473_f);
+            }
+
+            // Prevent 40 event-calls for less than a single pixel of movement >.>
+            double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2);
+            float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch());
+
+            if ((delta > 1f / 256 || deltaAngle > 10f) && (this.hasMoved && !this.playerEntity.isDead))
+            {
+                this.lastPosX = to.getX();
+                this.lastPosY = to.getY();
+                this.lastPosZ = to.getZ();
+                this.lastYaw = to.getYaw();
+                this.lastPitch = to.getPitch();
+
+                PlayerMoveEvent event = new PlayerMoveEvent(player, from, to);
+                this.server.getPluginManager().callEvent(event);
+
+                // If the event is cancelled we move the player back to their old location.
+                if (event.isCancelled())
+                {
+                    this.playerEntity.playerNetServerHandler.sendPacket(new S08PacketPlayerPosLook(from.getX(), from.getY() + 1.6200000047683716D, from
+                            .getZ(), from.getYaw(), from.getPitch(), false));
+                    return;
+                }
+
+                /* If a Plugin has changed the To destination then we teleport the Player
+                there to avoid any 'Moved wrongly' or 'Moved too quickly' errors.
+                We only do this if the Event was not cancelled. */
+                if (!to.equals(event.getTo()) && !event.isCancelled())
+                {
+                    this.playerEntity.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.UNKNOWN);
+                    return;
+                }
+
+                /* Check to see if the Players Location has some how changed during the call of the event.
+                This can happen due to a plugin teleporting the player instead of using .setTo() */
+                if (!from.equals(this.getPlayerB().getLocation()) && this.justTeleported)
+                {
+                    this.justTeleported = false;
+                    return;
+                }
+            }
+
+            if (this.hasMoved && !this.playerEntity.isDead)
+            {
+                // CraftBukkit end
                 double d1;
                 double d2;
                 double d3;
@@ -280,13 +466,6 @@
                     d3 = p_147347_1_.func_149472_e();
                     d4 = p_147347_1_.func_149471_f() - p_147347_1_.func_149467_d();
 
-                    if (!this.playerEntity.isPlayerSleeping() && (d4 > 1.65D || d4 < 0.1D))
-                    {
-                        this.kickPlayerFromServer("Illegal stance");
-                        logger.warn(this.playerEntity.getCommandSenderName() + " had an illegal stance: " + d4);
-                        return;
-                    }
-
                     if (Math.abs(p_147347_1_.func_149464_c()) > 3.2E7D || Math.abs(p_147347_1_.func_149472_e()) > 3.2E7D)
                     {
                         this.kickPlayerFromServer("Illegal position");
@@ -318,7 +497,7 @@
                 double d9 = Math.max(Math.abs(d6), Math.abs(this.playerEntity.motionZ));
                 double d10 = d7 * d7 + d8 * d8 + d9 * d9;
 
-                if (d10 > 100.0D && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(this.playerEntity.getCommandSenderName())))
+                if (d10 > 100.0D && this.hasMoved && (!this.serverController.isSinglePlayer() || !this.serverController.getServerOwner().equals(this.playerEntity.getCommandSenderName())))   // CraftBukkit - Added this.checkMovement condition to solve this check being triggered by teleports
                 {
                     logger.warn(this.playerEntity.getCommandSenderName() + " moved too quickly! " + d4 + "," + d5 + "," + d6 + " (" + d7 + ", " + d8 + ", " + d9 + ")");
                     this.setPlayerLocation(this.lastPosX, this.lastPosY, this.lastPosZ, this.playerEntity.rotationYaw, this.playerEntity.rotationPitch);
@@ -413,21 +592,85 @@
 
     public void setPlayerLocation(double p_147364_1_, double p_147364_3_, double p_147364_5_, float p_147364_7_, float p_147364_8_)
     {
+        // CraftBukkit start - Delegate to teleport(Location)
+        Player player = this.getPlayerB();
+        Location from = player.getLocation();
+        Location to = new Location(this.getPlayerB().getWorld(), p_147364_1_, p_147364_3_, p_147364_5_, p_147364_7_, p_147364_8_);
+        PlayerTeleportEvent event = new PlayerTeleportEvent(player, from, to, PlayerTeleportEvent.TeleportCause.UNKNOWN);
+        this.server.getPluginManager().callEvent(event);
+        from = event.getFrom();
+        to = event.isCancelled() ? from : event.getTo();
+        this.teleport(to);
+    }
+
+    public void teleport(Location dest)
+    {
+        double d0, d1, d2;
+        float f, f1;
+        d0 = dest.getX();
+        d1 = dest.getY();
+        d2 = dest.getZ();
+        f = dest.getYaw();
+        f1 = dest.getPitch();
+
+        // TODO: make sure this is the best way to address this.
+        if (Float.isNaN(f))
+        {
+            f = 0;
+        }
+
+        if (Float.isNaN(f1))
+        {
+            f1 = 0;
+        }
+
+        this.lastPosX = d0;
+        this.lastPosY = d1;
+        this.lastPosZ = d2;
+        this.lastYaw = f;
+        this.lastPitch = f1;
+        this.justTeleported = true;
+        // CraftBukkit end
         this.hasMoved = false;
-        this.lastPosX = p_147364_1_;
-        this.lastPosY = p_147364_3_;
-        this.lastPosZ = p_147364_5_;
-        this.playerEntity.setPositionAndRotation(p_147364_1_, p_147364_3_, p_147364_5_, p_147364_7_, p_147364_8_);
-        this.playerEntity.playerNetServerHandler.sendPacket(new S08PacketPlayerPosLook(p_147364_1_, p_147364_3_ + 1.6200000047683716D, p_147364_5_, p_147364_7_, p_147364_8_, false));
+        this.lastPosX = d0;
+        this.lastPosY = d1;
+        this.lastPosZ = d2;
+        this.playerEntity.setPositionAndRotation(d0, d1, d2, f, f1);
+        this.playerEntity.playerNetServerHandler.sendPacket(new S08PacketPlayerPosLook(d0, d1 + 1.6200000047683716D, d2, f, f1, false));
     }
 
     public void processPlayerDigging(C07PacketPlayerDigging p_147345_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         WorldServer worldserver = this.serverController.worldServerForDimension(this.playerEntity.dimension);
         this.playerEntity.func_143004_u();
 
         if (p_147345_1_.func_149506_g() == 4)
         {
+            // CraftBukkit start
+            // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick.
+            if (this.lastDropTick != MinecraftServer.currentTick)
+            {
+                this.dropCount = 0;
+                this.lastDropTick = MinecraftServer.currentTick;
+            }
+            else
+            {
+                // Else we increment the drop count and check the amount.
+                this.dropCount++;
+
+                if (this.dropCount >= 20)
+                {
+                    this.logger.warn(this.playerEntity.getCommandSenderName() + " dropped their items too quickly!");
+                    this.kickPlayerFromServer("You dropped your items too quickly (Hacking?)");
+                    return;
+                }
+            }
+            // CraftBukkit end
             this.playerEntity.dropOneItem(false);
         }
         else if (p_147345_1_.func_149506_g() == 3)
@@ -490,7 +733,17 @@
                 }
                 else
                 {
+                    // CraftBukkit start
+                    CraftEventFactory.callPlayerInteractEvent(this.playerEntity, Action.LEFT_CLICK_BLOCK, i, j, k, p_147345_1_.func_149501_f(), this.playerEntity.inventory.getCurrentItem());
                     this.playerEntity.playerNetServerHandler.sendPacket(new S23PacketBlockChange(i, j, k, worldserver));
+                    // Update any tile entity data for this block
+                    TileEntity tileentity = worldserver.getTileEntity(i, j, k);
+
+                    if (tileentity != null)
+                    {
+                        this.playerEntity.playerNetServerHandler.sendPacket(tileentity.getDescriptionPacket());
+                    }
+                    // CraftBukkit end
                 }
             }
             else if (p_147345_1_.func_149506_g() == 2)
@@ -517,6 +770,34 @@
     public void processPlayerBlockPlacement(C08PacketPlayerBlockPlacement p_147346_1_)
     {
         WorldServer worldserver = this.serverController.worldServerForDimension(this.playerEntity.dimension);
+        // CraftBukkit start
+        if (this.playerEntity.isDead)
+        {
+            return;
+        }
+
+        // This is a horrible hack needed because the client sends 2 packets on 'right mouse click'
+        // aimed at a block. We shouldn't need to get the second packet if the data is handled
+        // but we cannot know what the client will do, so we might still get it
+        //
+        // If the time between packets is small enough, and the 'signature' similar, we discard the
+        // second one. This sadly has to remain until Mojang makes their packets saner. :(
+        //  -- Grum
+        if (p_147346_1_.func_149568_f() == 255)
+        {
+            if (p_147346_1_.func_149574_g() != null && p_147346_1_.func_149574_g().getItem() == this.lastMaterial && this.lastPacket != null
+                    && p_147346_1_.timestamp - this.lastPacket < 100)
+            {
+                this.lastPacket = null;
+                return;
+            }
+        }
+        else
+        {
+            this.lastMaterial = p_147346_1_.func_149574_g() == null ? null : p_147346_1_.func_149574_g().getItem();
+            this.lastPacket = p_147346_1_.timestamp;
+        }
+
         ItemStack itemstack = this.playerEntity.inventory.getCurrentItem();
         boolean flag = false;
         boolean placeResult = true;
@@ -533,11 +814,21 @@
                 return;
             }
 
-            PlayerInteractEvent event = ForgeEventFactory.onPlayerInteract(playerEntity, PlayerInteractEvent.Action.RIGHT_CLICK_AIR, 0, 0, 0, -1, worldserver);
-            if (event.useItem != Event.Result.DENY)
+            PlayerInteractEvent forgeEvent = ForgeEventFactory.onPlayerInteract(playerEntity, PlayerInteractEvent.Action.RIGHT_CLICK_AIR, 0, 0, 0, -1,
+                    worldserver); // Cauldron - rename event
+            // CraftBukkit start
+            int itemstackAmount = itemstack.stackSize;
+            org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.playerEntity, Action.RIGHT_CLICK_AIR, itemstack);
+
+            if (forgeEvent.useItem != cpw.mods.fml.common.eventhandler.Event.Result.DENY && event.useItemInHand() != Event.Result.DENY)
             {
                 this.playerEntity.theItemInWorldManager.tryUseItem(this.playerEntity, worldserver, itemstack);
             }
+            // CraftBukkit - notch decrements the counter by 1 in the above method with food,
+            // snowballs and so forth, but he does it in a place that doesn't cause the
+            // inventory update packet to get sent
+            placeResult = itemstack.stackSize != itemstackAmount;
+            // CraftBukkit end
         }
         else if (p_147346_1_.func_149571_d() >= this.serverController.getBuildLimit() - 1 && (p_147346_1_.func_149568_f() == 1 || p_147346_1_.func_149571_d() >= this.serverController.getBuildLimit()))
         {
@@ -548,17 +839,24 @@
         }
         else
         {
-            double dist = playerEntity.theItemInWorldManager.getBlockReachDistance() + 1;
-            dist *= dist;
-            if (this.hasMoved && this.playerEntity.getDistanceSq((double)i + 0.5D, (double)j + 0.5D, (double)k + 0.5D) < dist && !this.serverController.isBlockProtected(worldserver, i, j, k, this.playerEntity))
+            // CraftBukkit start - Check if we can actually do something over this large a distance
+            Location eyeLoc = this.getPlayerB().getEyeLocation();
+            double reachDistance = NumberConversions.square(eyeLoc.getX() - i) + NumberConversions.square(eyeLoc.getY() - j)
+                    + NumberConversions.square(eyeLoc.getZ() - k);
+
+            if (reachDistance > (this.getPlayerB().getGameMode() == org.bukkit.GameMode.CREATIVE ? CREATIVE_PLACE_DISTANCE_SQUARED : SURVIVAL_PLACE_DISTANCE_SQUARED))
             {
-                // record block place result so we can update client itemstack size if place event was cancelled.
-                if (!this.playerEntity.theItemInWorldManager.activateBlockOrUseItem(this.playerEntity, worldserver, itemstack, i, j, k, l, p_147346_1_.func_149573_h(), p_147346_1_.func_149569_i(), p_147346_1_.func_149575_j()))
-                {
-                    placeResult = false;
-                }
+                return;
             }
 
+            // Cauldron start - record place result so we can update client inventory slot if place event is cancelled. Fixes stacksize client-side bug
+            if (!this.playerEntity.theItemInWorldManager.activateBlockOrUseItem(this.playerEntity, worldserver, itemstack, i, j, k, l,
+                    p_147346_1_.func_149573_h(), p_147346_1_.func_149569_i(), p_147346_1_.func_149575_j()))
+            {
+                placeResult = true;
+            }
+            // Cauldron end
+            // CraftBukkit end
             flag = true;
         }
 
@@ -615,7 +913,7 @@
             this.playerEntity.openContainer.detectAndSendChanges();
             this.playerEntity.isChangingQuantityOnly = false;
 
-            if (!ItemStack.areItemStacksEqual(this.playerEntity.inventory.getCurrentItem(), p_147346_1_.func_149574_g()) || !placeResult) // force client itemstack update if place event was cancelled
+            if (slot != null && (!ItemStack.areItemStacksEqual(this.playerEntity.inventory.getCurrentItem(), p_147346_1_.func_149574_g()) || !placeResult)) // Cauldron - always is needed to update client itemstack if placement is cancelled
             {
                 this.sendPacket(new S2FPacketSetSlot(this.playerEntity.openContainer.windowId, slot.slotNumber, this.playerEntity.inventory.getCurrentItem()));
             }
@@ -624,14 +922,34 @@
 
     public void onDisconnect(IChatComponent p_147231_1_)
     {
-        logger.info(this.playerEntity.getCommandSenderName() + " lost connection: " + p_147231_1_);
+        // CraftBukkit start - Rarely it would send a disconnect line twice
+        if (this.processedDisconnect)
+        {
+            return;
+        }
+        else
+        {
+            this.processedDisconnect = true;
+        }
+        // CraftBukkit end
+        logger.info(this.playerEntity.getCommandSenderName() + " lost connection: " + p_147231_1_.getUnformattedText()); // CraftBukkit - Don't toString the component
         this.serverController.func_147132_au();
-        ChatComponentTranslation chatcomponenttranslation = new ChatComponentTranslation("multiplayer.player.left", new Object[] {this.playerEntity.func_145748_c_()});
-        chatcomponenttranslation.getChatStyle().setColor(EnumChatFormatting.YELLOW);
-        this.serverController.getConfigurationManager().sendChatMsg(chatcomponenttranslation);
+        // CraftBukkit start - Replace vanilla quit message handling with our own.
+        /*
+        ChatMessage chatcomponenttranslation = new ChatMessage("multiplayer.player.left", new Object[] { this.player.getScoreboardDisplayName()});
+        
+        chatcomponenttranslation.b().setColor(EnumChatFormat.YELLOW);
+        this.minecraftServer.getPlayerList().sendMessage(chatcomponenttranslation);
+        */
         this.playerEntity.mountEntityAndWakeUp();
-        this.serverController.getConfigurationManager().playerLoggedOut(this.playerEntity);
+        String quitMessage = this.serverController.getConfigurationManager().disconnect(this.playerEntity);
 
+        if ((quitMessage != null) && (quitMessage.length() > 0))
+        {
+            this.serverController.getConfigurationManager().sendMessage(CraftChatMessage.fromString(quitMessage));
+        }
+        // CraftBukkit end
+
         if (this.serverController.isSinglePlayer() && this.playerEntity.getCommandSenderName().equals(this.serverController.getServerOwner()))
         {
             logger.info("Stopping singleplayer server as player logged out");
@@ -657,6 +975,18 @@
             }
         }
 
+        // CraftBukkit start
+        if (p_147359_1_ == null)
+        {
+            return;
+        }
+        else if (p_147359_1_ instanceof S05PacketSpawnPosition)
+        {
+            S05PacketSpawnPosition packet6 = (S05PacketSpawnPosition) p_147359_1_;
+            this.playerEntity.compassTarget = new Location(this.getPlayerB().getWorld(), packet6.field_149364_a, packet6.field_149362_b, packet6.field_149363_c);
+        }
+        // CraftBukkit end
+
         try
         {
             this.netManager.scheduleOutboundPacket(p_147359_1_, new GenericFutureListener[0]);
@@ -679,20 +1009,37 @@
 
     public void processHeldItemChange(C09PacketHeldItemChange p_147355_1_)
     {
+        // CraftBukkit start
+        if (this.playerEntity.isDead)
+        {
+            return;
+        }
+
         if (p_147355_1_.func_149614_c() >= 0 && p_147355_1_.func_149614_c() < InventoryPlayer.getHotbarSize())
         {
+            PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getPlayerB(), this.playerEntity.inventory.currentItem, p_147355_1_.func_149614_c());
+            this.server.getPluginManager().callEvent(event);
+
+            if (event.isCancelled())
+            {
+                this.sendPacket(new S09PacketHeldItemChange(this.playerEntity.inventory.currentItem));
+                this.playerEntity.func_143004_u();
+                return;
+            }
+            // CraftBukkit end
             this.playerEntity.inventory.currentItem = p_147355_1_.func_149614_c();
             this.playerEntity.func_143004_u();
         }
         else
         {
             logger.warn(this.playerEntity.getCommandSenderName() + " tried to set an invalid carried item");
+            this.kickPlayerFromServer("Nope!"); // CraftBukkit
         }
     }
 
     public void processChatMessage(C01PacketChatMessage p_147354_1_)
     {
-        if (this.playerEntity.func_147096_v() == EntityPlayer.EnumChatVisibility.HIDDEN)
+        if (this.playerEntity.isDead || this.playerEntity.func_147096_v() == EntityPlayer.EnumChatVisibility.HIDDEN) // CraftBukkit - dead men tell no tales
         {
             ChatComponentTranslation chatcomponenttranslation = new ChatComponentTranslation("chat.cannotSend", new Object[0]);
             chatcomponenttranslation.getChatStyle().setColor(EnumChatFormatting.RED);
@@ -708,51 +1055,385 @@
             {
                 if (!ChatAllowedCharacters.isAllowedCharacter(s.charAt(i)))
                 {
-                    this.kickPlayerFromServer("Illegal characters in chat");
+                    // CraftBukkit start - threadsafety
+                    if (p_147354_1_.hasPriority())
+                    {
+                        Waitable waitable = new Waitable() {
+                            @Override
+                            protected Object evaluate()
+                            {
+                                NetHandlerPlayServer.this.kickPlayerFromServer("Illegal characters in chat");
+                                return null;
+                            }
+                        };
+                        this.serverController.processQueue.add(waitable);
+
+                        try
+                        {
+                            waitable.get();
+                        }
+                        catch (InterruptedException e)
+                        {
+                            Thread.currentThread().interrupt();
+                        }
+                        catch (ExecutionException e)
+                        {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                    else
+                    {
+                        this.kickPlayerFromServer("Illegal characters in chat");
+                    }
+                    // CraftBukkit end
                     return;
                 }
             }
 
-            if (s.startsWith("/"))
+            // CraftBukkit start
+            if (!p_147354_1_.hasPriority())
             {
-                this.handleSlashCommand(s);
+                try
+                {
+                    this.serverController.server.playerCommandState = true;
+                    this.handleSlashCommand(s);
+                }
+                finally
+                {
+                    this.serverController.server.playerCommandState = false;
+                }
             }
-            else
+            else if (s.isEmpty())
             {
-                ChatComponentTranslation chatcomponenttranslation1 = new ChatComponentTranslation("chat.type.text", new Object[] {this.playerEntity.func_145748_c_(), ForgeHooks.newChatWithLinks(s)}); // Fixes chat links
-                chatcomponenttranslation1 = ForgeHooks.onServerChatEvent(this, s, chatcomponenttranslation1);
-                if (chatcomponenttranslation1 == null) return;
-                this.serverController.getConfigurationManager().sendChatMsgImpl(chatcomponenttranslation1, false);
+                logger.warn(this.playerEntity.getCommandSenderName() + " tried to send an empty message");
             }
+            else if (getPlayerB().isConversing())
+            {
+                getPlayerB().acceptConversationInput(s);
+            }
+            else if (this.playerEntity.func_147096_v() == EntityPlayer.EnumChatVisibility.SYSTEM) // Re-add "Command Only" flag check
+            {
+                ChatComponentTranslation chatcomponenttranslation = new ChatComponentTranslation("chat.cannotSend", new Object[0]);
+                chatcomponenttranslation.getChatStyle().setColor(EnumChatFormatting.RED);
+                this.sendPacket(new S02PacketChat(chatcomponenttranslation));
+            }
+            else if (true)
+            {
+                this.chat(s, true);
+                // CraftBukkit end - the below is for reference. :)
+            }
 
-            this.chatSpamThresholdCount += 20;
+            // CraftBukkit start - replaced with thread safe throttle
+            // this.chatSpamThresholdCount += 20;
+            if (chatSpamField.addAndGet(this, 20) > 200 && !this.serverController.getConfigurationManager().func_152596_g(this.playerEntity.getGameProfile()))
+            {
+                if (p_147354_1_.hasPriority())
+                {
+                    Waitable waitable = new Waitable() {
+                        @Override
+                        protected Object evaluate()
+                        {
+                            NetHandlerPlayServer.this.kickPlayerFromServer("disconnect.spam");
+                            return null;
+                        }
+                    };
+                    this.serverController.processQueue.add(waitable);
 
-            if (this.chatSpamThresholdCount > 200 && !this.serverController.getConfigurationManager().func_152596_g(this.playerEntity.getGameProfile()))
+                    try
+                    {
+                        waitable.get();
+                    }
+                    catch (InterruptedException e)
+                    {
+                        Thread.currentThread().interrupt();
+                    }
+                    catch (ExecutionException e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+                else
+                {
+                    this.kickPlayerFromServer("disconnect.spam");
+                }
+
+                // CraftBukkit end
+            }
+        }
+    }
+
+    // CraftBukkit start
+    public void chat(String s, boolean async)
+    {
+        if (s.isEmpty() || this.playerEntity.func_147096_v() == EntityPlayer.EnumChatVisibility.HIDDEN)
+        {
+            return;
+        }
+
+        if (!async && s.startsWith("/"))
+        {
+            this.handleSlashCommand(s);
+        }
+        else if (this.playerEntity.func_147096_v() == EntityPlayer.EnumChatVisibility.SYSTEM)
+        {
+            // Do nothing, this is coming from a plugin
+        }
+        else
+        {
+            // Cauldron start - handle Forge event
+            ChatComponentTranslation chatcomponenttranslation1 = new ChatComponentTranslation("chat.type.text", new Object[] {
+                    this.playerEntity.func_145748_c_(), s });
+            chatcomponenttranslation1 = ForgeHooks.onServerChatEvent(this, s, chatcomponenttranslation1);
+
+            if (chatcomponenttranslation1 != null
+                    && chatcomponenttranslation1.getFormatArgs()[chatcomponenttranslation1.getFormatArgs().length - 1] instanceof String)
             {
-                this.kickPlayerFromServer("disconnect.spam");
+                // use event message from Forge
+                s = (String) chatcomponenttranslation1.getFormatArgs()[chatcomponenttranslation1.getFormatArgs().length - 1];
             }
+            // Cauldron end
+            Player player = this.getPlayerB();
+            AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet()); // Cauldron - pass changed message if any from Forge
+            event.setCancelled(chatcomponenttranslation1 == null); // Cauldron - pre-cancel event if forge event was cancelled
+            this.server.getPluginManager().callEvent(event);
+            if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0)
+            {
+                // Evil plugins still listening to deprecated event
+                final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients());
+                queueEvent.setCancelled(event.isCancelled());
+                Waitable waitable = new Waitable() {
+                    @Override
+                    protected Object evaluate()
+                    {
+                        org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent);
+
+                        if (queueEvent.isCancelled())
+                        {
+                            return null;
+                        }
+
+                        String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage());
+                        NetHandlerPlayServer.this.serverController.console.sendMessage(message);
+                        if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy())
+                        {
+                            for (Object recipient : serverController.getConfigurationManager().playerEntityList)
+                            {
+                                ((EntityPlayerMP) recipient).sendMessage(CraftChatMessage.fromString(message));
+                            }
+                        }
+                        else
+                        {
+                            for (Player player : queueEvent.getRecipients())
+                            {
+                                player.sendMessage(message);
+                            }
+                        }
+
+                        return null;
+                    }
+                };
+
+                if (async)
+                {
+                    serverController.processQueue.add(waitable);
+                }
+                else
+                {
+                    waitable.run();
+                }
+
+                try
+                {
+                    waitable.get();
+                }
+                catch (InterruptedException e)
+                {
+                    Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on!
+                }
+                catch (ExecutionException e)
+                {
+                    throw new RuntimeException("Exception processing chat event", e.getCause());
+                }
+            }
+            else
+            {
+                if (event.isCancelled())
+                {
+                    return;
+                }
+
+                s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage());
+                serverController.console.sendMessage(s);
+                if (((LazyPlayerSet) event.getRecipients()).isLazy())
+                {
+                    for (Object recipient : serverController.getConfigurationManager().playerEntityList)
+                    {
+                        for (IChatComponent component : CraftChatMessage.fromString(s))
+                        {
+                            ((EntityPlayerMP) recipient).sendMessage(CraftChatMessage.fromString(s));
+                        }
+                    }
+                }
+                else
+                {
+                    for (Player recipient : event.getRecipients())
+                    {
+                        recipient.sendMessage(s);
+                    }
+                }
+            }
         }
     }
+    // CraftBukkit end
 
     private void handleSlashCommand(String p_147361_1_)
     {
-        this.serverController.getCommandManager().executeCommand(this.playerEntity, p_147361_1_);
+        org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.startTiming(); // Spigot
+        // CraftBukkit start
+        CraftPlayer player = this.getPlayerB();
+        PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, p_147361_1_, new LazyPlayerSet());
+        this.server.getPluginManager().callEvent(event);
+
+        if (event.isCancelled())
+        {
+            org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
+            return;
+        }
+
+        try
+        {
+            // Spigot Start
+            if (org.spigotmc.SpigotConfig.logCommands)
+            {
+                this.logger.info(event.getPlayer().getName() + " issued server command: " + event.getMessage()); // CraftBukkit
+            }
+
+            // Spigot end
+            // Cauldron start - handle bukkit/vanilla commands
+            int space = event.getMessage().indexOf(" ");
+            // if bukkit command exists then execute it over vanilla
+            if (this.server.getCommandMap().getCommand(event.getMessage().substring(1, space != -1 ? space : event.getMessage().length())) != null)
+            {
+                this.server.dispatchCommand(event.getPlayer(), event.getMessage().substring(1));
+                org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
+                return;
+            }
+            else
+            // process vanilla command
+            {
+                this.server.dispatchVanillaCommand(event.getPlayer(), event.getMessage().substring(1));
+                org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
+                return;
+            }
+        }
+        catch (org.bukkit.command.CommandException ex)
+        {
+            player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command");
+            java.util.logging.Logger.getLogger(NetHandlerPlayServer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
+            org.bukkit.craftbukkit.SpigotTimings.playerCommandTimer.stopTiming(); // Spigot
+            return;
+        }
+
+        // this.serverController.getCommandManager().executeCommand(this.playerEntity, p_147361_1_);
+        // CraftBukkit end
     }
 
     public void processAnimation(C0APacketAnimation p_147350_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         this.playerEntity.func_143004_u();
 
         if (p_147350_1_.func_149421_d() == 1)
         {
+            // CraftBukkit start - Raytrace to look for 'rogue armswings'
+            float f = 1.0F;
+            float f1 = this.playerEntity.prevRotationPitch + (this.playerEntity.rotationPitch - this.playerEntity.prevRotationPitch) * f;
+            float f2 = this.playerEntity.prevRotationYaw + (this.playerEntity.rotationYaw - this.playerEntity.prevRotationYaw) * f;
+            double d0 = this.playerEntity.prevPosX + (this.playerEntity.posX - this.playerEntity.prevPosX) * (double) f;
+            double d1 = this.playerEntity.prevPosY + (this.playerEntity.posY - this.playerEntity.prevPosY) * (double) f + 1.62D
+                    - (double) this.playerEntity.yOffset;
+            double d2 = this.playerEntity.prevPosZ + (this.playerEntity.posZ - this.playerEntity.prevPosZ) * (double) f;
+            Vec3 vec3 = Vec3.createVectorHelper(d0, d1, d2);
+            float f3 = MathHelper.cos(-f2 * 0.017453292F - (float) Math.PI);
+            float f4 = MathHelper.sin(-f2 * 0.017453292F - (float) Math.PI);
+            float f5 = -MathHelper.cos(-f1 * 0.017453292F);
+            float f6 = MathHelper.sin(-f1 * 0.017453292F);
+            float f7 = f4 * f5;
+            float f8 = f3 * f5;
+            double d3 = 5.0D;
+            Vec3 vec31 = vec3.addVector((double) f7 * d3, (double) f6 * d3, (double) f8 * d3);
+            MovingObjectPosition movingobjectposition = this.playerEntity.worldObj.rayTraceBlocks(vec3, vec31, true);
+            boolean valid = false;
+
+            if (movingobjectposition == null || movingobjectposition.typeOfHit != MovingObjectPosition.MovingObjectType.BLOCK)
+            {
+                valid = true;
+            }
+            else
+            {
+                Block block = this.playerEntity.worldObj.getBlock(movingobjectposition.blockX, movingobjectposition.blockY, movingobjectposition.blockZ);
+
+                if (!block.isOpaqueCube()) // Should be isBreakable?
+                {
+                    valid = true;
+                }
+            }
+
+            if (valid)
+            {
+                CraftEventFactory.callPlayerInteractEvent(this.playerEntity, Action.LEFT_CLICK_AIR, this.playerEntity.inventory.getCurrentItem());
+            }
+
+            // Arm swing animation
+            PlayerAnimationEvent event = new PlayerAnimationEvent(this.getPlayerB());
+            this.server.getPluginManager().callEvent(event);
+
+            if (event.isCancelled())
+            {
+                return;
+            }
+            // CraftBukkit end
             this.playerEntity.swingItem();
         }
     }
 
     public void processEntityAction(C0BPacketEntityAction p_147357_1_)
     {
+        // CraftBukkit start
+        if (this.playerEntity.isDead)
+        {
+            return;
+        }
+
         this.playerEntity.func_143004_u();
 
+        if (p_147357_1_.func_149513_d() == 1 || p_147357_1_.func_149513_d() == 2)
+        {
+            PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getPlayerB(), p_147357_1_.func_149513_d() == 1);
+            this.server.getPluginManager().callEvent(event);
+
+            if (event.isCancelled())
+            {
+                return;
+            }
+        }
+
+        if (p_147357_1_.func_149513_d() == 4 || p_147357_1_.func_149513_d() == 5)
+        {
+            PlayerToggleSprintEvent event = new PlayerToggleSprintEvent(this.getPlayerB(), p_147357_1_.func_149513_d() == 4);
+            this.server.getPluginManager().callEvent(event);
+
+            if (event.isCancelled())
+            {
+                return;
+            }
+        }
+        // CraftBukkit end
+
         if (p_147357_1_.func_149513_d() == 1)
         {
             this.playerEntity.setSneaking(true);
@@ -772,7 +1453,7 @@
         else if (p_147357_1_.func_149513_d() == 3)
         {
             this.playerEntity.wakeUpPlayer(false, true, true);
-            this.hasMoved = false;
+            // this.hasMoved = false; // CraftBukkit - this is handled in teleport
         }
         else if (p_147357_1_.func_149513_d() == 6)
         {
@@ -789,8 +1470,20 @@
 
     public void processUseEntity(C02PacketUseEntity p_147340_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         WorldServer worldserver = this.serverController.worldServerForDimension(this.playerEntity.dimension);
-        Entity entity = p_147340_1_.func_149564_a(worldserver);
+        Entity entity = p_147340_1_.func_149564_a((World) worldserver);
+        // Spigot Start
+        if (entity == playerEntity)
+        {
+            kickPlayerFromServer("Cannot interact with self!");
+            return;
+        }
+        // Spigot End
         this.playerEntity.func_143004_u();
 
         if (entity != null)
@@ -805,9 +1498,53 @@
 
             if (this.playerEntity.getDistanceSqToEntity(entity) < d0)
             {
+                ItemStack itemInHand = this.playerEntity.inventory.getCurrentItem(); // CraftBukkit
+
                 if (p_147340_1_.func_149565_c() == C02PacketUseEntity.Action.INTERACT)
                 {
+                    // CraftBukkit start
+                    boolean triggerTagUpdate = itemInHand != null && itemInHand.getItem() == Items.name_tag && entity instanceof EntityLiving;
+                    boolean triggerChestUpdate = itemInHand != null && itemInHand.getItem() == Item.getItemFromBlock(Blocks.chest)
+                            && entity instanceof EntityHorse;
+                    boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.lead && entity instanceof EntityLiving;
+                    PlayerInteractEntityEvent event = new PlayerInteractEntityEvent((Player) this.getPlayerB(), entity.getBukkitEntity());
+                    this.server.getPluginManager().callEvent(event);
+
+                    if (triggerLeashUpdate
+                            && (event.isCancelled() || this.playerEntity.inventory.getCurrentItem() == null || this.playerEntity.inventory.getCurrentItem()
+                                    .getItem() != Items.lead))
+                    {
+                        // Refresh the current leash state
+                        this.sendPacket(new S1BPacketEntityAttach(1, entity, ((EntityLiving) entity).getLeashedToEntity()));
+                    }
+
+                    if (triggerTagUpdate
+                            && (event.isCancelled() || this.playerEntity.inventory.getCurrentItem() == null || this.playerEntity.inventory.getCurrentItem()
+                                    .getItem() != Items.name_tag))
+                    {
+                        // Refresh the current entity metadata
+                        this.sendPacket(new S1CPacketEntityMetadata(entity.getEntityId(), entity.dataWatcher, true));
+                    }
+
+                    if (triggerChestUpdate
+                            && (event.isCancelled() || this.playerEntity.inventory.getCurrentItem() == null || this.playerEntity.inventory.getCurrentItem()
+                                    .getItem() != Item.getItemFromBlock(Blocks.chest)))
+                    {
+                        this.sendPacket(new S1CPacketEntityMetadata(entity.getEntityId(), entity.dataWatcher, true));
+                    }
+
+                    if (event.isCancelled())
+                    {
+                        return;
+                    }
+                    // CraftBukkit end
                     this.playerEntity.interactWith(entity);
+                    // CraftBukkit start
+                    if (itemInHand != null && itemInHand.stackSize <= -1)
+                    {
+                        this.playerEntity.sendContainerToPlayer(this.playerEntity.openContainer);
+                    }
+                    // CraftBukkit end
                 }
                 else if (p_147340_1_.func_149565_c() == C02PacketUseEntity.Action.ATTACK)
                 {
@@ -819,6 +1556,13 @@
                     }
 
                     this.playerEntity.attackTargetEntityWithCurrentItem(entity);
+
+                    // CraftBukkit start
+                    if (itemInHand != null && itemInHand.stackSize <= -1)
+                    {
+                        this.playerEntity.sendContainerToPlayer(this.playerEntity.openContainer);
+                    }
+                    // CraftBukkit end
                 }
             }
         }
@@ -834,8 +1578,19 @@
             case 1:
                 if (this.playerEntity.playerConqueredTheEnd)
                 {
-                    this.playerEntity = this.serverController.getConfigurationManager().respawnPlayer(this.playerEntity, 0, true);
+                // Cauldron start
+                if (this.playerEntity.dimension == 1) // coming from end
+                {
+                    // We really should be calling transferPlayerToDimension since the player is coming in contact with a portal.
+                    this.serverController.getConfigurationManager().respawnPlayer(this.playerEntity, 0, true); // set flag to indicate player is leaving end.
                 }
+                else
+                // not coming from end
+                {
+                    this.playerEntity = this.serverController.getConfigurationManager().respawnPlayer(this.playerEntity, 0, false);
+                }
+                // Cauldron end
+                }
                 else if (this.playerEntity.getServerForPlayer().getWorldInfo().isHardcoreModeEnabled())
                 {
                     if (this.serverController.isSinglePlayer() && this.playerEntity.getCommandSenderName().equals(this.serverController.getServerOwner()))
@@ -871,17 +1626,461 @@
 
     public void processCloseWindow(C0DPacketCloseWindow p_147356_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
+        // Cauldron start - vanilla compatibility
+        try
+        {
+            if (this.playerEntity.openContainer.getBukkitView() != null)
+            {
+                CraftEventFactory.handleInventoryCloseEvent(this.playerEntity); // CraftBukkit
+            }
+        }
+        catch (AbstractMethodError e)
+        {
+            // do nothing
+        }
+        // Cauldron end
         this.playerEntity.closeContainer();
     }
 
     public void processClickWindow(C0EPacketClickWindow p_147351_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         this.playerEntity.func_143004_u();
 
         if (this.playerEntity.openContainer.windowId == p_147351_1_.func_149548_c() && this.playerEntity.openContainer.isPlayerNotUsingContainer(this.playerEntity))
         {
-            ItemStack itemstack = this.playerEntity.openContainer.slotClick(p_147351_1_.func_149544_d(), p_147351_1_.func_149543_e(), p_147351_1_.func_149542_h(), this.playerEntity);
+            // CraftBukkit start - Call InventoryClickEvent
+            if (p_147351_1_.func_149544_d() < -1 && p_147351_1_.func_149544_d() != -999)
+            {
+                return;
+            }
 
+            InventoryView inventory = this.playerEntity.openContainer.getBukkitView();
+            SlotType type = CraftInventoryView.getSlotType(inventory, p_147351_1_.func_149544_d());
+            InventoryClickEvent event = null;
+            ClickType click = ClickType.UNKNOWN;
+            InventoryAction action = InventoryAction.UNKNOWN;
+            ItemStack itemstack = null;
+
+            // Cauldron start - some containers such as NEI's Creative Container does not have a view at this point so we need to create one
+            if (inventory == null)
+            {
+                inventory = new CraftInventoryView(this.playerEntity.getBukkitEntity(), MinecraftServer.getServer().server.createInventory(
+                        this.playerEntity.getBukkitEntity(), InventoryType.CHEST), this.playerEntity.openContainer);
+                this.playerEntity.openContainer.bukkitView = inventory;
+            }
+            // Cauldron end
+
+            if (p_147351_1_.func_149544_d() == -1)
+            {
+                type = SlotType.OUTSIDE; // override
+                click = p_147351_1_.func_149543_e() == 0 ? ClickType.WINDOW_BORDER_LEFT : ClickType.WINDOW_BORDER_RIGHT;
+                action = InventoryAction.NOTHING;
+            }
+            else if (p_147351_1_.func_149542_h() == 0)
+            {
+                if (p_147351_1_.func_149543_e() == 0)
+                {
+                    click = ClickType.LEFT;
+                }
+                else if (p_147351_1_.func_149543_e() == 1)
+                {
+                    click = ClickType.RIGHT;
+                }
+
+                if (p_147351_1_.func_149543_e() == 0 || p_147351_1_.func_149543_e() == 1)
+                {
+                    action = InventoryAction.NOTHING; // Don't want to repeat ourselves
+
+                    if (p_147351_1_.func_149544_d() == -999)
+                    {
+                        if (playerEntity.inventory.getItemStack() != null)
+                        {
+                            action = p_147351_1_.func_149543_e() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR;
+                        }
+                    }
+                    else
+                    {
+                        Slot slot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                        if (slot != null)
+                        {
+                            ItemStack clickedItem = slot.getStack();
+                            ItemStack cursor = playerEntity.inventory.getItemStack();
+
+                            if (clickedItem == null)
+                            {
+                                if (cursor != null)
+                                {
+                                    action = p_147351_1_.func_149543_e() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE;
+                                }
+                            }
+                            else if (slot.canTakeStack(playerEntity)) // Should be Slot.isPlayerAllowed
+                            {
+                                if (cursor == null)
+                                {
+                                    action = p_147351_1_.func_149543_e() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF;
+                                }
+                                else if (slot.isItemValid(cursor)) // Should be Slot.isItemAllowed
+                                {
+                                    if (clickedItem.isItemEqual(cursor) && ItemStack.areItemStackTagsEqual(clickedItem, cursor))
+                                    {
+                                        int toPlace = p_147351_1_.func_149543_e() == 0 ? cursor.stackSize : 1;
+                                        toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.stackSize);
+                                        toPlace = Math.min(toPlace, slot.inventory.getInventoryStackLimit() - clickedItem.stackSize);
+
+                                        if (toPlace == 1)
+                                        {
+                                            action = InventoryAction.PLACE_ONE;
+                                        }
+                                        else if (toPlace == cursor.stackSize)
+                                        {
+                                            action = InventoryAction.PLACE_ALL;
+                                        }
+                                        else if (toPlace < 0)
+                                        {
+                                            action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks
+                                        }
+                                        else if (toPlace != 0)
+                                        {
+                                            action = InventoryAction.PLACE_SOME;
+                                        }
+                                    }
+                                    else if (cursor.stackSize <= slot.getSlotStackLimit()) // Should be Slot.getMaxStackSize()
+                                    {
+                                        action = InventoryAction.SWAP_WITH_CURSOR;
+                                    }
+                                }
+                                else if (cursor.getItem() == clickedItem.getItem()
+                                        && (!cursor.getHasSubtypes() || cursor.getItemDamage() == clickedItem.getItemDamage())
+                                        && ItemStack.areItemStackTagsEqual(cursor, clickedItem))
+                                {
+                                    if (clickedItem.stackSize >= 0)
+                                    {
+                                        if (clickedItem.stackSize + cursor.stackSize <= cursor.getMaxStackSize())
+                                        {
+                                            // As of 1.5, this is result slots only
+                                            action = InventoryAction.PICKUP_ALL;
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            else if (p_147351_1_.func_149542_h() == 1)
+            {
+                if (p_147351_1_.func_149543_e() == 0)
+                {
+                    click = ClickType.SHIFT_LEFT;
+                }
+                else if (p_147351_1_.func_149543_e() == 1)
+                {
+                    click = ClickType.SHIFT_RIGHT;
+                }
+
+                if (p_147351_1_.func_149543_e() == 0 || p_147351_1_.func_149543_e() == 1)
+                {
+                    if (p_147351_1_.func_149544_d() < 0)
+                    {
+                        action = InventoryAction.NOTHING;
+                    }
+                    else
+                    {
+                        Slot slot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                        if (slot != null && slot.canTakeStack(this.playerEntity) && slot.getHasStack()) // Should be Slot.hasItem()
+                        {
+                            action = InventoryAction.MOVE_TO_OTHER_INVENTORY;
+                        }
+                        else
+                        {
+                            action = InventoryAction.NOTHING;
+                        }
+                    }
+                }
+            }
+            else if (p_147351_1_.func_149542_h() == 2)
+            {
+                if (p_147351_1_.func_149543_e() >= 0 && p_147351_1_.func_149543_e() < 9)
+                {
+                    click = ClickType.NUMBER_KEY;
+                    Slot clickedSlot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                    if (clickedSlot.canTakeStack(playerEntity))
+                    {
+                        ItemStack hotbar = this.playerEntity.inventory.getStackInSlot(p_147351_1_.func_149543_e());
+                        boolean canCleanSwap = hotbar == null || (clickedSlot.inventory == playerEntity.inventory && clickedSlot.isItemValid(hotbar)); // the slot will accept the hotbar item
+
+                        if (clickedSlot.getHasStack())
+                        {
+                            if (canCleanSwap)
+                            {
+                                action = InventoryAction.HOTBAR_SWAP;
+                            }
+                            else
+                            {
+                                int firstEmptySlot = playerEntity.inventory.getFirstEmptyStack(); // Should be Inventory.firstEmpty()
+
+                                if (firstEmptySlot > -1)
+                                {
+                                    action = InventoryAction.HOTBAR_MOVE_AND_READD;
+                                }
+                                else
+                                {
+                                    action = InventoryAction.NOTHING; // This is not sane! Mojang: You should test for other slots of same type
+                                }
+                            }
+                        }
+                        else if (!clickedSlot.getHasStack() && hotbar != null && clickedSlot.isItemValid(hotbar))
+                        {
+                            action = InventoryAction.HOTBAR_SWAP;
+                        }
+                        else
+                        {
+                            action = InventoryAction.NOTHING;
+                        }
+                    }
+                    else
+                    {
+                        action = InventoryAction.NOTHING;
+                    }
+
+                    // Special constructor for number key
+                    event = new InventoryClickEvent(inventory, type, p_147351_1_.func_149544_d(), click, action, p_147351_1_.func_149543_e());
+                }
+            }
+            else if (p_147351_1_.func_149542_h() == 3)
+            {
+                if (p_147351_1_.func_149543_e() == 2)
+                {
+                    click = ClickType.MIDDLE;
+
+                    if (p_147351_1_.func_149544_d() == -999)
+                    {
+                        action = InventoryAction.NOTHING;
+                    }
+                    else
+                    {
+                        Slot slot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                        if (slot != null && slot.getHasStack() && playerEntity.capabilities.isCreativeMode && playerEntity.inventory.getItemStack() == null)
+                        {
+                            action = InventoryAction.CLONE_STACK;
+                        }
+                        else
+                        {
+                            action = InventoryAction.NOTHING;
+                        }
+                    }
+                }
+                else
+                {
+                    click = ClickType.UNKNOWN;
+                    action = InventoryAction.UNKNOWN;
+                }
+            }
+            else if (p_147351_1_.func_149542_h() == 4)
+            {
+                if (p_147351_1_.func_149544_d() >= 0)
+                {
+                    if (p_147351_1_.func_149543_e() == 0)
+                    {
+                        click = ClickType.DROP;
+                        Slot slot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                        if (slot != null && slot.getHasStack() && slot.canTakeStack(playerEntity) && slot.getStack() != null
+                                && slot.getStack().getItem() != Item.getItemFromBlock(Blocks.air))
+                        {
+                            action = InventoryAction.DROP_ONE_SLOT;
+                        }
+                        else
+                        {
+                            action = InventoryAction.NOTHING;
+                        }
+                    }
+                    else if (p_147351_1_.func_149543_e() == 1)
+                    {
+                        click = ClickType.CONTROL_DROP;
+                        Slot slot = this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d());
+
+                        if (slot != null && slot.getHasStack() && slot.canTakeStack(playerEntity) && slot.getStack() != null
+                                && slot.getStack().getItem() != Item.getItemFromBlock(Blocks.air))
+                        {
+                            action = InventoryAction.DROP_ALL_SLOT;
+                        }
+                        else
+                        {
+                            action = InventoryAction.NOTHING;
+                        }
+                    }
+                }
+                else
+                {
+                    // Sane default (because this happens when they are holding nothing. Don't ask why.)
+                    click = ClickType.LEFT;
+
+                    if (p_147351_1_.func_149543_e() == 1)
+                    {
+                        click = ClickType.RIGHT;
+                    }
+
+                    action = InventoryAction.NOTHING;
+                }
+            }
+            else if (p_147351_1_.func_149542_h() == 5)
+            {
+                itemstack = this.playerEntity.openContainer.slotClick(p_147351_1_.func_149544_d(), p_147351_1_.func_149543_e(), 5, this.playerEntity);
+            }
+            else if (p_147351_1_.func_149542_h() == 6)
+            {
+                click = ClickType.DOUBLE_CLICK;
+                action = InventoryAction.NOTHING;
+
+                if (p_147351_1_.func_149544_d() >= 0 && this.playerEntity.inventory.getItemStack() != null)
+                {
+                    ItemStack cursor = this.playerEntity.inventory.getItemStack();
+                    action = InventoryAction.NOTHING;
+
+                    // Quick check for if we have any of the item
+                    // Cauldron start - can't call getContents() on modded IInventory; CB-added method
+                    try
+                    {
+                        if (inventory.getTopInventory().contains(org.bukkit.Material.getMaterial(Item.getIdFromItem(cursor.getItem())))
+                                || inventory.getBottomInventory().contains(org.bukkit.Material.getMaterial(Item.getIdFromItem(cursor.getItem()))))
+                        {
+                            action = InventoryAction.COLLECT_TO_CURSOR;
+                        }
+                    }
+                    catch (AbstractMethodError ex)
+                    {
+                        // nothing we can do
+                    }
+                    // Cauldron end
+                }
+            }
+
+            // TODO check on updates
+
+            if (p_147351_1_.func_149542_h() != 5)
+            {
+                if (click == ClickType.NUMBER_KEY)
+                {
+                    event = new InventoryClickEvent(inventory, type, p_147351_1_.func_149544_d(), click, action, p_147351_1_.func_149543_e());
+                }
+                else
+                {
+                    event = new InventoryClickEvent(inventory, type, p_147351_1_.func_149544_d(), click, action);
+                }
+
+                org.bukkit.inventory.Inventory top = inventory.getTopInventory();
+
+                if (p_147351_1_.func_149544_d() == 0 && top instanceof CraftingInventory)
+                {
+                    // Cauldron start - vanilla compatibility (mod recipes)
+                    org.bukkit.inventory.Recipe recipe = null;
+                    try
+                    {
+                        recipe = ((CraftingInventory) top).getRecipe();
+                    }
+                    catch (AbstractMethodError e)
+                    {
+                        // do nothing
+                    }
+                    // Cauldron end
+
+                    if (recipe != null)
+                    {
+                        if (click == ClickType.NUMBER_KEY)
+                        {
+                            event = new CraftItemEvent(recipe, inventory, type, p_147351_1_.func_149544_d(), click, action, p_147351_1_.func_149543_e());
+                        }
+                        else
+                        {
+                            event = new CraftItemEvent(recipe, inventory, type, p_147351_1_.func_149544_d(), click, action);
+                        }
+                    }
+                }
+
+                server.getPluginManager().callEvent(event);
+
+                switch (event.getResult())
+                {
+                case ALLOW:
+                case DEFAULT:
+                    itemstack = this.playerEntity.openContainer.slotClick(p_147351_1_.func_149544_d(), p_147351_1_.func_149543_e(),
+                            p_147351_1_.func_149542_h(), this.playerEntity);
+                    break;
+                case DENY:
+                    /* Needs enum constructor in InventoryAction
+                    if (action.modifiesOtherSlots()) {
+                    } else {
+                        if (action.modifiesCursor()) {
+                            this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried()));
+                        }
+                        if (action.modifiesClicked()) {
+                            this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem()));
+                        }
+                    }*/
+                    switch (action)
+                    {
+                    // Modified other slots
+                    case PICKUP_ALL:
+                    case MOVE_TO_OTHER_INVENTORY:
+                    case HOTBAR_MOVE_AND_READD:
+                    case HOTBAR_SWAP:
+                    case COLLECT_TO_CURSOR:
+                    case UNKNOWN:
+                        this.playerEntity.sendContainerToPlayer(this.playerEntity.openContainer);
+                        break;
+
+                    // Modified cursor and clicked
+                    case PICKUP_SOME:
+                    case PICKUP_HALF:
+                    case PICKUP_ONE:
+                    case PLACE_ALL:
+                    case PLACE_SOME:
+                    case PLACE_ONE:
+                    case SWAP_WITH_CURSOR:
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(-1, -1, this.playerEntity.inventory.getItemStack()));
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(this.playerEntity.openContainer.windowId, p_147351_1_
+                                .func_149544_d(), this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d()).getStack()));
+                        break;
+
+                    // Modified clicked only
+                    case DROP_ALL_SLOT:
+                    case DROP_ONE_SLOT:
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(this.playerEntity.openContainer.windowId, p_147351_1_
+                                .func_149544_d(), this.playerEntity.openContainer.getSlot(p_147351_1_.func_149544_d()).getStack()));
+                        break;
+
+                    // Modified cursor only
+                    case DROP_ALL_CURSOR:
+                    case DROP_ONE_CURSOR:
+                    case CLONE_STACK:
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(-1, -1, this.playerEntity.inventory.getItemStack()));
+                        break;
+
+                    // Nothing
+                    case NOTHING:
+                        break;
+                    }
+
+                    return;
+                }
+            }
+            // CraftBukkit end
+
             if (ItemStack.areItemStacksEqual(p_147351_1_.func_149546_g(), itemstack))
             {
                 this.playerEntity.playerNetServerHandler.sendPacket(new S32PacketConfirmTransaction(p_147351_1_.func_149548_c(), p_147351_1_.func_149547_f(), true));
@@ -903,6 +2102,12 @@
                 }
 
                 this.playerEntity.sendContainerAndContentsToPlayer(this.playerEntity.openContainer, arraylist);
+                // CraftBukkit start - Send a Set Slot to update the crafting result slot
+                if (type == SlotType.RESULT && itemstack != null)
+                {
+                    this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(this.playerEntity.openContainer.windowId, 0, itemstack));
+                }
+                // CraftBukkit end
             }
         }
     }
@@ -925,9 +2130,61 @@
             boolean flag = p_147344_1_.func_149627_c() < 0;
             ItemStack itemstack = p_147344_1_.func_149625_d();
             boolean flag1 = p_147344_1_.func_149627_c() >= 1 && p_147344_1_.func_149627_c() < 36 + InventoryPlayer.getHotbarSize();
-            boolean flag2 = itemstack == null || itemstack.getItem() != null;
+            // CraftBukkit - Add invalidItems check
+            boolean flag2 = itemstack == null || itemstack.getItem() != null && !invalidItems.contains(Item.getIdFromItem(itemstack.getItem()));
             boolean flag3 = itemstack == null || itemstack.getItemDamage() >= 0 && itemstack.stackSize <= 64 && itemstack.stackSize > 0;
+            // CraftBukkit start - Call click event
+            if (flag
+                    || (flag1 && !ItemStack.areItemStacksEqual(this.playerEntity.inventoryContainer.getSlot(p_147344_1_.func_149627_c()).getStack(),
+                            p_147344_1_.func_149625_d()))) // Insist on valid slot
+            {
+                org.bukkit.entity.HumanEntity player = this.playerEntity.getBukkitEntity();
+                InventoryView inventory = new CraftInventoryView(player, player.getInventory(), this.playerEntity.inventoryContainer);
+                org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(p_147344_1_.func_149625_d()); // Should be packet107setcreativeslot.newitem
+                SlotType type = SlotType.QUICKBAR;
 
+                if (flag)
+                {
+                    type = SlotType.OUTSIDE;
+                }
+                else if (p_147344_1_.func_149627_c() < 36)
+                {
+                    if (p_147344_1_.func_149627_c() >= 5 && p_147344_1_.func_149627_c() < 9)
+                    {
+                        type = SlotType.ARMOR;
+                    }
+                    else
+                    {
+                        type = SlotType.CONTAINER;
+                    }
+                }
+
+                InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : p_147344_1_.func_149627_c(), item);
+                server.getPluginManager().callEvent(event);
+                itemstack = CraftItemStack.asNMSCopy(event.getCursor());
+
+                switch (event.getResult())
+                {
+                case ALLOW:
+                    // Plugin cleared the id / stacksize checks
+                    flag2 = flag3 = true;
+                    break;
+                case DEFAULT:
+                    break;
+                case DENY:
+                    // Reset the slot
+                    if (p_147344_1_.func_149627_c() >= 0)
+                    {
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(this.playerEntity.inventoryContainer.windowId, p_147344_1_
+                                .func_149627_c(), this.playerEntity.inventoryContainer.getSlot(p_147344_1_.func_149627_c()).getStack()));
+                        this.playerEntity.playerNetServerHandler.sendPacket(new S2FPacketSetSlot(-1, -1, null));
+                    }
+
+                    return;
+                }
+            }
+            // CraftBukkit end
+
             if (flag1 && flag2 && flag3)
             {
                 if (itemstack == null)
@@ -956,6 +2213,11 @@
 
     public void processConfirmTransaction(C0FPacketConfirmTransaction p_147339_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         Short oshort = (Short)this.field_147372_n.lookup(this.playerEntity.openContainer.windowId);
 
         if (oshort != null && p_147339_1_.func_149533_d() == oshort.shortValue() && this.playerEntity.openContainer.windowId == p_147339_1_.func_149532_c() && !this.playerEntity.openContainer.isPlayerNotUsingContainer(this.playerEntity))
@@ -966,6 +2228,11 @@
 
     public void processUpdateSign(C12PacketUpdateSign p_147343_1_)
     {
+        if (this.playerEntity.isDead)
+        {
+            return; // CraftBukkit
+        }
+
         this.playerEntity.func_143004_u();
         WorldServer worldserver = this.serverController.worldServerForDimension(this.playerEntity.dimension);
 
@@ -980,6 +2247,7 @@
                 if (!tileentitysign.func_145914_a() || tileentitysign.func_145911_b() != this.playerEntity)
                 {
                     this.serverController.logWarning("Player " + this.playerEntity.getCommandSenderName() + " just tried to change non-editable sign");
+                    this.sendPacket(new S33PacketUpdateSign(p_147343_1_.func_149588_c(), p_147343_1_.func_149586_d(), p_147343_1_.func_149585_e(), tileentitysign.signText)); // CraftBukkit
                     return;
                 }
             }
@@ -990,6 +2258,7 @@
             for (j = 0; j < 4; ++j)
             {
                 boolean flag = true;
+                p_147343_1_.func_149589_f()[j] = p_147343_1_.func_149589_f()[j].replaceAll("\uF700", "").replaceAll("\uF701", ""); // Spigot - Mac OSX sends weird chars
 
                 if (p_147343_1_.func_149589_f()[j].length() > 15)
                 {
@@ -1018,7 +2287,29 @@
                 int k = p_147343_1_.func_149586_d();
                 i = p_147343_1_.func_149585_e();
                 TileEntitySign tileentitysign1 = (TileEntitySign)tileentity;
-                System.arraycopy(p_147343_1_.func_149589_f(), 0, tileentitysign1.signText, 0, 4);
+                // CraftBukkit start
+                Player player = this.server.getPlayer(this.playerEntity);
+                SignChangeEvent event = new SignChangeEvent((org.bukkit.craftbukkit.block.CraftBlock) player.getWorld().getBlockAt(j, k, i),
+                        this.server.getPlayer(this.playerEntity), p_147343_1_.func_149589_f());
+                this.server.getPluginManager().callEvent(event);
+
+                if (!event.isCancelled())
+                {
+                    for (int l = 0; l < 4; ++l)
+                    {
+                        tileentitysign1.signText[l] = event.getLine(l);
+
+                        if (tileentitysign1.signText[l] == null)
+                        {
+                            tileentitysign1.signText[l] = "";
+                        }
+                    }
+
+                    tileentitysign1.field_145916_j = false;
+                }
+
+                // System.arraycopy(p_147343_1_.func_149589_f(), 0, tileentitysign1.signText, 0, 4);
+                // CraftBukkit end
                 tileentitysign1.markDirty();
                 worldserver.markBlockForUpdate(j, k, i);
             }
@@ -1041,7 +2332,22 @@
 
     public void processPlayerAbilities(C13PacketPlayerAbilities p_147348_1_)
     {
-        this.playerEntity.capabilities.isFlying = p_147348_1_.func_149488_d() && this.playerEntity.capabilities.allowFlying;
+        // CraftBukkit start - d() should be isFlying()
+        if (this.playerEntity.capabilities.allowFlying && this.playerEntity.capabilities.isFlying != p_147348_1_.func_149488_d())
+        {
+            PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.server.getPlayer(this.playerEntity), p_147348_1_.func_149488_d());
+            this.server.getPluginManager().callEvent(event);
+
+            if (!event.isCancelled())
+            {
+                this.playerEntity.capabilities.isFlying = p_147348_1_.func_149488_d(); // Actually set the player's flying status
+            }
+            else
+            {
+                this.playerEntity.sendPlayerAbilities(); // Tell the player their ability was reverted
+            }
+        }
+        // CraftBukkit end
     }
 
     public void processTabComplete(C14PacketTabComplete p_147341_1_)
@@ -1068,7 +2374,6 @@
         PacketBuffer packetbuffer;
         ItemStack itemstack;
         ItemStack itemstack1;
-
         if ("MC|BEdit".equals(p_147349_1_.func_149559_c()))
         {
             packetbuffer = new PacketBuffer(Unpooled.wrappedBuffer(p_147349_1_.func_149558_e()));
@@ -1093,16 +2398,18 @@
 
                     if (itemstack.getItem() == Items.writable_book && itemstack.getItem() == itemstack1.getItem())
                     {
-                        itemstack1.setTagInfo("pages", itemstack.getTagCompound().getTagList("pages", 8));
+                        CraftEventFactory.handleEditBookEvent(playerEntity, itemstack); // CraftBukkit
                     }
 
                     return;
                 }
             }
-            catch (Exception exception4)
+            // CraftBukkit start
+            catch (Throwable throwable)
             {
-                logger.error("Couldn\'t handle book info", exception4);
-                return;
+                logger.error("Couldn\'t handle book info", throwable);
+                this.kickPlayerFromServer("Invalid book data!");
+                // CraftBukkit end
             }
             finally
             {
@@ -1135,19 +2442,18 @@
                 {
                     if (itemstack.getItem() == Items.written_book && itemstack1.getItem() == Items.writable_book)
                     {
-                        itemstack1.setTagInfo("author", new NBTTagString(this.playerEntity.getCommandSenderName()));
-                        itemstack1.setTagInfo("title", new NBTTagString(itemstack.getTagCompound().getString("title")));
-                        itemstack1.setTagInfo("pages", itemstack.getTagCompound().getTagList("pages", 8));
-                        itemstack1.func_150996_a(Items.written_book);
+                        CraftEventFactory.handleEditBookEvent(playerEntity, itemstack); // CraftBukkit
                     }
 
                     return;
                 }
             }
-            catch (Exception exception3)
+            // CraftBukkit start
+            catch (Throwable exception1)
             {
-                logger.error("Couldn\'t sign book", exception3);
-                return;
+                logger.error("Couldn\'t sign book", exception1);
+                this.kickPlayerFromServer("Invalid book data!");
+                // CraftBukkit end
             }
             finally
             {
@@ -1174,9 +2480,12 @@
                         ((ContainerMerchant)container).setCurrentRecipeIndex(i);
                     }
                 }
-                catch (Exception exception2)
+                // CraftBukkit start
+                catch (Throwable exception2)
                 {
                     logger.error("Couldn\'t select trade", exception2);
+                    this.kickPlayerFromServer("Invalid trade data!");
+                    // CraftBukkit end
                 }
             }
             else if ("MC|AdvCdm".equals(p_147349_1_.func_149559_c()))
@@ -1222,9 +2531,12 @@
                             this.playerEntity.addChatMessage(new ChatComponentTranslation("advMode.setCommand.success", new Object[] {s1}));
                         }
                     }
-                    catch (Exception exception1)
+                    // CraftBukkit start
+                    catch (Throwable exception3)
                     {
-                        logger.error("Couldn\'t set command block", exception1);
+                        logger.error("Couldn\'t set command block", exception3);
+                        this.kickPlayerFromServer("Invalid CommandBlock data!");
+                        // CraftBukkit end
                     }
                     finally
                     {
@@ -1257,9 +2569,12 @@
                             tileentitybeacon.markDirty();
                         }
                     }
-                    catch (Exception exception)
+                    // CraftBukkit start
+                    catch (Throwable exception4)
                     {
-                        logger.error("Couldn\'t set beacon", exception);
+                        logger.error("Couldn\'t set beacon", exception4);
+                        this.kickPlayerFromServer("Invalid beacon data!");
+                        // CraftBukkit end
                     }
                 }
             }
@@ -1281,6 +2596,13 @@
                     containerrepair.updateItemName("");
                 }
             }
+            // CraftBukkit start
+            // Cauldron - bukkit registration moved to FML's ChannelRegistrationHandler
+            else
+            {
+                server.getMessenger().dispatchIncomingMessage(playerEntity.getBukkitEntity(), p_147349_1_.func_149559_c(), p_147349_1_.func_149558_e());
+            }
+            // CraftBukkit end
         }
     }
 
@@ -1292,6 +2614,21 @@
         }
     }
 
+    // CraftBukkit start - Add "isDisconnected" method
+    public final boolean isDisconnected()
+    {
+        return !this.netManager.channel().config().isAutoRead();
+    }
+
+    // CraftBukkit end
+
+    // Cauldron start
+    public CraftServer getCraftServer()
+    {
+        return this.server;
+    }
+    // Cauldron end
+
     static final class SwitchEnumState
         {
             static final int[] field_151290_a = new int[C16PacketClientStatus.EnumState.values().length];
