diff options
Diffstat (limited to 'patch-remap/og/net/minecraft/server/network/PlayerConnection.patch')
-rw-r--r-- | patch-remap/og/net/minecraft/server/network/PlayerConnection.patch | 1663 |
1 files changed, 1663 insertions, 0 deletions
diff --git a/patch-remap/og/net/minecraft/server/network/PlayerConnection.patch b/patch-remap/og/net/minecraft/server/network/PlayerConnection.patch new file mode 100644 index 0000000000..de9913f60c --- /dev/null +++ b/patch-remap/og/net/minecraft/server/network/PlayerConnection.patch @@ -0,0 +1,1663 @@ +--- a/net/minecraft/server/network/PlayerConnection.java ++++ b/net/minecraft/server/network/PlayerConnection.java +@@ -183,6 +183,67 @@ + import net.minecraft.world.phys.shapes.VoxelShapes; + import org.slf4j.Logger; + ++// CraftBukkit start ++import com.mojang.datafixers.util.Pair; ++import java.util.Arrays; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Function; ++import net.minecraft.network.chat.OutgoingChatMessage; ++import net.minecraft.network.protocol.game.PacketPlayOutAttachEntity; ++import net.minecraft.network.protocol.game.PacketPlayOutEntityEquipment; ++import net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity; ++import net.minecraft.world.entity.EntityInsentient; ++import net.minecraft.world.entity.animal.Bucketable; ++import net.minecraft.world.entity.EntityLiving; ++import net.minecraft.world.entity.EnumItemSlot; ++import net.minecraft.world.entity.animal.allay.Allay; ++import net.minecraft.world.inventory.InventoryClickType; ++import net.minecraft.world.inventory.Slot; ++import net.minecraft.world.item.crafting.RecipeHolder; ++import net.minecraft.world.level.RayTrace; ++import net.minecraft.world.phys.MovingObjectPosition; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.block.Action; ++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.inventory.SmithItemEvent; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerAnimationEvent; ++import org.bukkit.event.player.PlayerAnimationType; ++import org.bukkit.event.player.PlayerChatEvent; ++import org.bukkit.event.player.PlayerCommandPreprocessEvent; ++import org.bukkit.event.player.PlayerInteractAtEntityEvent; ++import org.bukkit.event.player.PlayerInteractEntityEvent; ++import org.bukkit.event.player.PlayerItemHeldEvent; ++import org.bukkit.event.player.PlayerMoveEvent; ++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; ++import org.bukkit.event.player.PlayerSwapHandItemsEvent; ++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.EquipmentSlot; ++import org.bukkit.inventory.InventoryView; ++import org.bukkit.inventory.SmithingInventory; ++// CraftBukkit end ++ + public class PlayerConnection extends ServerCommonPacketListenerImpl implements PacketListenerPlayIn, ServerPlayerConnection, TickablePacketListener { + + static final Logger LOGGER = LogUtils.getLogger(); +@@ -194,7 +255,9 @@ + public final PlayerChunkSender chunkSender; + private int tickCount; + private int ackBlockChangesUpTo = -1; +- private int chatSpamTickCount; ++ // CraftBukkit start - multithreaded fields ++ private final AtomicInteger chatSpamTickCount = new AtomicInteger(); ++ // CraftBukkit end + private int dropSpamTickCount; + private double firstGoodX; + private double firstGoodY; +@@ -229,7 +292,7 @@ + private boolean waitingForSwitchToConfig; + + public PlayerConnection(MinecraftServer minecraftserver, NetworkManager networkmanager, EntityPlayer entityplayer, CommonListenerCookie commonlistenercookie) { +- super(minecraftserver, networkmanager, commonlistenercookie); ++ super(minecraftserver, networkmanager, commonlistenercookie, entityplayer); // CraftBukkit + this.chunkSender = new PlayerChunkSender(networkmanager.isMemoryConnection()); + networkmanager.setListener(this); + this.player = entityplayer; +@@ -239,9 +302,25 @@ + + Objects.requireNonNull(minecraftserver); + this.signedMessageDecoder = SignedMessageChain.b.unsigned(uuid, minecraftserver::enforceSecureProfile); +- this.chatMessageChain = new FutureChain(minecraftserver); ++ this.chatMessageChain = new FutureChain(minecraftserver.chatExecutor); // CraftBukkit - async chat + } + ++ // CraftBukkit start - add fields ++ private int lastTick = MinecraftServer.currentTick; ++ private int allowedPlayerTicks = 1; ++ private int lastDropTick = MinecraftServer.currentTick; ++ private int lastBookTick = MinecraftServer.currentTick; ++ private int dropCount = 0; ++ ++ // Get position of last block hit for BlockDamageLevel.STOPPED ++ 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; ++ // CraftBukkit end ++ + @Override + public void tick() { + if (this.ackBlockChangesUpTo > -1) { +@@ -293,15 +372,21 @@ + } + + this.keepConnectionAlive(); ++ // CraftBukkit start ++ for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; ++ /* Use thread-safe field access instead + if (this.chatSpamTickCount > 0) { + --this.chatSpamTickCount; + } ++ */ ++ // CraftBukkit end + + if (this.dropSpamTickCount > 0) { + --this.dropSpamTickCount; + } + + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && SystemUtils.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { ++ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect(IChatBaseComponent.translatable("multiplayer.disconnect.idling")); + } + +@@ -392,7 +477,34 @@ + double d9 = entity.getDeltaMovement().lengthSqr(); + double d10 = d6 * d6 + d7 * d7 + d8 * d8; + +- if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) { ++ ++ // CraftBukkit start - handle custom speeds and skipped ticks ++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); ++ this.lastTick = (int) (System.currentTimeMillis() / 50); ++ ++ ++this.receivedMovePacketCount; ++ int i = this.receivedMovePacketCount - this.knownMovePacketCount; ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { ++ PlayerConnection.LOGGER.debug(this.player.getScoreboardName() + " is sending move packets too frequently (" + i + " packets since last tick)"); ++ i = 1; ++ } ++ ++ if (d10 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (player.getAbilities().flying) { ++ speed = player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = player.getAbilities().walkingSpeed * 10f; ++ } ++ speed *= 2f; // TODO: Get the speed of the vehicle instead of the player ++ ++ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + PlayerConnection.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); + this.send(new PacketPlayOutVehicleMove(entity)); + return; +@@ -432,14 +544,72 @@ + } + + entity.absMoveTo(d3, d4, d5, f, f1); ++ player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + + if (flag && (flag2 || !flag3)) { + entity.absMoveTo(d0, d1, d2, f, f1); ++ player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.send(new PacketPlayOutVehicleMove(entity)); + return; + } + ++ // CraftBukkit start - fire PlayerMoveEvent ++ Player player = this.getCraftPlayer(); ++ 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. ++ to.setX(packetplayinvehiclemove.getX()); ++ to.setY(packetplayinvehiclemove.getY()); ++ to.setZ(packetplayinvehiclemove.getZ()); ++ ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ to.setYaw(packetplayinvehiclemove.getYRot()); ++ to.setPitch(packetplayinvehiclemove.getXRot()); ++ ++ // 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.player.isImmobile()) { ++ 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 (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ 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 (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ 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.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end ++ + this.player.serverLevel().getChunkSource().move(this.player); + this.player.checkMovementStatistics(this.player.getX() - d0, this.player.getY() - d1, this.player.getZ() - d2); + this.clientVehicleIsFloating = d11 >= -0.03125D && !flag1 && !this.server.isFlightAllowed() && !entity.isNoGravity() && this.noBlocksAround(entity); +@@ -473,6 +643,7 @@ + } + + this.awaitingPositionFromClient = null; ++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit + } + + } +@@ -480,7 +651,7 @@ + @Override + public void handleRecipeBookSeenRecipePacket(PacketPlayInRecipeDisplayed packetplayinrecipedisplayed) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinrecipedisplayed, this, this.player.serverLevel()); +- Optional optional = this.server.getRecipeManager().byKey(packetplayinrecipedisplayed.getRecipe()); ++ Optional<? extends RecipeHolder<?>> optional = this.server.getRecipeManager().byKey(packetplayinrecipedisplayed.getRecipe()); // CraftBukkit - decompile error + RecipeBookServer recipebookserver = this.player.getRecipeBook(); + + Objects.requireNonNull(recipebookserver); +@@ -490,6 +661,7 @@ + @Override + public void handleRecipeBookChangeSettingsPacket(PacketPlayInRecipeSettings packetplayinrecipesettings) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinrecipesettings, this, this.player.serverLevel()); ++ CraftEventFactory.callRecipeBookSettingsEvent(this.player, packetplayinrecipesettings.getBookType(), packetplayinrecipesettings.isOpen(), packetplayinrecipesettings.isFiltering()); // CraftBukkit + this.player.getRecipeBook().setBookSetting(packetplayinrecipesettings.getBookType(), packetplayinrecipesettings.isOpen(), packetplayinrecipesettings.isFiltering()); + } + +@@ -510,6 +682,12 @@ + @Override + public void handleCustomCommandSuggestions(PacketPlayInTabComplete packetplayintabcomplete) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayintabcomplete, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ this.disconnect(IChatBaseComponent.translatable("disconnect.spam")); ++ return; ++ } ++ // CraftBukkit end + StringReader stringreader = new StringReader(packetplayintabcomplete.getCommand()); + + if (stringreader.canRead() && stringreader.peek() == '/') { +@@ -519,6 +697,7 @@ + ParseResults<CommandListenerWrapper> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { ++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer + this.send(new PacketPlayOutTabComplete(packetplayintabcomplete.getId(), suggestions)); + }); + } +@@ -766,6 +945,13 @@ + + if (container instanceof ContainerMerchant) { + ContainerMerchant containermerchant = (ContainerMerchant) container; ++ // CraftBukkit start ++ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant); ++ if (tradeSelectEvent.isCancelled()) { ++ this.player.getBukkitEntity().updateInventory(); ++ return; ++ } ++ // CraftBukkit end + + if (!containermerchant.stillValid(this.player)) { + PlayerConnection.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containermerchant); +@@ -780,6 +966,13 @@ + + @Override + public void handleEditBook(PacketPlayInBEdit packetplayinbedit) { ++ // CraftBukkit start ++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ this.disconnect("Book edited too quickly!"); ++ return; ++ } ++ this.lastBookTick = MinecraftServer.currentTick; ++ // CraftBukkit end + int i = packetplayinbedit.getSlot(); + + if (PlayerInventory.isHotbarSlot(i) || i == 40) { +@@ -788,7 +981,7 @@ + + Objects.requireNonNull(list); + optional.ifPresent(list::add); +- Stream stream = packetplayinbedit.getPages().stream().limit(100L); ++ Stream<String> stream = packetplayinbedit.getPages().stream().limit(100L); // CraftBukkit - decompile error + + Objects.requireNonNull(list); + stream.forEach(list::add); +@@ -806,7 +999,7 @@ + ItemStack itemstack = this.player.getInventory().getItem(i); + + if (itemstack.is(Items.WRITABLE_BOOK)) { +- this.updateBookPages(list, UnaryOperator.identity(), itemstack); ++ this.updateBookPages(list, UnaryOperator.identity(), itemstack.copy(), i, itemstack); // CraftBukkit + } + } + +@@ -831,16 +1024,16 @@ + + this.updateBookPages(list, (s) -> { + return IChatBaseComponent.ChatSerializer.toJson(IChatBaseComponent.literal(s)); +- }, itemstack1); +- this.player.getInventory().setItem(i, itemstack1); ++ }, itemstack1, i, itemstack); // CraftBukkit ++ this.player.getInventory().setItem(i, itemstack); // CraftBukkit - event factory updates the hand book + } + } + +- private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack) { ++ private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit + NBTTagList nbttaglist = new NBTTagList(); + + if (this.player.isTextFilteringEnabled()) { +- Stream stream = list.stream().map((filteredtext) -> { ++ Stream<NBTTagString> stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error + return NBTTagString.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + }); + +@@ -866,6 +1059,7 @@ + } + + itemstack.addTagElement("pages", nbttaglist); ++ CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit + } + + @Override +@@ -922,7 +1116,7 @@ + } else { + WorldServer worldserver = this.player.serverLevel(); + +- if (!this.player.wonGame) { ++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit + if (this.tickCount == 0) { + this.resetPosition(); + } +@@ -932,7 +1126,7 @@ + this.awaitingTeleportTime = this.tickCount; + this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + } +- ++ this.allowedPlayerTicks = 20; // CraftBukkit + } else { + this.awaitingTeleportTime = this.tickCount; + double d0 = clampHorizontal(packetplayinflying.getX(this.player.getX())); +@@ -944,7 +1138,15 @@ + if (this.player.isPassenger()) { + this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); + this.player.serverLevel().getChunkSource().move(this.player); ++ this.allowedPlayerTicks = 20; // CraftBukkit + } else { ++ // CraftBukkit - Make sure the move is valid but then reset it for plugins to modify ++ double prevX = player.getX(); ++ double prevY = player.getY(); ++ double prevZ = player.getZ(); ++ float prevYaw = player.getYRot(); ++ float prevPitch = player.getXRot(); ++ // CraftBukkit end + double d3 = this.player.getX(); + double d4 = this.player.getY(); + double d5 = this.player.getZ(); +@@ -964,15 +1166,33 @@ + ++this.receivedMovePacketCount; + int i = this.receivedMovePacketCount - this.knownMovePacketCount; + +- if (i > 5) { ++ // CraftBukkit start - handle custom speeds and skipped ticks ++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); ++ this.lastTick = (int) (System.currentTimeMillis() / 50); ++ ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { + PlayerConnection.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); + i = 1; + } + ++ if (packetplayinflying.hasRot || d10 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (player.getAbilities().flying) { ++ speed = player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = player.getAbilities().walkingSpeed * 10f; ++ } ++ + if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) { + float f2 = this.player.isFallFlying() ? 300.0F : 100.0F; + +- if (d10 - d9 > (double) (f2 * (float) i) && !this.isSingleplayerOwner()) { ++ if (d10 - d9 > Math.max(f2, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + PlayerConnection.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); + this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); + return; +@@ -994,6 +1214,7 @@ + boolean flag1 = this.player.verticalCollisionBelow; + + this.player.move(EnumMoveType.PLAYER, new Vec3D(d6, d7, d8)); ++ this.player.onGround = packetplayinflying.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move + double d11 = d7; + + d6 = d0 - this.player.getX(); +@@ -1012,9 +1233,70 @@ + } + + if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { +- this.teleport(d3, d4, d5, f, f1); ++ this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. + this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packetplayinflying.isOnGround()); + } else { ++ // CraftBukkit start - fire PlayerMoveEvent ++ // Reset to old location first ++ this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch); ++ ++ Player player = this.getCraftPlayer(); ++ 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 (packetplayinflying.hasPos) { ++ to.setX(packetplayinflying.x); ++ to.setY(packetplayinflying.y); ++ to.setZ(packetplayinflying.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packetplayinflying.hasRot) { ++ to.setYaw(packetplayinflying.yRot); ++ to.setPitch(packetplayinflying.xRot); ++ } ++ ++ // 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.player.isImmobile()) { ++ 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 (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ 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 (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ 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.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end + this.player.absMoveTo(d0, d1, d2, f, f1); + this.clientIsFloating = d11 >= -0.03125D && !flag1 && this.player.gameMode.getGameModeForPlayer() != EnumGamemode.SPECTATOR && !this.server.isFlightAllowed() && !this.player.getAbilities().mayfly && !this.player.hasEffect(MobEffects.LEVITATION) && !this.player.isFallFlying() && !this.player.isAutoSpinAttack() && this.noBlocksAround(this.player); + this.player.serverLevel().getChunkSource().move(this.player); +@@ -1055,11 +1337,68 @@ + return true; + } + ++ // CraftBukkit start - Delegate to teleport(Location) + public void teleport(double d0, double d1, double d2, float f, float f1) { +- this.teleport(d0, d1, d2, f, f1, Collections.emptySet()); ++ this.teleport(d0, d1, d2, f, f1, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { ++ this.teleport(d0, d1, d2, f, f1, Collections.emptySet(), cause); + } + + public void teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) { ++ this.teleport(d0, d1, d2, f, f1, set, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status ++ Player player = this.getCraftPlayer(); ++ Location from = player.getLocation(); ++ ++ double x = d0; ++ double y = d1; ++ double z = d2; ++ float yaw = f; ++ float pitch = f1; ++ ++ Location to = new Location(this.getCraftPlayer().getWorld(), x, y, z, yaw, pitch); ++ // SPIGOT-5171: Triggered on join ++ if (from.equals(to)) { ++ this.internalTeleport(d0, d1, d2, f, f1, set); ++ return false; // CraftBukkit - Return event status ++ } ++ ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || !to.equals(event.getTo())) { ++ set.clear(); // Can't relative teleport ++ to = event.isCancelled() ? event.getFrom() : event.getTo(); ++ d0 = to.getX(); ++ d1 = to.getY(); ++ d2 = to.getZ(); ++ f = to.getYaw(); ++ f1 = to.getPitch(); ++ } ++ ++ this.internalTeleport(d0, d1, d2, f, f1, set); ++ return event.isCancelled(); // CraftBukkit - Return event status ++ } ++ ++ public void teleport(Location dest) { ++ internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); ++ } ++ ++ private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) { ++ // CraftBukkit start ++ if (Float.isNaN(f)) { ++ f = 0; ++ } ++ if (Float.isNaN(f1)) { ++ f1 = 0; ++ } ++ ++ this.justTeleported = true; ++ // CraftBukkit end + double d3 = set.contains(RelativeMovement.X) ? this.player.getX() : 0.0D; + double d4 = set.contains(RelativeMovement.Y) ? this.player.getY() : 0.0D; + double d5 = set.contains(RelativeMovement.Z) ? this.player.getZ() : 0.0D; +@@ -1071,6 +1410,14 @@ + this.awaitingTeleport = 0; + } + ++ // CraftBukkit start - update last location ++ this.lastPosX = this.awaitingPositionFromClient.x; ++ this.lastPosY = this.awaitingPositionFromClient.y; ++ this.lastPosZ = this.awaitingPositionFromClient.z; ++ this.lastYaw = f; ++ this.lastPitch = f1; ++ // CraftBukkit end ++ + this.awaitingTeleportTime = this.tickCount; + this.player.absMoveTo(d0, d1, d2, f, f1); + this.player.connection.send(new PacketPlayOutPosition(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); +@@ -1079,6 +1426,7 @@ + @Override + public void handlePlayerAction(PacketPlayInBlockDig packetplayinblockdig) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinblockdig, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + BlockPosition blockposition = packetplayinblockdig.getPos(); + + this.player.resetLastActionTime(); +@@ -1089,14 +1437,46 @@ + if (!this.player.isSpectator()) { + ItemStack itemstack = this.player.getItemInHand(EnumHand.OFF_HAND); + +- this.player.setItemInHand(EnumHand.OFF_HAND, this.player.getItemInHand(EnumHand.MAIN_HAND)); +- this.player.setItemInHand(EnumHand.MAIN_HAND, itemstack); ++ // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394) ++ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack); ++ CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(EnumHand.MAIN_HAND)); ++ PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(getCraftPlayer(), mainHand.clone(), offHand.clone()); ++ this.cserver.getPluginManager().callEvent(swapItemsEvent); ++ if (swapItemsEvent.isCancelled()) { ++ return; ++ } ++ if (swapItemsEvent.getOffHandItem().equals(offHand)) { ++ this.player.setItemInHand(EnumHand.OFF_HAND, this.player.getItemInHand(EnumHand.MAIN_HAND)); ++ } else { ++ this.player.setItemInHand(EnumHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem())); ++ } ++ if (swapItemsEvent.getMainHandItem().equals(mainHand)) { ++ this.player.setItemInHand(EnumHand.MAIN_HAND, itemstack); ++ } else { ++ this.player.setItemInHand(EnumHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem())); ++ } ++ // CraftBukkit end + this.player.stopUsingItem(); + } + + return; + case DROP_ITEM: + if (!this.player.isSpectator()) { ++ // limit how quickly items can be dropped ++ // 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) { ++ LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); ++ this.disconnect("You dropped your items too quickly (Hacking?)"); ++ return; ++ } ++ } ++ // CraftBukkit end + this.player.drop(false); + } + +@@ -1134,6 +1514,7 @@ + @Override + public void handleUseItemOn(PacketPlayInUseItem packetplayinuseitem) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinuseitem, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.connection.ackBlockChangesUpTo(packetplayinuseitem.getSequence()); + WorldServer worldserver = this.player.serverLevel(); + EnumHand enumhand = packetplayinuseitem.getHand(); +@@ -1157,6 +1538,7 @@ + + if (blockposition.getY() < i) { + if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { ++ this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 + EnumInteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + + if (enumdirection == EnumDirection.UP && !enuminteractionresult.consumesAction() && blockposition.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) { +@@ -1185,6 +1567,7 @@ + @Override + public void handleUseItem(PacketPlayInBlockPlace packetplayinblockplace) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinblockplace, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.ackBlockChangesUpTo(packetplayinblockplace.getSequence()); + WorldServer worldserver = this.player.serverLevel(); + EnumHand enumhand = packetplayinblockplace.getHand(); +@@ -1192,6 +1575,49 @@ + + this.player.resetLastActionTime(); + if (!itemstack.isEmpty() && itemstack.isItemEnabled(worldserver.enabledFeatures())) { ++ // CraftBukkit start ++ // Raytrace to look for 'rogue armswings' ++ float f1 = this.player.getXRot(); ++ float f2 = this.player.getYRot(); ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Vec3D vec3d = new Vec3D(d0, d1, d2); ++ ++ float f3 = MathHelper.cos(-f2 * 0.017453292F - 3.1415927F); ++ float f4 = MathHelper.sin(-f2 * 0.017453292F - 3.1415927F); ++ float f5 = -MathHelper.cos(-f1 * 0.017453292F); ++ float f6 = MathHelper.sin(-f1 * 0.017453292F); ++ float f7 = f4 * f5; ++ float f8 = f3 * f5; ++ double d3 = player.gameMode.getGameModeForPlayer()== EnumGamemode.CREATIVE ? 5.0D : 4.5D; ++ Vec3D vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); ++ MovingObjectPosition movingobjectposition = this.player.level().clip(new RayTrace(vec3d, vec3d1, RayTrace.BlockCollisionOption.OUTLINE, RayTrace.FluidCollisionOption.NONE, player)); ++ ++ boolean cancelled; ++ if (movingobjectposition == null || movingobjectposition.getType() != MovingObjectPosition.EnumMovingObjectType.BLOCK) { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } else { ++ MovingObjectPositionBlock movingobjectpositionblock = (MovingObjectPositionBlock) movingobjectposition; ++ if (player.gameMode.firedInteract && player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameTags(player.gameMode.interactItemStack, itemstack)) { ++ cancelled = player.gameMode.interactResult; ++ } else { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation()); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } ++ player.gameMode.firedInteract = false; ++ } ++ ++ if (cancelled) { ++ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 ++ return; ++ } ++ itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event ++ if (itemstack.isEmpty()) { ++ return; ++ } ++ // CraftBukkit end + EnumInteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); + + if (enuminteractionresult.shouldSwing()) { +@@ -1212,7 +1638,7 @@ + Entity entity = packetplayinspectate.getEntity(worldserver); + + if (entity != null) { +- this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); ++ this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit + return; + } + } +@@ -1235,6 +1661,13 @@ + + @Override + public void onDisconnect(IChatBaseComponent ichatbasecomponent) { ++ // CraftBukkit start - Rarely it would send a disconnect line twice ++ if (this.processedDisconnect) { ++ return; ++ } else { ++ this.processedDisconnect = true; ++ } ++ // CraftBukkit end + PlayerConnection.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), ichatbasecomponent.getString()); + this.removePlayerFromWorld(); + super.onDisconnect(ichatbasecomponent); +@@ -1242,10 +1675,18 @@ + + private void removePlayerFromWorld() { + this.chatMessageChain.close(); ++ // CraftBukkit start - Replace vanilla quit message handling with our own. ++ /* + this.server.invalidateStatus(); + this.server.getPlayerList().broadcastSystemMessage(IChatBaseComponent.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(EnumChatFormat.YELLOW), false); ++ */ ++ + this.player.disconnect(); +- this.server.getPlayerList().remove(this.player); ++ String quitMessage = this.server.getPlayerList().remove(this.player); ++ if ((quitMessage != null) && (quitMessage.length() > 0)) { ++ this.server.getPlayerList().broadcastMessage(CraftChatMessage.fromString(quitMessage)); ++ } ++ // CraftBukkit end + this.player.getTextFilter().leave(); + } + +@@ -1260,7 +1701,16 @@ + @Override + public void handleSetCarriedItem(PacketPlayInHeldItemSlot packetplayinhelditemslot) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinhelditemslot, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + if (packetplayinhelditemslot.getSlot() >= 0 && packetplayinhelditemslot.getSlot() < PlayerInventory.getSelectionSize()) { ++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packetplayinhelditemslot.getSlot()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.send(new PacketPlayOutHeldItemSlot(this.player.getInventory().selected)); ++ this.player.resetLastActionTime(); ++ return; ++ } ++ // CraftBukkit end + if (this.player.getInventory().selected != packetplayinhelditemslot.getSlot() && this.player.getUsedItemHand() == EnumHand.MAIN_HAND) { + this.player.stopUsingItem(); + } +@@ -1269,18 +1719,25 @@ + this.player.resetLastActionTime(); + } else { + PlayerConnection.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit + } + } + + @Override + public void handleChat(PacketPlayInChat packetplayinchat) { ++ // CraftBukkit start - async chat ++ // SPIGOT-3638 ++ if (this.server.isStopped()) { ++ return; ++ } ++ // CraftBukkit end + if (isChatMessageIllegal(packetplayinchat.message())) { + this.disconnect(IChatBaseComponent.translatable("multiplayer.disconnect.illegal_characters")); + } else { + Optional<LastSeenMessages> optional = this.tryHandleChat(packetplayinchat.lastSeenMessages()); + + if (optional.isPresent()) { +- this.server.submit(() -> { ++ // this.server.submit(() -> { // CraftBukkit - async chat + PlayerChatMessage playerchatmessage; + + try { +@@ -1290,7 +1747,7 @@ + return; + } + +- CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()); ++ CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat + IChatBaseComponent ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); + + this.chatMessageChain.append(completablefuture, (filteredtext) -> { +@@ -1298,7 +1755,7 @@ + + this.broadcastChatMessage(playerchatmessage1); + }); +- }); ++ // }); // CraftBukkit - async chat + } + + } +@@ -1313,6 +1770,12 @@ + + if (optional.isPresent()) { + this.server.submit(() -> { ++ // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands ++ if (player.hasDisconnected()) { ++ return; ++ } ++ // CraftBukkit end ++ + this.performChatCommand(serverboundchatcommandpacket, (LastSeenMessages) optional.get()); + this.detectRateSpam(); + }); +@@ -1322,12 +1785,25 @@ + } + + private void performChatCommand(ServerboundChatCommandPacket serverboundchatcommandpacket, LastSeenMessages lastseenmessages) { +- ParseResults parseresults = this.parseCommand(serverboundchatcommandpacket.command()); ++ // CraftBukkit start ++ String command = "/" + serverboundchatcommandpacket.command(); ++ PlayerConnection.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); ++ ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(getCraftPlayer(), command, new LazyPlayerSet(server)); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ command = event.getMessage().substring(1); ++ ++ ParseResults parseresults = this.parseCommand(command); ++ // CraftBukkit end + + Map map; + + try { +- map = this.collectSignedArguments(serverboundchatcommandpacket, SignableCommand.of(parseresults), lastseenmessages); ++ map = (serverboundchatcommandpacket.command().equals(command)) ? this.collectSignedArguments(serverboundchatcommandpacket, SignableCommand.of(parseresults), lastseenmessages) : Collections.emptyMap(); // CraftBukkit + } catch (SignedMessageChain.a signedmessagechain_a) { + this.handleMessageDecodeFailure(signedmessagechain_a); + return; +@@ -1335,10 +1811,10 @@ + + CommandSigningContext.a commandsigningcontext_a = new CommandSigningContext.a(map); + +- parseresults = CommandDispatcher.mapSource(parseresults, (commandlistenerwrapper) -> { ++ parseresults = CommandDispatcher.<CommandListenerWrapper>mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error + return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain); + }); +- this.server.getCommands().performCommand(parseresults, serverboundchatcommandpacket.command()); ++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit + } + + private void handleMessageDecodeFailure(SignedMessageChain.a signedmessagechain_a) { +@@ -1375,7 +1851,7 @@ + private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.b lastseenmessages_b) { + Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_b); + +- if (this.player.getChatVisibility() == EnumChatVisibility.HIDDEN) { ++ if (this.player.isRemoved() || this.player.getChatVisibility() == EnumChatVisibility.HIDDEN) { // CraftBukkit - dead men tell no tales + this.send(new ClientboundSystemChatPacket(IChatBaseComponent.translatable("chat.disabled.options").withStyle(EnumChatFormat.RED), false)); + return Optional.empty(); + } else { +@@ -1409,6 +1885,116 @@ + return false; + } + ++ // CraftBukkit start - add method ++ public void chat(String s, PlayerChatMessage original, boolean async) { ++ if (s.isEmpty() || this.player.getChatVisibility() == EnumChatVisibility.HIDDEN) { ++ return; ++ } ++ OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); ++ ++ if (!async && s.startsWith("/")) { ++ this.handleCommand(s); ++ } else if (this.player.getChatVisibility() == EnumChatVisibility.SYSTEM) { ++ // Do nothing, this is coming from a plugin ++ } else { ++ Player player = this.getCraftPlayer(); ++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(server)); ++ String originalFormat = event.getFormat(), originalMessage = event.getMessage(); ++ this.cserver.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()); ++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) { ++ if (originalFormat.equals(queueEvent.getFormat()) && originalMessage.equals(queueEvent.getMessage()) && queueEvent.getPlayer().getName().equalsIgnoreCase(queueEvent.getPlayer().getDisplayName())) { ++ PlayerConnection.this.server.getPlayerList().broadcastChatMessage(original, PlayerConnection.this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) PlayerConnection.this.player)); ++ return null; ++ } ++ ++ for (EntityPlayer recipient : server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(PlayerConnection.this.player.getUUID(), message); ++ } ++ } else { ++ for (Player player : queueEvent.getRecipients()) { ++ player.sendMessage(PlayerConnection.this.player.getUUID(), message); ++ } ++ } ++ PlayerConnection.this.server.console.sendMessage(message); ++ ++ return null; ++ }}; ++ if (async) { ++ server.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()); ++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) { ++ if (originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) { ++ PlayerConnection.this.server.getPlayerList().broadcastChatMessage(original, PlayerConnection.this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) PlayerConnection.this.player)); ++ return; ++ } ++ ++ for (EntityPlayer recipient : server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(PlayerConnection.this.player.getUUID(), s); ++ } ++ } else { ++ for (Player recipient : event.getRecipients()) { ++ recipient.sendMessage(PlayerConnection.this.player.getUUID(), s); ++ } ++ } ++ server.console.sendMessage(s); ++ } ++ } ++ } ++ ++ private void handleCommand(String s) { ++ this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); ++ ++ CraftPlayer player = this.getCraftPlayer(); ++ ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet(server)); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ try { ++ if (this.cserver.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { ++ 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(PlayerConnection.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); ++ return; ++ } ++ } ++ // CraftBukkit end ++ + private PlayerChatMessage getSignedMessage(PacketPlayInChat packetplayinchat, LastSeenMessages lastseenmessages) throws SignedMessageChain.a { + SignedMessageBody signedmessagebody = new SignedMessageBody(packetplayinchat.message(), packetplayinchat.timeStamp(), packetplayinchat.salt(), lastseenmessages); + +@@ -1416,13 +2002,33 @@ + } + + private void broadcastChatMessage(PlayerChatMessage playerchatmessage) { +- this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player)); ++ // CraftBukkit start ++ String s = playerchatmessage.signedContent(); ++ if (s.isEmpty()) { ++ LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message"); ++ } else if (getCraftPlayer().isConversing()) { ++ final String conversationInput = s; ++ this.server.processQueue.add(new Runnable() { ++ @Override ++ public void run() { ++ getCraftPlayer().acceptConversationInput(conversationInput); ++ } ++ }); ++ } else if (this.player.getChatVisibility() == EnumChatVisibility.SYSTEM) { // Re-add "Command Only" flag check ++ this.send(new ClientboundSystemChatPacket(IChatBaseComponent.translatable("chat.cannotSend").withStyle(EnumChatFormat.RED), false)); ++ } else { ++ this.chat(s, playerchatmessage, true); ++ } ++ // this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player)); ++ // CraftBukkit end + this.detectRateSpam(); + } + + private void detectRateSpam() { +- this.chatSpamTickCount += 20; +- if (this.chatSpamTickCount > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ // CraftBukkit start - replaced with thread safe throttle ++ // this.chatSpamTickCount += 20; ++ if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ // CraftBukkit end + this.disconnect(IChatBaseComponent.translatable("disconnect.spam")); + } + +@@ -1444,13 +2050,62 @@ + @Override + public void handleAnimate(PacketPlayInArmAnimation packetplayinarmanimation) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinarmanimation, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); ++ // CraftBukkit start - Raytrace to look for 'rogue armswings' ++ float f1 = this.player.getXRot(); ++ float f2 = this.player.getYRot(); ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Location origin = new Location(this.player.level().getWorld(), d0, d1, d2, f2, f1); ++ ++ double d3 = player.gameMode.getGameModeForPlayer() == EnumGamemode.CREATIVE ? 5.0D : 4.5D; ++ // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time ++ // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities ++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { ++ Entity handle = ((CraftEntity) entity).getHandle(); ++ return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(player); ++ }); ++ if (result == null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND); ++ } ++ ++ // Arm swing animation ++ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packetplayinarmanimation.getHand() == EnumHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end + this.player.swing(packetplayinarmanimation.getHand()); + } + + @Override + public void handlePlayerCommand(PacketPlayInEntityAction packetplayinentityaction) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinentityaction, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (this.player.isRemoved()) return; ++ switch (packetplayinentityaction.getAction()) { ++ case PRESS_SHIFT_KEY: ++ case RELEASE_SHIFT_KEY: ++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packetplayinentityaction.getAction() == PacketPlayInEntityAction.EnumPlayerAction.PRESS_SHIFT_KEY); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ break; ++ case START_SPRINTING: ++ case STOP_SPRINTING: ++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packetplayinentityaction.getAction() == PacketPlayInEntityAction.EnumPlayerAction.START_SPRINTING); ++ this.cserver.getPluginManager().callEvent(e2); ++ ++ if (e2.isCancelled()) { ++ return; ++ } ++ break; ++ } ++ // CraftBukkit end + this.player.resetLastActionTime(); + Entity entity; + IJumpable ijumpable; +@@ -1532,6 +2187,12 @@ + } + + public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatMessageType.a chatmessagetype_a) { ++ // CraftBukkit start - SPIGOT-7262: if hidden we have to send as disguised message. Query whether we should send at all (but changing this may not be expected). ++ if (!getCraftPlayer().canSee(playerchatmessage.link().sender())) { ++ sendDisguisedChatMessage(playerchatmessage.decoratedContent(), chatmessagetype_a); ++ return; ++ } ++ // CraftBukkit end + this.send(new ClientboundPlayerChatPacket(playerchatmessage.link().sender(), playerchatmessage.link().index(), playerchatmessage.signature(), playerchatmessage.signedBody().pack(this.messageSignatureCache), playerchatmessage.unsignedContent(), playerchatmessage.filterMask(), chatmessagetype_a.toNetwork(this.player.level().registryAccess()))); + this.addPendingMessage(playerchatmessage); + } +@@ -1558,6 +2219,7 @@ + @Override + public void handleInteract(PacketPlayInUseEntity packetplayinuseentity) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinuseentity, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + final WorldServer worldserver = this.player.serverLevel(); + final Entity entity = packetplayinuseentity.getTarget(worldserver); + +@@ -1572,13 +2234,51 @@ + + if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < PlayerConnection.MAX_INTERACTION_DISTANCE) { + packetplayinuseentity.dispatch(new PacketPlayInUseEntity.c() { +- private void performInteraction(EnumHand enumhand, PlayerConnection.a playerconnection_a) { ++ private void performInteraction(EnumHand enumhand, PlayerConnection.a playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit + ItemStack itemstack = PlayerConnection.this.player.getItemInHand(enumhand); + + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + ItemStack itemstack1 = itemstack.copy(); ++ // CraftBukkit start ++ ItemStack itemInHand = PlayerConnection.this.player.getItemInHand(enumhand); ++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof EntityInsentient; ++ Item origItem = player.getInventory().getSelected() == null ? null : player.getInventory().getSelected().getItem(); ++ ++ cserver.getPluginManager().callEvent(event); ++ ++ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a ++ if ((entity instanceof Bucketable && entity instanceof EntityLiving && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) { ++ send(new PacketPlayOutSpawnEntity(entity)); ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ ++ if (triggerLeashUpdate && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) { ++ // Refresh the current leash state ++ send(new PacketPlayOutAttachEntity(entity, ((EntityInsentient) entity).getLeashHolder())); ++ } ++ ++ if (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem) { ++ // Refresh the current entity metadata ++ entity.getEntityData().refresh(player); ++ // SPIGOT-7136 - Allays ++ if (entity instanceof Allay) { ++ send(new PacketPlayOutEntityEquipment(entity.getId(), Arrays.stream(EnumItemSlot.values()).map((slot) -> Pair.of(slot, ((EntityLiving) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ } ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end + EnumInteractionResult enuminteractionresult = playerconnection_a.run(PlayerConnection.this.player, entity, enumhand); + ++ // CraftBukkit start ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end ++ + if (enuminteractionresult.consumesAction()) { + CriterionTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(PlayerConnection.this.player, itemstack1, entity); + if (enuminteractionresult.shouldSwing()) { +@@ -1591,23 +2291,29 @@ + + @Override + public void onInteraction(EnumHand enumhand) { +- this.performInteraction(enumhand, EntityHuman::interactOn); ++ this.performInteraction(enumhand, EntityHuman::interactOn, new PlayerInteractEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), (enumhand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override + public void onInteraction(EnumHand enumhand, Vec3D vec3d) { + this.performInteraction(enumhand, (entityplayer, entity1, enumhand1) -> { + return entity1.interactAt(entityplayer, vec3d, enumhand1); +- }); ++ }, new PlayerInteractAtEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(vec3d.x, vec3d.y, vec3d.z), (enumhand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override + public void onAttack() { +- if (!(entity instanceof EntityItem) && !(entity instanceof EntityExperienceOrb) && !(entity instanceof EntityArrow) && entity != PlayerConnection.this.player) { ++ // CraftBukkit ++ if (!(entity instanceof EntityItem) && !(entity instanceof EntityExperienceOrb) && !(entity instanceof EntityArrow) && (entity != PlayerConnection.this.player || player.isSpectator())) { + ItemStack itemstack = PlayerConnection.this.player.getItemInHand(EnumHand.MAIN_HAND); + + if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + PlayerConnection.this.player.attack(entity); ++ // CraftBukkit start ++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end + } + } else { + PlayerConnection.this.disconnect(IChatBaseComponent.translatable("multiplayer.disconnect.invalid_entity_attacked")); +@@ -1630,14 +2336,14 @@ + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true); ++ this.player = this.server.getPlayerList().respawn(this.player, true, RespawnReason.END_PORTAL); + CriterionTriggers.CHANGED_DIMENSION.trigger(this.player, World.END, World.OVERWORLD); + } else { + if (this.player.getHealth() > 0.0F) { + return; + } + +- this.player = this.server.getPlayerList().respawn(this.player, false); ++ this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); + if (this.server.isHardcore()) { + this.player.setGameMode(EnumGamemode.SPECTATOR); + ((GameRules.GameRuleBoolean) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); +@@ -1653,15 +2359,21 @@ + @Override + public void handleContainerClose(PacketPlayInCloseWindow packetplayinclosewindow) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinclosewindow, this, this.player.serverLevel()); ++ ++ if (this.player.isImmobile()) return; // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ + this.player.doCloseContainer(); + } + + @Override + public void handleContainerClick(PacketPlayInWindowClick packetplayinwindowclick) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinwindowclick, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- if (this.player.containerMenu.containerId == packetplayinwindowclick.getContainerId()) { +- if (this.player.isSpectator()) { ++ if (this.player.containerMenu.containerId == packetplayinwindowclick.getContainerId() && this.player.containerMenu.stillValid(this.player)) { // CraftBukkit ++ boolean cancelled = this.player.isSpectator(); // CraftBukkit - see below if ++ if (false/*this.player.isSpectator()*/) { // CraftBukkit + this.player.containerMenu.sendAllDataToRemote(); + } else if (!this.player.containerMenu.stillValid(this.player)) { + PlayerConnection.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); +@@ -1674,7 +2386,284 @@ + boolean flag = packetplayinwindowclick.getStateId() != this.player.containerMenu.getStateId(); + + this.player.containerMenu.suppressRemoteUpdates(); +- this.player.containerMenu.clicked(i, packetplayinwindowclick.getButtonNum(), packetplayinwindowclick.getClickType(), this.player); ++ // CraftBukkit start - Call InventoryClickEvent ++ if (packetplayinwindowclick.getSlotNum() < -1 && packetplayinwindowclick.getSlotNum() != -999) { ++ return; ++ } ++ ++ InventoryView inventory = this.player.containerMenu.getBukkitView(); ++ SlotType type = inventory.getSlotType(packetplayinwindowclick.getSlotNum()); ++ ++ InventoryClickEvent event; ++ ClickType click = ClickType.UNKNOWN; ++ InventoryAction action = InventoryAction.UNKNOWN; ++ ++ ItemStack itemstack = ItemStack.EMPTY; ++ ++ switch (packetplayinwindowclick.getClickType()) { ++ case PICKUP: ++ if (packetplayinwindowclick.getButtonNum() == 0) { ++ click = ClickType.LEFT; ++ } else if (packetplayinwindowclick.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ if (packetplayinwindowclick.getButtonNum() == 0 || packetplayinwindowclick.getButtonNum() == 1) { ++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves ++ if (packetplayinwindowclick.getSlotNum() == -999) { ++ if (!player.containerMenu.getCarried().isEmpty()) { ++ action = packetplayinwindowclick.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; ++ } ++ } else if (packetplayinwindowclick.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (slot != null) { ++ ItemStack clickedItem = slot.getItem(); ++ ItemStack cursor = player.containerMenu.getCarried(); ++ if (clickedItem.isEmpty()) { ++ if (!cursor.isEmpty()) { ++ action = packetplayinwindowclick.getButtonNum() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE; ++ } ++ } else if (slot.mayPickup(player)) { ++ if (cursor.isEmpty()) { ++ action = packetplayinwindowclick.getButtonNum() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF; ++ } else if (slot.mayPlace(cursor)) { ++ if (ItemStack.isSameItemSameTags(clickedItem, cursor)) { ++ int toPlace = packetplayinwindowclick.getButtonNum() == 0 ? cursor.getCount() : 1; ++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount()); ++ toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount()); ++ if (toPlace == 1) { ++ action = InventoryAction.PLACE_ONE; ++ } else if (toPlace == cursor.getCount()) { ++ 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.getCount() <= slot.getMaxStackSize()) { ++ action = InventoryAction.SWAP_WITH_CURSOR; ++ } ++ } else if (ItemStack.isSameItemSameTags(cursor, clickedItem)) { ++ if (clickedItem.getCount() >= 0) { ++ if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) { ++ // As of 1.5, this is result slots only ++ action = InventoryAction.PICKUP_ALL; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ break; ++ // TODO check on updates ++ case QUICK_MOVE: ++ if (packetplayinwindowclick.getButtonNum() == 0) { ++ click = ClickType.SHIFT_LEFT; ++ } else if (packetplayinwindowclick.getButtonNum() == 1) { ++ click = ClickType.SHIFT_RIGHT; ++ } ++ if (packetplayinwindowclick.getButtonNum() == 0 || packetplayinwindowclick.getButtonNum() == 1) { ++ if (packetplayinwindowclick.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) { ++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } ++ break; ++ case SWAP: ++ if ((packetplayinwindowclick.getButtonNum() >= 0 && packetplayinwindowclick.getButtonNum() < 9) || packetplayinwindowclick.getButtonNum() == 40) { ++ click = (packetplayinwindowclick.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; ++ Slot clickedSlot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (clickedSlot.mayPickup(player)) { ++ ItemStack hotbar = this.player.getInventory().getItem(packetplayinwindowclick.getButtonNum()); ++ boolean canCleanSwap = hotbar.isEmpty() || (clickedSlot.container == player.getInventory() && clickedSlot.mayPlace(hotbar)); // the slot will accept the hotbar item ++ if (clickedSlot.hasItem()) { ++ if (canCleanSwap) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.HOTBAR_MOVE_AND_READD; ++ } ++ } else if (!clickedSlot.hasItem() && !hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ break; ++ case CLONE: ++ if (packetplayinwindowclick.getButtonNum() == 2) { ++ click = ClickType.MIDDLE; ++ if (packetplayinwindowclick.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (slot != null && slot.hasItem() && player.getAbilities().instabuild && player.containerMenu.getCarried().isEmpty()) { ++ action = InventoryAction.CLONE_STACK; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } else { ++ click = ClickType.UNKNOWN; ++ action = InventoryAction.UNKNOWN; ++ } ++ break; ++ case THROW: ++ if (packetplayinwindowclick.getSlotNum() >= 0) { ++ if (packetplayinwindowclick.getButtonNum() == 0) { ++ click = ClickType.DROP; ++ Slot slot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (slot != null && slot.hasItem() && slot.mayPickup(player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) { ++ action = InventoryAction.DROP_ONE_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else if (packetplayinwindowclick.getButtonNum() == 1) { ++ click = ClickType.CONTROL_DROP; ++ Slot slot = this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()); ++ if (slot != null && slot.hasItem() && slot.mayPickup(player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(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 (packetplayinwindowclick.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ action = InventoryAction.NOTHING; ++ } ++ break; ++ case QUICK_CRAFT: ++ this.player.containerMenu.clicked(packetplayinwindowclick.getSlotNum(), packetplayinwindowclick.getButtonNum(), packetplayinwindowclick.getClickType(), this.player); ++ break; ++ case PICKUP_ALL: ++ click = ClickType.DOUBLE_CLICK; ++ action = InventoryAction.NOTHING; ++ if (packetplayinwindowclick.getSlotNum() >= 0 && !this.player.containerMenu.getCarried().isEmpty()) { ++ ItemStack cursor = this.player.containerMenu.getCarried(); ++ action = InventoryAction.NOTHING; ++ // Quick check for if we have any of the item ++ if (inventory.getTopInventory().contains(CraftMagicNumbers.getMaterial(cursor.getItem())) || inventory.getBottomInventory().contains(CraftMagicNumbers.getMaterial(cursor.getItem()))) { ++ action = InventoryAction.COLLECT_TO_CURSOR; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (packetplayinwindowclick.getClickType() != InventoryClickType.QUICK_CRAFT) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new InventoryClickEvent(inventory, type, packetplayinwindowclick.getSlotNum(), click, action, packetplayinwindowclick.getButtonNum()); ++ } else { ++ event = new InventoryClickEvent(inventory, type, packetplayinwindowclick.getSlotNum(), click, action); ++ } ++ ++ org.bukkit.inventory.Inventory top = inventory.getTopInventory(); ++ if (packetplayinwindowclick.getSlotNum() == 0 && top instanceof CraftingInventory) { ++ org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe(); ++ if (recipe != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new CraftItemEvent(recipe, inventory, type, packetplayinwindowclick.getSlotNum(), click, action, packetplayinwindowclick.getButtonNum()); ++ } else { ++ event = new CraftItemEvent(recipe, inventory, type, packetplayinwindowclick.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ if (packetplayinwindowclick.getSlotNum() == 3 && top instanceof SmithingInventory) { ++ org.bukkit.inventory.ItemStack result = ((SmithingInventory) top).getResult(); ++ if (result != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new SmithItemEvent(inventory, type, packetplayinwindowclick.getSlotNum(), click, action, packetplayinwindowclick.getButtonNum()); ++ } else { ++ event = new SmithItemEvent(inventory, type, packetplayinwindowclick.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ event.setCancelled(cancelled); ++ Container oldContainer = this.player.containerMenu; // SPIGOT-1224 ++ cserver.getPluginManager().callEvent(event); ++ if (this.player.containerMenu != oldContainer) { ++ return; ++ } ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ case DEFAULT: ++ this.player.containerMenu.clicked(i, packetplayinwindowclick.getButtonNum(), packetplayinwindowclick.getClickType(), this.player); ++ 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.player.containerMenu.sendAllDataToRemote(); ++ 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.player.connection.send(new PacketPlayOutSetSlot(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried())); ++ this.player.connection.send(new PacketPlayOutSetSlot(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packetplayinwindowclick.getSlotNum(), this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()).getItem())); ++ break; ++ // Modified clicked only ++ case DROP_ALL_SLOT: ++ case DROP_ONE_SLOT: ++ this.player.connection.send(new PacketPlayOutSetSlot(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packetplayinwindowclick.getSlotNum(), this.player.containerMenu.getSlot(packetplayinwindowclick.getSlotNum()).getItem())); ++ break; ++ // Modified cursor only ++ case DROP_ALL_CURSOR: ++ case DROP_ONE_CURSOR: ++ case CLONE_STACK: ++ this.player.connection.send(new PacketPlayOutSetSlot(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried())); ++ break; ++ // Nothing ++ case NOTHING: ++ break; ++ } ++ } ++ ++ if (event instanceof CraftItemEvent || event instanceof SmithItemEvent) { ++ // Need to update the inventory on crafting to ++ // correctly support custom recipes ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ } ++ // CraftBukkit end + ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packetplayinwindowclick.getChangedSlots()).iterator(); + + while (objectiterator.hasNext()) { +@@ -1704,9 +2693,18 @@ + if (!this.player.containerMenu.stillValid(this.player)) { + PlayerConnection.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { +- this.server.getRecipeManager().byKey(packetplayinautorecipe.getRecipe()).ifPresent((recipeholder) -> { +- ((ContainerRecipeBook) this.player.containerMenu).handlePlacement(packetplayinautorecipe.isShiftDown(), recipeholder, this.player); ++ // CraftBukkit start - implement PlayerRecipeBookClickEvent ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packetplayinautorecipe.getRecipe())); ++ if (recipe == null) { ++ return; ++ } ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packetplayinautorecipe.isShiftDown()); ++ ++ // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. ++ this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> { ++ ((ContainerRecipeBook) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player); + }); ++ // CraftBukkit end + } + } + } +@@ -1714,6 +2712,7 @@ + @Override + public void handleContainerButtonClick(PacketPlayInEnchantItem packetplayinenchantitem) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinenchantitem, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); + if (this.player.containerMenu.containerId == packetplayinenchantitem.getContainerId() && !this.player.isSpectator()) { + if (!this.player.containerMenu.stillValid(this.player)) { +@@ -1756,6 +2755,43 @@ + + boolean flag1 = packetplayinsetcreativeslot.getSlotNum() >= 1 && packetplayinsetcreativeslot.getSlotNum() <= 45; + boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty(); ++ if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packetplayinsetcreativeslot.getSlotNum()).getItem(), packetplayinsetcreativeslot.getItem()))) { // Insist on valid slot ++ // CraftBukkit start - Call click event ++ InventoryView inventory = this.player.inventoryMenu.getBukkitView(); ++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packetplayinsetcreativeslot.getItem()); ++ ++ SlotType type = SlotType.QUICKBAR; ++ if (flag) { ++ type = SlotType.OUTSIDE; ++ } else if (packetplayinsetcreativeslot.getSlotNum() < 36) { ++ if (packetplayinsetcreativeslot.getSlotNum() >= 5 && packetplayinsetcreativeslot.getSlotNum() < 9) { ++ type = SlotType.ARMOR; ++ } else { ++ type = SlotType.CONTAINER; ++ } ++ } ++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packetplayinsetcreativeslot.getSlotNum(), item); ++ cserver.getPluginManager().callEvent(event); ++ ++ itemstack = CraftItemStack.asNMSCopy(event.getCursor()); ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ // Plugin cleared the id / stacksize checks ++ flag2 = true; ++ break; ++ case DEFAULT: ++ break; ++ case DENY: ++ // Reset the slot ++ if (packetplayinsetcreativeslot.getSlotNum() >= 0) { ++ this.player.connection.send(new PacketPlayOutSetSlot(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packetplayinsetcreativeslot.getSlotNum(), this.player.inventoryMenu.getSlot(packetplayinsetcreativeslot.getSlotNum()).getItem())); ++ this.player.connection.send(new PacketPlayOutSetSlot(-1, this.player.inventoryMenu.incrementStateId(), -1, ItemStack.EMPTY)); ++ } ++ return; ++ } ++ } ++ // CraftBukkit end + + if (flag1 && flag2) { + this.player.inventoryMenu.getSlot(packetplayinsetcreativeslot.getSlotNum()).setByPlayer(itemstack); +@@ -1778,6 +2814,7 @@ + } + + private void updateSignText(PacketPlayInUpdateSign packetplayinupdatesign, List<FilteredText> list) { ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); + WorldServer worldserver = this.player.serverLevel(); + BlockPosition blockposition = packetplayinupdatesign.getPos(); +@@ -1799,7 +2836,17 @@ + @Override + public void handlePlayerAbilities(PacketPlayInAbilities packetplayinabilities) { + PlayerConnectionUtils.ensureRunningOnSameThread(packetplayinabilities, this, this.player.serverLevel()); +- this.player.getAbilities().flying = packetplayinabilities.isFlying() && this.player.getAbilities().mayfly; ++ // CraftBukkit start ++ if (this.player.getAbilities().mayfly && this.player.getAbilities().flying != packetplayinabilities.isFlying()) { ++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.player.getBukkitEntity(), packetplayinabilities.isFlying()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.player.getAbilities().flying = packetplayinabilities.isFlying(); // Actually set the player's flying status ++ } else { ++ this.player.onUpdateAbilities(); // Tell the player their ability was reverted ++ } ++ } ++ // CraftBukkit end + } + + @Override +@@ -1858,7 +2905,7 @@ + if (!this.waitingForSwitchToConfig) { + throw new IllegalStateException("Client acknowledged config, but none was requested"); + } else { +- this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))); ++ this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit + } + } + |