--- ../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 invalidItems = new HashSet(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, 137, 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,78 @@ } } - 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(); + + // Skip the first time we do this + if (hasMoved) // Spigot - Better Check! + { + 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; + } + } + else + { + hasMoved = true; // Spigot - Better Check! + } + } + + if (this.hasMoved && !this.playerEntity.isDead) + { + // CraftBukkit end double d1; double d2; double d3; @@ -318,7 +512,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 +607,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 +748,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 +785,39 @@ 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; + } + + // CraftBukkit - if rightclick decremented the item, always send the update packet. */ + // this is not here for CraftBukkit's own functionality; rather it is to fix + // a notch bug where the item doesn't update correctly. + boolean always = false; + // CraftBukkit end ItemStack itemstack = this.playerEntity.inventory.getCurrentItem(); boolean flag = false; boolean placeResult = true; @@ -533,11 +834,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 + always = (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 +859,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())) + { + always = true; + } + // Cauldron end + // CraftBukkit end flag = true; } @@ -612,10 +930,17 @@ this.playerEntity.isChangingQuantityOnly = true; this.playerEntity.inventory.mainInventory[this.playerEntity.inventory.currentItem] = ItemStack.copyItemStack(this.playerEntity.inventory.mainInventory[this.playerEntity.inventory.currentItem]); Slot slot = this.playerEntity.openContainer.getSlotFromInventory(this.playerEntity.inventory, this.playerEntity.inventory.currentItem); + // Cauldron start - abort if no slot, fixes RP2 timer crash block place - see #181 + if (slot == null) + { + this.playerEntity.isChangingQuantityOnly = false; // set flag to false or it will cause inventory to glitch on death + return; + } + // Cauldron end 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 (!ItemStack.areItemStacksEqual(this.playerEntity.inventory.getCurrentItem(), p_147346_1_.func_149574_g()) || always) // 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 +949,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 +1002,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 +1036,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 +1082,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 +1480,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 +1497,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 +1525,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 +1583,13 @@ } this.playerEntity.attackTargetEntityWithCurrentItem(entity); + + // CraftBukkit start + if (itemInHand != null && itemInHand.stackSize <= -1) + { + this.playerEntity.sendContainerToPlayer(this.playerEntity.openContainer); + } + // CraftBukkit end } } } @@ -834,8 +1605,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 +1653,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 +2129,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 +2157,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 +2240,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 +2255,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 +2274,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 +2285,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 +2314,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 +2359,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 +2401,12 @@ PacketBuffer packetbuffer; ItemStack itemstack; ItemStack itemstack1; - + // CraftBukkit start - Ignore empty payloads + if (p_147349_1_.field_149560_b <= 0) + { + return; + } + // CraftBukkit end if ("MC|BEdit".equals(p_147349_1_.func_149559_c())) { packetbuffer = new PacketBuffer(Unpooled.wrappedBuffer(p_147349_1_.func_149558_e())); @@ -1093,16 +2431,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 +2475,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 +2513,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 +2564,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 +2602,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 +2629,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 +2647,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];