diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch similarity index 51% rename from paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch index a4f704f9e9..5ba39612d8 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/LegacyQueryHandler.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/LegacyQueryHandler.java.patch @@ -1,105 +1,117 @@ --- a/net/minecraft/server/network/LegacyQueryHandler.java +++ b/net/minecraft/server/network/LegacyQueryHandler.java -@@ -15,6 +15,7 @@ - +@@ -14,6 +_,7 @@ + public class LegacyQueryHandler extends ChannelInboundHandlerAdapter { private static final Logger LOGGER = LogUtils.getLogger(); private final ServerInfo server; + private ByteBuf buf; // Paper public LegacyQueryHandler(ServerInfo server) { this.server = server; -@@ -23,6 +24,16 @@ - public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) { - ByteBuf bytebuf = (ByteBuf) object; - +@@ -22,6 +_,17 @@ + @Override + public void channelRead(ChannelHandlerContext context, Object message) { + ByteBuf byteBuf = (ByteBuf)message; + // Paper start - Make legacy ping handler more reliable + if (this.buf != null) { + try { -+ readLegacy1_6(channelhandlercontext, bytebuf); ++ readLegacy1_6(context, byteBuf); + } finally { -+ bytebuf.release(); ++ byteBuf.release(); + } + return; + } -+ // Paper end - bytebuf.markReaderIndex(); ++ // Paper end - Make legacy ping handler more reliable ++ + byteBuf.markReaderIndex(); boolean flag = true; -@@ -34,11 +45,23 @@ +@@ -33,9 +_,21 @@ - SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress(); - int i = bytebuf.readableBytes(); -- String s; -+ String s = null; // Paper -+ // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper + SocketAddress socketAddress = context.channel().remoteAddress(); + int i = byteBuf.readableBytes(); ++ String string = null; // Paper ++ // org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketAddress, this.server.getMotd(), this.server.getPlayerCount(), this.server.getMaxPlayers()); // CraftBukkit // Paper + com.destroystokyo.paper.event.server.PaperServerListPingEvent event; // Paper - if (i == 0) { -- LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress); -- s = LegacyQueryHandler.createVersion0Response(this.server); -+ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: ""); // Paper - Respect logIPs option -+ +- LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress); +- String string = createVersion0Response(this.server); ++ LOGGER.debug("Ping: (<1.3.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress: ""); // Paper - Respect logIPs option + // Paper start - Call PaperServerListPingEvent and use results -+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 39, null); ++ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 39, null); + if (event == null) { -+ channelhandlercontext.close(); -+ bytebuf.release(); ++ context.close(); ++ byteBuf.release(); + flag = false; + return; + } -+ s = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); -+ // Paper end - LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); ++ string = String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", com.destroystokyo.paper.network.PaperLegacyStatusClient.getUnformattedMotd(event), event.getNumPlayers(), event.getMaxPlayers()); ++ // Paper end - Call PaperServerListPingEvent and use results + sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string)); } else { - if (bytebuf.readUnsignedByte() != 1) { -@@ -46,16 +69,35 @@ + if (byteBuf.readUnsignedByte() != 1) { +@@ -43,16 +_,35 @@ } - if (bytebuf.isReadable()) { -- if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) { -- return; + if (byteBuf.isReadable()) { +- if (!readCustomPayloadPacket(byteBuf)) { + // Paper start - Replace with improved version below -+ if (bytebuf.readUnsignedByte() != 250) { -+ s = this.readLegacy1_6(channelhandlercontext, bytebuf); -+ if (s == null) { ++ if (byteBuf.readUnsignedByte() != LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_ID) { ++ string = this.readLegacy1_6(context, byteBuf); ++ if (string == null) { + return; + } - } -- -- LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress); -+ // if (!LegacyQueryHandler.readCustomPayloadPacket(bytebuf)) { ++ } ++ // if (!readCustomPayloadPacket(byteBuf)) { + // return; + // } -+ // -+ // LegacyQueryHandler.LOGGER.debug("Ping: (1.6) from {}", socketaddress); -+ // Paper end - } else { -- LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress); -+ LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketaddress: ""); // Paper - Respect logIPs option - } - -- s = LegacyQueryHandler.createVersion1Response(this.server); -+ if (s == null) { -+ // Paper start - Call PaperServerListPingEvent and use results -+ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketaddress, 127, null); // Paper -+ if (event == null) { -+ channelhandlercontext.close(); -+ bytebuf.release(); -+ flag = false; -+ return; -+ } -+ s = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", new Object[] { event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()}); // CraftBukkit -+ // Paper end ++ ++ // LOGGER.debug("Ping: (1.6) from {}", socketAddress); ++ // Paper end - Replace with improved version below ++ } else { ++ LOGGER.debug("Ping: (1.4-1.5.x) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? socketAddress : ""); // Paper - Respect logIPs option + } - LegacyQueryHandler.sendFlushAndClose(channelhandlercontext, LegacyQueryHandler.createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); ++ ++ if (string == null) { ++ // Paper start - Call PaperServerListPingEvent and use results ++ event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest(net.minecraft.server.MinecraftServer.getServer(), (java.net.InetSocketAddress) socketAddress, 127, null); // Paper ++ if (event == null) { ++ context.close(); ++ byteBuf.release(); ++ flag = false; + return; + } +- +- LOGGER.debug("Ping: (1.6) from {}", socketAddress); +- } else { +- LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress); ++ string = String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), this.server.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); // CraftBukkit ++ // Paper end - Call PaperServerListPingEvent and use results + } +- +- String string = createVersion1Response(this.server); + sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string)); } -@@ -106,14 +148,110 @@ +@@ -95,21 +_,97 @@ } } - private static String createVersion0Response(ServerInfo server) { -- return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); +- return String.format(Locale.ROOT, "%s§%d§%d", server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); +- } +- +- private static String createVersion1Response(ServerInfo server) { +- return String.format( +- Locale.ROOT, +- "§1\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", +- 127, +- server.getServerVersion(), +- server.getMotd(), +- server.getPlayerCount(), +- server.getMaxPlayers() +- ); +- } + // Paper start + private static String readLegacyString(ByteBuf buf) { + int size = buf.readShort() * Character.BYTES; @@ -110,10 +122,8 @@ + String result = buf.toString(buf.readerIndex(), size, java.nio.charset.StandardCharsets.UTF_16BE); + buf.skipBytes(size); // toString doesn't increase readerIndex automatically + return result; - } - -- private static String createVersion1Response(ServerInfo server) { -- return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, server.getServerVersion(), server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); ++ } ++ + private String readLegacy1_6(ChannelHandlerContext ctx, ByteBuf part) { + ByteBuf buf = this.buf; + @@ -130,12 +140,12 @@ + return null; + } + -+ String s = readLegacyString(buf); -+ if (s == null) { ++ String string = readLegacyString(buf); ++ if (string == null) { + return null; + } + -+ if (!s.equals("MC|PingHost")) { ++ if (!string.equals(LegacyProtocolUtils.CUSTOM_PAYLOAD_PACKET_PING_CHANNEL)) { + removeHandler(ctx); + return null; + } @@ -144,15 +154,14 @@ + return null; + } + -+ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer(); + int protocolVersion = buf.readByte(); + String host = readLegacyString(buf); + if (host == null) { + removeHandler(ctx); + return null; + } -+ int port = buf.readInt(); + ++ int port = buf.readInt(); + if (buf.isReadable()) { + removeHandler(ctx); + return null; @@ -163,9 +172,10 @@ + + LOGGER.debug("Ping: (1.6) from {}", net.minecraft.server.MinecraftServer.getServer().logIPs() ? ctx.channel().remoteAddress(): ""); // Paper - Respect logIPs option + ++ net.minecraft.server.MinecraftServer server = net.minecraft.server.MinecraftServer.getServer(); + java.net.InetSocketAddress virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(host, port); + com.destroystokyo.paper.event.server.PaperServerListPingEvent event = com.destroystokyo.paper.network.PaperLegacyStatusClient.processRequest( -+ server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); ++ server, (java.net.InetSocketAddress) ctx.channel().remoteAddress(), protocolVersion, virtualHost); + if (event == null) { + ctx.close(); + return null; @@ -174,8 +184,8 @@ + String response = String.format("\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", event.getProtocolVersion(), event.getVersion(), + com.destroystokyo.paper.network.PaperLegacyStatusClient.getMotd(event), event.getNumPlayers(), event.getMaxPlayers()); + return response; - } - ++ } ++ + private void removeHandler(ChannelHandlerContext ctx) { + ByteBuf buf = this.buf; + this.buf = null; @@ -193,19 +203,6 @@ + } + } + // Paper end -+ -+ // CraftBukkit start -+ private static String createVersion0Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) { -+ return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); -+ // CraftBukkit end -+ } -+ -+ // CraftBukkit start -+ private static String createVersion1Response(ServerInfo serverinfo, org.bukkit.event.server.ServerListPingEvent event) { -+ return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), event.getMotd(), event.getNumPlayers(), event.getMaxPlayers()); -+ // CraftBukkit end -+ } -+ - private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buf) { - context.pipeline().firstContext().writeAndFlush(buf).addListener(ChannelFutureListener.CLOSE); - } + + private static void sendFlushAndClose(ChannelHandlerContext context, ByteBuf buffer) { + context.pipeline().firstContext().writeAndFlush(buffer).addListener(ChannelFutureListener.CLOSE); diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch similarity index 55% rename from paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch index e1ac133048..9c8b0b62af 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/PlayerChunkSender.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/PlayerChunkSender.java.patch @@ -1,26 +1,26 @@ --- a/net/minecraft/server/network/PlayerChunkSender.java +++ b/net/minecraft/server/network/PlayerChunkSender.java -@@ -44,6 +44,11 @@ - public void dropChunk(ServerPlayer player, ChunkPos pos) { - if (!this.pendingChunks.remove(pos.toLong()) && player.isAlive()) { - player.connection.send(new ClientboundForgetLevelChunkPacket(pos)); +@@ -44,6 +_,11 @@ + public void dropChunk(ServerPlayer player, ChunkPos chunkPos) { + if (!this.pendingChunks.remove(chunkPos.toLong()) && player.isAlive()) { + player.connection.send(new ClientboundForgetLevelChunkPacket(chunkPos)); + // Paper start - PlayerChunkUnloadEvent + if (io.papermc.paper.event.packet.PlayerChunkUnloadEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(pos.longKey), player.getBukkitEntity()).callEvent(); ++ new io.papermc.paper.event.packet.PlayerChunkUnloadEvent(player.getBukkitEntity().getWorld().getChunkAt(chunkPos.longKey), player.getBukkitEntity()).callEvent(); + } + // Paper end - PlayerChunkUnloadEvent } } -@@ -75,6 +80,11 @@ +@@ -75,6 +_,11 @@ - private static void sendChunk(ServerGamePacketListenerImpl handler, ServerLevel world, LevelChunk chunk) { - handler.send(new ClientboundLevelChunkWithLightPacket(chunk, world.getLightEngine(), null, null)); + private static void sendChunk(ServerGamePacketListenerImpl packetListener, ServerLevel level, LevelChunk chunk) { + packetListener.send(new ClientboundLevelChunkWithLightPacket(chunk, level.getLightEngine(), null, null)); + // Paper start - PlayerChunkLoadEvent + if (io.papermc.paper.event.packet.PlayerChunkLoadEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), handler.getPlayer().getBukkitEntity()).callEvent(); ++ new io.papermc.paper.event.packet.PlayerChunkLoadEvent(new org.bukkit.craftbukkit.CraftChunk(chunk), packetListener.getPlayer().getBukkitEntity()).callEvent(); + } + // Paper end - PlayerChunkLoadEvent - ChunkPos chunkPos = chunk.getPos(); - DebugPackets.sendPoiPacketsForChunk(world, chunkPos); + ChunkPos pos = chunk.getPos(); + DebugPackets.sendPoiPacketsForChunk(level, pos); } diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch similarity index 69% rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index 4ba7794d53..3d98b71ca6 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -1,46 +1,69 @@ --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -4,11 +4,13 @@ - import com.mojang.logging.LogUtils; - import java.util.Objects; - import javax.annotation.Nullable; -+import net.minecraft.ChatFormatting; - import net.minecraft.CrashReport; - import net.minecraft.CrashReportCategory; - import net.minecraft.ReportedException; - import net.minecraft.Util; - import net.minecraft.network.Connection; -+import net.minecraft.network.ConnectionProtocol; - import net.minecraft.network.DisconnectionDetails; - import net.minecraft.network.PacketSendListener; - import net.minecraft.network.chat.Component; -@@ -22,39 +24,88 @@ - import net.minecraft.network.protocol.common.ServerboundPongPacket; - import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; - import net.minecraft.network.protocol.cookie.ServerboundCookieResponsePacket; -+import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; -+import net.minecraft.resources.ResourceLocation; - import net.minecraft.server.MinecraftServer; - import net.minecraft.server.level.ClientInformation; -+import net.minecraft.server.level.ServerPlayer; - import net.minecraft.util.VisibleForDebug; +@@ -27,30 +_,82 @@ import net.minecraft.util.profiling.Profiler; - import net.minecraft.util.thread.BlockableEventLoop; import org.slf4j.Logger; -public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener { +// CraftBukkit start +import io.netty.buffer.ByteBuf; +import java.util.concurrent.ExecutionException; ++import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; ++import net.minecraft.network.protocol.game.ClientboundSetDefaultSpawnPositionPacket; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerPlayer; +import org.bukkit.craftbukkit.entity.CraftPlayer; -+import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.CraftLocation; +import org.bukkit.craftbukkit.util.Waitable; +import org.bukkit.event.player.PlayerKickEvent; +import org.bukkit.event.player.PlayerResourcePackStatusEvent; - ++ +public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener, CraftPlayer.TransferCookieConnection { ++ // CraftBukkit end + private static final Logger LOGGER = LogUtils.getLogger(); + public static final int LATENCY_CHECK_INTERVAL = 15000; + private static final int CLOSED_LISTENER_TIMEOUT = 15000; + private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout"); + static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); + protected final MinecraftServer server; +- protected final Connection connection; ++ public final Connection connection; // Paper + private final boolean transferred; +- private long keepAliveTime; ++ private long keepAliveTime = Util.getMillis(); // Paper + private boolean keepAlivePending; + private long keepAliveChallenge; + private long closedListenerTime; + private boolean closed = false; + private int latency; + private volatile boolean suspendFlushingOnServerThread = false; ++ // CraftBukkit start ++ protected final ServerPlayer player; ++ protected final org.bukkit.craftbukkit.CraftServer cserver; ++ public boolean processedDisconnect; ++ // CraftBukkit end ++ public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks ++ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit ++ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support + +- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { ++ public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { // CraftBukkit + this.server = server; + this.connection = connection; + this.keepAliveTime = Util.getMillis(); + this.latency = cookie.latency(); + this.transferred = cookie.transferred(); +- } ++ // CraftBukkit start - add fields and methods ++ this.player = player; ++ this.player.transferCookieConnection = this; ++ this.cserver = server.server; ++ } ++ ++ public CraftPlayer getCraftPlayer() { ++ return this.player == null ? null : this.player.getBukkitEntity(); ++ } + + @Override + public boolean isTransferred() { @@ -62,97 +85,45 @@ + this.disconnect(reason, cause); // Paper - kick event causes + } + // CraftBukkit end - private static final Logger LOGGER = LogUtils.getLogger(); - public static final int LATENCY_CHECK_INTERVAL = 15000; - private static final int CLOSED_LISTENER_TIMEOUT = 15000; - private static final Component TIMEOUT_DISCONNECTION_MESSAGE = Component.translatable("disconnect.timeout"); - static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); - protected final MinecraftServer server; -- protected final Connection connection; -+ public final Connection connection; // Paper - private final boolean transferred; -- private long keepAliveTime; -+ private long keepAliveTime = Util.getMillis(); // Paper - private boolean keepAlivePending; - private long keepAliveChallenge; - private long closedListenerTime; - private boolean closed = false; - private int latency; - private volatile boolean suspendFlushingOnServerThread = false; -+ public final java.util.Map packCallbacks = new java.util.concurrent.ConcurrentHashMap<>(); // Paper - adventure resource pack callbacks -+ private static final long KEEPALIVE_LIMIT = Long.getLong("paper.playerconnection.keepalive", 30) * 1000; // Paper - provide property to set keepalive limit -+ protected static final ResourceLocation MINECRAFT_BRAND = ResourceLocation.withDefaultNamespace("brand"); // Paper - Brand support -- public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) { -- this.server = server; -- this.connection = connection; -+ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit -+ this.server = minecraftserver; -+ this.connection = networkmanager; - this.keepAliveTime = Util.getMillis(); -- this.latency = clientData.latency(); -- this.transferred = clientData.transferred(); -+ this.latency = commonlistenercookie.latency(); -+ this.transferred = commonlistenercookie.transferred(); -+ // CraftBukkit start - add fields and methods -+ this.player = player; -+ this.player.transferCookieConnection = this; -+ this.cserver = minecraftserver.server; - } -+ protected final ServerPlayer player; -+ protected final org.bukkit.craftbukkit.CraftServer cserver; -+ public boolean processedDisconnect; - -+ public CraftPlayer getCraftPlayer() { -+ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity(); -+ // CraftBukkit end -+ } -+ private void close() { if (!this.closed) { - this.closedListenerTime = Util.getMillis(); -@@ -65,6 +116,11 @@ +@@ -61,6 +_,12 @@ @Override - public void onDisconnect(DisconnectionDetails info) { + public void onDisconnect(DisconnectionDetails details) { + // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(info, null); ++ this.onDisconnect(details, null); + } ++ + public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { + // Paper end - Fix kick event leave message not being sent if (this.isSingleplayerOwner()) { - ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); + LOGGER.info("Stopping singleplayer server as player logged out"); this.server.halt(false); -@@ -80,13 +136,14 @@ - - @Override - public void handleKeepAlive(ServerboundKeepAlivePacket packet) { -+ //PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // CraftBukkit // Paper - handle ServerboundKeepAlivePacket async - if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { - int i = (int) (Util.getMillis() - this.keepAliveTime); - +@@ -80,7 +_,7 @@ this.latency = (this.latency * 3 + i) / 4; this.keepAlivePending = false; } else if (!this.isSingleplayerOwner()) { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnectAsync(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect +- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnectAsync(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - add proper async disconnect } - } -@@ -94,38 +151,127 @@ - @Override - public void handlePong(ServerboundPongPacket packet) {} -+ // CraftBukkit start -+ private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); -+ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); +@@ -88,37 +_,124 @@ + public void handlePong(ServerboundPongPacket packet) { + } + ++ private static final ResourceLocation CUSTOM_REGISTER = ResourceLocation.withDefaultNamespace("register"); // CraftBukkit ++ private static final ResourceLocation CUSTOM_UNREGISTER = ResourceLocation.withDefaultNamespace("unregister"); // CraftBukkit + @Override -- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {} -+ public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { + public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { +- } ++ // CraftBukkit start + // Paper start - Brand support -+ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload brandPayload) { -+ this.player.clientBrandName = brandPayload.brand(); ++ if (packet.payload() instanceof net.minecraft.network.protocol.common.custom.BrandPayload(String brand)) { ++ this.player.clientBrandName = brand; + } + // Paper end - Brand support + if (!(packet.payload() instanceof DiscardedPayload)) { @@ -161,7 +132,7 @@ + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + ResourceLocation identifier = packet.payload().type().id(); + ByteBuf payload = ((DiscardedPayload)packet.payload()).data(); - ++ + if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_REGISTER)) { + try { + String channels = payload.toString(com.google.common.base.Charsets.UTF_8); @@ -169,7 +140,7 @@ + this.getCraftPlayer().addChannel(channel); + } + } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn't register custom payload", ex); + this.disconnect(Component.literal("Invalid payload REGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else if (identifier.equals(ServerCommonPacketListenerImpl.CUSTOM_UNREGISTER)) { @@ -179,7 +150,7 @@ + this.getCraftPlayer().removeChannel(channel); + } + } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn't unregister custom payload", ex); + this.disconnect(Component.literal("Invalid payload UNREGISTER!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } else { @@ -197,26 +168,26 @@ + // Paper end - Brand support + this.cserver.getMessenger().dispatchIncomingMessage(this.player.getBukkitEntity(), identifier.toString(), data); + } catch (Exception ex) { -+ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn't dispatch custom payload", ex); + this.disconnect(Component.literal("Invalid custom payload!"), PlayerKickEvent.Cause.INVALID_PAYLOAD); // Paper - kick event cause + } + } -+ + } + + public final boolean isDisconnected() { + return (!this.player.joining && !this.connection.isConnected()) || this.processedDisconnect; // Paper - Fix duplication bugs + } + // CraftBukkit end -+ + @Override public void handleResourcePackResponse(ServerboundResourcePackPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); + PacketUtils.ensureRunningOnSameThread(packet, this, this.server); if (packet.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { - ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); -- this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); -+ this.disconnect((Component) Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause - } + LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), packet.id()); +- this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); +- } ++ this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect"), PlayerKickEvent.Cause.RESOURCE_PACK_REJECTION); // Paper - kick event cause ++ } + // Paper start - adventure pack callbacks + // call the callbacks before the previously-existing event so the event has final say + final net.kyori.adventure.resource.ResourcePackCallback callback; @@ -231,43 +202,41 @@ + // Paper end + // Paper start - store last pack status + PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action().ordinal()]; -+ player.getBukkitEntity().resourcePackStatus = packStatus; ++ this.player.getBukkitEntity().resourcePackStatus = packStatus; + this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packet.id(), packStatus)); // CraftBukkit + // Paper end - store last pack status - } @Override public void handleCookieResponse(ServerboundCookieResponsePacket packet) { -- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); +- this.disconnect(DISCONNECT_UNEXPECTED_QUERY); + // CraftBukkit start -+ PacketUtils.ensureRunningOnSameThread(packet, this, (BlockableEventLoop) this.server); ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); + if (this.player.getBukkitEntity().handleCookieResponse(packet)) { + return; + } + // CraftBukkit end -+ this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause ++ this.disconnect(DISCONNECT_UNEXPECTED_QUERY, PlayerKickEvent.Cause.INVALID_COOKIE); // Paper - kick event cause } protected void keepConnectionAlive() { Profiler.get().push("keepAlive"); -- long i = Util.getMillis(); +- long millis = Util.getMillis(); +- if (!this.isSingleplayerOwner() && millis - this.keepAliveTime >= 15000L) { +- if (this.keepAlivePending) { +- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE); +- } else if (this.checkIfClosed(millis)) { + // Paper start - give clients a longer time to respond to pings as per pre 1.12.2 timings + // This should effectively place the keepalive handling back to "as it was" before 1.12.2 + long currentTime = Util.getMillis(); + long elapsedTime = currentTime - this.keepAliveTime; - -- if (!this.isSingleplayerOwner() && i - this.keepAliveTime >= 15000L) { -- if (this.keepAlivePending) { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -- } else if (this.checkIfClosed(i)) { + if (!this.isSingleplayerOwner() && elapsedTime >= 15000L) { // Paper - use vanilla's 15000L between keep alive packets + if (this.keepAlivePending && !this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // Paper - check keepalive limit, don't fire if already disconnected -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause ++ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause + } else if (this.checkIfClosed(currentTime)) { // Paper this.keepAlivePending = true; -- this.keepAliveTime = i; -- this.keepAliveChallenge = i; +- this.keepAliveTime = millis; +- this.keepAliveChallenge = millis; + this.keepAliveTime = currentTime; + this.keepAliveChallenge = currentTime; this.send(new ClientboundKeepAlivePacket(this.keepAliveChallenge)); @@ -277,31 +246,30 @@ Profiler.get().pop(); } -@@ -133,7 +279,7 @@ +@@ -126,7 +_,7 @@ private boolean checkIfClosed(long time) { if (this.closed) { if (time - this.closedListenerTime >= 15000L) { -- this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); -+ this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause +- this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE); ++ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE, PlayerKickEvent.Cause.TIMEOUT); // Paper - kick event cause } return false; -@@ -156,6 +302,14 @@ +@@ -149,6 +_,13 @@ } - public void send(Packet packet, @Nullable PacketSendListener callbacks) { + public void send(Packet packet, @Nullable PacketSendListener listener) { + // CraftBukkit start + if (packet == null || this.processedDisconnect) { // Spigot + return; -+ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) { -+ ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet; -+ this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld()); ++ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket defaultSpawnPositionPacket) { ++ this.player.compassTarget = CraftLocation.toBukkit(defaultSpawnPositionPacket.getPos(), this.getCraftPlayer().getWorld()); + } + // CraftBukkit end if (packet.isTerminal()) { this.close(); } -@@ -175,22 +329,109 @@ +@@ -165,19 +_,108 @@ } } @@ -309,27 +277,28 @@ + public void disconnect(final net.kyori.adventure.text.Component reason) { + this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); + } ++ + public void disconnect(final net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { + this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); -+ // Paper end - kick event causes + } -+ // Paper end - adventure + ++ // Paper end - adventure + @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - kick event causes public void disconnect(Component reason) { - this.disconnect(new DisconnectionDetails(reason)); +- } +- +- public void disconnect(DisconnectionDetails disconnectionDetails) { + // Paper start - kick event causes + this.disconnect(reason, PlayerKickEvent.Cause.UNKNOWN); - } ++ } ++ + public void disconnect(final Component reason, PlayerKickEvent.Cause cause) { + this.disconnect(new DisconnectionDetails(reason), cause); + // Paper end - kick event causes + } - -- public void disconnect(DisconnectionDetails disconnectionInfo) { -- this.connection.send(new ClientboundDisconnectPacket(disconnectionInfo.reason()), PacketSendListener.thenRun(() -> { -- this.connection.disconnect(disconnectionInfo); -+ public void disconnect(DisconnectionDetails disconnectionInfo, PlayerKickEvent.Cause cause) { // Paper - kick event cause ++ ++ public void disconnect(DisconnectionDetails disconnectionDetails, PlayerKickEvent.Cause cause) { // Paper - kick event cause + // CraftBukkit start - fire PlayerKickEvent + if (this.processedDisconnect) { + return; @@ -338,7 +307,7 @@ + Waitable waitable = new Waitable() { + @Override + protected Object evaluate() { -+ ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); // Paper - kick event causes ++ ServerCommonPacketListenerImpl.this.disconnect(disconnectionDetails, cause); // Paper - kick event causes + return null; + } + }; @@ -357,7 +326,7 @@ + + net.kyori.adventure.text.Component leaveMessage = net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? this.player.getBukkitEntity().displayName() : net.kyori.adventure.text.Component.text(this.player.getScoreboardName())); // Paper - Adventure + -+ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionInfo.reason()), leaveMessage, cause); // Paper - adventure & kick event causes ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), io.papermc.paper.adventure.PaperAdventure.asAdventure(disconnectionDetails.reason()), leaveMessage, cause); // Paper - adventure & kick event causes + + if (this.cserver.getServer().isRunning()) { + this.cserver.getPluginManager().callEvent(event); @@ -368,26 +337,26 @@ + return; + } + // Send the possibly modified leave message -+ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionInfo.report(), disconnectionInfo.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message ++ this.disconnect0(new DisconnectionDetails(io.papermc.paper.adventure.PaperAdventure.asVanilla(event.reason()), disconnectionDetails.report(), disconnectionDetails.bugReportLink()), event.leaveMessage()); // Paper - Adventure & use kick event leave message + } + -+ private void disconnect0(DisconnectionDetails disconnectiondetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message ++ private void disconnect0(DisconnectionDetails disconnectionDetails, @Nullable net.kyori.adventure.text.Component leaveMessage) { // Paper - use kick event leave message + // CraftBukkit end + this.player.quitReason = org.bukkit.event.player.PlayerQuitEvent.QuitReason.KICKED; // Paper - Add API for quit reason -+ this.connection.send(new ClientboundDisconnectPacket(disconnectiondetails.reason()), PacketSendListener.thenRun(() -> { -+ this.connection.disconnect(disconnectiondetails); - })); -+ this.onDisconnect(disconnectiondetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message - this.connection.setReadOnly(); - MinecraftServer minecraftserver = this.server; - Connection networkmanager = this.connection; - - Objects.requireNonNull(this.connection); -- minecraftserver.executeBlocking(networkmanager::handleDisconnection); + this.connection + .send( + new ClientboundDisconnectPacket(disconnectionDetails.reason()), + PacketSendListener.thenRun(() -> this.connection.disconnect(disconnectionDetails)) + ); +- this.connection.setReadOnly(); +- this.server.executeBlocking(this.connection::handleDisconnection); +- } ++ this.onDisconnect(disconnectionDetails, leaveMessage); // CraftBukkit - fire quit instantly // Paper - use kick event leave message ++ this.connection.setReadOnly(); + // CraftBukkit - Don't wait -+ minecraftserver.scheduleOnMain(networkmanager::handleDisconnection); // Paper - } - ++ this.server.scheduleOnMain(this.connection::handleDisconnection); // Paper ++ } ++ + // Paper start - add proper async disconnect + public void disconnectAsync(net.kyori.adventure.text.Component reason, PlayerKickEvent.Cause cause) { + this.disconnectAsync(io.papermc.paper.adventure.PaperAdventure.asVanilla(reason), cause); @@ -402,6 +371,7 @@ + this.disconnect(disconnectionInfo, cause); + return; + } ++ + this.connection.setReadOnly(); + this.server.scheduleOnMain(() -> { + ServerCommonPacketListenerImpl.this.disconnect(disconnectionInfo, cause); @@ -412,7 +382,6 @@ + }); + } + // Paper end - add proper async disconnect -+ + protected boolean isSingleplayerOwner() { return this.server.isSingleplayerOwner(this.playerProfile()); - } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch new file mode 100644 index 0000000000..08bccdf22e --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -0,0 +1,71 @@ +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -48,8 +_,10 @@ + @Nullable + private SynchronizeRegistriesTask synchronizeRegistriesTask; + +- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { +- super(server, connection, cookie); ++ // CraftBukkit start ++ public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie, ServerPlayer player) { ++ super(server, connection, cookie, player); ++ // CraftBukkit end + this.gameProfile = cookie.gameProfile(); + this.clientInformation = cookie.clientInformation(); + } +@@ -61,6 +_,10 @@ + + @Override + public void onDisconnect(DisconnectionDetails details) { ++ // Paper start - Debugging ++ if (this.server.isDebugging()) { ++ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, details.reason().getString(), this.currentTask != null ? this.currentTask.type().id() : "null"); ++ } else // Paper end + LOGGER.info("{} lost connection: {}", this.gameProfile, details.reason().getString()); + super.onDisconnect(details); + } +@@ -73,6 +_,12 @@ + public void startConfiguration() { + this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName()))); + ServerLinks serverLinks = this.server.serverLinks(); ++ // CraftBukkit start ++ org.bukkit.craftbukkit.CraftServerLinks wrapper = new org.bukkit.craftbukkit.CraftServerLinks(serverLinks); ++ org.bukkit.event.player.PlayerLinksSendEvent event = new org.bukkit.event.player.PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper); ++ this.cserver.getPluginManager().callEvent(event); ++ serverLinks = wrapper.getServerLinks(); ++ // CraftBukkit end + if (!serverLinks.isEmpty()) { + this.send(new ClientboundServerLinksPacket(serverLinks.untrust())); + } +@@ -105,6 +_,7 @@ + @Override + public void handleClientInformation(ServerboundClientInformationPacket packet) { + this.clientInformation = packet.information(); ++ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper + } + + @Override +@@ -139,16 +_,21 @@ + return; + } + +- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile); ++ Component component = null; // CraftBukkit - login checks already completed + if (component != null) { + this.disconnect(component); + return; + } + +- ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation); ++ ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit + playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation)); + } catch (Exception var5) { + LOGGER.error("Couldn't place player in world", (Throwable)var5); ++ // Paper start - Debugging ++ if (this.server.isDebugging()) { ++ var5.printStackTrace(); ++ } ++ // Paper end - Debugging + this.connection.send(new ClientboundDisconnectPacket(DISCONNECT_REASON_INVALID_DATA)); + this.connection.disconnect(DISCONNECT_REASON_INVALID_DATA); + } diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch new file mode 100644 index 0000000000..fa6e15d0d7 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConnectionListener.java.patch @@ -0,0 +1,154 @@ +--- a/net/minecraft/server/network/ServerConnectionListener.java ++++ b/net/minecraft/server/network/ServerConnectionListener.java +@@ -49,10 +_,10 @@ + public class ServerConnectionListener { + private static final Logger LOGGER = LogUtils.getLogger(); + public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize( +- () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).build()) ++ () -> new NioEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper + ); + public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize( +- () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()) ++ () -> new EpollEventLoopGroup(0, new ThreadFactoryBuilder().setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()) // Paper + ); + final MinecraftServer server; + public volatile boolean running; +@@ -64,12 +_,35 @@ + this.running = true; + } + ++ // Paper start - prevent blocking on adding a new connection while the server is ticking ++ private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); ++ ++ private final void addPending() { ++ Connection connection; ++ while ((connection = this.pending.poll()) != null) { ++ this.connections.add(connection); ++ } ++ } ++ // Paper end - prevent blocking on adding a new connection while the server is ticking ++ + public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { ++ // Paper start - Unix domain socket support ++ this.startTcpServerListener(new java.net.InetSocketAddress(address, port)); ++ } ++ ++ public void startTcpServerListener(SocketAddress address) throws IOException { ++ // Paper end - Unix domain socket support + synchronized (this.channels) { +- Class clazz; ++ Class clazz; // Paper - Unix domain socket support + EventLoopGroup eventLoopGroup; + if (Epoll.isAvailable() && this.server.isEpollEnabled()) { ++ // Paper start - Unix domain socket support ++ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { ++ clazz = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; ++ } else { + clazz = EpollServerSocketChannel.class; ++ } ++ // Paper end - Unix domain socket support + eventLoopGroup = SERVER_EPOLL_EVENT_GROUP.get(); + LOGGER.info("Using epoll channel type"); + } else { +@@ -78,6 +_,12 @@ + LOGGER.info("Using default channel type"); + } + ++ // Paper start - Warn people with console access that HAProxy is in use. ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled."); ++ } ++ // Paper end - Warn people with console access that HAProxy is in use. ++ + this.channels + .add( + new ServerBootstrap() +@@ -101,22 +_,64 @@ + Connection connection = (Connection)(rateLimitPacketsPerSecond > 0 + ? new RateKickingConnection(rateLimitPacketsPerSecond) + : new Connection(PacketFlow.SERVERBOUND)); +- ServerConnectionListener.this.connections.add(connection); ++ // ServerConnectionListener.this.connections.add(connection); ++ // Paper start - Add support for Proxy Protocol ++ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { ++ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); ++ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { ++ @Override ++ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ++ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { ++ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { ++ String realaddress = message.sourceAddress(); ++ int realport = message.sourcePort(); ++ ++ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); ++ ++ Connection connection = (Connection) channel.pipeline().get("packet_handler"); ++ connection.address = socketaddr; ++ // Paper start - Add API to get player's proxy address ++ final String proxyAddress = message.destinationAddress(); ++ final int proxyPort = message.destinationPort(); ++ ++ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort); ++ // Paper end - Add API to get player's proxy address ++ } ++ } else { ++ super.channelRead(ctx, msg); ++ } ++ } ++ }); ++ } ++ // Paper end - Add support for proxy protocol ++ pending.add(connection); // Paper - prevent blocking on adding a new connection while the server is ticking + connection.configurePacketHandler(channelPipeline); + connection.setListenerForServerboundHandshake( + new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection) + ); ++ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners + } + } + ) + .group(eventLoopGroup) +- .localAddress(address, port) ++ .localAddress(address) // Paper - Unix domain socket support ++ .option(ChannelOption.AUTO_READ, false) // CraftBukkit + .bind() + .syncUninterruptibly() + ); + } + } + ++ // CraftBukkit start ++ public void acceptConnections() { ++ synchronized (this.channels) { ++ for (ChannelFuture future : this.channels) { ++ future.channel().config().setAutoRead(true); ++ } ++ } ++ } ++ // CraftBukkit end ++ + public SocketAddress startMemoryChannel() { + ChannelFuture channelFuture; + synchronized (this.channels) { +@@ -161,6 +_,13 @@ + + public void tick() { + synchronized (this.connections) { ++ // Spigot start ++ this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking ++ // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order ++ if (org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0) { ++ Collections.shuffle(this.connections); ++ } ++ // Spigot end + Iterator iterator = this.connections.iterator(); + + while (iterator.hasNext()) { +@@ -180,6 +_,7 @@ + connection.setReadOnly(); + } + } else { ++ if (connection.preparing) continue; // Spigot - Fix a race condition where a NetworkManager could be unregistered just before connection + iterator.remove(); + connection.handleDisconnection(); + } diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch similarity index 60% rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 6c1745ae43..80b64ccf25 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -1,93 +1,34 @@ --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -45,6 +45,7 @@ - import net.minecraft.network.Connection; - import net.minecraft.network.DisconnectionDetails; - import net.minecraft.network.TickablePacketListener; -+import net.minecraft.network.chat.ChatDecorator; - import net.minecraft.network.chat.ChatType; - import net.minecraft.network.chat.Component; - import net.minecraft.network.chat.LastSeenMessages; -@@ -65,12 +66,15 @@ - import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket; - import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; - import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; -+import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket; - import net.minecraft.network.protocol.game.ClientboundDisguisedChatPacket; - import net.minecraft.network.protocol.game.ClientboundMoveVehiclePacket; - import net.minecraft.network.protocol.game.ClientboundPlaceGhostRecipePacket; - import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; - import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; - import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; -+import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; -+import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; - import net.minecraft.network.protocol.game.ClientboundSetHeldSlotPacket; - import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket; - import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; -@@ -148,14 +152,13 @@ - import net.minecraft.world.entity.ExperienceOrb; - import net.minecraft.world.entity.HasCustomInventoryScreen; - import net.minecraft.world.entity.LivingEntity; -+import net.minecraft.world.entity.Mob; - import net.minecraft.world.entity.MoverType; - import net.minecraft.world.entity.PlayerRideableJumping; - import net.minecraft.world.entity.PositionMoveRotation; - import net.minecraft.world.entity.Relative; --import net.minecraft.world.entity.item.ItemEntity; - import net.minecraft.world.entity.player.ChatVisiblity; - import net.minecraft.world.entity.player.Inventory; --import net.minecraft.world.entity.player.Player; - import net.minecraft.world.entity.player.PlayerModelPart; - import net.minecraft.world.entity.player.ProfilePublicKey; - import net.minecraft.world.entity.projectile.AbstractArrow; -@@ -176,6 +179,7 @@ - import net.minecraft.world.item.crafting.RecipeHolder; - import net.minecraft.world.item.crafting.RecipeManager; - import net.minecraft.world.level.BaseCommandBlock; -+import net.minecraft.world.level.ClipContext; - import net.minecraft.world.level.GameRules; - import net.minecraft.world.level.GameType; - import net.minecraft.world.level.Level; -@@ -192,11 +196,72 @@ - import net.minecraft.world.level.block.state.BlockState; - import net.minecraft.world.phys.AABB; - import net.minecraft.world.phys.BlockHitResult; -+import net.minecraft.world.phys.HitResult; - import net.minecraft.world.phys.Vec3; - import net.minecraft.world.phys.shapes.BooleanOp; - import net.minecraft.world.phys.shapes.Shapes; +@@ -193,6 +_,59 @@ import net.minecraft.world.phys.shapes.VoxelShape; -+import org.bukkit.NamespacedKey; import org.slf4j.Logger; -+ + +// CraftBukkit start +import io.papermc.paper.adventure.ChatProcessor; // Paper +import io.papermc.paper.adventure.PaperAdventure; // Paper +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.protocol.game.ClientboundContainerSetSlotPacket; +import net.minecraft.network.chat.OutgoingChatMessage; +import net.minecraft.world.entity.animal.Bucketable; +import net.minecraft.world.entity.animal.allay.Allay; -+import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.Slot; -+import net.minecraft.world.item.crafting.RecipeHolder; ++import net.minecraft.world.level.ClipContext; ++import net.minecraft.world.phys.HitResult; +import org.bukkit.Location; ++import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.CraftInput; +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.inventory.CraftItemType; -+import org.bukkit.craftbukkit.inventory.CraftRecipe; -+import org.bukkit.craftbukkit.util.CraftChatMessage; +import org.bukkit.craftbukkit.util.CraftLocation; -+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; @@ -98,8 +39,6 @@ +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.PlayerInputEvent; @@ -114,14 +53,14 @@ +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 ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener { - -@@ -212,7 +277,9 @@ ++ + public class ServerGamePacketListenerImpl + extends ServerCommonPacketListenerImpl + implements ServerGamePacketListener, +@@ -210,7 +_,9 @@ private int tickCount; private int ackBlockChangesUpTo = -1; private final TickThrottler chatSpamThrottler = new TickThrottler(20, 200); @@ -131,33 +70,11 @@ private double firstGoodX; private double firstGoodY; private double firstGoodZ; -@@ -240,14 +307,16 @@ +@@ -236,22 +_,39 @@ + private int receivedMovePacketCount; + private int knownMovePacketCount; private boolean receivedMovementThisTick; - @Nullable - private RemoteChatSession chatSession; -+ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - private SignedMessageChain.Decoder signedMessageDecoder; - private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); - private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); - private final FutureChain chatMessageChain; - private boolean waitingForSwitchToConfig; -+ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length - - public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie clientData) { -- super(server, connection, clientData); -+ super(server, connection, clientData, player); // CraftBukkit - this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); - this.player = player; - player.connection = this; -@@ -256,9 +325,25 @@ - - Objects.requireNonNull(server); - this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, server::enforceSecureProfile); -- this.chatMessageChain = new FutureChain(server); -+ this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat - } - -+ // CraftBukkit start - add fields and methods ++ // CraftBukkit start - add fields + private int lastTick = MinecraftServer.currentTick; + private int allowedPlayerTicks = 1; + private int lastDropTick = MinecraftServer.currentTick; @@ -172,53 +89,72 @@ + private float lastYaw = Float.MAX_VALUE; + private boolean justTeleported = false; + // CraftBukkit end -+ + @Nullable + private RemoteChatSession chatSession; ++ private boolean hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins + private SignedMessageChain.Decoder signedMessageDecoder; + private final LastSeenMessagesValidator lastSeenMessages = new LastSeenMessagesValidator(20); + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; ++ private static final int MAX_SIGN_LINE_LENGTH = Integer.getInteger("Paper.maxSignLength", 80); // Paper - Limit client sign length + + public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) { +- super(server, connection, cookie); ++ super(server, connection, cookie, player); // CraftBukkit + this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); + this.player = player; + player.connection = this; + player.getTextFilter().join(); + this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile); +- this.chatMessageChain = new FutureChain(server); ++ this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat + } + @Override - public void tick() { - if (this.ackBlockChangesUpTo > -1) { -@@ -277,7 +362,7 @@ +@@ -272,7 +_,7 @@ if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { if (++this.aboveGroundTickCount > this.getMaximumFlyingTicks(this.player)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying")); + LOGGER.warn("{} was kicked for floating too long!", this.player.getName().getString()); +- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); + this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingPlayer, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_PLAYER); // Paper - use configurable kick message & kick event cause return; } } else { -@@ -296,7 +381,7 @@ +@@ -291,7 +_,7 @@ if (this.clientVehicleIsFloating && this.lastVehicle.getControllingPassenger() == this.player) { if (++this.aboveGroundVehicleTickCount > this.getMaximumFlyingTicks(this.lastVehicle)) { - ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.flying")); + LOGGER.warn("{} was kicked for floating a vehicle too long!", this.player.getName().getString()); +- this.disconnect(Component.translatable("multiplayer.disconnect.flying")); + this.disconnect(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.flyingVehicle, org.bukkit.event.player.PlayerKickEvent.Cause.FLYING_VEHICLE); // Paper - use configurable kick message & kick event cause return; } } else { -@@ -311,11 +396,21 @@ - +@@ -307,11 +_,20 @@ this.keepConnectionAlive(); this.chatSpamThrottler.tick(); + this.dropSpamThrottler.tick(); + this.tabSpamThrottler.tick(); // Paper - configurable tab spam limits + this.recipeSpamPackets.tick(); // Paper - auto recipe limit - this.dropSpamThrottler.tick(); -- if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling")); -+ if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + if (this.player.getLastActionTime() > 0L + && this.server.getPlayerIdleTimeout() > 0 +- && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L) { +- this.disconnect(Component.translatable("multiplayer.disconnect.idling")); +- } ++ && Util.getMillis() - this.player.getLastActionTime() > this.server.getPlayerIdleTimeout() * 1000L * 60L && !this.player.wonGame) { // Paper - Prevent AFK kick while watching end credits + this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause - } - ++ this.disconnect(Component.translatable("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause ++ } + // Paper start - Prevent causing expired keys from impacting new joins -+ if (!hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { -+ LOGGER.info("Player profile key for {} has expired!", this.player.getName().getString()); -+ hasLoggedExpiry = true; ++ if (!this.hasLoggedExpiry && this.chatSession != null && this.chatSession.profilePublicKey().data().hasExpired()) { ++ LOGGER.info("Player profile key for {} has expired!", this.player.getGameProfile().getName()); ++ this.hasLoggedExpiry = true; + } + // Paper end - Prevent causing expired keys from impacting new joins -+ } - private int getMaximumFlyingTicks(Entity vehicle) { -@@ -376,6 +471,12 @@ + private int getMaximumFlyingTicks(Entity entity) { +@@ -371,6 +_,12 @@ @Override public void handlePlayerInput(ServerboundPlayerInputPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -231,28 +167,21 @@ this.player.setLastClientInput(packet.input()); } -@@ -395,27 +496,84 @@ +@@ -390,17 +_,29 @@ public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause + if (containsInvalidValues(packet.position().x(), packet.position().y(), packet.position().z(), packet.yRot(), packet.xRot())) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_VEHICLE_MOVEMENT); // Paper - kick event cause } else if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) { - Entity entity = this.player.getRootVehicle(); + Entity rootVehicle = this.player.getRootVehicle(); + // Paper start - Don't allow vehicle movement from players while teleporting -+ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || entity.isRemoved()) { ++ if (this.awaitingPositionFromClient != null || this.player.isImmobile() || rootVehicle.isRemoved()) { + return; + } + // Paper end - Don't allow vehicle movement from players while teleporting - - if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { - ServerLevel worldserver = this.player.serverLevel(); -- double d0 = entity.getX(); -- double d1 = entity.getY(); -- double d2 = entity.getZ(); -- double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x()); -- double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y()); -- double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z()); + if (rootVehicle != this.player && rootVehicle.getControllingPassenger() == this.player && rootVehicle == this.lastVehicle) { + ServerLevel serverLevel = this.player.serverLevel(); + // CraftBukkit - store current player position + double prevX = this.player.getX(); + double prevY = this.player.getY(); @@ -260,31 +189,35 @@ + float prevYaw = this.player.getYRot(); + float prevPitch = this.player.getXRot(); + // CraftBukkit end -+ double d0 = entity.getX(); final double fromX = d0; // Paper - OBFHELPER -+ double d1 = entity.getY(); final double fromY = d1; // Paper - OBFHELPER -+ double d2 = entity.getZ(); final double fromZ = d2; // Paper - OBFHELPER -+ double d3 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().x()); final double toX = d3; // Paper - OBFHELPER -+ double d4 = ServerGamePacketListenerImpl.clampVertical(packet.position().y()); final double toY = d4; // Paper - OBFHELPER -+ double d5 = ServerGamePacketListenerImpl.clampHorizontal(packet.position().z()); final double toZ = d5; // Paper - OBFHELPER + double x = rootVehicle.getX(); + double y = rootVehicle.getY(); + double z = rootVehicle.getZ(); +- double d = clampHorizontal(packet.position().x()); +- double d1 = clampVertical(packet.position().y()); +- double d2 = clampHorizontal(packet.position().z()); ++ double d = clampHorizontal(packet.position().x()); final double toX = d; // Paper - OBFHELPER ++ double d1 = clampVertical(packet.position().y()); final double toY = d1; // Paper - OBFHELPER ++ double d2 = clampHorizontal(packet.position().z()); final double toZ = d2; // Paper - OBFHELPER float f = Mth.wrapDegrees(packet.yRot()); float f1 = Mth.wrapDegrees(packet.xRot()); - double d6 = d3 - this.vehicleFirstGoodX; - double d7 = d4 - this.vehicleFirstGoodY; - double d8 = d5 - this.vehicleFirstGoodZ; - double d9 = entity.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; + double d3 = d - this.vehicleFirstGoodX; +@@ -408,7 +_,54 @@ + double d5 = d2 - this.vehicleFirstGoodZ; + double d6 = rootVehicle.getDeltaMovement().lengthSqr(); + double d7 = d3 * d3 + d4 * d4 + d5 * d5; +- if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) { + // Paper start - fix large move vectors killing the server -+ double currDeltaX = toX - fromX; -+ double currDeltaY = toY - fromY; -+ double currDeltaZ = toZ - fromZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double currDeltaX = toX - x; ++ double currDeltaY = toY - y; ++ double currDeltaZ = toZ - z; ++ double d10 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); + double otherFieldX = d3 - this.vehicleLastGoodX; + double otherFieldY = d4 - this.vehicleLastGoodY; + double otherFieldZ = d5 - this.vehicleLastGoodZ; + d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); + // Paper end - fix large move vectors killing the server - -- if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) { ++ //if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) { ++ + // CraftBukkit start - handle custom speeds and skipped ticks + this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; + this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); @@ -312,55 +245,53 @@ + + // Paper start - Prevent moving into unloaded chunks + if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && ( -+ !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || -+ !worldserver.areChunksLoadedForMove(entity.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(entity.position()))) -+ )) { -+ this.connection.send(ClientboundMoveVehiclePacket.fromEntity(entity)); ++ !serverLevel.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position()))) || ++ !serverLevel.areChunksLoadedForMove(rootVehicle.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(rootVehicle.position()))) ++ )) { ++ this.connection.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle)); + return; + } + // Paper end - Prevent moving into unloaded chunks -+ -+ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { -+ // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); - this.send(ClientboundMoveVehiclePacket.fromEntity(entity)); - return; -@@ -423,9 +581,9 @@ - - boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); - -- d6 = d3 - this.vehicleLastGoodX; -- d7 = d4 - this.vehicleLastGoodY; -- d8 = d5 - this.vehicleLastGoodZ; -+ d6 = d3 - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d4 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above -+ d8 = d5 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = entity.verticalCollisionBelow; - - if (entity instanceof LivingEntity) { -@@ -449,19 +607,72 @@ - d10 = d6 * d6 + d7 * d7 + d8 * d8; - boolean flag2 = false; - -- if (d10 > 0.0625D) { -+ if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot - flag2 = true; - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", new Object[]{entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)}); ++ if (d7 - d6 > Math.max(100.0D, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + LOGGER.warn( + "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5 + ); +@@ -417,9 +_,9 @@ } - entity.absMoveTo(d3, d4, d5, f, f1); -+ this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit - boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + boolean flag = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625)); +- d3 = d - this.vehicleLastGoodX; +- d4 = d1 - this.vehicleLastGoodY; +- d5 = d2 - this.vehicleLastGoodZ; ++ d3 = d - this.vehicleLastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d4 = d1 - this.vehicleLastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d5 = d2 - this.vehicleLastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag1 = rootVehicle.verticalCollisionBelow; + if (rootVehicle instanceof LivingEntity livingEntity && livingEntity.onClimbable()) { + livingEntity.resetFallDistance(); +@@ -435,18 +_,71 @@ + d5 = d2 - rootVehicle.getZ(); + d7 = d3 * d3 + d4 * d4 + d5 * d5; + boolean flag2 = false; +- if (d7 > 0.0625) { ++ if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot + flag2 = true; + LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7)); + } + rootVehicle.absMoveTo(d, d1, d2, f, f1); ++ this.player.absMoveTo(d, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + boolean flag3 = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625)); if (flag && (flag2 || !flag3)) { - entity.absMoveTo(d0, d1, d2, f, f1); -+ this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit - this.send(ClientboundMoveVehiclePacket.fromEntity(entity)); + rootVehicle.absMoveTo(x, y, z, f, f1); ++ this.player.absMoveTo(x, y, z, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle)); return; -+ } + } + + // CraftBukkit start - fire PlayerMoveEvent -+ Player player = this.getCraftPlayer(); ++ org.bukkit.entity.Player player = this.getCraftPlayer(); + if (!this.hasMoved) { + this.lastPosX = prevX; + this.lastPosY = prevY; @@ -407,32 +338,35 @@ + this.justTeleported = false; + return; + } - } ++ } + // CraftBukkit end this.player.serverLevel().getChunkSource().move(this.player); - entity.recordMovementThroughBlocks(new Vec3(d0, d1, d2), entity.position()); -@@ -489,16 +700,17 @@ + rootVehicle.recordMovementThroughBlocks(new Vec3(x, y, z), rootVehicle.position()); +@@ -478,12 +_,12 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (packet.getId() == this.awaitingTeleport) { if (this.awaitingPositionFromClient == null) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause return; } -- this.player.absMoveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); -+ this.player.moveTo(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); // Paper - Fix Entity Teleportation and cancel velocity if teleported - this.lastGoodX = this.awaitingPositionFromClient.x; - this.lastGoodY = this.awaitingPositionFromClient.y; + this.player +- .absMoveTo( ++ .moveTo( // Paper - Fix Entity Teleportation and cancel velocity if teleported + this.awaitingPositionFromClient.x, + this.awaitingPositionFromClient.y, + this.awaitingPositionFromClient.z, +@@ -495,6 +_,7 @@ this.lastGoodZ = this.awaitingPositionFromClient.z; this.player.hasChangedDimension(); this.awaitingPositionFromClient = null; + this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit } - } -@@ -528,6 +740,7 @@ + +@@ -521,6 +_,7 @@ @Override public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -440,18 +374,19 @@ this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering()); } -@@ -545,21 +758,104 @@ - +@@ -536,14 +_,84 @@ + } } + // Paper start - AsyncTabCompleteEvent + private static final java.util.concurrent.ExecutorService TAB_COMPLETE_EXECUTOR = java.util.concurrent.Executors.newFixedThreadPool(4, -+ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(net.minecraft.server.MinecraftServer.LOGGER)).build()); ++ new com.google.common.util.concurrent.ThreadFactoryBuilder().setDaemon(true).setNameFormat("Async Tab Complete Thread - #%d").setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(MinecraftServer.LOGGER)).build()); + // Paper end - AsyncTabCompleteEvent ++ @Override public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { - PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); -+ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); // Paper - AsyncTabCompleteEvent; run this async ++ // PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + // CraftBukkit start + if (!this.tabSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { // Paper - configurable tab spam limits + this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - Kick event cause // Paper - add proper async disconnect @@ -475,12 +410,13 @@ + } + + private void handleCustomCommandSuggestions0(final ServerboundCommandSuggestionPacket packet) { - StringReader stringreader = new StringReader(packet.getCommand()); - - if (stringreader.canRead() && stringreader.peek() == '/') { - stringreader.skip(); ++ // Paper end - AsyncTabCompleteEvent + StringReader stringReader = new StringReader(packet.getCommand()); + if (stringReader.canRead() && stringReader.peek() == '/') { + stringReader.skip(); } ++ // Paper start - AsyncTabCompleteEvent + final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent event = new com.destroystokyo.paper.event.server.AsyncTabCompleteEvent(this.getCraftPlayer(), packet.getCommand(), true, null); + event.callEvent(); + final List completions = event.isCancelled() ? com.google.common.collect.ImmutableList.of() : event.completions(); @@ -491,9 +427,9 @@ + } + + // This needs to be on main -+ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringreader)); ++ this.server.scheduleOnMain(() -> this.sendServerSuggestions(packet, stringReader)); + } else if (!completions.isEmpty()) { -+ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringreader.getTotalLength()); ++ final com.mojang.brigadier.suggestion.SuggestionsBuilder builder0 = new com.mojang.brigadier.suggestion.SuggestionsBuilder(packet.getCommand(), stringReader.getTotalLength()); + final com.mojang.brigadier.suggestion.SuggestionsBuilder builder = builder0.createOffset(builder0.getInput().lastIndexOf(' ') + 1); + for (final com.destroystokyo.paper.event.server.AsyncTabCompleteEvent.Completion completion : completions) { + final Integer intSuggestion = com.google.common.primitives.Ints.tryParse(completion.suggestion()); @@ -519,37 +455,35 @@ + } + // Paper end - brig API + -+ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringreader) { ++ private void sendServerSuggestions(final ServerboundCommandSuggestionPacket packet, final StringReader stringReader) { + // Paper end - AsyncTabCompleteEvent - ParseResults parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); -+ // Paper start - Handle non-recoverable exceptions -+ if (!parseresults.getExceptions().isEmpty() -+ && parseresults.getExceptions().values().stream().anyMatch(e -> e instanceof io.papermc.paper.brigadier.TagParseCommandSyntaxException)) { -+ this.disconnect(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); -+ return; -+ } -+ // Paper end - Handle non-recoverable exceptions - - this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { -- Suggestions suggestions1 = suggestions.getList().size() <= 1000 ? suggestions : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000)); -- -- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1)); -+ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false -+ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { -+ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); -+ } -+ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false -+ // Paper start - Brigadier API -+ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); -+ suggestEvent.setCancelled(suggestions.isEmpty()); -+ if (suggestEvent.callEvent()) { -+ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); -+ } -+ // Paper end - Brigadier API - }); + ParseResults parseResults = this.server.getCommands().getDispatcher().parse(stringReader, this.player.createCommandSourceStack()); + this.server + .getCommands() +@@ -551,10 +_,18 @@ + .getCompletionSuggestions(parseResults) + .thenAccept( + suggestions -> { +- Suggestions suggestions1 = suggestions.getList().size() <= 1000 +- ? suggestions +- : new Suggestions(suggestions.getRange(), suggestions.getList().subList(0, 1000)); +- this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions1)); ++ // Paper start - Don't tab-complete namespaced commands if send-namespaced is false ++ if (!org.spigotmc.SpigotConfig.sendNamespaced && suggestions.getRange().getStart() <= 1) { ++ suggestions.getList().removeIf(suggestion -> suggestion.getText().contains(":")); ++ } ++ // Paper end - Don't tab-complete namespaced commands if send-namespaced is false ++ // Paper start - Brigadier API ++ com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent suggestEvent = new com.destroystokyo.paper.event.brigadier.AsyncPlayerSendSuggestionsEvent(this.getCraftPlayer(), suggestions, packet.getCommand()); ++ suggestEvent.setCancelled(suggestions.isEmpty()); ++ if (suggestEvent.callEvent()) { ++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), limitTo(suggestEvent.getSuggestions(), ServerGamePacketListenerImpl.MAX_COMMAND_SUGGESTIONS))); ++ } ++ // Paper end - Brigadier API + } + ); } - -@@ -568,7 +864,7 @@ +@@ -564,7 +_,7 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (!this.server.isCommandBlockEnabled()) { this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); @@ -557,8 +491,8 @@ + } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); } else { - BaseCommandBlock commandblocklistenerabstract = null; -@@ -635,7 +931,7 @@ + BaseCommandBlock baseCommandBlock = null; +@@ -620,7 +_,7 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (!this.server.isCommandBlockEnabled()) { this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); @@ -566,61 +500,60 @@ + } else if (!this.player.canUseGameMasterBlocks() && (!this.player.isCreative() || !this.player.getBukkitEntity().hasPermission("minecraft.commandblock"))) { // Paper - command block permission this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); } else { - BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); -@@ -668,7 +964,7 @@ - ItemStack itemstack = iblockdata.getCloneItemStack(worldserver, blockposition, flag); - - if (!itemstack.isEmpty()) { + BaseCommandBlock commandBlock = packet.getCommandBlock(this.player.level()); +@@ -648,7 +_,7 @@ + boolean flag = this.player.hasInfiniteMaterials() && packet.includeData(); + ItemStack cloneItemStack = blockState.getCloneItemStack(serverLevel, blockPos, flag); + if (!cloneItemStack.isEmpty()) { - if (flag) { + if (flag && this.player.getBukkitEntity().hasPermission("minecraft.nbt.copy")) { // Spigot - ServerGamePacketListenerImpl.addBlockDataToItem(iblockdata, worldserver, blockposition, itemstack); + addBlockDataToItem(blockState, serverLevel, blockPos, cloneItemStack); } -@@ -712,15 +1008,25 @@ +@@ -685,14 +_,24 @@ if (stack.isItemEnabled(this.player.level().enabledFeatures())) { - Inventory playerinventory = this.player.getInventory(); - int i = playerinventory.findSlotMatchingItem(stack); + Inventory inventory = this.player.getInventory(); + int i = inventory.findSlotMatchingItem(stack); + // Paper start - Add PlayerPickItemEvent + final int sourceSlot = i; -+ final int targetSlot = Inventory.isHotbarSlot(sourceSlot) ? sourceSlot : playerinventory.getSuitableHotbarSlot(); -+ final Player bukkitPlayer = this.player.getBukkitEntity(); ++ final int targetSlot = Inventory.isHotbarSlot(sourceSlot) ? sourceSlot : inventory.getSuitableHotbarSlot(); ++ final org.bukkit.entity.Player bukkitPlayer = this.player.getBukkitEntity(); + final io.papermc.paper.event.player.PlayerPickItemEvent event = new io.papermc.paper.event.player.PlayerPickItemEvent(bukkitPlayer, targetSlot, sourceSlot); + if (!event.callEvent()) { + return; + } + i = event.getSourceSlot(); - ++ // Paper end - Add PlayerPickItemEvent if (i != -1) { - if (Inventory.isHotbarSlot(i)) { -- playerinventory.selected = i; -+ if (Inventory.isHotbarSlot(i) && Inventory.isHotbarSlot(event.getTargetSlot())) { -+ playerinventory.selected = event.getTargetSlot(); +- inventory.selected = i; ++ if (Inventory.isHotbarSlot(i) && Inventory.isHotbarSlot(event.getTargetSlot())) { // Paper - Add PlayerPickItemEvent ++ inventory.selected = event.getTargetSlot(); // Paper - Add PlayerPickItemEvent } else { -- playerinventory.pickSlot(i); -+ playerinventory.pickSlot(i, event.getTargetSlot()); +- inventory.pickSlot(i); ++ inventory.pickSlot(i, event.getTargetSlot()); // Paper - Add PlayerPickItemEvent } } else if (this.player.hasInfiniteMaterials()) { -- playerinventory.addAndPickItem(stack); -+ playerinventory.addAndPickItem(stack, event.getTargetSlot()); -+ // Paper end - Add PlayerPickItemEvent +- inventory.addAndPickItem(stack); ++ inventory.addAndPickItem(stack, event.getTargetSlot()); // Paper - Add PlayerPickItemEvent } - this.player.connection.send(new ClientboundSetHeldSlotPacket(playerinventory.selected)); -@@ -866,6 +1172,13 @@ - AbstractContainerMenu container = this.player.containerMenu; - - if (container instanceof MerchantMenu containermerchant) { + this.player.connection.send(new ClientboundSetHeldSlotPacket(inventory.selected)); +@@ -814,6 +_,13 @@ + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + int item = packet.getItem(); + if (this.player.containerMenu instanceof MerchantMenu merchantMenu) { + // CraftBukkit start -+ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant); ++ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, item, merchantMenu); + if (tradeSelectEvent.isCancelled()) { -+ this.player.getBukkitEntity().updateInventory(); ++ this.player.containerMenu.sendAllDataToRemote(); + return; + } + // CraftBukkit end - if (!containermerchant.stillValid(this.player)) { - ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containermerchant); + if (!merchantMenu.stillValid(this.player)) { + LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantMenu); return; -@@ -879,6 +1192,51 @@ +@@ -826,6 +_,51 @@ @Override public void handleEditBook(ServerboundEditBookPacket packet) { @@ -669,52 +602,43 @@ + } + this.lastBookTick = MinecraftServer.currentTick; + // CraftBukkit end - int i = packet.slot(); - - if (Inventory.isHotbarSlot(i) || i == 40) { -@@ -899,12 +1257,16 @@ + int slot = packet.slot(); + if (Inventory.isHotbarSlot(slot) || slot == 40) { + List list = Lists.newArrayList(); +@@ -840,10 +_,14 @@ } - private void updateBookContents(List pages, int slotId) { -- ItemStack itemstack = this.player.getInventory().getItem(slotId); + private void updateBookContents(List pages, int index) { +- ItemStack item = this.player.getInventory().getItem(index); + // CraftBukkit start -+ ItemStack handItem = this.player.getInventory().getItem(slotId); -+ ItemStack itemstack = handItem.copy(); ++ ItemStack handItem = this.player.getInventory().getItem(index); ++ ItemStack item = handItem.copy(); + // CraftBukkit end - - if (itemstack.has(DataComponents.WRITABLE_BOOK_CONTENT)) { - List> list1 = pages.stream().map(this::filterableFromOutgoing).toList(); - - itemstack.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list1)); -+ this.player.getInventory().setItem(slotId, CraftEventFactory.handleEditBookEvent(this.player, slotId, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) + if (item.has(DataComponents.WRITABLE_BOOK_CONTENT)) { + List> list = pages.stream().map(this::filterableFromOutgoing).toList(); + item.set(DataComponents.WRITABLE_BOOK_CONTENT, new WritableBookContent(list)); ++ this.player.getInventory().setItem(index, CraftEventFactory.handleEditBookEvent(this.player, index, handItem, item)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) } } -@@ -915,12 +1277,13 @@ - ItemStack itemstack1 = itemstack.transmuteCopy(Items.WRITTEN_BOOK); - - itemstack1.remove(DataComponents.WRITABLE_BOOK_CONTENT); -- List> list1 = pages.stream().map((filteredtext1) -> { -+ List> list1 = (List>) (List) pages.stream().map((filteredtext1) -> { // CraftBukkit - decompile error - return this.filterableFromOutgoing(filteredtext1).map(Component::literal); - }).toList(); - - itemstack1.set(DataComponents.WRITTEN_BOOK_CONTENT, new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list1, true)); -- this.player.getInventory().setItem(slotId, itemstack1); -+ CraftEventFactory.handleEditBookEvent(this.player, slotId, itemstack, itemstack1); // CraftBukkit -+ this.player.getInventory().setItem(slotId, itemstack); // CraftBukkit - event factory updates the hand book +@@ -857,7 +_,8 @@ + DataComponents.WRITTEN_BOOK_CONTENT, + new WrittenBookContent(this.filterableFromOutgoing(title), this.player.getName().getString(), 0, list, true) + ); +- this.player.getInventory().setItem(index, itemStack); ++ CraftEventFactory.handleEditBookEvent(this.player, index, item, itemStack); // CraftBukkit ++ this.player.getInventory().setItem(index, item); // CraftBukkit - event factory updates the hand book } } -@@ -978,26 +1341,34 @@ +@@ -901,24 +_,32 @@ public void handleMovePlayer(ServerboundMovePlayerPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); - if (ServerGamePacketListenerImpl.containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement")); -+ this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause + if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) { +- this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); ++ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_PLAYER_MOVEMENT); // Paper - kick event cause } else { - ServerLevel worldserver = this.player.serverLevel(); - + ServerLevel serverLevel = this.player.serverLevel(); - if (!this.player.wonGame) { + if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit if (this.tickCount == 0) { @@ -722,17 +646,16 @@ } if (!this.updateAwaitingTeleport() && this.player.hasClientLoaded()) { -- double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); -- double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); -- double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); +- double d = clampHorizontal(packet.getX(this.player.getX())); +- double d1 = clampVertical(packet.getY(this.player.getY())); +- double d2 = clampHorizontal(packet.getZ(this.player.getZ())); - float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); - float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); -+ double d0 = ServerGamePacketListenerImpl.clampHorizontal(packet.getX(this.player.getX())); final double toX = d0; // Paper - OBFHELPER -+ double d1 = ServerGamePacketListenerImpl.clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER -+ double d2 = ServerGamePacketListenerImpl.clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER ++ double d = clampHorizontal(packet.getX(this.player.getX())); final double toX = d; // Paper - OBFHELPER ++ double d1 = clampVertical(packet.getY(this.player.getY())); final double toY = d1; // Paper - OBFHELPER ++ double d2 = clampHorizontal(packet.getZ(this.player.getZ())); final double toZ = d2; // Paper - OBFHELPER + float f = Mth.wrapDegrees(packet.getYRot(this.player.getYRot())); final float toYaw = f; // Paper - OBFHELPER + float f1 = Mth.wrapDegrees(packet.getXRot(this.player.getXRot())); final float toPitch = f1; // Paper - OBFHELPER - 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); @@ -745,43 +668,43 @@ + float prevYaw = this.player.getYRot(); + float prevPitch = this.player.getXRot(); + // CraftBukkit end - double d3 = this.player.getX(); - double d4 = this.player.getY(); - double d5 = this.player.getZ(); -@@ -1005,7 +1376,16 @@ - double d7 = d1 - this.firstGoodY; - double d8 = d2 - this.firstGoodZ; - double d9 = this.player.getDeltaMovement().lengthSqr(); -- double d10 = d6 * d6 + d7 * d7 + d8 * d8; + double x = this.player.getX(); + double y = this.player.getY(); + double z = this.player.getZ(); +@@ -927,6 +_,16 @@ + double d5 = d2 - this.firstGoodZ; + double d6 = this.player.getDeltaMovement().lengthSqr(); + double d7 = d3 * d3 + d4 * d4 + d5 * d5; + // Paper start - fix large move vectors killing the server + double currDeltaX = toX - prevX; + double currDeltaY = toY - prevY; + double currDeltaZ = toZ - prevZ; -+ double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); -+ double otherFieldX = d0 - this.lastGoodX; ++ d7 = Math.max(d7, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); ++ double otherFieldX = d - this.lastGoodX; + double otherFieldY = d1 - this.lastGoodY; + double otherFieldZ = d2 - this.lastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); ++ d7 = Math.max(d7, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); + // Paper end - fix large move vectors killing the server - if (this.player.isSleeping()) { - if (d10 > 1.0D) { -@@ -1019,36 +1399,106 @@ - ++this.receivedMovePacketCount; + if (d7 > 1.0) { + this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); +@@ -936,32 +_,104 @@ + if (serverLevel.tickRateManager().runsNormally()) { + 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)) { - ServerGamePacketListenerImpl.LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); + LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i); i = 1; } -+ if (packet.hasRot || d10 > 0) { ++ if (packet.hasRot || d7 > 0) { + this.allowedPlayerTicks -= 1; + } else { + this.allowedPlayerTicks = 20; @@ -793,7 +716,7 @@ + speed = this.player.getAbilities().walkingSpeed * 10f; + } + // Paper start - Prevent moving into unloaded chunks -+ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !worldserver.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { ++ if (this.player.level().paperConfig().chunks.preventMovingIntoUnloadedChunks && (this.player.getX() != toX || this.player.getZ() != toZ) && !serverLevel.areChunksLoadedForMove(this.player.getBoundingBox().expandTowards(new Vec3(toX, toY, toZ).subtract(this.player.position())))) { + // Paper start - Add fail move event + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_INTO_UNLOADED_CHUNK, + toX, toY, toZ, toYaw, toPitch, false); @@ -805,21 +728,21 @@ + } + // Paper end - Prevent moving into unloaded chunks + - if (this.shouldCheckPlayerMovement(flag)) { - float f2 = flag ? 300.0F : 100.0F; - -- if (d10 - d9 > (double) (f2 * (float) i)) { -- ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); + if (this.shouldCheckPlayerMovement(isFallFlying)) { + float f2 = isFallFlying ? 300.0F : 100.0F; +- if (d7 - d6 > f2 * i) { +- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); - this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); - return; -+ if (d10 - d9 > Math.max(f2, Math.pow((double) (org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed), 2))) { -+ // CraftBukkit end ++ if (d7 - d6 > Math.max(f2, Mth.square((org.spigotmc.SpigotConfig.movedTooQuicklyMultiplier * (float) i * speed)))) { ++ // CraftBukkit end + // Paper start - Add fail move event + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY, + toX, toY, toZ, toYaw, toPitch, true); + if (!event.isAllowed()) { -+ if (event.getLogWarning()) -+ ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); ++ if (event.getLogWarning()) { ++ LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5); ++ } + this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); + return; + } @@ -828,21 +751,19 @@ } } - AABB axisalignedbb = this.player.getBoundingBox(); - -- d6 = d0 - this.lastGoodX; -- d7 = d1 - this.lastGoodY; -- d8 = d2 - this.lastGoodZ; -+ d6 = d0 - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above -+ d7 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above -+ d8 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above - boolean flag1 = d7 > 0.0D; - - if (this.player.onGround() && !packet.isOnGround() && flag1) { + AABB boundingBox = this.player.getBoundingBox(); +- d3 = d - this.lastGoodX; +- d4 = d1 - this.lastGoodY; +- d5 = d2 - this.lastGoodZ; ++ d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above ++ d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above ++ d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above + boolean flag = d4 > 0.0; + if (this.player.onGround() && !packet.isOnGround() && flag) { - this.player.jumpFromGround(); + // Paper start - Add PlayerJumpEvent -+ Player player = this.getCraftPlayer(); -+ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ org.bukkit.entity.Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.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. @@ -870,43 +791,50 @@ + // Paper end - Add PlayerJumpEvent } - boolean flag2 = this.player.verticalCollisionBelow; - - this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); + boolean flag1 = this.player.verticalCollisionBelow; + this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5)); + this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move + // Paper start - prevent position desync + if (this.awaitingPositionFromClient != null) { + return; // ... thanks Mojang for letting move calls teleport across dimensions. + } + // Paper end - prevent position desync - double d11 = d7; + d3 = d - this.player.getX(); + d4 = d1 - this.player.getY(); + if (d4 > -0.5 || d4 < 0.5) { +@@ -970,21 +_,106 @@ - d6 = d0 - this.player.getX(); -@@ -1059,17 +1509,100 @@ - - d8 = d2 - this.player.getZ(); - d10 = d6 * d6 + d7 * d7 + d8 * d8; -- boolean flag3 = false; + d5 = d2 - this.player.getZ(); + d7 = d3 * d3 + d4 * d4 + d5 * d5; +- boolean flag2 = false; + boolean movedWrongly = false; // Paper - Add fail move event; rename - -- if (!this.player.isChangingDimension() && d10 > 0.0625D && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { -- flag3 = true; -+ if (!this.player.isChangingDimension() && d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot + if (!this.player.isChangingDimension() +- && d7 > 0.0625 ++ && d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold // Spigot + && !this.player.isSleeping() + && !this.player.gameMode.isCreative() + && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { +- flag2 = true; +- LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); +- } +- +- if (this.player.noPhysics +- || this.player.isSleeping() +- || (!flag2 || !serverLevel.noCollision(this.player, boundingBox)) +- && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) { + // Paper start - Add fail move event + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.MOVED_WRONGLY, + toX, toY, toZ, toYaw, toPitch, true); + if (!event.isAllowed()) { + movedWrongly = true; + if (event.getLogWarning()) -+ // Paper end - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ // Paper end ++ LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + } // Paper - } - -- if (!this.player.noPhysics && !this.player.isSleeping() && (flag3 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { -- this.teleport(d3, d4, d5, f, f1); ++ } ++ + // Paper start - Add fail move event -+ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2)); ++ boolean teleportBack = !this.player.noPhysics && !this.player.isSleeping() && (movedWrongly && serverLevel.noCollision(this.player, boundingBox) || this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)); + if (teleportBack) { + io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, + toX, toY, toZ, toYaw, toPitch, false); @@ -915,15 +843,15 @@ + } + } + if (teleportBack) { -+ // Paper end - Add fail move event -+ this.internalTeleport(d3, d4, d5, f, f1); // 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, packet.isOnGround()); - } else { ++ // Paper end - Add fail move event ++ this.internalTeleport(x, y, z, f, f1); // 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() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround()); ++ } else { + // CraftBukkit start - fire PlayerMoveEvent + // Reset to old location first + this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch); + -+ Player player = this.getCraftPlayer(); ++ org.bukkit.entity.Player player = this.getCraftPlayer(); + if (!this.hasMoved) { + this.lastPosX = prevX; + this.lastPosY = prevY; @@ -932,6 +860,7 @@ + this.lastPitch = prevPitch; + this.hasMoved = true; + } ++ + Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); // Get the Players previous Event location. + Location to = player.getLocation().clone(); // Start off the To location as the Players current location. + @@ -985,50 +914,71 @@ + } + } + // CraftBukkit end - this.player.absMoveTo(d0, d1, d2, f, f1); - boolean flag4 = this.player.isAutoSpinAttack(); - -@@ -1119,6 +1652,7 @@ - this.awaitingTeleportTime = this.tickCount; - this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + this.player.absMoveTo(d, d1, d2, f, f1); ++ + boolean isAutoSpinAttack = this.player.isAutoSpinAttack(); + this.clientIsFloating = d4 >= -0.03125 + && !flag1 +@@ -996,6 +_,7 @@ + && !isAutoSpinAttack + && this.noBlocksAround(this.player); + this.player.serverLevel().getChunkSource().move(this.player); ++ + Vec3 vec3 = new Vec3(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z); + this.player.setOnGroundWithMovement(packet.isOnGround(), packet.horizontalCollision(), vec3); + this.player.doCheckFallDamage(vec3.x, vec3.y, vec3.z, packet.isOnGround()); +@@ -1018,9 +_,6 @@ + this.lastGoodX = this.player.getX(); + this.lastGoodY = this.player.getY(); + this.lastGoodZ = this.player.getZ(); +- } else { +- this.teleport(x, y, z, f, f1); +- this.player.doCheckFallDamage(this.player.getX() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround()); + } + } + } +@@ -1053,6 +_,7 @@ + this.player.getXRot() + ); } + this.allowedPlayerTicks = 20; // CraftBukkit return true; } else { -@@ -1147,23 +1681,98 @@ +@@ -1076,10 +_,78 @@ } public void teleport(double x, double y, double z, float yaw, float pitch) { - this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet()); ++ //this.teleport(new PositionMoveRotation(new Vec3(x, y, z), Vec3.ZERO, yaw, pitch), Collections.emptySet()); + // CraftBukkit start - Delegate to teleport(Location) + this.teleport(x, y, z, yaw, pitch, PlayerTeleportEvent.TeleportCause.UNKNOWN); - } - ++ } ++ + public boolean teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { + return this.teleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet(), cause); + // CraftBukkit end -+ } -+ - public void teleport(PositionMoveRotation pos, Set flags) { + } + + public void teleport(PositionMoveRotation posMoveRotation, Set relatives) { + // CraftBukkit start -+ this.teleport(pos, flags, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ this.teleport(posMoveRotation, relatives, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + -+ public boolean teleport(PositionMoveRotation positionmoverotation, Set set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status -+ Player player = this.getCraftPlayer(); ++ public boolean teleport(PositionMoveRotation posMoveRotation, Set relatives, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status ++ org.bukkit.entity.Player player = this.getCraftPlayer(); + Location from = player.getLocation(); -+ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this.player), positionmoverotation, set); ++ PositionMoveRotation absolutePosition = PositionMoveRotation.calculateAbsolute(PositionMoveRotation.of(this.player), posMoveRotation, relatives); + Location to = CraftLocation.toBukkit(absolutePosition.position(), this.getCraftPlayer().getWorld(), absolutePosition.yRot(), absolutePosition.xRot()); + // SPIGOT-5171: Triggered on join + if (from.equals(to)) { -+ this.internalTeleport(positionmoverotation, set); ++ this.internalTeleport(posMoveRotation, relatives); + return true; // CraftBukkit - Return event status + } + + // Paper start - Teleport API + final Set relativeFlags = java.util.EnumSet.noneOf(io.papermc.paper.entity.TeleportFlag.Relative.class); -+ for (final Relative relativeArgument : set) { ++ for (final Relative relativeArgument : relatives) { + final io.papermc.paper.entity.TeleportFlag.Relative flag = org.bukkit.craftbukkit.entity.CraftPlayer.deltaRelativeToAPI(relativeArgument); + if (flag != null) relativeFlags.add(flag); + } @@ -1039,10 +989,10 @@ + if (event.isCancelled() || !to.equals(event.getTo())) { + // set = Collections.emptySet(); // Can't relative teleport // Paper - Teleport API; Now you can! + to = event.isCancelled() ? event.getFrom() : event.getTo(); -+ positionmoverotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch()); ++ posMoveRotation = new PositionMoveRotation(CraftLocation.toVec3D(to), Vec3.ZERO, to.getYaw(), to.getPitch()); + } + -+ this.internalTeleport(positionmoverotation, set); ++ this.internalTeleport(posMoveRotation, relatives); + return !event.isCancelled(); // CraftBukkit - Return event status + } + @@ -1054,20 +1004,20 @@ + this.internalTeleport(new PositionMoveRotation(new Vec3(d0, d1, d2), Vec3.ZERO, f, f1), Collections.emptySet()); + } + -+ public void internalTeleport(PositionMoveRotation positionmoverotation, Set set) { ++ public void internalTeleport(PositionMoveRotation posMoveRotation, Set relatives) { + org.spigotmc.AsyncCatcher.catchOp("teleport"); // Paper + // Paper start - Prevent teleporting dead entities -+ if (player.isRemoved()) { ++ if (this.player.isRemoved()) { + LOGGER.info("Attempt to teleport removed player {} restricted", player.getScoreboardName()); -+ if (server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); ++ if (this.server.isDebugging()) io.papermc.paper.util.TraceUtil.dumpTraceForThread("Attempt to teleport removed player"); + return; + } + // Paper end - Prevent teleporting dead entities -+ if (Float.isNaN(positionmoverotation.yRot())) { -+ positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), 0, positionmoverotation.xRot()); ++ if (Float.isNaN(posMoveRotation.yRot())) { ++ posMoveRotation = new PositionMoveRotation(posMoveRotation.position(), posMoveRotation.deltaMovement(), 0, posMoveRotation.xRot()); + } -+ if (Float.isNaN(positionmoverotation.xRot())) { -+ positionmoverotation = new PositionMoveRotation(positionmoverotation.position(), positionmoverotation.deltaMovement(), positionmoverotation.yRot(), 0); ++ if (Float.isNaN(posMoveRotation.xRot())) { ++ posMoveRotation = new PositionMoveRotation(posMoveRotation.position(), posMoveRotation.deltaMovement(), posMoveRotation.yRot(), 0); + } + + this.justTeleported = true; @@ -1075,12 +1025,10 @@ this.awaitingTeleportTime = this.tickCount; if (++this.awaitingTeleport == Integer.MAX_VALUE) { this.awaitingTeleport = 0; - } +@@ -1087,12 +_,20 @@ -- this.player.teleportSetPosition(pos, flags); -+ this.player.teleportSetPosition(positionmoverotation, set); + this.player.teleportSetPosition(posMoveRotation, relatives); this.awaitingPositionFromClient = this.player.position(); -- this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, pos, flags)); + // CraftBukkit start - update last location + this.lastPosX = this.awaitingPositionFromClient.x; + this.lastPosY = this.awaitingPositionFromClient.y; @@ -1088,7 +1036,7 @@ + this.lastYaw = this.player.getYRot(); + this.lastPitch = this.player.getXRot(); + // CraftBukkit end -+ this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, positionmoverotation, set)); + this.player.connection.send(ClientboundPlayerPositionPacket.of(this.awaitingTeleport, posMoveRotation, relatives)); } @Override @@ -1096,16 +1044,16 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.isImmobile()) return; // CraftBukkit if (this.player.hasClientLoaded()) { - BlockPos blockposition = packet.getPos(); - -@@ -1175,14 +1784,46 @@ + BlockPos pos = packet.getPos(); + this.player.resetLastActionTime(); +@@ -1101,14 +_,46 @@ + case SWAP_ITEM_WITH_OFFHAND: if (!this.player.isSpectator()) { - ItemStack itemstack = this.player.getItemInHand(InteractionHand.OFF_HAND); - + ItemStack itemInHand = this.player.getItemInHand(InteractionHand.OFF_HAND); - this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND)); -- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack); +- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand); + // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394) -+ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack); ++ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemInHand); + CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(InteractionHand.MAIN_HAND)); + PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(this.getCraftPlayer(), mainHand.clone(), offHand.clone()); + this.cserver.getPluginManager().callEvent(swapItemsEvent); @@ -1118,7 +1066,7 @@ + this.player.setItemInHand(InteractionHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem())); + } + if (swapItemsEvent.getMainHandItem().equals(mainHand)) { -+ this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack); ++ this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand); + } else { + this.player.setItemInHand(InteractionHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem())); + } @@ -1147,48 +1095,45 @@ this.player.drop(false); } -@@ -1199,8 +1840,34 @@ +@@ -1125,8 +_,34 @@ case START_DESTROY_BLOCK: case ABORT_DESTROY_BLOCK: case STOP_DESTROY_BLOCK: + // Paper start - Don't allow digging into unloaded chunks -+ if (this.player.level().getChunkIfLoadedImmediately(blockposition.getX() >> 4, blockposition.getZ() >> 4) == null) { ++ if (this.player.level().getChunkIfLoadedImmediately(pos.getX() >> 4, pos.getZ() >> 4) == null) { + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + return; + } + // Paper end - Don't allow digging into unloaded chunks -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.capturedBlockEntity = false; -+ this.player.gameMode.captureSentBlockEntities = true; -+ // Paper end - Send block entities after destroy prediction - this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); - this.player.connection.ackBlockChangesUpTo(packet.getSequence()); -+ // Paper start - Send block entities after destroy prediction -+ this.player.gameMode.captureSentBlockEntities = false; -+ // If a block entity was modified speedup the block change ack to avoid the block entity -+ // being overriden. -+ if (this.player.gameMode.capturedBlockEntity) { -+ // manually tick -+ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); -+ this.player.connection.ackBlockChangesUpTo = -1; -+ ++ // Paper start - Send block entities after destroy prediction + this.player.gameMode.capturedBlockEntity = false; -+ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); -+ if (tileentity != null) { -+ this.player.connection.send(tileentity.getUpdatePacket()); ++ this.player.gameMode.captureSentBlockEntities = true; ++ // Paper end - Send block entities after destroy prediction + this.player.gameMode.handleBlockBreakAction(pos, action, packet.getDirection(), this.player.level().getMaxY(), packet.getSequence()); + this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ // Paper start - Send block entities after destroy prediction ++ this.player.gameMode.captureSentBlockEntities = false; ++ // If a block entity was modified speedup the block change ack to avoid the block entity ++ // being overridden. ++ if (this.player.gameMode.capturedBlockEntity) { ++ // manually tick ++ this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); ++ this.player.connection.ackBlockChangesUpTo = -1; ++ ++ this.player.gameMode.capturedBlockEntity = false; ++ BlockEntity blockEntity = this.player.level().getBlockEntity(pos); ++ if (blockEntity != null) { ++ this.player.connection.send(blockEntity.getUpdatePacket()); ++ } + } -+ } -+ // Paper end - Send block entities after destroy prediction ++ // Paper end - Send block entities after destroy prediction return; default: throw new IllegalArgumentException("Invalid player action"); -@@ -1215,12 +1882,34 @@ - Item item = stack.getItem(); +@@ -1143,9 +_,31 @@ + } + } - return (item instanceof BlockItem || item instanceof BucketItem) && !player.getCooldowns().isOnCooldown(stack); -+ } -+ } -+ + // Spigot start - limit place/interactions + private int limitedPackets; + private long lastLimitedPacket = -1; @@ -1203,12 +1148,12 @@ + this.lastLimitedPacket = timestamp; + this.limitedPackets = 0; + return true; - } ++ } + + return true; - } -+ // Spigot end - ++ } ++ // Spigot end - limit place/interactions ++ @Override public void handleUseItemOn(ServerboundUseItemOnPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1216,44 +1161,43 @@ + if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit if (this.player.hasClientLoaded()) { this.player.connection.ackBlockChangesUpTo(packet.getSequence()); - ServerLevel worldserver = this.player.serverLevel(); -@@ -1230,6 +1919,11 @@ - if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { - BlockHitResult movingobjectpositionblock = packet.getHitResult(); - Vec3 vec3d = movingobjectpositionblock.getLocation(); -+ // Paper start - improve distance check -+ if (!Double.isFinite(vec3d.x) || !Double.isFinite(vec3d.y) || !Double.isFinite(vec3d.z)) { -+ return; -+ } -+ // Paper end - improve distance check - BlockPos blockposition = movingobjectpositionblock.getBlockPos(); - - if (this.player.canInteractWithBlock(blockposition, 1.0D)) { -@@ -1243,7 +1937,8 @@ - int i = this.player.level().getMaxY(); - - if (blockposition.getY() <= i) { -- if (this.awaitingPositionFromClient == null && worldserver.mayInteract(this.player, blockposition)) { -+ if (this.awaitingPositionFromClient == null && (worldserver.mayInteract(this.player, blockposition) || (worldserver.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && worldserver.getBlockState(blockposition).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection + ServerLevel serverLevel = this.player.serverLevel(); +@@ -1154,6 +_,11 @@ + if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) { + BlockHitResult hitResult = packet.getHitResult(); + Vec3 location = hitResult.getLocation(); ++ // Paper start - improve distance check ++ if (!Double.isFinite(location.x()) || !Double.isFinite(location.y()) || !Double.isFinite(location.z())) { ++ return; ++ } ++ // Paper end - improve distance check + BlockPos blockPos = hitResult.getBlockPos(); + if (this.player.canInteractWithBlock(blockPos, 1.0)) { + Vec3 vec3 = location.subtract(Vec3.atCenterOf(blockPos)); +@@ -1163,7 +_,8 @@ + this.player.resetLastActionTime(); + int maxY = this.player.level().getMaxY(); + if (blockPos.getY() <= maxY) { +- if (this.awaitingPositionFromClient == null && serverLevel.mayInteract(this.player, blockPos)) { ++ if (this.awaitingPositionFromClient == null && (serverLevel.mayInteract(this.player, blockPos) || (serverLevel.paperConfig().spawn.allowUsingSignsInsideSpawnProtection && serverLevel.getBlockState(blockPos).getBlock() instanceof net.minecraft.world.level.block.SignBlock))) { // Paper - Allow using signs inside spawn protection + this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 - InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); - - if (enuminteractionresult.consumesAction()) { -@@ -1257,11 +1952,11 @@ - } else if (enuminteractionresult instanceof InteractionResult.Success) { - InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; - -- if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) { -+ if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { // Paper - Call interact event - this.player.swing(enumhand, true); - } + InteractionResult interactionResult = this.player.gameMode.useItemOn(this.player, serverLevel, itemInHand, hand, hitResult); + if (interactionResult.consumesAction()) { + CriteriaTriggers.ANY_BLOCK_USE.trigger(this.player, hitResult.getBlockPos(), itemInHand.copy()); +@@ -1176,10 +_,10 @@ + Component component = Component.translatable("build.tooHigh", maxY).withStyle(ChatFormatting.RED); + this.player.sendSystemMessage(component, true); + } else if (interactionResult instanceof InteractionResult.Success success +- && success.swingSource() == InteractionResult.SwingSource.SERVER) { ++ && success.swingSource() == InteractionResult.SwingSource.SERVER && !this.player.gameMode.interactResult) { // Paper - Call interact event + this.player.swing(hand, true); } - } + } else { this.player.containerMenu.sendAllDataToRemote(); } // Paper - Fix inventory desync; MC-99075 } else { - MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i).withStyle(ChatFormatting.RED); - -@@ -1281,6 +1976,8 @@ + Component component1 = Component.translatable("build.tooHigh", maxY).withStyle(ChatFormatting.RED); + this.player.sendSystemMessage(component1, true); +@@ -1203,6 +_,8 @@ @Override public void handleUseItem(ServerboundUseItemPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1261,17 +1205,17 @@ + if (!this.checkLimit(packet.timestamp)) return; // Spigot - check limit if (this.player.hasClientLoaded()) { this.ackBlockChangesUpTo(packet.getSequence()); - ServerLevel worldserver = this.player.serverLevel(); -@@ -1296,6 +1993,48 @@ + ServerLevel serverLevel = this.player.serverLevel(); +@@ -1216,6 +_,48 @@ this.player.absRotateTo(f, f1); } + // CraftBukkit start + // Raytrace to look for 'rogue armswings' -+ double d0 = this.player.getX(); -+ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); -+ double d2 = this.player.getZ(); -+ Vec3 vec3d = new Vec3(d0, d1, d2); ++ double x = this.player.getX(); ++ double eyeY = this.player.getEyeY(); ++ double z = this.player.getZ(); ++ Vec3 from = new Vec3(x, eyeY, z); + + float f3 = Mth.cos(-f * 0.017453292F - 3.1415927F); + float f4 = Mth.sin(-f * 0.017453292F - 3.1415927F); @@ -1280,55 +1224,56 @@ + float f7 = f4 * f5; + float f8 = f3 * f5; + double d3 = this.player.blockInteractionRange(); -+ Vec3 vec3d1 = vec3d.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); -+ HitResult movingobjectposition = this.player.level().clip(new ClipContext(vec3d, vec3d1, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player)); ++ Vec3 to = from.add((double) f7 * d3, (double) f6 * d3, (double) f8 * d3); ++ BlockHitResult hitResult = this.player.level().clip(new ClipContext(from, to, ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, this.player)); + + boolean cancelled; -+ if (movingobjectposition == null || movingobjectposition.getType() != HitResult.Type.BLOCK) { -+ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); ++ if (hitResult == null || hitResult.getType() != HitResult.Type.BLOCK) { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemInHand, hand); + cancelled = event.useItemInHand() == Event.Result.DENY; + } else { -+ BlockHitResult movingobjectpositionblock = (BlockHitResult) movingobjectposition; -+ if (this.player.gameMode.firedInteract && this.player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && this.player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameComponents(this.player.gameMode.interactItemStack, itemstack)) { ++ if (this.player.gameMode.firedInteract && this.player.gameMode.interactPosition.equals(hitResult.getBlockPos()) && this.player.gameMode.interactHand == hand && ItemStack.isSameItemSameComponents(this.player.gameMode.interactItemStack, itemInHand)) { + cancelled = this.player.gameMode.interactResult; + } else { -+ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation()); ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_BLOCK, hitResult.getBlockPos(), hitResult.getDirection(), itemInHand, true, hand, hitResult.getLocation()); + cancelled = event.useItemInHand() == Event.Result.DENY; + } + this.player.gameMode.firedInteract = false; + } + + if (cancelled) { -+ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items -+ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 ++ this.player.resyncUsingItem(this.player); // Paper - Properly cancel usable items ++ this.player.containerMenu.sendAllDataToRemote(); // SPIGOT-2524 + return; + } -+ itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event -+ if (itemstack.isEmpty()) { ++ itemInHand = this.player.getItemInHand(hand); // Update in case it was changed in the event ++ if (itemInHand.isEmpty()) { + return; + } + // CraftBukkit end - InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); - - if (enuminteractionresult instanceof InteractionResult.Success) { -@@ -1321,7 +2060,7 @@ - Entity entity = packet.getEntity(worldserver); - ++ + if (this.player.gameMode.useItem(this.player, serverLevel, itemInHand, hand) instanceof InteractionResult.Success success + && success.swingSource() == InteractionResult.SwingSource.SERVER) { + this.player.swing(hand, true); +@@ -1231,7 +_,7 @@ + for (ServerLevel serverLevel : this.server.getAllLevels()) { + Entity entity = packet.getEntity(serverLevel); if (entity != null) { -- this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true); -+ this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit +- this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true); ++ this.player.teleportTo(serverLevel, entity.getX(), entity.getY(), entity.getZ(), Set.of(), entity.getYRot(), entity.getXRot(), true, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit return; } } -@@ -1342,22 +2081,52 @@ +@@ -1248,24 +_,54 @@ @Override - public void onDisconnect(DisconnectionDetails info) { + public void onDisconnect(DisconnectionDetails details) { + // Paper start - Fix kick event leave message not being sent -+ this.onDisconnect(info, null); ++ this.onDisconnect(details, null); + } ++ + @Override -+ public void onDisconnect(DisconnectionDetails info, @Nullable net.kyori.adventure.text.Component quitMessage) { ++ public void onDisconnect(DisconnectionDetails details, @Nullable net.kyori.adventure.text.Component quitMessage) { + // Paper end - Fix kick event leave message not being sent + // CraftBukkit start - Rarely it would send a disconnect line twice + if (this.processedDisconnect) { @@ -1337,15 +1282,15 @@ + this.processedDisconnect = true; + } + // CraftBukkit end - ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), info.reason().getString()); + LOGGER.info("{} lost connection: {}", this.player.getName().getString(), details.reason().getString()); - this.removePlayerFromWorld(); -- super.onDisconnect(info); +- super.onDisconnect(details); + this.removePlayerFromWorld(quitMessage); // Paper - Fix kick event leave message not being sent -+ super.onDisconnect(info, quitMessage); // Paper - Fix kick event leave message not being sent ++ super.onDisconnect(details, quitMessage); // Paper - Fix kick event leave message not being sent } -+ // Paper start - Fix kick event leave message not being sent private void removePlayerFromWorld() { ++ // Paper start - Fix kick event leave message not being sent + this.removePlayerFromWorld(null); + } + @@ -1355,17 +1300,17 @@ + // CraftBukkit start - Replace vanilla quit message handling with our own. + /* this.server.invalidateStatus(); -- this.server.getPlayerList().broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false); -+ this.server.getPlayerList().broadcastSystemMessage(IChatBaseComponent.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(EnumChatFormat.YELLOW), false); -+ */ -+ + this.server + .getPlayerList() + .broadcastSystemMessage(Component.translatable("multiplayer.player.left", this.player.getDisplayName()).withStyle(ChatFormatting.YELLOW), false); ++ */ this.player.disconnect(); - this.server.getPlayerList().remove(this.player); + // Paper start - Adventure + quitMessage = quitMessage == null ? this.server.getPlayerList().remove(this.player) : this.server.getPlayerList().remove(this.player, quitMessage); // Paper - pass in quitMessage to fix kick message not being used + if ((quitMessage != null) && !quitMessage.equals(net.kyori.adventure.text.Component.empty())) { + this.server.getPlayerList().broadcastSystemMessage(PaperAdventure.asVanilla(quitMessage), false); -+ // Paper end ++ // Paper end - Adventure + } + // CraftBukkit end this.player.getTextFilter().leave(); @@ -1377,7 +1322,7 @@ throw new IllegalArgumentException("Expected packet sequence nr >= 0"); } else { this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); -@@ -1367,7 +2136,17 @@ +@@ -1275,7 +_,17 @@ @Override public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1395,10 +1340,10 @@ if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) { this.player.stopUsingItem(); } -@@ -1376,11 +2155,18 @@ +@@ -1284,11 +_,18 @@ this.player.resetLastActionTime(); } else { - ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); + LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); + this.disconnect(Component.literal("Invalid hotbar selection (Hacking?)"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); // CraftBukkit // Paper - kick event cause } } @@ -1412,23 +1357,22 @@ + } + // CraftBukkit end Optional optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages()); - if (!optional.isEmpty()) { -@@ -1394,27 +2180,46 @@ + this.tryHandleChat(packet.message(), () -> { +@@ -1300,25 +_,45 @@ return; } -- CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()); -- Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); -+ CompletableFuture completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat -+ CompletableFuture componentFuture = this.server.getChatDecorator().decorate(this.player, null, playerchatmessage.decoratedContent()); // Paper - Adventure - -- this.chatMessageChain.append(completablefuture, (filteredtext) -> { -- PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(ichatbasecomponent).filter(filteredtext.mask()); -+ this.chatMessageChain.append(CompletableFuture.allOf(completablefuture, componentFuture), (filteredtext) -> { // Paper - Adventure -+ PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(componentFuture.join()).filter(completablefuture.join().mask()); // Paper - Adventure - - this.broadcastChatMessage(playerchatmessage1); +- CompletableFuture completableFuture = this.filterTextPacket(signedMessage.signedContent()); +- Component component = this.server.getChatDecorator().decorate(this.player, signedMessage.decoratedContent()); +- this.chatMessageChain.append(completableFuture, filteredText -> { +- PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(component).filter(filteredText.mask()); ++ CompletableFuture completableFuture = this.filterTextPacket(signedMessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat ++ CompletableFuture componentFuture = this.server.getChatDecorator().decorate(this.player, null, signedMessage.decoratedContent()); // Paper - Adventure ++ ++ this.chatMessageChain.append(CompletableFuture.allOf(completableFuture, componentFuture), (filteredtext) -> { // Paper - Adventure ++ PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(componentFuture.join()).filter(completableFuture.join().mask()); // Paper - Adventure + this.broadcastChatMessage(playerChatMessage); }); - }); + }, false); // CraftBukkit - async chat @@ -1452,12 +1396,12 @@ private void performUnsignedChatCommand(String command) { + // CraftBukkit start -+ String command1 = "/" + command; ++ String prefixedCommand = "/" + command; + if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check -+ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command1); ++ LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), prefixedCommand); + } + -+ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command1, new LazyPlayerSet(this.server)); ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), prefixedCommand, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); + + if (event.isCancelled()) { @@ -1465,11 +1409,11 @@ + } + command = event.getMessage().substring(1); + // CraftBukkit end - ParseResults parseresults = this.parseCommand(command); - - if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseresults)) { -@@ -1431,30 +2236,58 @@ - + ParseResults parseResults = this.parseCommand(command); + if (this.server.enforceSecureProfile() && SignableCommand.hasSignableArguments(parseResults)) { + LOGGER.error( +@@ -1335,13 +_,29 @@ + Optional optional = this.unpackAndApplyLastSeen(packet.lastSeenMessages()); if (!optional.isEmpty()) { this.tryHandleChat(packet.command(), () -> { + // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands @@ -1477,7 +1421,7 @@ + return; + } + // CraftBukkit end - this.performSignedChatCommand(packet, (LastSeenMessages) optional.get()); + this.performSignedChatCommand(packet, optional.get()); - this.detectRateSpam(); - }); + this.detectRateSpam("/" + packet.command()); // Spigot @@ -1486,27 +1430,21 @@ } private void performSignedChatCommand(ServerboundChatCommandSignedPacket packet, LastSeenMessages lastSeenMessages) { -- ParseResults parseresults = this.parseCommand(packet.command()); + // CraftBukkit start + String command = "/" + packet.command(); + if (org.spigotmc.SpigotConfig.logCommands) { // Paper - Add missing SpigotConfig logCommands check -+ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); ++ LOGGER.info("{} issued server command: {}", this.player.getScoreboardName(), command); + } // Paper - Add missing SpigotConfig logCommands check - -- Map map; ++ + PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(this.getCraftPlayer(), command, new LazyPlayerSet(this.server)); + this.cserver.getPluginManager().callEvent(event); + command = event.getMessage().substring(1); - -+ // Paper start - Fix cancellation and message changing -+ ParseResults parseresults = this.parseCommand(packet.command()); + -+ Map map; - try { -+ // Always parse the original command to add to the chat chain - map = this.collectSignedArguments(packet, SignableCommand.of(parseresults), lastSeenMessages); - } catch (SignedMessageChain.DecodeException signedmessagechain_a) { - this.handleMessageDecodeFailure(signedmessagechain_a); ++ // Paper start - Fix cancellation and message changing + ParseResults parseResults = this.parseCommand(packet.command()); + + Map map; +@@ -1352,9 +_,21 @@ return; } @@ -1517,73 +1455,59 @@ + + // Remove signed parts if the command was changed + if (!command.equals(packet.command())) { -+ parseresults = this.parseCommand(command); ++ parseResults = this.parseCommand(command); + map = Collections.emptyMap(); + } + // Paper end - Fix cancellation and message changing + - CommandSigningContext.SignedArguments commandsigningcontext_a = new CommandSigningContext.SignedArguments(map); - -- parseresults = Commands.mapSource(parseresults, (commandlistenerwrapper) -> { -+ parseresults = Commands.mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error - return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain); - }); -- this.server.getCommands().performCommand(parseresults, packet.command()); -+ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit + CommandSigningContext commandSigningContext = new CommandSigningContext.SignedArguments(map); + parseResults = Commands.mapSource(parseResults, source -> source.withSigningContext(commandSigningContext, this.chatMessageChain)); +- this.server.getCommands().performCommand(parseResults, packet.command()); ++ this.server.getCommands().performCommand(parseResults, command); // CraftBukkit } private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) { -@@ -1530,14 +2363,20 @@ - return com_mojang_brigadier_commanddispatcher.parse(command, this.player.createCommandSourceStack()); +@@ -1418,14 +_,20 @@ + return dispatcher.parse(command, this.player.createCommandSourceStack()); } -- private void tryHandleChat(String message, Runnable callback) { -- if (ServerGamePacketListenerImpl.isChatMessageIllegal(message)) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.illegal_characters")); +- private void tryHandleChat(String message, Runnable handler) { ++ private void tryHandleChat(String message, Runnable handler, boolean sync) { // CraftBukkit + if (isChatMessageIllegal(message)) { +- this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); - } else if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { -+ private void tryHandleChat(String s, Runnable runnable, boolean sync) { // CraftBukkit -+ if (ServerGamePacketListenerImpl.isChatMessageIllegal(s)) { -+ this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper // Paper - add proper async disconnect ++ this.disconnectAsync(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add proper async disconnect + } else if (this.player.isRemoved() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { // CraftBukkit - dead men tell no tales this.send(new ClientboundSystemChatPacket(Component.translatable("chat.disabled.options").withStyle(ChatFormatting.RED), false)); } else { this.player.resetLastActionTime(); -- this.server.execute(callback); +- this.server.execute(handler); + // CraftBukkit start + if (sync) { -+ this.server.execute(runnable); ++ this.server.execute(handler); + } else { -+ runnable.run(); ++ handler.run(); + } + // CraftBukkit end } } -@@ -1549,7 +2388,7 @@ - - if (optional.isEmpty()) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect - } - - return optional; -@@ -1566,6 +2405,117 @@ +@@ -1451,22 +_,155 @@ return false; } + // CraftBukkit start - add method -+ public void chat(String s, PlayerChatMessage original, boolean async) { -+ if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { ++ public void chat(String msg, PlayerChatMessage original, boolean async) { ++ if (msg.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { + return; + } + OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); + -+ if (false && !async && s.startsWith("/")) { // Paper - Don't handle commands in chat logic -+ this.handleCommand(s); ++ if (false && !async && msg.startsWith("/")) { // Paper - Don't handle commands in chat logic ++ this.handleCommand(msg); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { + // Do nothing, this is coming from a plugin -+ // Paper start ++ // Paper start + } else if (true) { + if (!async && !org.bukkit.Bukkit.isPrimaryThread()) { + org.spigotmc.AsyncCatcher.catchOp("Asynchronous player chat is not allowed here"); @@ -1592,8 +1516,8 @@ + cp.process(); + // Paper end + } else if (false) { // Paper -+ Player player = this.getCraftPlayer(); -+ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(this.server)); ++ org.bukkit.entity.Player player = this.getCraftPlayer(); ++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, msg, new LazyPlayerSet(this.server)); + String originalFormat = event.getFormat(), originalMessage = event.getMessage(); + this.cserver.getPluginManager().callEvent(event); + @@ -1601,7 +1525,7 @@ + // 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() { ++ Waitable waitable = new Waitable<>() { + @Override + protected Object evaluate() { + org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent); @@ -1621,8 +1545,8 @@ + recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); + } + } else { -+ for (Player player : queueEvent.getRecipients()) { -+ player.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); ++ for (org.bukkit.entity.Player recipient : queueEvent.getRecipients()) { ++ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); + } + } + ServerGamePacketListenerImpl.this.server.console.sendMessage(message); @@ -1646,22 +1570,22 @@ + return; + } + -+ s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); ++ msg = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); + if (((LazyPlayerSet) event.getRecipients()).isLazy()) { + if (!org.spigotmc.SpigotConfig.bungee && originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) { // Spigot -+ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player)); ++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, ServerGamePacketListenerImpl.this.player)); + return; + } + + for (ServerPlayer recipient : this.server.getPlayerList().players) { -+ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), msg); + } + } else { -+ for (Player recipient : event.getRecipients()) { -+ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ for (org.bukkit.entity.Player recipient : event.getRecipients()) { ++ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), msg); + } + } -+ this.server.console.sendMessage(s); ++ this.server.console.sendMessage(msg); + } + } + } @@ -1684,69 +1608,62 @@ + // CraftBukkit end + private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException { - SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages); - -@@ -1573,15 +2523,44 @@ + SignedMessageBody signedMessageBody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages); + return this.signedMessageDecoder.unpack(packet.signature(), signedMessageBody); } private void broadcastChatMessage(PlayerChatMessage message) { -- this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, (Entity) this.player)); +- this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player)); - this.detectRateSpam(); + // CraftBukkit start -+ String s = message.signedContent(); -+ if (s.isEmpty()) { -+ ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message"); ++ String rawMessage = message.signedContent(); ++ if (rawMessage.isEmpty()) { ++ LOGGER.warn("{} tried to send an empty message", this.player.getScoreboardName()); + } else if (this.getCraftPlayer().isConversing()) { -+ final String conversationInput = s; -+ this.server.processQueue.add(new Runnable() { -+ @Override -+ public void run() { -+ ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput); -+ } -+ }); ++ final String conversationInput = rawMessage; ++ this.server.processQueue.add(() -> ServerGamePacketListenerImpl.this.getCraftPlayer().acceptConversationInput(conversationInput)); + } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { // Re-add "Command Only" flag check + this.send(new ClientboundSystemChatPacket(Component.translatable("chat.cannotSend").withStyle(ChatFormatting.RED), false)); + } else { -+ this.chat(s, message, true); ++ this.chat(rawMessage, message, true); + } -+ // this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player)); ++ // this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player)); + // CraftBukkit end -+ this.detectRateSpam(s); // Spigot ++ this.detectRateSpam(rawMessage); // Spigot } - private void detectRateSpam() { - this.chatSpamThrottler.increment(); -- if (!this.chatSpamThrottler.isUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { -- this.disconnect((Component) Component.translatable("disconnect.spam")); +- if (!this.chatSpamThrottler.isUnderThreshold() + // Spigot start - spam exclusions -+ private void detectRateSpam(String s) { ++ private void detectRateSpam(String message) { + // CraftBukkit start - replaced with thread safe throttle -+ for ( String exclude : org.spigotmc.SpigotConfig.spamExclusions ) -+ { -+ if ( exclude != null && s.startsWith( exclude ) ) -+ { ++ for (String exclude : org.spigotmc.SpigotConfig.spamExclusions) { ++ if (exclude != null && message.startsWith(exclude)) { + return; + } - } ++ } + // Spigot end + // this.chatSpamThrottler.increment(); -+ if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() && !this.server.getPlayerList().isOp(this.player.getGameProfile()) && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { ++ if (!this.chatSpamThrottler.isIncrementAndUnderThreshold() + // CraftBukkit end -+ this.disconnectAsync((Component) Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause // Paper - add proper async disconnect -+ } - + && !this.server.getPlayerList().isOp(this.player.getGameProfile()) + && !this.server.isSingleplayerOwner(this.player.getGameProfile())) { +- this.disconnect(Component.translatable("disconnect.spam")); ++ this.disconnectAsync(Component.translatable("disconnect.spam"), org.bukkit.event.player.PlayerKickEvent.Cause.SPAM); // Paper - kick event cause & add proper async disconnect + } } -@@ -1592,7 +2571,7 @@ +@@ -1475,7 +_,7 @@ synchronized (this.lastSeenMessages) { if (!this.lastSeenMessages.applyOffset(packet.offset())) { - ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); -- this.disconnect(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED); -+ this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes // Paper - add proper async disconnect + LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +- this.disconnect(CHAT_VALIDATION_FAILED); ++ this.disconnectAsync(ServerGamePacketListenerImpl.CHAT_VALIDATION_FAILED, org.bukkit.event.player.PlayerKickEvent.Cause.CHAT_VALIDATION_FAILED); // Paper - kick event causes & add proper async disconnect } - } -@@ -1601,7 +2580,40 @@ + } +@@ -1483,7 +_,40 @@ @Override public void handleAnimate(ServerboundSwingPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -1787,7 +1704,7 @@ this.player.swing(packet.getHand()); } -@@ -1609,6 +2621,29 @@ +@@ -1491,10 +_,41 @@ public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (this.player.hasClientLoaded()) { @@ -1795,7 +1712,7 @@ + if (this.player.isRemoved()) return; + switch (packet.getAction()) { + case PRESS_SHIFT_KEY: -+ case RELEASE_SHIFT_KEY: ++ case RELEASE_SHIFT_KEY: { + PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.PRESS_SHIFT_KEY); + this.cserver.getPluginManager().callEvent(event); + @@ -1803,21 +1720,21 @@ + return; + } + break; ++ } + case START_SPRINTING: -+ case STOP_SPRINTING: -+ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.START_SPRINTING); -+ this.cserver.getPluginManager().callEvent(e2); ++ case STOP_SPRINTING: { ++ PlayerToggleSprintEvent event = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.Action.START_SPRINTING); ++ this.cserver.getPluginManager().callEvent(event); + -+ if (e2.isCancelled()) { ++ if (event.isCancelled()) { + return; + } + break; ++ } + } + // CraftBukkit end ++ this.player.resetLastActionTime(); - Entity entity; - PlayerRideableJumping ijumpable; -@@ -1616,6 +2651,11 @@ switch (packet.getAction()) { case PRESS_SHIFT_KEY: this.player.setShiftKeyDown(true); @@ -1829,34 +1746,38 @@ break; case RELEASE_SHIFT_KEY: this.player.setShiftKeyDown(false); -@@ -1684,15 +2724,25 @@ +@@ -1551,12 +_,20 @@ } if (i > 4096) { -- this.disconnect((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats")); -+ this.disconnectAsync((Component) Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause // Paper - add proper async disconnect +- this.disconnect(Component.translatable("multiplayer.disconnect.too_many_pending_chats")); ++ this.disconnectAsync(Component.translatable("multiplayer.disconnect.too_many_pending_chats"), org.bukkit.event.player.PlayerKickEvent.Cause.TOO_MANY_PENDING_CHATS); // Paper - kick event cause & add proper async disconnect } - } } - public void sendPlayerChatMessage(PlayerChatMessage message, ChatType.Bound params) { + public void sendPlayerChatMessage(PlayerChatMessage chatMessage, ChatType.Bound boundType) { + // 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 (!this.getCraftPlayer().canSeePlayer(message.link().sender())) { -+ this.sendDisguisedChatMessage(message.decoratedContent(), params); ++ if (!this.getCraftPlayer().canSeePlayer(chatMessage.link().sender())) { ++ this.sendDisguisedChatMessage(chatMessage.decoratedContent(), boundType); + return; + } + // CraftBukkit end + // Paper start - Ensure that client receives chat packets in the same order that we add into the message signature cache + synchronized (this.messageSignatureCache) { - this.send(new ClientboundPlayerChatPacket(message.link().sender(), message.link().index(), message.signature(), message.signedBody().pack(this.messageSignatureCache), message.unsignedContent(), message.filterMask(), params)); - this.addPendingMessage(message); + this.send( + new ClientboundPlayerChatPacket( + chatMessage.link().sender(), +@@ -1569,6 +_,8 @@ + ) + ); + this.addPendingMessage(chatMessage); + } + // Paper end - Ensure that client receives chat packets in the same order that we add into the message signature cache } - public void sendDisguisedChatMessage(Component message, ChatType.Bound params) { -@@ -1703,6 +2753,18 @@ + public void sendDisguisedChatMessage(Component message, ChatType.Bound boundType) { +@@ -1579,6 +_,18 @@ return this.connection.getRemoteAddress(); } @@ -1875,133 +1796,124 @@ public void switchToConfig() { this.waitingForSwitchToConfig = true; this.removePlayerFromWorld(); -@@ -1718,9 +2780,17 @@ +@@ -1594,9 +_,16 @@ @Override public void handleInteract(ServerboundInteractPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.isImmobile()) return; // CraftBukkit if (this.player.hasClientLoaded()) { - final ServerLevel worldserver = this.player.serverLevel(); - final Entity entity = packet.getTarget(worldserver); + final ServerLevel serverLevel = this.player.serverLevel(); + final Entity target = packet.getTarget(serverLevel); + // Spigot Start -+ if ( entity == this.player && !this.player.isSpectator() ) -+ { -+ this.disconnect( Component.literal( "Cannot interact with self!" ), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION ); // Paper - kick event cause ++ if (target == this.player && !this.player.isSpectator()) { ++ this.disconnect(Component.literal("Cannot interact with self!"), org.bukkit.event.player.PlayerKickEvent.Cause.SELF_INTERACTION); // Paper - kick event cause + return; + } + // Spigot End - this.player.resetLastActionTime(); this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); -@@ -1731,22 +2801,61 @@ + if (target != null) { +@@ -1605,16 +_,55 @@ + } - AABB axisalignedbb = entity.getBoundingBox(); - -- if (this.player.canInteractWithEntity(axisalignedbb, 3.0D)) { -+ if (this.player.canInteractWithEntity(axisalignedbb, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range - packet.dispatch(new ServerboundInteractPacket.Handler() { -- private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction action) { -- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(hand); -+ private void performInteraction(InteractionHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit -+ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); - - if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { - ItemStack itemstack1 = itemstack.copy(); -- InteractionResult enuminteractionresult = action.run(ServerGamePacketListenerImpl.this.player, entity, hand); -+ // CraftBukkit start -+ ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); -+ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof Mob; -+ Item origItem = ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null ? null : ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem(); - -+ ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); + AABB boundingBox = target.getBoundingBox(); +- if (this.player.canInteractWithEntity(boundingBox, 3.0)) { ++ if (this.player.canInteractWithEntity(boundingBox, io.papermc.paper.configuration.GlobalConfiguration.get().misc.clientInteractionLeniencyDistance.or(3.0D))) { // Paper - configurable lenience value for interact range + packet.dispatch( + new ServerboundInteractPacket.Handler() { +- private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction) { ++ private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction, PlayerInteractEntityEvent event) { // CraftBukkit + ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(hand); + if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) { + ItemStack itemStack = itemInHand.copy(); +- if (entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand) instanceof InteractionResult.Success success +- ) +- { ++ // CraftBukkit start ++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && target instanceof net.minecraft.world.entity.Mob; ++ Item origItem = ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null ? null : ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem(); + -+ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a -+ if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { -+ entity.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it -+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); -+ } ++ ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); + -+ if (triggerLeashUpdate && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { -+ // Refresh the current leash state -+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder())); -+ } -+ -+ if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) { -+ // Refresh the current entity metadata -+ entity.refreshEntityData(ServerGamePacketListenerImpl.this.player); -+ // SPIGOT-7136 - Allays -+ if (entity instanceof Allay || entity instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync -+ ServerGamePacketListenerImpl.this.send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize ++ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a ++ if ((target instanceof Bucketable && target instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { ++ target.resendPossiblyDesyncedEntityData(ServerGamePacketListenerImpl.this.player); // Paper - The entire mob gets deleted, so resend it ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); + } + -+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory -+ } ++ if (triggerLeashUpdate && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { ++ // Refresh the current leash state ++ ServerGamePacketListenerImpl.this.send(new net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket(target, ((net.minecraft.world.entity.Mob) target).getLeashHolder())); ++ } + -+ if (event.isCancelled()) { -+ return; -+ } -+ // CraftBukkit end -+ InteractionResult enuminteractionresult = playerconnection_a.run(ServerGamePacketListenerImpl.this.player, entity, enumhand); ++ if (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem) { ++ // Refresh the current entity metadata ++ target.refreshEntityData(ServerGamePacketListenerImpl.this.player); ++ // SPIGOT-7136 - Allays ++ if (target instanceof Allay || target instanceof net.minecraft.world.entity.animal.horse.AbstractHorse) { // Paper - Fix horse armor desync ++ ServerGamePacketListenerImpl.this.send(new net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket(target.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) target).getItemBySlot(slot).copy())).collect(Collectors.toList()), true)); // Paper - sanitize ++ } + -+ // CraftBukkit start -+ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { -+ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); -+ } -+ // CraftBukkit end ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); // Paper - fix slot desync - always refresh player inventory ++ } ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ InteractionResult result = entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand); + - if (enuminteractionresult instanceof InteractionResult.Success) { - InteractionResult.Success enuminteractionresult_d = (InteractionResult.Success) enuminteractionresult; - ItemStack itemstack2 = enuminteractionresult_d.wasItemInteraction() ? itemstack1 : ItemStack.EMPTY; - - CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemstack2, entity); - if (enuminteractionresult_d.swingSource() == InteractionResult.SwingSource.SERVER) { -- ServerGamePacketListenerImpl.this.player.swing(hand, true); -+ ServerGamePacketListenerImpl.this.player.swing(enumhand, true); - } - } - -@@ -1755,19 +2864,20 @@ - - @Override - public void onInteraction(InteractionHand hand) { -- this.performInteraction(hand, Player::interactOn); -+ this.performInteraction(hand, net.minecraft.world.entity.player.Player::interactOn, new PlayerInteractEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit - } - - @Override - public void onInteraction(InteractionHand hand, Vec3 pos) { - this.performInteraction(hand, (entityplayer, entity1, enumhand1) -> { - return entity1.interactAt(entityplayer, pos, enumhand1); -- }); -+ }, new PlayerInteractAtEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(pos.x, pos.y, pos.z), (hand == InteractionHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit - } - - @Override - public void onAttack() { -- if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && entity != ServerGamePacketListenerImpl.this.player) { -+ // CraftBukkit -+ if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && (entity != ServerGamePacketListenerImpl.this.player || ServerGamePacketListenerImpl.this.player.isSpectator())) { - label23: - { - if (entity instanceof AbstractArrow) { -@@ -1785,17 +2895,41 @@ - } - - ServerGamePacketListenerImpl.this.player.attack(entity); + // CraftBukkit start -+ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) { ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { + ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); + } + // CraftBukkit end - return; - } ++ ++ if (result instanceof InteractionResult.Success success // CraftBukkit ++ ) { + ItemStack itemStack1 = success.wasItemInteraction() ? itemStack : ItemStack.EMPTY; + CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemStack1, target); + if (success.swingSource() == InteractionResult.SwingSource.SERVER) { +@@ -1626,13 +_,13 @@ + + @Override + public void onInteraction(InteractionHand hand) { +- this.performInteraction(hand, Player::interactOn); ++ this.performInteraction(hand, Player::interactOn, new PlayerInteractEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), target.getBukkitEntity(), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand))); // CraftBukkit } -- ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); -+ ServerGamePacketListenerImpl.this.disconnect((Component) Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause - ServerGamePacketListenerImpl.LOGGER.warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); - } - }); + @Override + public void onInteraction(InteractionHand hand, Vec3 interactionLocation) { + this.performInteraction( +- hand, (player, entity, interactionHand) -> entity.interactAt(player, interactionLocation, interactionHand) ++ hand, (player, entity, interactionHand) -> entity.interactAt(player, interactionLocation, interactionHand), new PlayerInteractAtEntityEvent(ServerGamePacketListenerImpl.this.getCraftPlayer(), target.getBukkitEntity(), org.bukkit.craftbukkit.util.CraftVector.toBukkit(interactionLocation), org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(hand)) // CraftBukkit + ); + } + +@@ -1640,14 +_,19 @@ + public void onAttack() { + if (!(target instanceof ItemEntity) + && !(target instanceof ExperienceOrb) +- && target != ServerGamePacketListenerImpl.this.player ++ && (target != ServerGamePacketListenerImpl.this.player || ServerGamePacketListenerImpl.this.player.isSpectator()) // CraftBukkit + && !(target instanceof AbstractArrow abstractArrow && !abstractArrow.isAttackable())) { + ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND); + if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) { + ServerGamePacketListenerImpl.this.player.attack(target); ++ // CraftBukkit start ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ ServerGamePacketListenerImpl.this.player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end + } + } else { +- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); ++ ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"), org.bukkit.event.player.PlayerKickEvent.Cause.INVALID_ENTITY_ATTACKED); // Paper - add cause + ServerGamePacketListenerImpl.LOGGER + .warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString()); + } +@@ -1656,6 +_,26 @@ + ); } } + // Paper start - PlayerUseUnknownEntityEvent @@ -2011,7 +1923,7 @@ + public void onInteraction(net.minecraft.world.InteractionHand hand) { + CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, null); + } - ++ + @Override + public void onInteraction(net.minecraft.world.InteractionHand hand, net.minecraft.world.phys.Vec3 pos) { + CraftEventFactory.callPlayerUseUnknownEntityEvent(ServerGamePacketListenerImpl.this.player, packet, hand, pos); @@ -2027,7 +1939,7 @@ } } -@@ -1809,7 +2943,7 @@ +@@ -1668,7 +_,7 @@ case PERFORM_RESPAWN: if (this.player.wonGame) { this.player.wonGame = false; @@ -2036,7 +1948,7 @@ this.resetPosition(); CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); } else { -@@ -1817,11 +2951,11 @@ +@@ -1676,11 +_,11 @@ return; } @@ -2045,19 +1957,20 @@ this.resetPosition(); if (this.server.isHardcore()) { - this.player.setGameMode(GameType.SPECTATOR); -- ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); +- this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS).set(false, this.server); + this.player.setGameMode(GameType.SPECTATOR, org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.HARDCORE_DEATH, null); // Paper - Expand PlayerGameModeChangeEvent -+ ((GameRules.BooleanValue) this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.player.serverLevel()); // CraftBukkit - per-world ++ this.player.serverLevel().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS).set(false, this.player.serverLevel()); // CraftBukkit - per-world } } break; -@@ -1833,16 +2967,27 @@ +@@ -1691,16 +_,28 @@ @Override public void handleContainerClose(ServerboundContainerClosePacket packet) { + // Paper start - Inventory close reason + this.handleContainerClose(packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason.PLAYER); + } ++ + public void handleContainerClose(ServerboundContainerClosePacket packet, org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { + // Paper end - Inventory close reason PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -2080,26 +1993,24 @@ + if (false/*this.player.isSpectator()*/) { // CraftBukkit this.player.containerMenu.sendAllDataToRemote(); } else if (!this.player.containerMenu.stillValid(this.player)) { - ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); -@@ -1855,7 +3000,315 @@ + LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); +@@ -1713,7 +_,313 @@ + } else { boolean flag = packet.getStateId() != this.player.containerMenu.getStateId(); - this.player.containerMenu.suppressRemoteUpdates(); -- this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player); +- this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player); + // CraftBukkit start - Call InventoryClickEvent -+ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) { ++ if (slotNum < -1 && slotNum != AbstractContainerMenu.SLOT_CLICKED_OUTSIDE) { + return; + } + + InventoryView inventory = this.player.containerMenu.getBukkitView(); -+ SlotType type = inventory.getSlotType(packet.getSlotNum()); ++ SlotType type = inventory.getSlotType(slotNum); + + InventoryClickEvent event; + ClickType click = ClickType.UNKNOWN; + InventoryAction action = InventoryAction.UNKNOWN; + -+ ItemStack itemstack = ItemStack.EMPTY; -+ + switch (packet.getClickType()) { + case PICKUP: + if (packet.getButtonNum() == 0) { @@ -2109,14 +2020,14 @@ + } + if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { + action = InventoryAction.NOTHING; // Don't want to repeat ourselves -+ if (packet.getSlotNum() == -999) { ++ if (slotNum == AbstractContainerMenu.SLOT_CLICKED_OUTSIDE) { + if (!this.player.containerMenu.getCarried().isEmpty()) { + action = packet.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; + } -+ } else if (packet.getSlotNum() < 0) { ++ } else if (slotNum < 0) { + action = InventoryAction.NOTHING; + } else { -+ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ Slot slot = this.player.containerMenu.getSlot(slotNum); + if (slot != null) { + ItemStack clickedItem = slot.getItem(); + ItemStack cursor = this.player.containerMenu.getCarried(); @@ -2157,7 +2068,7 @@ + } + } + break; -+ // TODO check on updates ++ // TODO check on updates + case QUICK_MOVE: + if (packet.getButtonNum() == 0) { + click = ClickType.SHIFT_LEFT; @@ -2165,10 +2076,10 @@ + click = ClickType.SHIFT_RIGHT; + } + if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { -+ if (packet.getSlotNum() < 0) { ++ if (slotNum < 0) { + action = InventoryAction.NOTHING; + } else { -+ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ Slot slot = this.player.containerMenu.getSlot(slotNum); + if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) { + action = InventoryAction.MOVE_TO_OTHER_INVENTORY; + } else { @@ -2177,150 +2088,150 @@ + } + } + break; -+ case SWAP: -+ if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { -+ // Paper start - Add slot sanity checks to container clicks -+ if (packet.getSlotNum() < 0) { -+ action = InventoryAction.NOTHING; -+ break; -+ } -+ // Paper end - Add slot sanity checks to container clicks -+ click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; -+ Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); -+ if (clickedSlot.mayPickup(this.player)) { -+ ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum()); -+ if ((!hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) || (hotbar.isEmpty() && clickedSlot.hasItem())) { // Paper - modernify this logic (no such thing as a "hotbar move and readd" -+ action = InventoryAction.HOTBAR_SWAP; ++ case SWAP: ++ if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == Inventory.SLOT_OFFHAND) { ++ // Paper start - Add slot sanity checks to container clicks ++ if (slotNum < 0) { ++ action = InventoryAction.NOTHING; ++ break; ++ } ++ // Paper end - Add slot sanity checks to container clicks ++ click = (packet.getButtonNum() == Inventory.SLOT_OFFHAND) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; ++ Slot clickedSlot = this.player.containerMenu.getSlot(slotNum); ++ if (clickedSlot.mayPickup(this.player)) { ++ ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum()); ++ if ((!hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) || (hotbar.isEmpty() && clickedSlot.hasItem())) { // Paper - modernify this logic (no such thing as a "hotbar move and readd" ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.NOTHING; ++ } + } else { + action = InventoryAction.NOTHING; + } ++ } ++ break; ++ case CLONE: ++ if (packet.getButtonNum() == 2) { ++ click = ClickType.MIDDLE; ++ if (slotNum < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(slotNum); ++ if (slot != null && slot.hasItem() && this.player.getAbilities().instabuild && this.player.containerMenu.getCarried().isEmpty()) { ++ action = InventoryAction.CLONE_STACK; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } + } else { -+ action = InventoryAction.NOTHING; ++ click = ClickType.UNKNOWN; ++ action = InventoryAction.UNKNOWN; + } -+ } -+ break; -+ case CLONE: -+ if (packet.getButtonNum() == 2) { -+ click = ClickType.MIDDLE; -+ if (packet.getSlotNum() < 0) { -+ action = InventoryAction.NOTHING; ++ break; ++ case THROW: ++ if (slotNum >= 0) { ++ if (packet.getButtonNum() == 0) { ++ click = ClickType.DROP; ++ Slot slot = this.player.containerMenu.getSlot(slotNum); ++ if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Items.AIR) { ++ action = InventoryAction.DROP_ONE_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.CONTROL_DROP; ++ Slot slot = this.player.containerMenu.getSlot(slotNum); ++ if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Items.AIR) { ++ action = InventoryAction.DROP_ALL_SLOT; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } + } else { -+ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); -+ if (slot != null && slot.hasItem() && this.player.getAbilities().instabuild && this.player.containerMenu.getCarried().isEmpty()) { -+ action = InventoryAction.CLONE_STACK; -+ } else { -+ action = InventoryAction.NOTHING; ++ // Sane default (because this happens when they are holding nothing. Don't ask why.) ++ click = ClickType.LEFT; ++ if (packet.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ action = InventoryAction.NOTHING; ++ } ++ break; ++ case QUICK_CRAFT: ++ // Paper start - Fix CraftBukkit drag system ++ AbstractContainerMenu containerMenu = this.player.containerMenu; ++ int currentStatus = this.player.containerMenu.quickcraftStatus; ++ int newStatus = AbstractContainerMenu.getQuickcraftHeader(packet.getButtonNum()); ++ if ((currentStatus != 1 || newStatus != 2 && currentStatus != newStatus)) { ++ } else if (containerMenu.getCarried().isEmpty()) { ++ } else if (newStatus == 0) { ++ } else if (newStatus == 1) { ++ } else if (newStatus == 2) { ++ if (!this.player.containerMenu.quickcraftSlots.isEmpty()) { ++ if (this.player.containerMenu.quickcraftSlots.size() == 1) { ++ int index = containerMenu.quickcraftSlots.iterator().next().index; ++ containerMenu.resetQuickCraft(); ++ this.handleContainerClick(new ServerboundContainerClickPacket(packet.getContainerId(), packet.getStateId(), index, containerMenu.quickcraftType, net.minecraft.world.inventory.ClickType.PICKUP, packet.getCarriedItem(), packet.getChangedSlots())); ++ return; ++ } + } + } -+ } else { -+ click = ClickType.UNKNOWN; -+ action = InventoryAction.UNKNOWN; -+ } -+ break; -+ case THROW: -+ if (packet.getSlotNum() >= 0) { -+ if (packet.getButtonNum() == 0) { -+ click = ClickType.DROP; -+ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); -+ if (slot != null && slot.hasItem() && slot.mayPickup(this.player) && !slot.getItem().isEmpty() && slot.getItem().getItem() != Item.byBlock(Blocks.AIR)) { -+ action = InventoryAction.DROP_ONE_SLOT; -+ } else { -+ action = InventoryAction.NOTHING; -+ } -+ } else if (packet.getButtonNum() == 1) { -+ click = ClickType.CONTROL_DROP; -+ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); -+ if (slot != null && slot.hasItem() && slot.mayPickup(this.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 (packet.getButtonNum() == 1) { -+ click = ClickType.RIGHT; -+ } ++ // Paper end - Fix CraftBukkit drag system ++ this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player); ++ break; ++ case PICKUP_ALL: ++ click = ClickType.DOUBLE_CLICK; + action = InventoryAction.NOTHING; -+ } -+ break; -+ case QUICK_CRAFT: -+ // Paper start - Fix CraftBukkit drag system -+ AbstractContainerMenu containerMenu = this.player.containerMenu; -+ int currentStatus = this.player.containerMenu.quickcraftStatus; -+ int newStatus = AbstractContainerMenu.getQuickcraftHeader(packet.getButtonNum()); -+ if ((currentStatus != 1 || newStatus != 2 && currentStatus != newStatus)) { -+ } else if (containerMenu.getCarried().isEmpty()) { -+ } else if (newStatus == 0) { -+ } else if (newStatus == 1) { -+ } else if (newStatus == 2) { -+ if (!this.player.containerMenu.quickcraftSlots.isEmpty()) { -+ if (this.player.containerMenu.quickcraftSlots.size() == 1) { -+ int index = containerMenu.quickcraftSlots.iterator().next().index; -+ containerMenu.resetQuickCraft(); -+ this.handleContainerClick(new ServerboundContainerClickPacket(packet.getContainerId(), packet.getStateId(), index, containerMenu.quickcraftType, net.minecraft.world.inventory.ClickType.PICKUP, packet.getCarriedItem(), packet.getChangedSlots())); -+ return; ++ if (slotNum >= 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(CraftItemType.minecraftToBukkit(cursor.getItem())) || inventory.getBottomInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem()))) { ++ action = InventoryAction.COLLECT_TO_CURSOR; + } + } -+ } -+ // Paper end - Fix CraftBukkit drag system -+ this.player.containerMenu.clicked(packet.getSlotNum(), packet.getButtonNum(), packet.getClickType(), this.player); -+ break; -+ case PICKUP_ALL: -+ click = ClickType.DOUBLE_CLICK; -+ action = InventoryAction.NOTHING; -+ if (packet.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(CraftItemType.minecraftToBukkit(cursor.getItem())) || inventory.getBottomInventory().contains(CraftItemType.minecraftToBukkit(cursor.getItem()))) { -+ action = InventoryAction.COLLECT_TO_CURSOR; -+ } -+ } -+ break; -+ default: -+ break; ++ break; ++ default: ++ break; + } + + if (packet.getClickType() != net.minecraft.world.inventory.ClickType.QUICK_CRAFT) { + if (click == ClickType.NUMBER_KEY) { -+ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ event = new InventoryClickEvent(inventory, type, slotNum, click, action, packet.getButtonNum()); + } else { -+ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action); ++ event = new InventoryClickEvent(inventory, type, slotNum, click, action); + } + + org.bukkit.inventory.Inventory top = inventory.getTopInventory(); -+ if (packet.getSlotNum() == 0 && top instanceof CraftingInventory) { ++ if (slotNum == 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, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ event = new CraftItemEvent(recipe, inventory, type, slotNum, click, action, packet.getButtonNum()); + } else { -+ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action); ++ event = new CraftItemEvent(recipe, inventory, type, slotNum, click, action); + } + } + } + -+ if (packet.getSlotNum() == 3 && top instanceof SmithingInventory) { ++ if (slotNum == 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, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ event = new SmithItemEvent(inventory, type, slotNum, click, action, packet.getButtonNum()); + } else { -+ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ event = new SmithItemEvent(inventory, type, slotNum, click, action); + } + } + } + + // Paper start - cartography item event -+ if (packet.getSlotNum() == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) { ++ if (slotNum == net.minecraft.world.inventory.CartographyTableMenu.RESULT_SLOT && top instanceof org.bukkit.inventory.CartographyInventory cartographyInventory) { + org.bukkit.inventory.ItemStack result = cartographyInventory.getResult(); + if (result != null && !result.isEmpty()) { + if (click == ClickType.NUMBER_KEY) { -+ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, slotNum, click, action, packet.getButtonNum()); + } else { -+ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ event = new io.papermc.paper.event.player.CartographyItemEvent(inventory, type, slotNum, click, action); + } + } + } @@ -2336,7 +2247,7 @@ + switch (event.getResult()) { + case ALLOW: + case DEFAULT: -+ this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player); ++ this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player); + break; + case DENY: + /* Needs enum constructor in InventoryAction @@ -2369,22 +2280,22 @@ + case PLACE_ONE: + case SWAP_WITH_CURSOR: + this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor -+ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem())); ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), slotNum, this.player.containerMenu.getSlot(slotNum).getItem())); + break; + // Modified clicked only + case DROP_ALL_SLOT: + case DROP_ONE_SLOT: -+ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).getItem())); ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), slotNum, this.player.containerMenu.getSlot(slotNum).getItem())); + break; + // Modified cursor only + case DROP_ALL_CURSOR: -+ case DROP_ONE_CURSOR: -+ case CLONE_STACK: ++ case DROP_ONE_CURSOR: ++ case CLONE_STACK: + this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(this.player.containerMenu.getCarried().copy())); // Paper - correctly set cursor + break; + // Nothing + case NOTHING: -+ break; ++ break; + } + } + @@ -2394,11 +2305,11 @@ + this.player.containerMenu.sendAllDataToRemote(); + } + } -+ // CraftBukkit end - ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator(); ++ // CraftBukkit end - while (objectiterator.hasNext()) { -@@ -1879,6 +3332,14 @@ + for (Entry entry : Int2ObjectMaps.fastIterable(packet.getChangedSlots())) { + this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), entry.getValue()); +@@ -1733,6 +_,14 @@ @Override public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { @@ -2413,12 +2324,12 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); this.player.resetLastActionTime(); if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.containerId()) { -@@ -1900,9 +3361,43 @@ - ServerGamePacketListenerImpl.LOGGER.debug("Player {} tried to place impossible recipe {}", this.player, recipeholder.id().location()); +@@ -1749,9 +_,44 @@ return; } + + // Paper start - Add PlayerRecipeBookClickEvent -+ NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeholder.id().location()); ++ NamespacedKey recipeName = org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(recipeHolder.id().location()); + boolean makeAll = packet.useMaxItems(); + com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent paperEvent = new com.destroystokyo.paper.event.player.PlayerRecipeBookClickEvent( + this.player.getBukkitEntity(), recipeName, makeAll @@ -2429,36 +2340,37 @@ + recipeName = paperEvent.getRecipe(); + makeAll = paperEvent.isMakeAll(); + if (org.bukkit.event.player.PlayerRecipeBookClickEvent.getHandlerList().getRegisteredListeners().length > 0) { -+ // Paper end - Add PlayerRecipeBookClickEvent - -- RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(packet.useMaxItems(), this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); -+ // CraftBukkit start - implement PlayerRecipeBookClickEvent -+ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event -+ if (recipe == null) { -+ return; -+ } -+ // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event -+ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); -+ recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey(); -+ makeAll = event.isShiftClick(); ++ // Paper end - Add PlayerRecipeBookClickEvent ++ // CraftBukkit start - implement PlayerRecipeBookClickEvent ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(recipeName); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event ++ if (recipe == null) { ++ return; ++ } ++ // Paper start - Add PlayerRecipeBookClickEvent - forward to legacy event ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, makeAll); ++ recipeName = ((org.bukkit.Keyed) event.getRecipe()).getKey(); ++ makeAll = event.isShiftClick(); + } + if (!(this.player.containerMenu instanceof RecipeBookMenu)) { + return; + } + // Paper end - Add PlayerRecipeBookClickEvent - forward to legacy event - ++ + // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. -+ recipeholder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event -+ if (recipeholder == null) { ++ recipeHolder = this.server.getRecipeManager().byKey(net.minecraft.resources.ResourceKey.create(net.minecraft.core.registries.Registries.RECIPE, org.bukkit.craftbukkit.util.CraftNamespacedKey.toMinecraft(recipeName))).orElse(null); // Paper - Add PlayerRecipeBookClickEvent - forward to legacy event ++ if (recipeHolder == null) { + return; + } -+ RecipeBookMenu.PostPlaceAction containerrecipebook_a = containerrecipebook.handlePlacement(makeAll, this.player.isCreative(), recipeholder, this.player.serverLevel(), this.player.getInventory()); -+ // CraftBukkit end + - if (containerrecipebook_a == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) { - this.player.connection.send(new ClientboundPlaceGhostRecipePacket(this.player.containerMenu.containerId, craftingmanager_d.display().display())); - } -@@ -1917,6 +3412,7 @@ + RecipeBookMenu.PostPlaceAction postPlaceAction = recipeBookMenu.handlePlacement( +- packet.useMaxItems(), this.player.isCreative(), recipeHolder, this.player.serverLevel(), this.player.getInventory() ++ makeAll, this.player.isCreative(), recipeHolder, this.player.serverLevel(), this.player.getInventory() + ); ++ // CraftBukkit end + if (postPlaceAction == RecipeBookMenu.PostPlaceAction.PLACE_GHOST_RECIPE) { + this.player + .connection +@@ -1767,6 +_,7 @@ @Override public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -2466,15 +2378,15 @@ this.player.resetLastActionTime(); if (this.player.containerMenu.containerId == packet.containerId() && !this.player.isSpectator()) { if (!this.player.containerMenu.stillValid(this.player)) { -@@ -1945,7 +3441,44 @@ +@@ -1792,6 +_,43 @@ boolean flag1 = packet.slotNum() >= 1 && packet.slotNum() <= 45; - boolean flag2 = itemstack.isEmpty() || itemstack.getCount() <= itemstack.getMaxStackSize(); + boolean flag2 = itemStack.isEmpty() || itemStack.getCount() <= itemStack.getMaxStackSize(); + if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.slotNum()).getItem(), packet.itemStack()))) { // Insist on valid slot + // CraftBukkit start - Call click event + InventoryView inventory = this.player.inventoryMenu.getBukkitView(); + org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet.itemStack()); - ++ + SlotType type = SlotType.QUICKBAR; + if (flag) { + type = SlotType.OUTSIDE; @@ -2485,37 +2397,36 @@ + type = SlotType.CONTAINER; + } + } -+ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet.slotNum(), item); ++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? AbstractContainerMenu.SLOT_CLICKED_OUTSIDE : packet.slotNum(), item); + this.cserver.getPluginManager().callEvent(event); + -+ itemstack = CraftItemStack.asNMSCopy(event.getCursor()); ++ 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 (packet.slotNum() >= 0) { -+ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.slotNum(), this.player.inventoryMenu.getSlot(packet.slotNum()).getItem())); -+ this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(ItemStack.EMPTY.copy())); // Paper - correctly set cursor -+ } -+ return; ++ case ALLOW: ++ // Plugin cleared the id / stacksize checks ++ flag2 = true; ++ break; ++ case DEFAULT: ++ break; ++ case DENY: ++ // Reset the slot ++ if (packet.slotNum() >= 0) { ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.slotNum(), this.player.inventoryMenu.getSlot(packet.slotNum()).getItem())); ++ this.player.connection.send(new net.minecraft.network.protocol.game.ClientboundSetCursorItemPacket(ItemStack.EMPTY.copy())); // Paper - correctly set cursor ++ } ++ return; + } + } + // CraftBukkit end -+ if (flag1 && flag2) { - this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemstack); - this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemstack); -@@ -1964,7 +3497,19 @@ + this.player.inventoryMenu.getSlot(packet.slotNum()).setByPlayer(itemStack); + this.player.inventoryMenu.setRemoteSlot(packet.slotNum(), itemStack); +@@ -1809,11 +_,24 @@ @Override public void handleSignUpdate(ServerboundSignUpdatePacket packet) { -- List list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); +- List list = Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); + // Paper start - Limit client sign length + String[] lines = packet.getLines(); + for (int i = 0; i < lines.length; ++i) { @@ -2527,20 +2438,17 @@ + } + } + } -+ List list = (List) Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ List list = Stream.of(lines).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); + // Paper end - Limit client sign length - - this.filterTextPacket(list).thenAcceptAsync((list1) -> { - this.updateSignText(packet, list1); -@@ -1972,6 +3517,7 @@ + this.filterTextPacket(list).thenAcceptAsync(list1 -> this.updateSignText(packet, (List)list1), this.server); } - private void updateSignText(ServerboundSignUpdatePacket packet, List signText) { + private void updateSignText(ServerboundSignUpdatePacket packet, List filteredText) { + if (this.player.isImmobile()) return; // CraftBukkit this.player.resetLastActionTime(); - ServerLevel worldserver = this.player.serverLevel(); - BlockPos blockposition = packet.getPos(); -@@ -1993,15 +3539,33 @@ + ServerLevel serverLevel = this.player.serverLevel(); + BlockPos pos = packet.getPos(); +@@ -1829,14 +_,32 @@ @Override public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); @@ -2563,19 +2471,18 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + // Paper start - do not accept invalid information + if (packet.information().viewDistance() < 0) { -+ LOGGER.warn("Disconnecting " + this.player.getScoreboardName() + " for invalid view distance: " + packet.information().viewDistance()); ++ LOGGER.warn("Disconnecting {} for invalid view distance: {}", this.player.getScoreboardName(), packet.information().viewDistance()); + this.disconnect(Component.literal("Invalid client settings"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION); + return; + } + // Paper end - do not accept invalid information - boolean flag = this.player.isModelPartShown(PlayerModelPart.HAT); - + boolean isModelPartShown = this.player.isModelPartShown(PlayerModelPart.HAT); this.player.updateOptions(packet.information()); + this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper - if (this.player.isModelPartShown(PlayerModelPart.HAT) != flag) { + if (this.player.isModelPartShown(PlayerModelPart.HAT) != isModelPartShown) { this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT, this.player)); } -@@ -2012,7 +3576,7 @@ +@@ -1846,7 +_,7 @@ public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { @@ -2584,66 +2491,74 @@ } } -@@ -2033,7 +3597,7 @@ - - if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { - if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { +@@ -1866,7 +_,7 @@ + ProfilePublicKey.Data data2 = data.profilePublicKey(); + if (!Objects.equals(data1, data2)) { + if (data1 != null && data2.expiresAt().isBefore(data1.expiresAt())) { - this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); + this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes } else { try { - SignatureValidator signaturevalidator = this.server.getProfileKeySignatureValidator(); -@@ -2045,8 +3609,8 @@ + SignatureValidator profileKeySignatureValidator = this.server.getProfileKeySignatureValidator(); +@@ -1877,8 +_,8 @@ - this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); - } catch (ProfilePublicKey.ValidationException profilepublickey_b) { -- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); -- this.disconnect(profilepublickey_b.getComponent()); -+ // ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); // Paper - Improve logging and errors -+ this.disconnect(profilepublickey_b.getComponent(), profilepublickey_b.kickCause); // Paper - kick event causes + this.resetPlayerChatState(data.validate(this.player.getGameProfile(), profileKeySignatureValidator)); + } catch (ProfilePublicKey.ValidationException var6) { +- LOGGER.error("Failed to validate profile key: {}", var6.getMessage()); +- this.disconnect(var6.getComponent()); ++ // LOGGER.error("Failed to validate profile key: {}", var6.getMessage()); // Paper - Improve logging and errors ++ this.disconnect(var6.getComponent(), var6.kickCause); // Paper - kick event causes } - } -@@ -2058,7 +3622,7 @@ - if (!this.waitingForSwitchToConfig) { - throw new IllegalStateException("Client acknowledged config, but none was requested"); - } else { -- this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))); -+ this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit + } +@@ -1892,7 +_,7 @@ + this.connection + .setupInboundProtocol( + ConfigurationProtocols.SERVERBOUND, +- new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())) ++ new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player) // CraftBukkit + ); } } +@@ -1911,6 +_,7 @@ -@@ -2076,15 +3640,18 @@ - - private void resetPlayerChatState(RemoteChatSession session) { - this.chatSession = session; + private void resetPlayerChatState(RemoteChatSession chatSession) { + this.chatSession = chatSession; + this.hasLoggedExpiry = false; // Paper - Prevent causing expired keys from impacting new joins - this.signedMessageDecoder = session.createMessageDecoder(this.player.getUUID()); - this.chatMessageChain.append(() -> { - this.player.setChatSession(session); -- this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); -+ this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player); // Paper - Use single player info update packet on join - }); + this.signedMessageDecoder = chatSession.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain + .append( +@@ -1919,15 +_,17 @@ + this.server + .getPlayerList() + .broadcastAll( +- new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)) ++ new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player)), this.player // Paper - Use single player info update packet on join + ); + } + ); } - @Override -- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) {} +- public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { +- } + // CraftBukkit start - handled in super + // @Override -+ // public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) {} ++ // public void handleCustomPayload(ServerboundCustomPayloadPacket packet) { ++ // } + // CraftBukkit end @Override public void handleClientTickEnd(ServerboundClientTickEndPacket packet) { -@@ -2115,4 +3682,17 @@ - +@@ -1957,4 +_,17 @@ + interface EntityInteraction { InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); } + + // Paper start - Add fail move event + private io.papermc.paper.event.player.PlayerFailMoveEvent fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason failReason, + double toX, double toY, double toZ, float toYaw, float toPitch, boolean logWarning) { -+ Player player = this.getCraftPlayer(); ++ org.bukkit.entity.Player player = this.getCraftPlayer(); + Location from = new Location(player.getWorld(), this.lastPosX, this.lastPosY, this.lastPosZ, this.lastYaw, this.lastPitch); + Location to = new Location(player.getWorld(), toX, toY, toZ, toYaw, toPitch); + io.papermc.paper.event.player.PlayerFailMoveEvent event = new io.papermc.paper.event.player.PlayerFailMoveEvent(player, failReason, diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch similarity index 65% rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch index bf3a6ca144..64f87203f2 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -13,11 +13,26 @@ +@@ -12,11 +_,27 @@ import net.minecraft.network.protocol.status.StatusProtocols; import net.minecraft.server.MinecraftServer; @@ -10,24 +10,25 @@ +// CraftBukkit end + public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener { - + private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request"); + // Spigot start + private static final com.google.gson.Gson gson = new com.google.gson.Gson(); + static final java.util.regex.Pattern HOST_PATTERN = java.util.regex.Pattern.compile("[0-9a-f\\.:]{0,45}"); + static final java.util.regex.Pattern PROP_PATTERN = java.util.regex.Pattern.compile("\\w{0,16}"); + // Spigot end + // CraftBukkit start - add fields -+ private static final HashMap throttleTracker = new HashMap(); ++ private static final HashMap throttleTracker = new HashMap<>(); + private static int throttleCounter = 0; + // CraftBukkit end - private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request"); ++ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper private final MinecraftServer server; private final Connection connection; -+ private static final boolean BYPASS_HOSTCHECK = Boolean.getBoolean("Paper.bypassHostCheck"); // Paper ++ public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { this.server = server; -@@ -26,6 +41,7 @@ + this.connection = connection; +@@ -24,6 +_,7 @@ @Override public void handleIntention(ClientIntentionPacket packet) { @@ -35,73 +36,66 @@ switch (packet.intention()) { case LOGIN: this.beginLogin(packet, false); -@@ -55,23 +71,127 @@ - throw new UnsupportedOperationException("Invalid intention " + String.valueOf(packet.intention())); +@@ -50,22 +_,117 @@ + default: + throw new UnsupportedOperationException("Invalid intention " + packet.intention()); } - ++ + // Paper start - NetworkClient implementation + this.connection.protocolVersion = packet.protocolVersion(); + this.connection.virtualHost = com.destroystokyo.paper.network.PaperNetworkClient.prepareVirtualHost(packet.hostName(), packet.port()); + // Paper end } - private void beginLogin(ClientIntentionPacket packet, boolean transfer) { + private void beginLogin(ClientIntentionPacket packet, boolean transferred) { this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); + // CraftBukkit start - Connection throttle + try { + if (!(this.connection.channel.localAddress() instanceof io.netty.channel.unix.DomainSocketAddress)) { // Paper - Unix domain socket support; the connection throttle is useless when you have a Unix domain socket -+ long currentTime = System.currentTimeMillis(); -+ long connectionThrottle = this.server.server.getConnectionThrottle(); -+ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); ++ long currentTime = System.currentTimeMillis(); ++ long connectionThrottle = this.server.server.getConnectionThrottle(); ++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); ++ ++ synchronized (ServerHandshakePacketListenerImpl.throttleTracker) { ++ if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) { ++ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime); ++ Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message ++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.connection.disconnect(chatmessage); ++ return; ++ } + -+ synchronized (ServerHandshakePacketListenerImpl.throttleTracker) { -+ if (ServerHandshakePacketListenerImpl.throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - ServerHandshakePacketListenerImpl.throttleTracker.get(address) < connectionThrottle) { + ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime); -+ Component chatmessage = io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.connectionThrottle); // Paper - Configurable connection throttle kick message -+ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); -+ this.connection.disconnect(chatmessage); -+ return; -+ } ++ ServerHandshakePacketListenerImpl.throttleCounter++; ++ if (ServerHandshakePacketListenerImpl.throttleCounter > 200) { ++ ServerHandshakePacketListenerImpl.throttleCounter = 0; + -+ ServerHandshakePacketListenerImpl.throttleTracker.put(address, currentTime); -+ ServerHandshakePacketListenerImpl.throttleCounter++; -+ if (ServerHandshakePacketListenerImpl.throttleCounter > 200) { -+ ServerHandshakePacketListenerImpl.throttleCounter = 0; -+ -+ // Cleanup stale entries -+ java.util.Iterator iter = ServerHandshakePacketListenerImpl.throttleTracker.entrySet().iterator(); -+ while (iter.hasNext()) { -+ java.util.Map.Entry entry = (java.util.Map.Entry) iter.next(); -+ if (entry.getValue() > connectionThrottle) { -+ iter.remove(); -+ } ++ // Cleanup stale entries ++ ServerHandshakePacketListenerImpl.throttleTracker.values().removeIf(time -> time > connectionThrottle); + } + } -+ } + } // Paper - Unix domain socket support + } catch (Throwable t) { + org.apache.logging.log4j.LogManager.getLogger().debug("Failed to check connection throttle", t); + } + // CraftBukkit end if (packet.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { -- MutableComponent ichatmutablecomponent; -+ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages - +- Component component; - if (packet.protocolVersion() < 754) { -- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName()); +- component = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName()); ++ net.kyori.adventure.text.Component adventureComponent; // Paper - Fix hex colors not working in some kick messages + if (packet.protocolVersion() < SharedConstants.getCurrentVersion().getProtocolVersion()) { // Spigot - SPIGOT-7546: Handle version check correctly for outdated client message -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedClientMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages } else { -- ichatmutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName()); -+ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages +- component = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName()); ++ adventureComponent = net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(java.text.MessageFormat.format(org.spigotmc.SpigotConfig.outdatedServerMessage.replaceAll("'", "''"), SharedConstants.getCurrentVersion().getName())); // Spigot // Paper - Fix hex colors not working in some kick messages } ++ Component component = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages -+ Component ichatmutablecomponent = io.papermc.paper.adventure.PaperAdventure.asVanilla(adventureComponent); // Paper - Fix hex colors not working in some kick messages -+ - this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); - this.connection.disconnect((Component) ichatmutablecomponent); + this.connection.send(new ClientboundLoginDisconnectPacket(component)); + this.connection.disconnect(component); } else { - this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transfer)); + this.connection.setupInboundProtocol(LoginProtocols.SERVERBOUND, new ServerLoginPacketListenerImpl(this.server, this.connection, transferred)); + // Paper start - PlayerHandshakeEvent + boolean proxyLogicEnabled = org.spigotmc.SpigotConfig.bungee; + boolean handledByEvent = false; @@ -139,31 +133,28 @@ + String[] split = packet.hostName().split("\00"); + if (!handledByEvent && proxyLogicEnabled) { // Paper + // if (org.spigotmc.SpigotConfig.bungee) { // Paper - comment out, we check above! -+ if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { // Paper - Add bypass host check ++ if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.BYPASS_HOSTCHECK || ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { // Paper - Add bypass host check + // Paper start - Unix domain socket support + java.net.SocketAddress socketAddress = this.connection.getRemoteAddress(); + this.connection.hostname = split[0]; + this.connection.address = new java.net.InetSocketAddress(split[1], socketAddress instanceof java.net.InetSocketAddress ? ((java.net.InetSocketAddress) socketAddress).getPort() : 0); + // Paper end - Unix domain socket support + this.connection.spoofedUUID = com.mojang.util.UndashedUuid.fromStringLenient( split[2] ); -+ } else -+ { ++ } else { + Component chatmessage = Component.literal("If you wish to use IP forwarding, please enable it in your BungeeCord config as well!"); + this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); + this.connection.disconnect(chatmessage); + return; + } -+ if ( split.length == 4 ) -+ { ++ if (split.length == 4) { + this.connection.spoofedProfile = ServerHandshakePacketListenerImpl.gson.fromJson(split[3], com.mojang.authlib.properties.Property[].class); + } -+ } else if ( ( split.length == 3 || split.length == 4 ) && ( ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher( split[1] ).matches() ) ) { ++ } else if ((split.length == 3 || split.length == 4) && (ServerHandshakePacketListenerImpl.HOST_PATTERN.matcher(split[1]).matches())) { + Component chatmessage = Component.literal("Unknown data in login hostname, did you forget to enable BungeeCord in spigot.yml?"); + this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); + this.connection.disconnect(chatmessage); -+ return; + } + // Spigot End } - } + diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch similarity index 68% rename from paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch rename to paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch index 2f73ee117e..579db13c3c 100644 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch @@ -1,25 +1,10 @@ --- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -20,6 +20,7 @@ - import net.minecraft.DefaultUncaughtExceptionHandler; - import net.minecraft.core.UUIDUtil; - import net.minecraft.network.Connection; -+import net.minecraft.network.ConnectionProtocol; - import net.minecraft.network.DisconnectionDetails; - import net.minecraft.network.PacketSendListener; - import net.minecraft.network.TickablePacketListener; -@@ -36,6 +37,7 @@ - import net.minecraft.network.protocol.login.ServerboundKeyPacket; - import net.minecraft.network.protocol.login.ServerboundLoginAcknowledgedPacket; - import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; - import net.minecraft.server.players.PlayerList; - import net.minecraft.util.Crypt; - import net.minecraft.util.CryptException; -@@ -43,11 +45,38 @@ +@@ -43,10 +_,19 @@ import net.minecraft.util.StringUtil; import org.apache.commons.lang3.Validate; import org.slf4j.Logger; ++// CraftBukkit start +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketUtils; +import org.bukkit.craftbukkit.entity.CraftPlayer; @@ -29,15 +14,40 @@ -public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { +public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection { ++ // CraftBukkit end + private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); + static final Logger LOGGER = LogUtils.getLogger(); ++ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads + private static final int MAX_TICKS_BEFORE_LOGIN = 600; + private final byte[] challenge; + final MinecraftServer server; +@@ -56,9 +_,12 @@ + @Nullable + String requestedUsername; + @Nullable +- private GameProfile authenticatedProfile; ++ public GameProfile authenticatedProfile; // Paper - public + private final String serverId = ""; + private final boolean transferred; ++ private net.minecraft.server.level.ServerPlayer player; // CraftBukkit ++ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding ++ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support + public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { + this.server = server; +@@ -67,11 +_,48 @@ + this.transferred = transferred; + } + ++ // CraftBukkit start + @Override + public boolean isTransferred() { + return this.transferred; + } + + @Override -+ public ConnectionProtocol getProtocol() { -+ return ConnectionProtocol.LOGIN; ++ public net.minecraft.network.ConnectionProtocol getProtocol() { ++ return net.minecraft.network.ConnectionProtocol.LOGIN; + } + + @Override @@ -50,81 +60,61 @@ + this.disconnect(reason); + } + // CraftBukkit end - private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); - static final Logger LOGGER = LogUtils.getLogger(); -+ private static final java.util.concurrent.ExecutorService authenticatorPool = java.util.concurrent.Executors.newCachedThreadPool(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("User Authenticator #%d").setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)).build()); // Paper - Cache authenticator threads - private static final int MAX_TICKS_BEFORE_LOGIN = 600; - private final byte[] challenge; - final MinecraftServer server; -@@ -57,9 +86,12 @@ - @Nullable - String requestedUsername; - @Nullable -- private GameProfile authenticatedProfile; -+ public GameProfile authenticatedProfile; // Paper - public - private final String serverId; - private final boolean transferred; -+ private ServerPlayer player; // CraftBukkit -+ public boolean iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation = false; // Paper - username validation overriding -+ private int velocityLoginMessageId = -1; // Paper - Add Velocity IP Forwarding Support - - public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection, boolean transferred) { - this.state = ServerLoginPacketListenerImpl.State.HELLO; -@@ -72,10 +104,24 @@ - ++ @Override public void tick() { + // Paper start - Do not allow logins while the server is shutting down -+ if (!MinecraftServer.getServer().isRunning()) { ++ if (!this.server.isRunning()) { + this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(org.spigotmc.SpigotConfig.restartMessage)[0]); + return; + } + // Paper end - Do not allow logins while the server is shutting down ++ if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { + if (this.connection.isConnected()) { // Paper - prevent logins to be processed even though disconnect was called - this.verifyLoginAndFinishConnectionSetup((GameProfile) Objects.requireNonNull(this.authenticatedProfile)); + this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile)); +- } + } // Paper - prevent logins to be processed even though disconnect was called - } - ++ } ++ + // CraftBukkit start + if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES && !this.player.getBukkitEntity().isAwaitingCookies()) { + this.postCookies(this.authenticatedProfile); + } + // CraftBukkit end -+ - if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) { - this.finishLoginAndWaitForClient(this.authenticatedProfile); - } -@@ -86,6 +132,13 @@ + if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT + && !this.isPlayerAlreadyInWorld(Objects.requireNonNull(this.authenticatedProfile))) { +@@ -83,6 +_,13 @@ + } } + // CraftBukkit start + @Deprecated -+ public void disconnect(String s) { -+ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(s))); // Paper - Fix hex colors not working in some kick messages ++ public void disconnect(String reason) { ++ this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(reason))); // Paper - Fix hex colors not working in some kick messages + } + // CraftBukkit end + @Override public boolean isAcceptingMessages() { return this.connection.isConnected(); -@@ -120,7 +173,13 @@ +@@ -115,7 +_,13 @@ @Override public void handleHello(ServerboundHelloPacket packet) { - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); -- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username", new Object[0]); + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet"); +- Validate.validState(StringUtil.isValidPlayerName(packet.name()), "Invalid characters in username"); + // Paper start - Validate usernames + if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() + && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation + && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { -+ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username"); + } + // Paper end - Validate usernames this.requestedUsername = packet.name(); - GameProfile gameprofile = this.server.getSingleplayerProfile(); - -@@ -131,7 +190,36 @@ + GameProfile singleplayerProfile = this.server.getSingleplayerProfile(); + if (singleplayerProfile != null && this.requestedUsername.equalsIgnoreCase(singleplayerProfile.getName())) { +@@ -125,7 +_,32 @@ this.state = ServerLoginPacketListenerImpl.State.KEY; this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge, true)); } else { @@ -141,37 +131,32 @@ + // Paper end - Add Velocity IP Forwarding Support + // CraftBukkit start + // Paper start - Cache authenticator threads -+ authenticatorPool.execute(new Runnable() { ++ authenticatorPool.execute(() -> { ++ try { ++ GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot + -+ @Override -+ public void run() { -+ try { -+ GameProfile gameprofile = ServerLoginPacketListenerImpl.this.createOfflineProfile(ServerLoginPacketListenerImpl.this.requestedUsername); // Spigot -+ -+ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent -+ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); -+ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); -+ } catch (Exception ex) { -+ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); -+ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex); -+ } ++ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); ++ ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); ++ } catch (Exception ex) { ++ ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); ++ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + ServerLoginPacketListenerImpl.this.requestedUsername, ex); + } + }); + // Paper end - Cache authenticator threads + // CraftBukkit end } - } -@@ -144,10 +232,24 @@ + } +@@ -137,9 +_,23 @@ private void verifyLoginAndFinishConnectionSetup(GameProfile profile) { - PlayerList playerlist = this.server.getPlayerList(); -- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), profile); + PlayerList playerList = this.server.getPlayerList(); +- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), profile); +- if (component != null) { +- this.disconnect(component); + // CraftBukkit start - fire PlayerLoginEvent -+ this.player = playerlist.canPlayerLogin(this, profile); // CraftBukkit - -- if (ichatbasecomponent != null) { -- this.disconnect(ichatbasecomponent); ++ this.player = playerList.canPlayerLogin(this, profile); // CraftBukkit + if (this.player != null) { + if (this.player.getBukkitEntity().isAwaitingCookies()) { + this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_COOKIES; @@ -181,94 +166,88 @@ + } + } + -+ private void postCookies(GameProfile gameprofile) { -+ PlayerList playerlist = this.server.getPlayerList(); ++ private void postCookies(GameProfile profile) { ++ PlayerList playerList = this.server.getPlayerList(); + + if (this.player == null) { -+ // this.disconnect(ichatbasecomponent); ++ // this.disconnect(component); + // CraftBukkit end } else { if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) { - this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> { -@@ -155,12 +257,12 @@ - })); + this.connection +@@ -149,7 +_,7 @@ + ); } -- boolean flag = playerlist.disconnectAllPlayersWithProfile(profile); -+ boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference - +- boolean flag = playerList.disconnectAllPlayersWithProfile(profile); ++ boolean flag = playerList.disconnectAllPlayersWithProfile(profile, this.player); // CraftBukkit - add player reference if (flag) { this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT; } else { -- this.finishLoginAndWaitForClient(profile); -+ this.finishLoginAndWaitForClient(gameprofile); - } +@@ -184,7 +_,8 @@ + throw new IllegalStateException("Protocol error", var7); } -@@ -195,7 +297,8 @@ - throw new IllegalStateException("Protocol error", cryptographyexception); - } - -- Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) { +- Thread thread = new Thread("User Authenticator #" + UNIQUE_THREAD_ID.incrementAndGet()) { + // Paper start - Cache authenticator threads + authenticatorPool.execute(new Runnable() { + @Override public void run() { - String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); - -@@ -205,11 +308,17 @@ - if (profileresult != null) { - GameProfile gameprofile = profileresult.profile(); - + String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); +@@ -195,11 +_,17 @@ + .hasJoinedServer(string1, string, this.getAddress()); + if (profileResult != null) { + GameProfile gameProfile = profileResult.profile(); + // CraftBukkit start - fire PlayerPreLoginEvent + if (!ServerLoginPacketListenerImpl.this.connection.isConnected()) { + return; + } -+ gameprofile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameprofile); // Paper - Add more fields to AsyncPlayerPreLoginEvent ++ gameProfile = ServerLoginPacketListenerImpl.this.callPlayerPreLoginEvents(gameProfile); // Paper - Add more fields to AsyncPlayerPreLoginEvent + // CraftBukkit end - ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); - ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); + ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameProfile.getName(), gameProfile.getId()); + ServerLoginPacketListenerImpl.this.startClientVerification(gameProfile); } else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!"); -- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1)); -+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot +- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1)); ++ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot } else { ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); - ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", s1); -@@ -217,11 +326,16 @@ - } catch (AuthenticationUnavailableException authenticationunavailableexception) { + ServerLoginPacketListenerImpl.LOGGER.error("Username '{}' tried to join with an invalid session", string1); +@@ -207,11 +_,16 @@ + } catch (AuthenticationUnavailableException var4) { if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!"); -- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1)); -+ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(s1)); // Spigot +- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1)); ++ ServerLoginPacketListenerImpl.this.startClientVerification(ServerLoginPacketListenerImpl.this.createOfflineProfile(string1)); // Spigot } else { -- ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); -+ ServerLoginPacketListenerImpl.this.disconnect(io.papermc.paper.adventure.PaperAdventure.asVanilla(io.papermc.paper.configuration.GlobalConfiguration.get().messages.kick.authenticationServersDown)); // Paper - Configurable kick message + ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable"); } + // CraftBukkit start - catch all exceptions -+ } catch (Exception exception) { ++ } catch (Exception ex) { + ServerLoginPacketListenerImpl.this.disconnect("Failed to verify username!"); -+ ServerLoginPacketListenerImpl.this.server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception); ++ ServerLoginPacketListenerImpl.LOGGER.warn("Exception verifying {}", string1, ex); + // CraftBukkit end } - } -@@ -232,23 +346,118 @@ - return ServerLoginPacketListenerImpl.this.server.getPreventProxyConnections() && socketaddress instanceof InetSocketAddress ? ((InetSocketAddress) socketaddress).getAddress() : null; +@@ -222,24 +_,120 @@ + ? ((InetSocketAddress)remoteAddress).getAddress() + : null; } - }; +- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); +- thread.start(); +- } + }); + // Paper end - Cache authenticator threads + } - -- thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(ServerLoginPacketListenerImpl.LOGGER)); -- thread.start(); ++ + // CraftBukkit start + private GameProfile callPlayerPreLoginEvents(GameProfile gameprofile) throws Exception { // Paper - Add more fields to AsyncPlayerPreLoginEvent + // Paper start - Add Velocity IP Forwarding Support + if (ServerLoginPacketListenerImpl.this.velocityLoginMessageId == -1 && io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled) { -+ disconnect("This server requires you to connect with Velocity."); ++ this.disconnect("This server requires you to connect with Velocity."); + return gameprofile; + } + // Paper end - Add Velocity IP Forwarding Support @@ -294,7 +273,7 @@ + if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { + event.disallow(asyncEvent.getResult(), asyncEvent.kickMessage()); // Paper - Adventure + } -+ Waitable waitable = new Waitable() { ++ Waitable waitable = new Waitable<>() { + @Override + protected PlayerPreLoginEvent.Result evaluate() { + server.getPluginManager().callEvent(event); @@ -312,11 +291,12 @@ + } + } + return gameprofile; // Paper - Add more fields to AsyncPlayerPreLoginEvent - } ++ } + // CraftBukkit end @Override public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket packet) { +- this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + // Paper start - Add Velocity IP Forwarding Support + if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.velocity.enabled && packet.transactionId() == this.velocityLoginMessageId) { + ServerboundCustomQueryAnswerPacket.QueryAnswerPayload payload = (ServerboundCustomQueryAnswerPacket.QueryAnswerPayload)packet.payload(); @@ -326,7 +306,6 @@ + } + + net.minecraft.network.FriendlyByteBuf buf = payload.buffer; -+ + if (!com.destroystokyo.paper.proxy.VelocityProxy.checkIntegrity(buf)) { + this.disconnect("Unable to verify player details"); + return; @@ -362,21 +341,24 @@ + return; + } + // Paper end - Add Velocity IP Forwarding Support - this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); ++ // this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); } @Override public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.server); // CraftBukkit - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]); + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet"); this.connection.setupOutboundProtocol(ConfigurationProtocols.CLIENTBOUND); - CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile), this.transferred); -- ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie); -+ ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit - - this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverconfigurationpacketlistenerimpl); - serverconfigurationpacketlistenerimpl.startConfiguration(); -@@ -264,12 +473,44 @@ + CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile), this.transferred); + ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl( +- this.server, this.connection, commonListenerCookie ++ this.server, this.connection, commonListenerCookie, this.player // CraftBukkit + ); ++ + this.connection.setupInboundProtocol(ConfigurationProtocols.SERVERBOUND, serverConfigurationPacketListenerImpl); + serverConfigurationPacketListenerImpl.startConfiguration(); + this.state = ServerLoginPacketListenerImpl.State.ACCEPTED; +@@ -252,8 +_,36 @@ @Override public void handleCookieResponse(ServerboundCookieResponsePacket packet) { @@ -388,37 +370,36 @@ + // CraftBukkit end this.disconnect(ServerCommonPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); } - ++ + // Spigot start + protected GameProfile createOfflineProfile(String s) { + java.util.UUID uuid; -+ if ( this.connection.spoofedUUID != null ) -+ { ++ if (this.connection.spoofedUUID != null) { + uuid = this.connection.spoofedUUID; -+ } else -+ { -+ uuid = UUIDUtil.createOfflinePlayerUUID( s ); ++ } else { ++ uuid = UUIDUtil.createOfflinePlayerUUID(s); + } + -+ GameProfile gameProfile = new GameProfile( uuid, s ); ++ GameProfile gameProfile = new GameProfile(uuid, s); + -+ if (this.connection.spoofedProfile != null) -+ { -+ for ( com.mojang.authlib.properties.Property property : this.connection.spoofedProfile ) -+ { -+ if ( !ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher( property.name()).matches() ) continue; -+ gameProfile.getProperties().put( property.name(), property ); ++ if (this.connection.spoofedProfile != null) { ++ for (com.mojang.authlib.properties.Property property : this.connection.spoofedProfile) { ++ if (!ServerHandshakePacketListenerImpl.PROP_PATTERN.matcher(property.name()).matches()) continue; ++ gameProfile.getProperties().put(property.name(), property); + } + } + + return gameProfile; + } + // Spigot end -+ + public static enum State { - -- HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; -+ HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_COOKIES, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; // CraftBukkit - - private State() {} - } + HELLO, +@@ -261,6 +_,7 @@ + AUTHENTICATING, + NEGOTIATING, + VERIFYING, ++ WAITING_FOR_COOKIES, // CraftBukkit + WAITING_FOR_DUPE_DISCONNECT, + PROTOCOL_SWITCHING, + ACCEPTED; diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch new file mode 100644 index 0000000000..7a4ce9578f --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch @@ -0,0 +1,12 @@ +--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -36,7 +_,8 @@ + this.connection.disconnect(DISCONNECT_REASON); + } else { + this.hasRequestedStatus = true; +- this.connection.send(new ClientboundStatusResponsePacket(this.status)); ++ // this.connection.send(new ClientboundStatusResponsePacket(this.status)); ++ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(net.minecraft.server.MinecraftServer.getServer(), this.connection); // Paper - handle status request + } + } + diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch deleted file mode 100644 index 813b398d03..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch +++ /dev/null @@ -1,89 +0,0 @@ ---- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -@@ -38,6 +38,11 @@ - import net.minecraft.world.flag.FeatureFlags; - import org.slf4j.Logger; - -+// CraftBukkit start -+import org.bukkit.craftbukkit.CraftServerLinks; -+import org.bukkit.event.player.PlayerLinksSendEvent; -+// CraftBukkit end -+ - public class ServerConfigurationPacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerConfigurationPacketListener, TickablePacketListener { - - private static final Logger LOGGER = LogUtils.getLogger(); -@@ -50,10 +55,12 @@ - @Nullable - private SynchronizeRegistriesTask synchronizeRegistriesTask; - -- public ServerConfigurationPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie clientData) { -- super(server, connection, clientData); -- this.gameProfile = clientData.gameProfile(); -- this.clientInformation = clientData.clientInformation(); -+ // CraftBukkit start -+ public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { -+ super(minecraftserver, networkmanager, commonlistenercookie, player); -+ // CraftBukkit end -+ this.gameProfile = commonlistenercookie.gameProfile(); -+ this.clientInformation = commonlistenercookie.clientInformation(); - } - - @Override -@@ -63,6 +70,10 @@ - - @Override - public void onDisconnect(DisconnectionDetails info) { -+ // Paper start - Debugging -+ if (net.minecraft.server.MinecraftServer.getServer().isDebugging()) { -+ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}, while in configuration phase {}", this.gameProfile, info.reason().getString(), currentTask != null ? currentTask.type().id() : "null"); -+ } else // Paper end - ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, info.reason().getString()); - super.onDisconnect(info); - } -@@ -75,6 +86,12 @@ - public void startConfiguration() { - this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName()))); - ServerLinks serverlinks = this.server.serverLinks(); -+ // CraftBukkit start -+ CraftServerLinks wrapper = new CraftServerLinks(serverlinks); -+ PlayerLinksSendEvent event = new PlayerLinksSendEvent(this.player.getBukkitEntity(), wrapper); -+ this.player.getBukkitEntity().getServer().getPluginManager().callEvent(event); -+ serverlinks = wrapper.getServerLinks(); -+ // CraftBukkit end - - if (!serverlinks.isEmpty()) { - this.send(new ClientboundServerLinksPacket(serverlinks.untrust())); -@@ -107,6 +124,7 @@ - @Override - public void handleClientInformation(ServerboundClientInformationPacket packet) { - this.clientInformation = packet.information(); -+ this.connection.channel.attr(io.papermc.paper.adventure.PaperAdventure.LOCALE_ATTRIBUTE).set(net.kyori.adventure.translation.Translator.parseLocale(packet.information().language())); // Paper - } - - @Override -@@ -143,18 +161,23 @@ - return; - } - -- Component ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile); -+ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed - - if (ichatbasecomponent != null) { - this.disconnect(ichatbasecomponent); - return; - } - -- ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation); -+ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit - - playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation)); - } catch (Exception exception) { - ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception); -+ // Paper start - Debugging -+ if (MinecraftServer.getServer().isDebugging()) { -+ exception.printStackTrace(); -+ } -+ // Paper end - Debugging - this.connection.send(new ClientboundDisconnectPacket(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA)); - this.connection.disconnect(ServerConfigurationPacketListenerImpl.DISCONNECT_REASON_INVALID_DATA); - } diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch deleted file mode 100644 index adc3cc15f4..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerConnectionListener.java.patch +++ /dev/null @@ -1,156 +0,0 @@ ---- a/net/minecraft/server/network/ServerConnectionListener.java -+++ b/net/minecraft/server/network/ServerConnectionListener.java -@@ -52,22 +52,36 @@ - - private static final Logger LOGGER = LogUtils.getLogger(); - public static final Supplier SERVER_EVENT_GROUP = Suppliers.memoize(() -> { -- return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).build()); -+ return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - public static final Supplier SERVER_EPOLL_EVENT_GROUP = Suppliers.memoize(() -> { -- return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).build()); -+ return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Server IO #%d").setDaemon(true).setUncaughtExceptionHandler(new net.minecraft.DefaultUncaughtExceptionHandlerWithName(LOGGER)).build()); // Paper - }); - final MinecraftServer server; - public volatile boolean running; - private final List channels = Collections.synchronizedList(Lists.newArrayList()); - final List connections = Collections.synchronizedList(Lists.newArrayList()); -+ // Paper start - prevent blocking on adding a new connection while the server is ticking -+ private final java.util.Queue pending = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ private final void addPending() { -+ Connection connection; -+ while ((connection = pending.poll()) != null) { -+ connections.add(connection); -+ } -+ } -+ // Paper end - prevent blocking on adding a new connection while the server is ticking - - public ServerConnectionListener(MinecraftServer server) { - this.server = server; - this.running = true; - } - -+ // Paper start - Unix domain socket support - public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { -+ bind(new java.net.InetSocketAddress(address, port)); -+ } -+ public void bind(java.net.SocketAddress address) throws IOException { -+ // Paper end - Unix domain socket support - List list = this.channels; - - synchronized (this.channels) { -@@ -75,7 +89,13 @@ - EventLoopGroup eventloopgroup; - - if (Epoll.isAvailable() && this.server.isEpollEnabled()) { -+ // Paper start - Unix domain socket support -+ if (address instanceof io.netty.channel.unix.DomainSocketAddress) { -+ oclass = io.netty.channel.epoll.EpollServerDomainSocketChannel.class; -+ } else { - oclass = EpollServerSocketChannel.class; -+ } -+ // Paper end - Unix domain socket support - eventloopgroup = (EventLoopGroup) ServerConnectionListener.SERVER_EPOLL_EVENT_GROUP.get(); - ServerConnectionListener.LOGGER.info("Using epoll channel type"); - } else { -@@ -84,6 +104,12 @@ - ServerConnectionListener.LOGGER.info("Using default channel type"); - } - -+ // Paper start - Warn people with console access that HAProxy is in use. -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ ServerConnectionListener.LOGGER.warn("Using HAProxy, please ensure the server port is adequately firewalled."); -+ } -+ // Paper end - Warn people with console access that HAProxy is in use. -+ - this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer() { - protected void initChannel(Channel channel) { - try { -@@ -100,16 +126,58 @@ - - Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, false, (BandwidthDebugMonitor) null); - int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond(); -- Object object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); -+ Connection object = j > 0 ? new RateKickingConnection(j) : new Connection(PacketFlow.SERVERBOUND); // CraftBukkit - decompile error - -- ServerConnectionListener.this.connections.add(object); -+ //ServerConnectionListener.this.connections.add(object); // Paper -+ // Paper start - Add support for Proxy Protocol -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.proxyProtocol) { -+ channel.pipeline().addAfter("timeout", "haproxy-decoder", new io.netty.handler.codec.haproxy.HAProxyMessageDecoder()); -+ channel.pipeline().addAfter("haproxy-decoder", "haproxy-handler", new ChannelInboundHandlerAdapter() { -+ @Override -+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { -+ if (msg instanceof io.netty.handler.codec.haproxy.HAProxyMessage message) { -+ if (message.command() == io.netty.handler.codec.haproxy.HAProxyCommand.PROXY) { -+ String realaddress = message.sourceAddress(); -+ int realport = message.sourcePort(); -+ -+ SocketAddress socketaddr = new java.net.InetSocketAddress(realaddress, realport); -+ -+ Connection connection = (Connection) channel.pipeline().get("packet_handler"); -+ connection.address = socketaddr; -+ -+ // Paper start - Add API to get player's proxy address -+ final String proxyAddress = message.destinationAddress(); -+ final int proxyPort = message.destinationPort(); -+ -+ connection.haProxyAddress = new java.net.InetSocketAddress(proxyAddress, proxyPort); -+ // Paper end - Add API to get player's proxy address -+ } -+ } else { -+ super.channelRead(ctx, msg); -+ } -+ } -+ }); -+ } -+ // Paper end - Add support for proxy protocol -+ pending.add(object); // Paper - prevent blocking on adding a new connection while the server is ticking - ((Connection) object).configurePacketHandler(channelpipeline); - ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); -+ io.papermc.paper.network.ChannelInitializeListenerHolder.callListeners(channel); // Paper - Add Channel initialization listeners - } -- }).group(eventloopgroup).localAddress(address, port)).bind().syncUninterruptibly()); -+ }).group(eventloopgroup).localAddress(address)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit // Paper - Unix domain socket support - } - } - -+ // CraftBukkit start -+ public void acceptConnections() { -+ synchronized (this.channels) { -+ for (ChannelFuture future : this.channels) { -+ future.channel().config().setAutoRead(true); -+ } -+ } -+ } -+ // CraftBukkit end -+ - public SocketAddress startMemoryChannel() { - List list = this.channels; - ChannelFuture channelfuture; -@@ -153,6 +221,14 @@ - List list = this.connections; - - synchronized (this.connections) { -+ // Spigot Start -+ this.addPending(); // Paper - prevent blocking on adding a new connection while the server is ticking -+ // This prevents players from 'gaming' the server, and strategically relogging to increase their position in the tick order -+ if ( org.spigotmc.SpigotConfig.playerShuffle > 0 && MinecraftServer.currentTick % org.spigotmc.SpigotConfig.playerShuffle == 0 ) -+ { -+ Collections.shuffle( this.connections ); -+ } -+ // Spigot End - Iterator iterator = this.connections.iterator(); - - while (iterator.hasNext()) { -@@ -176,6 +252,10 @@ - networkmanager.setReadOnly(); - } - } else { -+ // Spigot Start -+ // Fix a race condition where a NetworkManager could be unregistered just before connection. -+ if (networkmanager.preparing) continue; -+ // Spigot End - iterator.remove(); - networkmanager.handleDisconnection(); - } diff --git a/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch deleted file mode 100644 index 7ea7e46622..0000000000 --- a/paper-server/patches/unapplied/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch +++ /dev/null @@ -1,137 +0,0 @@ ---- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -@@ -9,6 +9,19 @@ - import net.minecraft.network.protocol.status.ServerStatus; - import net.minecraft.network.protocol.status.ServerStatusPacketListener; - import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; -+// CraftBukkit start -+import com.mojang.authlib.GameProfile; -+import java.net.InetSocketAddress; -+import java.util.Collections; -+import java.util.Iterator; -+import java.util.Optional; -+import net.minecraft.SharedConstants; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import org.bukkit.craftbukkit.util.CraftChatMessage; -+import org.bukkit.craftbukkit.util.CraftIconCache; -+import org.bukkit.entity.Player; -+// CraftBukkit end - - public class ServerStatusPacketListenerImpl implements ServerStatusPacketListener { - -@@ -36,7 +49,113 @@ - this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON); - } else { - this.hasRequestedStatus = true; -- this.connection.send(new ClientboundStatusResponsePacket(this.status)); -+ // Paper start - Replace everything -+ /* -+ // CraftBukkit start -+ // this.connection.send(new PacketStatusOutServerInfo(this.status)); -+ MinecraftServer server = MinecraftServer.getServer(); -+ final Object[] players = server.getPlayerList().players.toArray(); -+ class ServerListPingEvent extends org.bukkit.event.server.ServerListPingEvent { -+ -+ CraftIconCache icon = server.server.getServerIcon(); -+ -+ ServerListPingEvent() { -+ super(ServerStatusPacketListenerImpl.this.connection.hostname, ((InetSocketAddress) ServerStatusPacketListenerImpl.this.connection.getRemoteAddress()).getAddress(), server.server.motd(), server.getPlayerList().getMaxPlayers()); // Paper - Adventure -+ } -+ -+ @Override -+ public void setServerIcon(org.bukkit.util.CachedServerIcon icon) { -+ if (!(icon instanceof CraftIconCache)) { -+ throw new IllegalArgumentException(icon + " was not created by " + org.bukkit.craftbukkit.CraftServer.class); -+ } -+ this.icon = (CraftIconCache) icon; -+ } -+ -+ @Override -+ public Iterator iterator() throws UnsupportedOperationException { -+ return new Iterator() { -+ int i; -+ int ret = Integer.MIN_VALUE; -+ ServerPlayer player; -+ -+ @Override -+ public boolean hasNext() { -+ if (this.player != null) { -+ return true; -+ } -+ final Object[] currentPlayers = players; -+ for (int length = currentPlayers.length, i = this.i; i < length; i++) { -+ final ServerPlayer player = (ServerPlayer) currentPlayers[i]; -+ if (player != null) { -+ this.i = i + 1; -+ this.player = player; -+ return true; -+ } -+ } -+ return false; -+ } -+ -+ @Override -+ public Player next() { -+ if (!this.hasNext()) { -+ throw new java.util.NoSuchElementException(); -+ } -+ final ServerPlayer player = this.player; -+ this.player = null; -+ this.ret = this.i - 1; -+ return player.getBukkitEntity(); -+ } -+ -+ @Override -+ public void remove() { -+ final Object[] currentPlayers = players; -+ final int i = this.ret; -+ if (i < 0 || currentPlayers[i] == null) { -+ throw new IllegalStateException(); -+ } -+ currentPlayers[i] = null; -+ } -+ }; -+ } -+ } -+ -+ ServerListPingEvent event = new ServerListPingEvent(); -+ server.server.getPluginManager().callEvent(event); -+ -+ java.util.List profiles = new java.util.ArrayList(players.length); -+ for (Object player : players) { -+ if (player != null) { -+ ServerPlayer entityPlayer = ((ServerPlayer) player); -+ if (entityPlayer.allowsListing()) { -+ profiles.add(entityPlayer.getGameProfile()); -+ } else { -+ profiles.add(MinecraftServer.ANONYMOUS_PLAYER_PROFILE); -+ } -+ } -+ } -+ -+ // Spigot Start -+ if ( !server.hidesOnlinePlayers() && !profiles.isEmpty() ) -+ { -+ java.util.Collections.shuffle( profiles ); // This sucks, its inefficient but we have no simple way of doing it differently -+ profiles = profiles.subList( 0, Math.min( profiles.size(), org.spigotmc.SpigotConfig.playerSample ) ); // Cap the sample to n (or less) displayed players, ie: Vanilla behaviour -+ } -+ // Spigot End -+ ServerStatus.Players playerSample = new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), (server.hidesOnlinePlayers()) ? Collections.emptyList() : profiles); -+ -+ ServerStatus ping = new ServerStatus( -+ CraftChatMessage.fromString(event.getMotd(), true)[0], -+ Optional.of(playerSample), -+ Optional.of(new ServerStatus.Version(server.getServerModName() + " " + server.getServerVersion(), SharedConstants.getCurrentVersion().getProtocolVersion())), -+ (event.icon.value != null) ? Optional.of(new ServerStatus.Favicon(event.icon.value)) : Optional.empty(), -+ server.enforceSecureProfile() -+ ); -+ -+ this.connection.send(new ClientboundStatusResponsePacket(ping)); -+ // CraftBukkit end -+ */ -+ com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection); -+ // Paper end - } - } -