diff options
Diffstat (limited to 'patch-remap/mache-spigotflower/net/minecraft/server/network')
8 files changed, 4317 insertions, 0 deletions
diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/LegacyQueryHandler.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/LegacyQueryHandler.java.patch new file mode 100644 index 0000000000..a64a289029 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/LegacyQueryHandler.java.patch @@ -0,0 +1,59 @@ +--- a/net/minecraft/server/network/LegacyQueryHandler.java ++++ b/net/minecraft/server/network/LegacyQueryHandler.java +@@ -20,7 +20,6 @@ + this.server = serverinfo; + } + +- @Override + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) { + ByteBuf bytebuf = (ByteBuf) object; + +@@ -36,10 +35,11 @@ + SocketAddress socketaddress = channelhandlercontext.channel().remoteAddress(); + int i = bytebuf.readableBytes(); + String s; ++ org.bukkit.event.server.ServerListPingEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callServerListPingEvent(socketaddress, server.getMotd(), server.getPlayerCount(), server.getMaxPlayers()); // CraftBukkit + + if (i == 0) { + LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress); +- s = createVersion0Response(this.server); ++ s = createVersion0Response(this.server, event); // CraftBukkit + sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); + } else { + if (bytebuf.readUnsignedByte() != 1) { +@@ -56,7 +56,7 @@ + LegacyQueryHandler.LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketaddress); + } + +- s = createVersion1Response(this.server); ++ s = createVersion1Response(this.server, event); // CraftBukkit + sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s)); + } + +@@ -107,16 +107,20 @@ + } + } + +- private static String createVersion0Response(ServerInfo serverinfo) { +- return String.format(Locale.ROOT, "%s\u00a7%d\u00a7%d", serverinfo.getMotd(), serverinfo.getPlayerCount(), serverinfo.getMaxPlayers()); ++ // 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 + } + +- private static String createVersion1Response(ServerInfo serverinfo) { +- return String.format(Locale.ROOT, "\u00a71\u0000%d\u0000%s\u0000%s\u0000%d\u0000%d", 127, serverinfo.getServerVersion(), serverinfo.getMotd(), serverinfo.getPlayerCount(), serverinfo.getMaxPlayers()); ++ // 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 channelhandlercontext, ByteBuf bytebuf) { +- channelhandlercontext.pipeline().firstContext().writeAndFlush(bytebuf).addListener(ChannelFutureListener.CLOSE); ++ private static void sendFlushAndClose(ChannelHandlerContext channelhandlercontext, ByteBuf context) { ++ channelhandlercontext.pipeline().firstContext().writeAndFlush(context).addListener(ChannelFutureListener.CLOSE); + } + + private static ByteBuf createLegacyDisconnectPacket(ByteBufAllocator bytebufallocator, String s) { diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch new file mode 100644 index 0000000000..f05e92b025 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -0,0 +1,258 @@ +--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +@@ -4,6 +4,7 @@ + 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; +@@ -20,12 +21,26 @@ + import net.minecraft.network.protocol.common.ServerboundKeepAlivePacket; + import net.minecraft.network.protocol.common.ServerboundPongPacket; + import net.minecraft.network.protocol.common.ServerboundResourcePackPacket; ++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; + import net.minecraft.util.thread.BlockableEventLoop; + import org.slf4j.Logger; + ++// CraftBukkit start ++import io.netty.buffer.ByteBuf; ++import java.util.concurrent.ExecutionException; ++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; ++// CraftBukkit end ++ + public abstract class ServerCommonPacketListenerImpl implements ServerCommonPacketListener { + + private static final Logger LOGGER = LogUtils.getLogger(); +@@ -39,16 +54,26 @@ + private int latency; + private volatile boolean suspendFlushingOnServerThread = false; + +- public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection connection, CommonListenerCookie commonlistenercookie) { ++ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit + this.server = minecraftserver; +- this.connection = connection; ++ this.connection = networkmanager; + this.keepAliveTime = Util.getMillis(); + this.latency = commonlistenercookie.latency(); ++ // CraftBukkit start - add fields and methods ++ this.player = player; ++ 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 ++ } ++ + @Override +- @Override +- public void onDisconnect(Component component) { ++ public void onDisconnect(Component reason) { + if (this.isSingleplayerOwner()) { + ServerCommonPacketListenerImpl.LOGGER.info("Stopping singleplayer server as player logged out"); + this.server.halt(false); +@@ -57,8 +82,8 @@ + } + + @Override +- @Override + public void handleKeepAlive(ServerboundKeepAlivePacket serverboundkeepalivepacket) { ++ PacketUtils.ensureRunningOnSameThread(serverboundkeepalivepacket, this, this.player.serverLevel()); // CraftBukkit + if (this.keepAlivePending && serverboundkeepalivepacket.getId() == this.keepAliveChallenge) { + int i = (int) (Util.getMillis() - this.keepAliveTime); + +@@ -71,21 +96,67 @@ + } + + @Override +- @Override + public void handlePong(ServerboundPongPacket serverboundpongpacket) {} + ++ // CraftBukkit start ++ private static final ResourceLocation CUSTOM_REGISTER = new ResourceLocation("register"); ++ private static final ResourceLocation CUSTOM_UNREGISTER = new ResourceLocation("unregister"); ++ + @Override +- @Override +- public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) {} ++ public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundcustompayloadpacket) { ++ if (!(serverboundcustompayloadpacket.payload() instanceof ServerboundCustomPayloadPacket.UnknownPayload)) { ++ return; ++ } ++ PacketUtils.ensureRunningOnSameThread(serverboundcustompayloadpacket, this, this.player.serverLevel()); ++ ResourceLocation identifier = serverboundcustompayloadpacket.payload().id(); ++ ByteBuf payload = ((ServerboundCustomPayloadPacket.UnknownPayload)serverboundcustompayloadpacket.payload()).data(); + ++ if (identifier.equals(CUSTOM_REGISTER)) { ++ try { ++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ getCraftPlayer().addChannel(channel); ++ } ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t register custom payload", ex); ++ this.disconnect("Invalid payload REGISTER!"); ++ } ++ } else if (identifier.equals(CUSTOM_UNREGISTER)) { ++ try { ++ String channels = payload.toString(com.google.common.base.Charsets.UTF_8); ++ for (String channel : channels.split("\0")) { ++ getCraftPlayer().removeChannel(channel); ++ } ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t unregister custom payload", ex); ++ this.disconnect("Invalid payload UNREGISTER!"); ++ } ++ } else { ++ try { ++ byte[] data = new byte[payload.readableBytes()]; ++ payload.readBytes(data); ++ cserver.getMessenger().dispatchIncomingMessage(player.getBukkitEntity(), identifier.toString(), data); ++ } catch (Exception ex) { ++ ServerGamePacketListenerImpl.LOGGER.error("Couldn\'t dispatch custom payload", ex); ++ this.disconnect("Invalid custom payload!"); ++ } ++ } ++ ++ } ++ ++ public final boolean isDisconnected() { ++ return !this.player.joining && !this.connection.isConnected(); ++ } ++ // CraftBukkit end ++ + @Override +- @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket serverboundresourcepackpacket) { + PacketUtils.ensureRunningOnSameThread(serverboundresourcepackpacket, this, (BlockableEventLoop) this.server); +- if (serverboundresourcepackpacket.action() == ServerboundResourcePackPacket.Action.DECLINED && this.server.isResourcePackRequired()) { ++ if (serverboundresourcepackpacket.action() == ServerboundResourcePackPacket.a.DECLINED && this.server.isResourcePackRequired()) { + ServerCommonPacketListenerImpl.LOGGER.info("Disconnecting {} due to resource pack {} rejection", this.playerProfile().getName(), serverboundresourcepackpacket.id()); + this.disconnect(Component.translatable("multiplayer.requiredTexturePrompt.disconnect")); + } ++ this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(getCraftPlayer(), serverboundresourcepackpacket.id(), PlayerResourcePackStatusEvent.Status.values()[serverboundresourcepackpacket.action().ordinal()])); // CraftBukkit + + } + +@@ -93,7 +164,7 @@ + this.server.getProfiler().push("keepAlive"); + long i = Util.getMillis(); + +- if (i - this.keepAliveTime >= 15000L) { ++ if (i - this.keepAliveTime >= 25000L) { // CraftBukkit + if (this.keepAlivePending) { + this.disconnect(ServerCommonPacketListenerImpl.TIMEOUT_DISCONNECTION_MESSAGE); + } else { +@@ -121,31 +192,90 @@ + } + + public void send(Packet<?> packet, @Nullable PacketSendListener packetsendlistener) { ++ // CraftBukkit start ++ if (packet == null) { ++ return; ++ } else if (packet instanceof ClientboundSetDefaultSpawnPositionPacket) { ++ ClientboundSetDefaultSpawnPositionPacket packet6 = (ClientboundSetDefaultSpawnPositionPacket) packet; ++ this.player.compassTarget = CraftLocation.toBukkit(packet6.pos, this.getCraftPlayer().getWorld()); ++ } ++ // CraftBukkit end + boolean flag = !this.suspendFlushingOnServerThread || !this.server.isSameThread(); + + try { + this.connection.send(packet, packetsendlistener, flag); + } catch (Throwable throwable) { + CrashReport crashreport = CrashReport.forThrowable(throwable, "Sending packet"); +- CrashReportCategory crashreportcategory = crashreport.addCategory("Packet being sent"); ++ CrashReportCategory crashreportsystemdetails = crashreport.addCategory("Packet being sent"); + +- crashreportcategory.setDetail("Packet class", () -> { ++ crashreportsystemdetails.setDetail("Packet class", () -> { + return packet.getClass().getCanonicalName(); + }); + throw new ReportedException(crashreport); + } + } + +- public void disconnect(Component component) { +- this.connection.send(new ClientboundDisconnectPacket(component), PacketSendListener.thenRun(() -> { +- this.connection.disconnect(component); ++ // CraftBukkit start ++ @Deprecated ++ public void disconnect(Component ichatbasecomponent) { ++ disconnect(CraftChatMessage.fromComponent(ichatbasecomponent)); ++ } ++ // CraftBukkit end ++ ++ public void disconnect(String s) { ++ // CraftBukkit start - fire PlayerKickEvent ++ if (this.processedDisconnect) { ++ return; ++ } ++ if (!this.cserver.isPrimaryThread()) { ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ ServerCommonPacketListenerImpl.this.disconnect(s); ++ return null; ++ } ++ }; ++ ++ this.server.processQueue.add(waitable); ++ ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); ++ } catch (ExecutionException e) { ++ throw new RuntimeException(e); ++ } ++ return; ++ } ++ ++ String leaveMessage = ChatFormatting.YELLOW + this.player.getScoreboardName() + " left the game."; ++ ++ PlayerKickEvent event = new PlayerKickEvent(this.player.getBukkitEntity(), s, leaveMessage); ++ ++ if (this.cserver.getServer().isRunning()) { ++ this.cserver.getPluginManager().callEvent(event); ++ } ++ ++ if (event.isCancelled()) { ++ // Do not kick the player ++ return; ++ } ++ this.player.kickLeaveMessage = event.getLeaveMessage(); // CraftBukkit - SPIGOT-3034: Forward leave message to PlayerQuitEvent ++ // Send the possibly modified leave message ++ final Component ichatbasecomponent = CraftChatMessage.fromString(event.getReason(), true)[0]; ++ // CraftBukkit end ++ ++ this.connection.send(new ClientboundDisconnectPacket(ichatbasecomponent), PacketSendListener.thenRun(() -> { ++ this.connection.disconnect(ichatbasecomponent); + })); ++ this.onDisconnect(ichatbasecomponent); // CraftBukkit - fire quit instantly + this.connection.setReadOnly(); + MinecraftServer minecraftserver = this.server; +- Connection connection = this.connection; ++ Connection networkmanager = this.connection; + + Objects.requireNonNull(this.connection); +- minecraftserver.executeBlocking(connection::handleDisconnection); ++ // CraftBukkit - Don't wait ++ minecraftserver.wrapRunnable(networkmanager::handleDisconnection); + } + + protected boolean isSingleplayerOwner() { diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch new file mode 100644 index 0000000000..0134548fc6 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -0,0 +1,103 @@ +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -44,27 +44,24 @@ + private ConfigurationTask currentTask; + private ClientInformation clientInformation; + +- public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection connection, CommonListenerCookie commonlistenercookie) { +- super(minecraftserver, connection, commonlistenercookie); ++ public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit ++ super(minecraftserver, networkmanager, commonlistenercookie, player); // CraftBukkit + this.gameProfile = commonlistenercookie.gameProfile(); + this.clientInformation = commonlistenercookie.clientInformation(); + } + + @Override +- @Override + protected GameProfile playerProfile() { + return this.gameProfile; + } + + @Override +- @Override +- public void onDisconnect(Component component) { +- ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, component.getString()); +- super.onDisconnect(component); ++ public void onDisconnect(Component reason) { ++ ServerConfigurationPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.gameProfile, reason.getString()); ++ super.onDisconnect(reason); + } + + @Override +- @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected(); + } +@@ -93,13 +90,11 @@ + } + + @Override +- @Override + public void handleClientInformation(ServerboundClientInformationPacket serverboundclientinformationpacket) { + this.clientInformation = serverboundclientinformationpacket.information(); + } + + @Override +- @Override + public void handleResourcePackResponse(ServerboundResourcePackPacket serverboundresourcepackpacket) { + super.handleResourcePackResponse(serverboundresourcepackpacket); + if (serverboundresourcepackpacket.action().isTerminal()) { +@@ -109,7 +104,6 @@ + } + + @Override +- @Override + public void handleConfigurationFinished(ServerboundFinishConfigurationPacket serverboundfinishconfigurationpacket) { + this.connection.suspendInboundAfterProtocolChange(); + PacketUtils.ensureRunningOnSameThread(serverboundfinishconfigurationpacket, this, (BlockableEventLoop) this.server); +@@ -123,16 +117,16 @@ + return; + } + +- Component component = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile); ++ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed + +- if (component != null) { +- this.disconnect(component); ++ if (ichatbasecomponent != null) { ++ this.disconnect(ichatbasecomponent); + return; + } + +- ServerPlayer serverplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation); ++ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit + +- playerlist.placeNewPlayer(this.connection, serverplayer, this.createCookie(this.clientInformation)); ++ playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation)); + this.connection.resumeInboundAfterProtocolChange(); + } catch (Exception exception) { + ServerConfigurationPacketListenerImpl.LOGGER.error("Couldn't place player in world", exception); +@@ -143,7 +137,6 @@ + } + + @Override +- @Override + public void tick() { + this.keepConnectionAlive(); + } +@@ -162,11 +155,11 @@ + } + } + +- private void finishCurrentTask(ConfigurationTask.Type configurationtask_type) { +- ConfigurationTask.Type configurationtask_type1 = this.currentTask != null ? this.currentTask.type() : null; ++ private void finishCurrentTask(ConfigurationTask.a configurationtask_a) { ++ ConfigurationTask.a configurationtask_a1 = this.currentTask != null ? this.currentTask.type() : null; + +- if (!configurationtask_type.equals(configurationtask_type1)) { +- throw new IllegalStateException("Unexpected request for task finish, current task: " + configurationtask_type1 + ", requested: " + configurationtask_type); ++ if (!configurationtask_a.equals(configurationtask_a1)) { ++ throw new IllegalStateException("Unexpected request for task finish, current task: " + configurationtask_a1 + ", requested: " + configurationtask_a); + } else { + this.currentTask = null; + this.startNextTask(); diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConnectionListener.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConnectionListener.java.patch new file mode 100644 index 0000000000..5789ca1f2e --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConnectionListener.java.patch @@ -0,0 +1,151 @@ +--- a/net/minecraft/server/network/ServerConnectionListener.java ++++ b/net/minecraft/server/network/ServerConnectionListener.java +@@ -62,12 +62,12 @@ + private final List<ChannelFuture> channels = Collections.synchronizedList(Lists.newArrayList()); + final List<Connection> connections = Collections.synchronizedList(Lists.newArrayList()); + +- public ServerConnectionListener(MinecraftServer minecraftserver) { +- this.server = minecraftserver; ++ public ServerConnectionListener(MinecraftServer server) { ++ this.server = server; + this.running = true; + } + +- public void startTcpServerListener(@Nullable InetAddress inetaddress, int i) throws IOException { ++ public void startTcpServerListener(@Nullable InetAddress address, int port) throws IOException { + List list = this.channels; + + synchronized (this.channels) { +@@ -85,7 +85,6 @@ + } + + this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() { +- @Override + protected void initChannel(Channel channel) { + Connection.setInitialProtocolAttributes(channel); + +@@ -99,33 +98,42 @@ + + Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, (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); + ((Connection) object).configurePacketHandler(channelpipeline); + ((Connection) object).setListenerForServerboundHandshake(new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, (Connection) object)); + } +- }).group(eventloopgroup).localAddress(inetaddress, i)).bind().syncUninterruptibly()); ++ }).group(eventloopgroup).localAddress(address, port)).option(ChannelOption.AUTO_READ, false).bind().syncUninterruptibly()); // CraftBukkit + } + } + ++ // 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; + + synchronized (this.channels) { + channelfuture = ((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(LocalServerChannel.class)).childHandler(new ChannelInitializer<Channel>() { +- @Override + protected void initChannel(Channel channel) { + Connection.setInitialProtocolAttributes(channel); +- Connection connection = new Connection(PacketFlow.SERVERBOUND); ++ Connection networkmanager = new Connection(PacketFlow.SERVERBOUND); + +- connection.setListenerForServerboundHandshake(new MemoryServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)); +- ServerConnectionListener.this.connections.add(connection); ++ networkmanager.setListenerForServerboundHandshake(new MemoryServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, networkmanager)); ++ ServerConnectionListener.this.connections.add(networkmanager); + ChannelPipeline channelpipeline = channel.pipeline(); + + Connection.configureInMemoryPipeline(channelpipeline, PacketFlow.SERVERBOUND); +- connection.configurePacketHandler(channelpipeline); ++ networkmanager.configurePacketHandler(channelpipeline); + } + }).group((EventLoopGroup) ServerConnectionListener.SERVER_EVENT_GROUP.get()).localAddress(LocalAddress.ANY)).bind().syncUninterruptibly(); + this.channels.add(channelfuture); +@@ -157,28 +165,28 @@ + Iterator iterator = this.connections.iterator(); + + while (iterator.hasNext()) { +- Connection connection = (Connection) iterator.next(); ++ Connection networkmanager = (Connection) iterator.next(); + +- if (!connection.isConnecting()) { +- if (connection.isConnected()) { ++ if (!networkmanager.isConnecting()) { ++ if (networkmanager.isConnected()) { + try { +- connection.tick(); ++ networkmanager.tick(); + } catch (Exception exception) { +- if (connection.isMemoryConnection()) { ++ if (networkmanager.isMemoryConnection()) { + throw new ReportedException(CrashReport.forThrowable(exception, "Ticking memory connection")); + } + +- ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", connection.getLoggableAddress(this.server.logIPs()), exception); +- MutableComponent mutablecomponent = Component.literal("Internal server error"); ++ ServerConnectionListener.LOGGER.warn("Failed to handle packet for {}", networkmanager.getLoggableAddress(this.server.logIPs()), exception); ++ MutableComponent ichatmutablecomponent = Component.literal("Internal server error"); + +- connection.send(new ClientboundDisconnectPacket(mutablecomponent), PacketSendListener.thenRun(() -> { +- connection.disconnect(mutablecomponent); ++ networkmanager.send(new ClientboundDisconnectPacket(ichatmutablecomponent), PacketSendListener.thenRun(() -> { ++ networkmanager.disconnect(ichatmutablecomponent); + })); +- connection.setReadOnly(); ++ networkmanager.setReadOnly(); + } + } else { + iterator.remove(); +- connection.handleDisconnection(); ++ networkmanager.handleDisconnection(); + } + } + } +@@ -201,27 +209,26 @@ + private final int jitter; + private final List<ServerConnectionListener.LatencySimulator.DelayedMessage> queuedMessages = Lists.newArrayList(); + +- public LatencySimulator(int i, int j) { +- this.delay = i; +- this.jitter = j; ++ public LatencySimulator(int delay, int jitter) { ++ this.delay = delay; ++ this.jitter = jitter; + } + +- @Override + public void channelRead(ChannelHandlerContext channelhandlercontext, Object object) { + this.delayDownstream(channelhandlercontext, object); + } + +- private void delayDownstream(ChannelHandlerContext channelhandlercontext, Object object) { ++ private void delayDownstream(ChannelHandlerContext ctx, Object msg) { + int i = this.delay + (int) (Math.random() * (double) this.jitter); + +- this.queuedMessages.add(new ServerConnectionListener.LatencySimulator.DelayedMessage(channelhandlercontext, object)); ++ this.queuedMessages.add(new ServerConnectionListener.LatencySimulator.DelayedMessage(ctx, msg)); + ServerConnectionListener.LatencySimulator.TIMER.newTimeout(this::onTimeout, (long) i, TimeUnit.MILLISECONDS); + } + + private void onTimeout(Timeout timeout) { +- ServerConnectionListener.LatencySimulator.DelayedMessage serverconnectionlistener_latencysimulator_delayedmessage = (ServerConnectionListener.LatencySimulator.DelayedMessage) this.queuedMessages.remove(0); ++ ServerConnectionListener.LatencySimulator.DelayedMessage serverconnection_latencysimulator_delayedmessage = (ServerConnectionListener.LatencySimulator.DelayedMessage) this.queuedMessages.remove(0); + +- serverconnectionlistener_latencysimulator_delayedmessage.ctx.fireChannelRead(serverconnectionlistener_latencysimulator_delayedmessage.msg); ++ serverconnection_latencysimulator_delayedmessage.ctx.fireChannelRead(serverconnection_latencysimulator_delayedmessage.msg); + } + + private static class DelayedMessage { diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch new file mode 100644 index 0000000000..b586b52e06 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -0,0 +1,3151 @@ +--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -3,7 +3,6 @@ + import com.google.common.collect.Lists; + import com.google.common.primitives.Floats; + import com.mojang.authlib.GameProfile; +-import com.mojang.brigadier.CommandDispatcher; + import com.mojang.brigadier.ParseResults; + import com.mojang.brigadier.StringReader; + import com.mojang.logging.LogUtils; +@@ -60,6 +59,7 @@ + import net.minecraft.network.protocol.Packet; + import net.minecraft.network.protocol.PacketUtils; + import net.minecraft.network.protocol.common.ServerboundClientInformationPacket; ++import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; + import net.minecraft.network.protocol.game.ClientboundBlockChangedAckPacket; + import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundCommandSuggestionsPacket; +@@ -70,6 +70,8 @@ + import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket; + import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket; + import net.minecraft.network.protocol.game.ClientboundSetCarriedItemPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket; ++import net.minecraft.network.protocol.game.ClientboundSetEquipmentPacket; + import net.minecraft.network.protocol.game.ClientboundStartConfigurationPacket; + import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; + import net.minecraft.network.protocol.game.ClientboundTagQueryPacket; +@@ -133,20 +135,19 @@ + import net.minecraft.util.SignatureValidator; + import net.minecraft.util.StringUtil; + import net.minecraft.world.Container; +-import net.minecraft.world.InteractionHand; ++import net.minecraft.world.EnumHand; + import net.minecraft.world.InteractionResult; + import net.minecraft.world.effect.MobEffects; + import net.minecraft.world.entity.Entity; ++import net.minecraft.world.entity.EnumMoveType; + import net.minecraft.world.entity.ExperienceOrb; + import net.minecraft.world.entity.HasCustomInventoryScreen; + import net.minecraft.world.entity.LivingEntity; +-import net.minecraft.world.entity.MoverType; ++import net.minecraft.world.entity.Mob; + import net.minecraft.world.entity.PlayerRideableJumping; + import net.minecraft.world.entity.RelativeMovement; +-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.ProfilePublicKey; + import net.minecraft.world.entity.projectile.AbstractArrow; + import net.minecraft.world.entity.vehicle.Boat; +@@ -154,14 +155,13 @@ + import net.minecraft.world.inventory.AnvilMenu; + import net.minecraft.world.inventory.BeaconMenu; + import net.minecraft.world.inventory.CrafterMenu; +-import net.minecraft.world.inventory.MerchantMenu; +-import net.minecraft.world.inventory.RecipeBookMenu; + import net.minecraft.world.item.BlockItem; + import net.minecraft.world.item.BucketItem; + import net.minecraft.world.item.Item; + import net.minecraft.world.item.ItemStack; + import net.minecraft.world.item.Items; + 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; +@@ -175,15 +175,72 @@ + import net.minecraft.world.level.block.entity.SignBlockEntity; + import net.minecraft.world.level.block.entity.StructureBlockEntity; + import net.minecraft.world.level.block.state.BlockBehaviour; +-import net.minecraft.world.level.block.state.BlockState; ++import net.minecraft.world.level.block.state.IBlockData; + 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; + import net.minecraft.world.phys.shapes.VoxelShape; + import org.slf4j.Logger; + ++// CraftBukkit start ++import com.mojang.datafixers.util.Pair; ++import java.util.Arrays; ++import java.util.concurrent.ExecutionException; ++import java.util.concurrent.atomic.AtomicInteger; ++import java.util.function.Function; ++import net.minecraft.network.chat.OutgoingChatMessage; ++import net.minecraft.world.entity.animal.Bucketable; ++import net.minecraft.world.entity.animal.allay.Allay; ++import net.minecraft.world.entity.item.ItemEntity; ++import net.minecraft.world.inventory.InventoryClickType; ++import net.minecraft.world.inventory.MerchantMenu; ++import net.minecraft.world.inventory.RecipeBookMenu; ++import net.minecraft.world.inventory.Slot; ++import net.minecraft.world.item.crafting.RecipeHolder; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.entity.CraftEntity; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.craftbukkit.event.CraftEventFactory; ++import org.bukkit.craftbukkit.inventory.CraftItemStack; ++import org.bukkit.craftbukkit.util.CraftChatMessage; ++import org.bukkit.craftbukkit.util.CraftMagicNumbers; ++import org.bukkit.craftbukkit.util.CraftNamespacedKey; ++import org.bukkit.craftbukkit.util.LazyPlayerSet; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Event; ++import org.bukkit.event.block.Action; ++import org.bukkit.event.inventory.ClickType; ++import org.bukkit.event.inventory.CraftItemEvent; ++import org.bukkit.event.inventory.InventoryAction; ++import org.bukkit.event.inventory.InventoryClickEvent; ++import org.bukkit.event.inventory.InventoryCreativeEvent; ++import org.bukkit.event.inventory.InventoryType.SlotType; ++import org.bukkit.event.inventory.SmithItemEvent; ++import org.bukkit.event.player.AsyncPlayerChatEvent; ++import org.bukkit.event.player.PlayerAnimationEvent; ++import org.bukkit.event.player.PlayerAnimationType; ++import org.bukkit.event.player.PlayerChatEvent; ++import org.bukkit.event.player.PlayerCommandPreprocessEvent; ++import org.bukkit.event.player.PlayerInteractAtEntityEvent; ++import org.bukkit.event.player.PlayerInteractEntityEvent; ++import org.bukkit.event.player.PlayerItemHeldEvent; ++import org.bukkit.event.player.PlayerMoveEvent; ++import org.bukkit.event.player.PlayerRespawnEvent.RespawnReason; ++import org.bukkit.event.player.PlayerSwapHandItemsEvent; ++import org.bukkit.event.player.PlayerTeleportEvent; ++import org.bukkit.event.player.PlayerToggleFlightEvent; ++import org.bukkit.event.player.PlayerToggleSneakEvent; ++import org.bukkit.event.player.PlayerToggleSprintEvent; ++import org.bukkit.inventory.CraftingInventory; ++import org.bukkit.inventory.EquipmentSlot; ++import org.bukkit.inventory.InventoryView; ++import org.bukkit.inventory.SmithingInventory; ++// CraftBukkit end ++ + public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener { + + static final Logger LOGGER = LogUtils.getLogger(); +@@ -195,7 +252,9 @@ + public final PlayerChunkSender chunkSender; + private int tickCount; + private int ackBlockChangesUpTo = -1; +- private int chatSpamTickCount; ++ // CraftBukkit start - multithreaded fields ++ private final AtomicInteger chatSpamTickCount = new AtomicInteger(); ++ // CraftBukkit end + private int dropSpamTickCount; + private double firstGoodX; + private double firstGoodY; +@@ -229,22 +288,37 @@ + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; + +- public ServerGamePacketListenerImpl(MinecraftServer minecraftserver, Connection connection, ServerPlayer serverplayer, CommonListenerCookie commonlistenercookie) { +- super(minecraftserver, connection, commonlistenercookie); +- this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); +- connection.setListener(this); +- this.player = serverplayer; +- serverplayer.connection = this; +- serverplayer.getTextFilter().join(); +- UUID uuid = serverplayer.getUUID(); ++ public ServerGamePacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, ServerPlayer entityplayer, CommonListenerCookie commonlistenercookie) { ++ super(minecraftserver, networkmanager, commonlistenercookie, entityplayer); // CraftBukkit ++ this.chunkSender = new PlayerChunkSender(networkmanager.isMemoryConnection()); ++ networkmanager.setListener(this); ++ this.player = entityplayer; ++ entityplayer.connection = this; ++ entityplayer.getTextFilter().join(); ++ UUID uuid = entityplayer.getUUID(); + + Objects.requireNonNull(minecraftserver); + this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, minecraftserver::enforceSecureProfile); +- this.chatMessageChain = new FutureChain(minecraftserver); ++ this.chatMessageChain = new FutureChain(minecraftserver.chatExecutor); // CraftBukkit - async chat + } + ++ // CraftBukkit start - add fields ++ private int lastTick = MinecraftServer.currentTick; ++ private int allowedPlayerTicks = 1; ++ private int lastDropTick = MinecraftServer.currentTick; ++ private int lastBookTick = MinecraftServer.currentTick; ++ private int dropCount = 0; ++ ++ // Get position of last block hit for BlockDamageLevel.STOPPED ++ private double lastPosX = Double.MAX_VALUE; ++ private double lastPosY = Double.MAX_VALUE; ++ private double lastPosZ = Double.MAX_VALUE; ++ private float lastPitch = Float.MAX_VALUE; ++ private float lastYaw = Float.MAX_VALUE; ++ private boolean justTeleported = false; ++ // CraftBukkit end ++ + @Override +- @Override + public void tick() { + if (this.ackBlockChangesUpTo > -1) { + this.send(new ClientboundBlockChangedAckPacket(this.ackBlockChangesUpTo)); +@@ -295,15 +369,21 @@ + } + + this.keepConnectionAlive(); ++ // CraftBukkit start ++ for (int spam; (spam = this.chatSpamTickCount.get()) > 0 && !chatSpamTickCount.compareAndSet(spam, spam - 1); ) ; ++ /* Use thread-safe field access instead + if (this.chatSpamTickCount > 0) { + --this.chatSpamTickCount; + } ++ */ ++ // CraftBukkit end + + if (this.dropSpamTickCount > 0) { + --this.dropSpamTickCount; + } + + if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) this.server.getPlayerIdleTimeout() * 1000L * 60L) { ++ this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 + this.disconnect(Component.translatable("multiplayer.disconnect.idling")); + } + +@@ -319,25 +399,22 @@ + } + + @Override +- @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected() && !this.waitingForSwitchToConfig; + } + + @Override +- @Override + public boolean shouldHandleMessage(Packet<?> packet) { + return super.shouldHandleMessage(packet) ? true : this.waitingForSwitchToConfig && this.connection.isConnected() && packet instanceof ServerboundConfigurationAcknowledgedPacket; + } + + @Override +- @Override + protected GameProfile playerProfile() { + return this.player.getGameProfile(); + } + +- private <T, R> CompletableFuture<R> filterTextPacket(T t0, BiFunction<TextFilter, T, CompletableFuture<R>> bifunction) { +- return ((CompletableFuture) bifunction.apply(this.player.getTextFilter(), t0)).thenApply((object) -> { ++ private <T, R> CompletableFuture<R> filterTextPacket(T message, BiFunction<TextFilter, T, CompletableFuture<R>> processor) { ++ return ((CompletableFuture) processor.apply(this.player.getTextFilter(), message)).thenApply((object) -> { + if (!this.isAcceptingMessages()) { + ServerGamePacketListenerImpl.LOGGER.debug("Ignoring packet due to disconnection"); + throw new CancellationException("disconnected"); +@@ -347,65 +424,90 @@ + }); + } + +- private CompletableFuture<FilteredText> filterTextPacket(String s) { +- return this.filterTextPacket(s, TextFilter::processStreamMessage); ++ private CompletableFuture<FilteredText> filterTextPacket(String text) { ++ return this.filterTextPacket(text, TextFilter::processStreamMessage); + } + +- private CompletableFuture<List<FilteredText>> filterTextPacket(List<String> list) { +- return this.filterTextPacket(list, TextFilter::processMessageBundle); ++ private CompletableFuture<List<FilteredText>> filterTextPacket(List<String> texts) { ++ return this.filterTextPacket(texts, TextFilter::processMessageBundle); + } + + @Override +- @Override +- public void handlePlayerInput(ServerboundPlayerInputPacket serverboundplayerinputpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundplayerinputpacket, this, this.player.serverLevel()); +- this.player.setPlayerInput(serverboundplayerinputpacket.getXxa(), serverboundplayerinputpacket.getZza(), serverboundplayerinputpacket.isJumping(), serverboundplayerinputpacket.isShiftKeyDown()); ++ public void handlePlayerInput(ServerboundPlayerInputPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ this.player.setPlayerInput(packet.getXxa(), packet.getZza(), packet.isJumping(), packet.isShiftKeyDown()); + } + +- private static boolean containsInvalidValues(double d0, double d1, double d2, float f, float f1) { +- return Double.isNaN(d0) || Double.isNaN(d1) || Double.isNaN(d2) || !Floats.isFinite(f1) || !Floats.isFinite(f); ++ private static boolean containsInvalidValues(double x, double d1, double y, float f, float z) { ++ return Double.isNaN(x) || Double.isNaN(d1) || Double.isNaN(y) || !Floats.isFinite(z) || !Floats.isFinite(f); + } + +- private static double clampHorizontal(double d0) { +- return Mth.clamp(d0, -3.0E7D, 3.0E7D); ++ private static double clampHorizontal(double value) { ++ return Mth.clamp(value, -3.0E7D, 3.0E7D); + } + +- private static double clampVertical(double d0) { +- return Mth.clamp(d0, -2.0E7D, 2.0E7D); ++ private static double clampVertical(double value) { ++ return Mth.clamp(value, -2.0E7D, 2.0E7D); + } + + @Override +- @Override +- public void handleMoveVehicle(ServerboundMoveVehiclePacket serverboundmovevehiclepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundmovevehiclepacket, this, this.player.serverLevel()); +- if (containsInvalidValues(serverboundmovevehiclepacket.getX(), serverboundmovevehiclepacket.getY(), serverboundmovevehiclepacket.getZ(), serverboundmovevehiclepacket.getYRot(), serverboundmovevehiclepacket.getXRot())) { ++ public void handleMoveVehicle(ServerboundMoveVehiclePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (containsInvalidValues(packet.getX(), packet.getY(), packet.getZ(), packet.getYRot(), packet.getXRot())) { + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_vehicle_movement")); + } else { + Entity entity = this.player.getRootVehicle(); + + if (entity != this.player && entity.getControllingPassenger() == this.player && entity == this.lastVehicle) { +- ServerLevel serverlevel = this.player.serverLevel(); ++ ServerLevel worldserver = this.player.serverLevel(); + double d0 = entity.getX(); + double d1 = entity.getY(); + double d2 = entity.getZ(); +- double d3 = clampHorizontal(serverboundmovevehiclepacket.getX()); +- double d4 = clampVertical(serverboundmovevehiclepacket.getY()); +- double d5 = clampHorizontal(serverboundmovevehiclepacket.getZ()); +- float f = Mth.wrapDegrees(serverboundmovevehiclepacket.getYRot()); +- float f1 = Mth.wrapDegrees(serverboundmovevehiclepacket.getXRot()); ++ double d3 = clampHorizontal(packet.getX()); ++ double d4 = clampVertical(packet.getY()); ++ double d5 = clampHorizontal(packet.getZ()); ++ float f = Mth.wrapDegrees(packet.getYRot()); ++ float f1 = Mth.wrapDegrees(packet.getXRot()); + 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; + +- if (d10 - d9 > 100.0D && !this.isSingleplayerOwner()) { ++ ++ // CraftBukkit start - handle custom speeds and skipped ticks ++ this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; ++ this.allowedPlayerTicks = Math.max(this.allowedPlayerTicks, 1); ++ this.lastTick = (int) (System.currentTimeMillis() / 50); ++ ++ ++this.receivedMovePacketCount; ++ int i = this.receivedMovePacketCount - this.knownMovePacketCount; ++ if (i > Math.max(this.allowedPlayerTicks, 5)) { ++ ServerGamePacketListenerImpl.LOGGER.debug(this.player.getScoreboardName() + " is sending move packets too frequently (" + i + " packets since last tick)"); ++ i = 1; ++ } ++ ++ if (d10 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (player.getAbilities().flying) { ++ speed = player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = player.getAbilities().walkingSpeed * 10f; ++ } ++ speed *= 2f; // TODO: Get the speed of the vehicle instead of the player ++ ++ if (d10 - d9 > Math.max(100.0D, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved too quickly! {},{},{}", new Object[]{entity.getName().getString(), this.player.getName().getString(), d6, d7, d8}); + this.send(new ClientboundMoveVehiclePacket(entity)); + return; + } + +- boolean flag = serverlevel.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); ++ boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + + d6 = d3 - this.vehicleLastGoodX; + d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; +@@ -413,14 +515,14 @@ + boolean flag1 = entity.verticalCollisionBelow; + + if (entity instanceof LivingEntity) { +- LivingEntity livingentity = (LivingEntity) entity; ++ LivingEntity entityliving = (LivingEntity) entity; + +- if (livingentity.onClimbable()) { +- livingentity.resetFallDistance(); ++ if (entityliving.onClimbable()) { ++ entityliving.resetFallDistance(); + } + } + +- entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); ++ entity.move(EnumMoveType.PLAYER, new Vec3(d6, d7, d8)); + double d11 = d7; + + d6 = d3 - entity.getX(); +@@ -439,14 +541,72 @@ + } + + entity.absMoveTo(d3, d4, d5, f, f1); +- boolean flag3 = serverlevel.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); ++ player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit ++ boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); + + if (flag && (flag2 || !flag3)) { + entity.absMoveTo(d0, d1, d2, f, f1); ++ player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit + this.send(new ClientboundMoveVehiclePacket(entity)); + return; + } + ++ // CraftBukkit start - fire PlayerMoveEvent ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ to.setX(packet.getX()); ++ to.setY(packet.getY()); ++ to.setZ(packet.getZ()); ++ ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ to.setYaw(packet.getYRot()); ++ to.setPitch(packet.getXRot()); ++ ++ // Prevent 40 event-calls for less than a single pixel of movement >.> ++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2); ++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch()); ++ ++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isImmobile()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ // Skip the first time we do this ++ if (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ return; ++ } ++ ++ // If a Plugin has changed the To destination then we teleport the Player ++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. ++ // We only do this if the Event was not cancelled. ++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ return; ++ } ++ ++ // Check to see if the Players Location has some how changed during the call of the event. ++ // This can happen due to a plugin teleporting the player instead of using .setTo() ++ if (!from.equals(this.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end ++ + this.player.serverLevel().getChunkSource().move(this.player); + this.player.checkMovementStatistics(this.player.getX() - d0, this.player.getY() - d1, this.player.getZ() - d2); + this.clientVehicleIsFloating = d11 >= -0.03125D && !flag1 && !this.server.isFlightAllowed() && !entity.isNoGravity() && this.noBlocksAround(entity); +@@ -463,10 +623,9 @@ + } + + @Override +- @Override +- public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket serverboundacceptteleportationpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundacceptteleportationpacket, this, this.player.serverLevel()); +- if (serverboundacceptteleportationpacket.getId() == this.awaitingTeleport) { ++ public void handleAcceptTeleportPacket(ServerboundAcceptTeleportationPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (packet.getId() == this.awaitingTeleport) { + if (this.awaitingPositionFromClient == null) { + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); + return; +@@ -481,35 +640,34 @@ + } + + this.awaitingPositionFromClient = null; ++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit + } + + } + + @Override +- @Override +- public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket serverboundrecipebookseenrecipepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundrecipebookseenrecipepacket, this, this.player.serverLevel()); +- Optional optional = this.server.getRecipeManager().byKey(serverboundrecipebookseenrecipepacket.getRecipe()); +- ServerRecipeBook serverrecipebook = this.player.getRecipeBook(); ++ public void handleRecipeBookSeenRecipePacket(ServerboundRecipeBookSeenRecipePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ Optional<? extends RecipeHolder<?>> optional = this.server.getRecipeManager().byKey(packet.getRecipe()); // CraftBukkit - decompile error ++ ServerRecipeBook recipebookserver = this.player.getRecipeBook(); + +- Objects.requireNonNull(serverrecipebook); +- optional.ifPresent(serverrecipebook::removeHighlight); ++ Objects.requireNonNull(recipebookserver); ++ optional.ifPresent(recipebookserver::removeHighlight); + } + + @Override +- @Override +- public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket serverboundrecipebookchangesettingspacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundrecipebookchangesettingspacket, this, this.player.serverLevel()); +- this.player.getRecipeBook().setBookSetting(serverboundrecipebookchangesettingspacket.getBookType(), serverboundrecipebookchangesettingspacket.isOpen(), serverboundrecipebookchangesettingspacket.isFiltering()); ++ public void handleRecipeBookChangeSettingsPacket(ServerboundRecipeBookChangeSettingsPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ CraftEventFactory.callRecipeBookSettingsEvent(this.player, packet.getBookType(), packet.isOpen(), packet.isFiltering()); // CraftBukkit ++ this.player.getRecipeBook().setBookSetting(packet.getBookType(), packet.isOpen(), packet.isFiltering()); + } + + @Override +- @Override +- public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket serverboundseenadvancementspacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundseenadvancementspacket, this, this.player.serverLevel()); +- if (serverboundseenadvancementspacket.getAction() == ServerboundSeenAdvancementsPacket.Action.OPENED_TAB) { +- ResourceLocation resourcelocation = (ResourceLocation) Objects.requireNonNull(serverboundseenadvancementspacket.getTab()); +- AdvancementHolder advancementholder = this.server.getAdvancements().get(resourcelocation); ++ public void handleSeenAdvancements(ServerboundSeenAdvancementsPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (packet.getAction() == ServerboundSeenAdvancementsPacket.Status.OPENED_TAB) { ++ ResourceLocation minecraftkey = (ResourceLocation) Objects.requireNonNull(packet.getTab()); ++ AdvancementHolder advancementholder = this.server.getAdvancements().get(minecraftkey); + + if (advancementholder != null) { + this.player.getAdvancements().setSelectedTab(advancementholder); +@@ -519,10 +677,15 @@ + } + + @Override +- @Override +- public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket serverboundcommandsuggestionpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundcommandsuggestionpacket, this, this.player.serverLevel()); +- StringReader stringreader = new StringReader(serverboundcommandsuggestionpacket.getCommand()); ++ public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (chatSpamTickCount.addAndGet(1) > 500 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ this.disconnect(Component.translatable("disconnect.spam")); ++ return; ++ } ++ // CraftBukkit end ++ StringReader stringreader = new StringReader(packet.getCommand()); + + if (stringreader.canRead() && stringreader.peek() == '/') { + stringreader.skip(); +@@ -531,70 +694,70 @@ + ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack()); + + this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> { +- this.send(new ClientboundCommandSuggestionsPacket(serverboundcommandsuggestionpacket.getId(), suggestions)); ++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer ++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)); + }); + } + + @Override +- @Override +- public void handleSetCommandBlock(ServerboundSetCommandBlockPacket serverboundsetcommandblockpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetcommandblockpacket, this, this.player.serverLevel()); ++ public void handleSetCommandBlock(ServerboundSetCommandBlockPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); + } else if (!this.player.canUseGameMasterBlocks()) { + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { +- BaseCommandBlock basecommandblock = null; +- CommandBlockEntity commandblockentity = null; +- BlockPos blockpos = serverboundsetcommandblockpacket.getPos(); +- BlockEntity blockentity = this.player.level().getBlockEntity(blockpos); ++ BaseCommandBlock commandblocklistenerabstract = null; ++ CommandBlockEntity tileentitycommand = null; ++ BlockPos blockposition = packet.getPos(); ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); + +- if (blockentity instanceof CommandBlockEntity) { +- commandblockentity = (CommandBlockEntity) blockentity; +- basecommandblock = commandblockentity.getCommandBlock(); ++ if (tileentity instanceof CommandBlockEntity) { ++ tileentitycommand = (CommandBlockEntity) tileentity; ++ commandblocklistenerabstract = tileentitycommand.getCommandBlock(); + } + +- String s = serverboundsetcommandblockpacket.getCommand(); +- boolean flag = serverboundsetcommandblockpacket.isTrackOutput(); ++ String s = packet.getCommand(); ++ boolean flag = packet.isTrackOutput(); + +- if (basecommandblock != null) { +- CommandBlockEntity.Mode commandblockentity_mode = commandblockentity.getMode(); +- BlockState blockstate = this.player.level().getBlockState(blockpos); +- Direction direction = (Direction) blockstate.getValue(CommandBlock.FACING); +- BlockState blockstate1; ++ if (commandblocklistenerabstract != null) { ++ CommandBlockEntity.Type tileentitycommand_type = tileentitycommand.getMode(); ++ IBlockData iblockdata = this.player.level().getBlockState(blockposition); ++ Direction enumdirection = (Direction) iblockdata.getValue(CommandBlock.FACING); ++ IBlockData iblockdata1; + +- switch (serverboundsetcommandblockpacket.getMode()) { ++ switch (packet.getMode()) { + case SEQUENCE: +- blockstate1 = Blocks.CHAIN_COMMAND_BLOCK.defaultBlockState(); ++ iblockdata1 = Blocks.CHAIN_COMMAND_BLOCK.defaultBlockState(); + break; + case AUTO: +- blockstate1 = Blocks.REPEATING_COMMAND_BLOCK.defaultBlockState(); ++ iblockdata1 = Blocks.REPEATING_COMMAND_BLOCK.defaultBlockState(); + break; + case REDSTONE: + default: +- blockstate1 = Blocks.COMMAND_BLOCK.defaultBlockState(); ++ iblockdata1 = Blocks.COMMAND_BLOCK.defaultBlockState(); + } + +- BlockState blockstate2 = (BlockState) ((BlockState) blockstate1.setValue(CommandBlock.FACING, direction)).setValue(CommandBlock.CONDITIONAL, serverboundsetcommandblockpacket.isConditional()); ++ IBlockData iblockdata2 = (IBlockData) ((IBlockData) iblockdata1.setValue(CommandBlock.FACING, enumdirection)).setValue(CommandBlock.CONDITIONAL, packet.isConditional()); + +- if (blockstate2 != blockstate) { +- this.player.level().setBlock(blockpos, blockstate2, 2); +- blockentity.setBlockState(blockstate2); +- this.player.level().getChunkAt(blockpos).setBlockEntity(blockentity); ++ if (iblockdata2 != iblockdata) { ++ this.player.level().setBlock(blockposition, iblockdata2, 2); ++ tileentity.setBlockState(iblockdata2); ++ this.player.level().getChunkAt(blockposition).setBlockEntity(tileentity); + } + +- basecommandblock.setCommand(s); +- basecommandblock.setTrackOutput(flag); ++ commandblocklistenerabstract.setCommand(s); ++ commandblocklistenerabstract.setTrackOutput(flag); + if (!flag) { +- basecommandblock.setLastOutput((Component) null); ++ commandblocklistenerabstract.setLastOutput((Component) null); + } + +- commandblockentity.setAutomatic(serverboundsetcommandblockpacket.isAutomatic()); +- if (commandblockentity_mode != serverboundsetcommandblockpacket.getMode()) { +- commandblockentity.onModeSwitch(); ++ tileentitycommand.setAutomatic(packet.isAutomatic()); ++ if (tileentitycommand_type != packet.getMode()) { ++ tileentitycommand.onModeSwitch(); + } + +- basecommandblock.onUpdated(); ++ commandblocklistenerabstract.onUpdated(); + if (!StringUtil.isNullOrEmpty(s)) { + this.player.sendSystemMessage(Component.translatable("advMode.setCommand.success", s)); + } +@@ -604,213 +767,218 @@ + } + + @Override +- @Override +- public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket serverboundsetcommandminecartpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetcommandminecartpacket, this, this.player.serverLevel()); ++ public void handleSetCommandMinecart(ServerboundSetCommandMinecartPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (!this.server.isCommandBlockEnabled()) { + this.player.sendSystemMessage(Component.translatable("advMode.notEnabled")); + } else if (!this.player.canUseGameMasterBlocks()) { + this.player.sendSystemMessage(Component.translatable("advMode.notAllowed")); + } else { +- BaseCommandBlock basecommandblock = serverboundsetcommandminecartpacket.getCommandBlock(this.player.level()); ++ BaseCommandBlock commandblocklistenerabstract = packet.getCommandBlock(this.player.level()); + +- if (basecommandblock != null) { +- basecommandblock.setCommand(serverboundsetcommandminecartpacket.getCommand()); +- basecommandblock.setTrackOutput(serverboundsetcommandminecartpacket.isTrackOutput()); +- if (!serverboundsetcommandminecartpacket.isTrackOutput()) { +- basecommandblock.setLastOutput((Component) null); ++ if (commandblocklistenerabstract != null) { ++ commandblocklistenerabstract.setCommand(packet.getCommand()); ++ commandblocklistenerabstract.setTrackOutput(packet.isTrackOutput()); ++ if (!packet.isTrackOutput()) { ++ commandblocklistenerabstract.setLastOutput((Component) null); + } + +- basecommandblock.onUpdated(); +- this.player.sendSystemMessage(Component.translatable("advMode.setCommand.success", serverboundsetcommandminecartpacket.getCommand())); ++ commandblocklistenerabstract.onUpdated(); ++ this.player.sendSystemMessage(Component.translatable("advMode.setCommand.success", packet.getCommand())); + } + + } + } + + @Override +- @Override +- public void handlePickItem(ServerboundPickItemPacket serverboundpickitempacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundpickitempacket, this, this.player.serverLevel()); +- this.player.getInventory().pickSlot(serverboundpickitempacket.getSlot()); ++ public void handlePickItem(ServerboundPickItemPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ this.player.getInventory().pickSlot(packet.getSlot()); + this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, this.player.getInventory().selected, this.player.getInventory().getItem(this.player.getInventory().selected))); +- this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, serverboundpickitempacket.getSlot(), this.player.getInventory().getItem(serverboundpickitempacket.getSlot()))); ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-2, 0, packet.getSlot(), this.player.getInventory().getItem(packet.getSlot()))); + this.player.connection.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); + } + + @Override +- @Override +- public void handleRenameItem(ServerboundRenameItemPacket serverboundrenameitempacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundrenameitempacket, this, this.player.serverLevel()); +- AbstractContainerMenu abstractcontainermenu = this.player.containerMenu; ++ public void handleRenameItem(ServerboundRenameItemPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ AbstractContainerMenu container = this.player.containerMenu; + +- if (abstractcontainermenu instanceof AnvilMenu) { +- AnvilMenu anvilmenu = (AnvilMenu) abstractcontainermenu; ++ if (container instanceof AnvilMenu) { ++ AnvilMenu containeranvil = (AnvilMenu) container; + +- if (!anvilmenu.stillValid(this.player)) { +- ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, anvilmenu); ++ if (!containeranvil.stillValid(this.player)) { ++ ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containeranvil); + return; + } + +- anvilmenu.setItemName(serverboundrenameitempacket.getName()); ++ containeranvil.setItemName(packet.getName()); + } + + } + + @Override +- @Override +- public void handleSetBeaconPacket(ServerboundSetBeaconPacket serverboundsetbeaconpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetbeaconpacket, this, this.player.serverLevel()); +- AbstractContainerMenu abstractcontainermenu = this.player.containerMenu; ++ public void handleSetBeaconPacket(ServerboundSetBeaconPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ AbstractContainerMenu container = this.player.containerMenu; + +- if (abstractcontainermenu instanceof BeaconMenu) { +- BeaconMenu beaconmenu = (BeaconMenu) abstractcontainermenu; ++ if (container instanceof BeaconMenu) { ++ BeaconMenu containerbeacon = (BeaconMenu) container; + + if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + return; + } + +- beaconmenu.updateEffects(serverboundsetbeaconpacket.getPrimary(), serverboundsetbeaconpacket.getSecondary()); ++ containerbeacon.updateEffects(packet.getPrimary(), packet.getSecondary()); + } + + } + + @Override +- @Override +- public void handleSetStructureBlock(ServerboundSetStructureBlockPacket serverboundsetstructureblockpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetstructureblockpacket, this, this.player.serverLevel()); ++ public void handleSetStructureBlock(ServerboundSetStructureBlockPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.canUseGameMasterBlocks()) { +- BlockPos blockpos = serverboundsetstructureblockpacket.getPos(); +- BlockState blockstate = this.player.level().getBlockState(blockpos); +- BlockEntity blockentity = this.player.level().getBlockEntity(blockpos); ++ BlockPos blockposition = packet.getPos(); ++ IBlockData iblockdata = this.player.level().getBlockState(blockposition); ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); + +- if (blockentity instanceof StructureBlockEntity) { +- StructureBlockEntity structureblockentity = (StructureBlockEntity) blockentity; ++ if (tileentity instanceof StructureBlockEntity) { ++ StructureBlockEntity tileentitystructure = (StructureBlockEntity) tileentity; + +- structureblockentity.setMode(serverboundsetstructureblockpacket.getMode()); +- structureblockentity.setStructureName(serverboundsetstructureblockpacket.getName()); +- structureblockentity.setStructurePos(serverboundsetstructureblockpacket.getOffset()); +- structureblockentity.setStructureSize(serverboundsetstructureblockpacket.getSize()); +- structureblockentity.setMirror(serverboundsetstructureblockpacket.getMirror()); +- structureblockentity.setRotation(serverboundsetstructureblockpacket.getRotation()); +- structureblockentity.setMetaData(serverboundsetstructureblockpacket.getData()); +- structureblockentity.setIgnoreEntities(serverboundsetstructureblockpacket.isIgnoreEntities()); +- structureblockentity.setShowAir(serverboundsetstructureblockpacket.isShowAir()); +- structureblockentity.setShowBoundingBox(serverboundsetstructureblockpacket.isShowBoundingBox()); +- structureblockentity.setIntegrity(serverboundsetstructureblockpacket.getIntegrity()); +- structureblockentity.setSeed(serverboundsetstructureblockpacket.getSeed()); +- if (structureblockentity.hasStructureName()) { +- String s = structureblockentity.getStructureName(); ++ tileentitystructure.setMode(packet.getMode()); ++ tileentitystructure.setStructureName(packet.getName()); ++ tileentitystructure.setStructurePos(packet.getOffset()); ++ tileentitystructure.setStructureSize(packet.getSize()); ++ tileentitystructure.setMirror(packet.getMirror()); ++ tileentitystructure.setRotation(packet.getRotation()); ++ tileentitystructure.setMetaData(packet.getData()); ++ tileentitystructure.setIgnoreEntities(packet.isIgnoreEntities()); ++ tileentitystructure.setShowAir(packet.isShowAir()); ++ tileentitystructure.setShowBoundingBox(packet.isShowBoundingBox()); ++ tileentitystructure.setIntegrity(packet.getIntegrity()); ++ tileentitystructure.setSeed(packet.getSeed()); ++ if (tileentitystructure.hasStructureName()) { ++ String s = tileentitystructure.getStructureName(); + +- if (serverboundsetstructureblockpacket.getUpdateType() == StructureBlockEntity.UpdateType.SAVE_AREA) { +- if (structureblockentity.saveStructure()) { ++ if (packet.getUpdateType() == StructureBlockEntity.UpdateType.SAVE_AREA) { ++ if (tileentitystructure.saveStructure()) { + this.player.displayClientMessage(Component.translatable("structure_block.save_success", s), false); + } else { + this.player.displayClientMessage(Component.translatable("structure_block.save_failure", s), false); + } +- } else if (serverboundsetstructureblockpacket.getUpdateType() == StructureBlockEntity.UpdateType.LOAD_AREA) { +- if (!structureblockentity.isStructureLoadable()) { ++ } else if (packet.getUpdateType() == StructureBlockEntity.UpdateType.LOAD_AREA) { ++ if (!tileentitystructure.isStructureLoadable()) { + this.player.displayClientMessage(Component.translatable("structure_block.load_not_found", s), false); +- } else if (structureblockentity.placeStructureIfSameSize(this.player.serverLevel())) { ++ } else if (tileentitystructure.placeStructureIfSameSize(this.player.serverLevel())) { + this.player.displayClientMessage(Component.translatable("structure_block.load_success", s), false); + } else { + this.player.displayClientMessage(Component.translatable("structure_block.load_prepare", s), false); + } +- } else if (serverboundsetstructureblockpacket.getUpdateType() == StructureBlockEntity.UpdateType.SCAN_AREA) { +- if (structureblockentity.detectSize()) { ++ } else if (packet.getUpdateType() == StructureBlockEntity.UpdateType.SCAN_AREA) { ++ if (tileentitystructure.detectSize()) { + this.player.displayClientMessage(Component.translatable("structure_block.size_success", s), false); + } else { + this.player.displayClientMessage(Component.translatable("structure_block.size_failure"), false); + } + } + } else { +- this.player.displayClientMessage(Component.translatable("structure_block.invalid_structure_name", serverboundsetstructureblockpacket.getName()), false); ++ this.player.displayClientMessage(Component.translatable("structure_block.invalid_structure_name", packet.getName()), false); + } + +- structureblockentity.setChanged(); +- this.player.level().sendBlockUpdated(blockpos, blockstate, blockstate, 3); ++ tileentitystructure.setChanged(); ++ this.player.level().sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); + } + + } + } + + @Override +- @Override +- public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket serverboundsetjigsawblockpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetjigsawblockpacket, this, this.player.serverLevel()); ++ public void handleSetJigsawBlock(ServerboundSetJigsawBlockPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.canUseGameMasterBlocks()) { +- BlockPos blockpos = serverboundsetjigsawblockpacket.getPos(); +- BlockState blockstate = this.player.level().getBlockState(blockpos); +- BlockEntity blockentity = this.player.level().getBlockEntity(blockpos); ++ BlockPos blockposition = packet.getPos(); ++ IBlockData iblockdata = this.player.level().getBlockState(blockposition); ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); + +- if (blockentity instanceof JigsawBlockEntity) { +- JigsawBlockEntity jigsawblockentity = (JigsawBlockEntity) blockentity; ++ if (tileentity instanceof JigsawBlockEntity) { ++ JigsawBlockEntity tileentityjigsaw = (JigsawBlockEntity) tileentity; + +- jigsawblockentity.setName(serverboundsetjigsawblockpacket.getName()); +- jigsawblockentity.setTarget(serverboundsetjigsawblockpacket.getTarget()); +- jigsawblockentity.setPool(ResourceKey.create(Registries.TEMPLATE_POOL, serverboundsetjigsawblockpacket.getPool())); +- jigsawblockentity.setFinalState(serverboundsetjigsawblockpacket.getFinalState()); +- jigsawblockentity.setJoint(serverboundsetjigsawblockpacket.getJoint()); +- jigsawblockentity.setPlacementPriority(serverboundsetjigsawblockpacket.getPlacementPriority()); +- jigsawblockentity.setSelectionPriority(serverboundsetjigsawblockpacket.getSelectionPriority()); +- jigsawblockentity.setChanged(); +- this.player.level().sendBlockUpdated(blockpos, blockstate, blockstate, 3); ++ tileentityjigsaw.setName(packet.getName()); ++ tileentityjigsaw.setTarget(packet.getTarget()); ++ tileentityjigsaw.setPool(ResourceKey.create(Registries.TEMPLATE_POOL, packet.getPool())); ++ tileentityjigsaw.setFinalState(packet.getFinalState()); ++ tileentityjigsaw.setJoint(packet.getJoint()); ++ tileentityjigsaw.setPlacementPriority(packet.getPlacementPriority()); ++ tileentityjigsaw.setSelectionPriority(packet.getSelectionPriority()); ++ tileentityjigsaw.setChanged(); ++ this.player.level().sendBlockUpdated(blockposition, iblockdata, iblockdata, 3); + } + + } + } + + @Override +- @Override +- public void handleJigsawGenerate(ServerboundJigsawGeneratePacket serverboundjigsawgeneratepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundjigsawgeneratepacket, this, this.player.serverLevel()); ++ public void handleJigsawGenerate(ServerboundJigsawGeneratePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.canUseGameMasterBlocks()) { +- BlockPos blockpos = serverboundjigsawgeneratepacket.getPos(); +- BlockEntity blockentity = this.player.level().getBlockEntity(blockpos); ++ BlockPos blockposition = packet.getPos(); ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); + +- if (blockentity instanceof JigsawBlockEntity) { +- JigsawBlockEntity jigsawblockentity = (JigsawBlockEntity) blockentity; ++ if (tileentity instanceof JigsawBlockEntity) { ++ JigsawBlockEntity tileentityjigsaw = (JigsawBlockEntity) tileentity; + +- jigsawblockentity.generate(this.player.serverLevel(), serverboundjigsawgeneratepacket.levels(), serverboundjigsawgeneratepacket.keepJigsaws()); ++ tileentityjigsaw.generate(this.player.serverLevel(), packet.levels(), packet.keepJigsaws()); + } + + } + } + + @Override +- @Override +- public void handleSelectTrade(ServerboundSelectTradePacket serverboundselecttradepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundselecttradepacket, this, this.player.serverLevel()); +- int i = serverboundselecttradepacket.getItem(); +- AbstractContainerMenu abstractcontainermenu = this.player.containerMenu; ++ public void handleSelectTrade(ServerboundSelectTradePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ int i = packet.getItem(); ++ AbstractContainerMenu container = this.player.containerMenu; + +- if (abstractcontainermenu instanceof MerchantMenu) { +- MerchantMenu merchantmenu = (MerchantMenu) abstractcontainermenu; ++ if (container instanceof MerchantMenu) { ++ MerchantMenu containermerchant = (MerchantMenu) container; ++ // CraftBukkit start ++ final org.bukkit.event.inventory.TradeSelectEvent tradeSelectEvent = CraftEventFactory.callTradeSelectEvent(this.player, i, containermerchant); ++ if (tradeSelectEvent.isCancelled()) { ++ this.player.getBukkitEntity().updateInventory(); ++ return; ++ } ++ // CraftBukkit end + +- if (!merchantmenu.stillValid(this.player)) { +- ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantmenu); ++ if (!containermerchant.stillValid(this.player)) { ++ ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, containermerchant); + return; + } + +- merchantmenu.setSelectionHint(i); +- merchantmenu.tryMoveItems(i); ++ containermerchant.setSelectionHint(i); ++ containermerchant.tryMoveItems(i); + } + + } + + @Override +- @Override +- public void handleEditBook(ServerboundEditBookPacket serverboundeditbookpacket) { +- int i = serverboundeditbookpacket.getSlot(); ++ public void handleEditBook(ServerboundEditBookPacket packet) { ++ // CraftBukkit start ++ if (this.lastBookTick + 20 > MinecraftServer.currentTick) { ++ this.disconnect("Book edited too quickly!"); ++ return; ++ } ++ this.lastBookTick = MinecraftServer.currentTick; ++ // CraftBukkit end ++ int i = packet.getSlot(); + + if (Inventory.isHotbarSlot(i) || i == 40) { + List<String> list = Lists.newArrayList(); +- Optional<String> optional = serverboundeditbookpacket.getTitle(); ++ Optional<String> optional = packet.getTitle(); + + Objects.requireNonNull(list); + optional.ifPresent(list::add); +- Stream stream = serverboundeditbookpacket.getPages().stream().limit(100L); ++ Stream<String> stream = packet.getPages().stream().limit(100L); // CraftBukkit - decompile error + + Objects.requireNonNull(list); + stream.forEach(list::add); +@@ -824,101 +992,100 @@ + } + } + +- private void updateBookContents(List<FilteredText> list, int i) { +- ItemStack itemstack = this.player.getInventory().getItem(i); ++ private void updateBookContents(List<FilteredText> pages, int index) { ++ ItemStack itemstack = this.player.getInventory().getItem(index); + + if (itemstack.is(Items.WRITABLE_BOOK)) { +- this.updateBookPages(list, UnaryOperator.identity(), itemstack); ++ this.updateBookPages(pages, UnaryOperator.identity(), itemstack.copy(), index, itemstack); // CraftBukkit + } + } + +- private void signBook(FilteredText filteredtext, List<FilteredText> list, int i) { +- ItemStack itemstack = this.player.getInventory().getItem(i); ++ private void signBook(FilteredText title, List<FilteredText> pages, int index) { ++ ItemStack itemstack = this.player.getInventory().getItem(index); + + if (itemstack.is(Items.WRITABLE_BOOK)) { + ItemStack itemstack1 = new ItemStack(Items.WRITTEN_BOOK); +- CompoundTag compoundtag = itemstack.getTag(); ++ CompoundTag nbttagcompound = itemstack.getTag(); + +- if (compoundtag != null) { +- itemstack1.setTag(compoundtag.copy()); ++ if (nbttagcompound != null) { ++ itemstack1.setTag(nbttagcompound.copy()); + } + + itemstack1.addTagElement("author", StringTag.valueOf(this.player.getName().getString())); + if (this.player.isTextFilteringEnabled()) { +- itemstack1.addTagElement("title", StringTag.valueOf(filteredtext.filteredOrEmpty())); ++ itemstack1.addTagElement("title", StringTag.valueOf(title.filteredOrEmpty())); + } else { +- itemstack1.addTagElement("filtered_title", StringTag.valueOf(filteredtext.filteredOrEmpty())); +- itemstack1.addTagElement("title", StringTag.valueOf(filteredtext.raw())); ++ itemstack1.addTagElement("filtered_title", StringTag.valueOf(title.filteredOrEmpty())); ++ itemstack1.addTagElement("title", StringTag.valueOf(title.raw())); + } + +- this.updateBookPages(list, (s) -> { ++ this.updateBookPages(pages, (s) -> { + return Component.Serializer.toJson(Component.literal(s)); +- }, itemstack1); +- this.player.getInventory().setItem(i, itemstack1); ++ }, itemstack1, index, itemstack); // CraftBukkit ++ this.player.getInventory().setItem(index, itemstack); // CraftBukkit - event factory updates the hand book + } + } + +- private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack) { +- ListTag listtag = new ListTag(); ++ private void updateBookPages(List<FilteredText> list, UnaryOperator<String> unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit ++ ListTag nbttaglist = new ListTag(); + + if (this.player.isTextFilteringEnabled()) { +- Stream stream = list.stream().map((filteredtext) -> { ++ Stream<StringTag> stream = list.stream().map((filteredtext) -> { // CraftBukkit - decompile error + return StringTag.valueOf((String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + }); + +- Objects.requireNonNull(listtag); +- stream.forEach(listtag::add); ++ Objects.requireNonNull(nbttaglist); ++ stream.forEach(nbttaglist::add); + } else { +- CompoundTag compoundtag = new CompoundTag(); ++ CompoundTag nbttagcompound = new CompoundTag(); + int i = 0; + + for (int j = list.size(); i < j; ++i) { + FilteredText filteredtext = (FilteredText) list.get(i); + String s = filteredtext.raw(); + +- listtag.add(StringTag.valueOf((String) unaryoperator.apply(s))); ++ nbttaglist.add(StringTag.valueOf((String) unaryoperator.apply(s))); + if (filteredtext.isFiltered()) { +- compoundtag.putString(String.valueOf(i), (String) unaryoperator.apply(filteredtext.filteredOrEmpty())); ++ nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply(filteredtext.filteredOrEmpty())); + } + } + +- if (!compoundtag.isEmpty()) { +- itemstack.addTagElement("filtered_pages", compoundtag); ++ if (!nbttagcompound.isEmpty()) { ++ itemstack.addTagElement("filtered_pages", nbttagcompound); + } + } + +- itemstack.addTagElement("pages", listtag); ++ itemstack.addTagElement("pages", nbttaglist); ++ CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit + } + + @Override +- @Override +- public void handleEntityTagQuery(ServerboundEntityTagQuery serverboundentitytagquery) { +- PacketUtils.ensureRunningOnSameThread(serverboundentitytagquery, this, this.player.serverLevel()); ++ public void handleEntityTagQuery(ServerboundEntityTagQuery packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2)) { +- Entity entity = this.player.level().getEntity(serverboundentitytagquery.getEntityId()); ++ Entity entity = this.player.level().getEntity(packet.getEntityId()); + + if (entity != null) { +- CompoundTag compoundtag = entity.saveWithoutId(new CompoundTag()); ++ CompoundTag nbttagcompound = entity.saveWithoutId(new CompoundTag()); + +- this.player.connection.send(new ClientboundTagQueryPacket(serverboundentitytagquery.getTransactionId(), compoundtag)); ++ this.player.connection.send(new ClientboundTagQueryPacket(packet.getTransactionId(), nbttagcompound)); + } + + } + } + + @Override +- @Override + public void handleContainerSlotStateChanged(ServerboundContainerSlotStateChangedPacket serverboundcontainerslotstatechangedpacket) { + PacketUtils.ensureRunningOnSameThread(serverboundcontainerslotstatechangedpacket, this, this.player.serverLevel()); + if (!this.player.isSpectator() && serverboundcontainerslotstatechangedpacket.containerId() == this.player.containerMenu.containerId) { +- AbstractContainerMenu abstractcontainermenu = this.player.containerMenu; ++ AbstractContainerMenu container = this.player.containerMenu; + +- if (abstractcontainermenu instanceof CrafterMenu) { +- CrafterMenu craftermenu = (CrafterMenu) abstractcontainermenu; +- Container container = craftermenu.getContainer(); ++ if (container instanceof CrafterMenu) { ++ CrafterMenu craftermenu = (CrafterMenu) container; ++ Container iinventory = craftermenu.getContainer(); + +- if (container instanceof CrafterBlockEntity) { +- CrafterBlockEntity crafterblockentity = (CrafterBlockEntity) container; ++ if (iinventory instanceof CrafterBlockEntity) { ++ CrafterBlockEntity crafterblockentity = (CrafterBlockEntity) iinventory; + + crafterblockentity.setSlotState(serverboundcontainerslotstatechangedpacket.slotId(), serverboundcontainerslotstatechangedpacket.newState()); + } +@@ -928,27 +1095,25 @@ + } + + @Override +- @Override +- public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQuery serverboundblockentitytagquery) { +- PacketUtils.ensureRunningOnSameThread(serverboundblockentitytagquery, this, this.player.serverLevel()); ++ public void handleBlockEntityTagQuery(ServerboundBlockEntityTagQuery packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2)) { +- BlockEntity blockentity = this.player.level().getBlockEntity(serverboundblockentitytagquery.getPos()); +- CompoundTag compoundtag = blockentity != null ? blockentity.saveWithoutMetadata() : null; ++ BlockEntity tileentity = this.player.level().getBlockEntity(packet.getPos()); ++ CompoundTag nbttagcompound = tileentity != null ? tileentity.saveWithoutMetadata() : null; + +- this.player.connection.send(new ClientboundTagQueryPacket(serverboundblockentitytagquery.getTransactionId(), compoundtag)); ++ this.player.connection.send(new ClientboundTagQueryPacket(packet.getTransactionId(), nbttagcompound)); + } + } + + @Override +- @Override +- public void handleMovePlayer(ServerboundMovePlayerPacket serverboundmoveplayerpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundmoveplayerpacket, this, this.player.serverLevel()); +- if (containsInvalidValues(serverboundmoveplayerpacket.getX(0.0D), serverboundmoveplayerpacket.getY(0.0D), serverboundmoveplayerpacket.getZ(0.0D), serverboundmoveplayerpacket.getYRot(0.0F), serverboundmoveplayerpacket.getXRot(0.0F))) { ++ public void handleMovePlayer(ServerboundMovePlayerPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (containsInvalidValues(packet.getX(0.0D), packet.getY(0.0D), packet.getZ(0.0D), packet.getYRot(0.0F), packet.getXRot(0.0F))) { + this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement")); + } else { +- ServerLevel serverlevel = this.player.serverLevel(); ++ ServerLevel worldserver = this.player.serverLevel(); + +- if (!this.player.wonGame) { ++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit + if (this.tickCount == 0) { + this.resetPosition(); + } +@@ -958,19 +1123,27 @@ + this.awaitingTeleportTime = this.tickCount; + this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); + } +- ++ this.allowedPlayerTicks = 20; // CraftBukkit + } else { + this.awaitingTeleportTime = this.tickCount; +- double d0 = clampHorizontal(serverboundmoveplayerpacket.getX(this.player.getX())); +- double d1 = clampVertical(serverboundmoveplayerpacket.getY(this.player.getY())); +- double d2 = clampHorizontal(serverboundmoveplayerpacket.getZ(this.player.getZ())); +- float f = Mth.wrapDegrees(serverboundmoveplayerpacket.getYRot(this.player.getYRot())); +- float f1 = Mth.wrapDegrees(serverboundmoveplayerpacket.getXRot(this.player.getXRot())); ++ double d0 = 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())); + + if (this.player.isPassenger()) { + this.player.absMoveTo(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1); + this.player.serverLevel().getChunkSource().move(this.player); ++ this.allowedPlayerTicks = 20; // CraftBukkit + } else { ++ // CraftBukkit - Make sure the move is valid but then reset it for plugins to modify ++ double prevX = player.getX(); ++ double prevY = player.getY(); ++ double prevZ = player.getZ(); ++ float prevYaw = player.getYRot(); ++ float prevPitch = player.getXRot(); ++ // CraftBukkit end + double d3 = this.player.getX(); + double d4 = this.player.getY(); + double d5 = this.player.getZ(); +@@ -986,19 +1159,37 @@ + } + + } else { +- if (serverlevel.tickRateManager().runsNormally()) { ++ if (worldserver.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); + i = 1; + } + ++ if (packet.hasRot || d10 > 0) { ++ allowedPlayerTicks -= 1; ++ } else { ++ allowedPlayerTicks = 20; ++ } ++ double speed; ++ if (player.getAbilities().flying) { ++ speed = player.getAbilities().flyingSpeed * 20f; ++ } else { ++ speed = player.getAbilities().walkingSpeed * 10f; ++ } ++ + if (!this.player.isChangingDimension() && (!this.player.level().getGameRules().getBoolean(GameRules.RULE_DISABLE_ELYTRA_MOVEMENT_CHECK) || !this.player.isFallFlying())) { + float f2 = this.player.isFallFlying() ? 300.0F : 100.0F; + +- if (d10 - d9 > (double) (f2 * (float) i) && !this.isSingleplayerOwner()) { ++ if (d10 - d9 > Math.max(f2, Math.pow((double) (10.0F * (float) i * speed), 2)) && !this.isSingleplayerOwner()) { ++ // CraftBukkit end + ServerGamePacketListenerImpl.LOGGER.warn("{} moved too quickly! {},{},{}", new Object[]{this.player.getName().getString(), d6, d7, d8}); + this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), this.player.getYRot(), this.player.getXRot()); + return; +@@ -1006,20 +1197,21 @@ + } + } + +- AABB aabb = this.player.getBoundingBox(); ++ AABB axisalignedbb = this.player.getBoundingBox(); + + d6 = d0 - this.lastGoodX; + d7 = d1 - this.lastGoodY; + d8 = d2 - this.lastGoodZ; + boolean flag = d7 > 0.0D; + +- if (this.player.onGround() && !serverboundmoveplayerpacket.isOnGround() && flag) { ++ if (this.player.onGround() && !packet.isOnGround() && flag) { + this.player.jumpFromGround(); + } + + boolean flag1 = this.player.verticalCollisionBelow; + +- this.player.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); ++ this.player.move(EnumMoveType.PLAYER, new Vec3(d6, d7, d8)); ++ this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move + double d11 = d7; + + d6 = d0 - this.player.getX(); +@@ -1037,15 +1229,76 @@ + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); + } + +- if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && serverlevel.noCollision(this.player, aabb) || this.isPlayerCollidingWithAnythingNew(serverlevel, aabb, d0, d1, d2))) { +- this.teleport(d3, d4, d5, f, f1); +- this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, serverboundmoveplayerpacket.isOnGround()); ++ if (!this.player.noPhysics && !this.player.isSleeping() && (flag2 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew(worldserver, axisalignedbb, d0, d1, d2))) { ++ this.internalTeleport(d3, d4, d5, f, f1, Collections.emptySet()); // CraftBukkit - SPIGOT-1807: Don't call teleport event, when the client thinks the player is falling, because the chunks are not loaded on the client yet. ++ this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); + } else { ++ // CraftBukkit start - fire PlayerMoveEvent ++ // Reset to old location first ++ this.player.absMoveTo(prevX, prevY, prevZ, prevYaw, prevPitch); ++ ++ Player player = this.getCraftPlayer(); ++ Location from = new Location(player.getWorld(), lastPosX, lastPosY, lastPosZ, lastYaw, lastPitch); // Get the Players previous Event location. ++ Location to = player.getLocation().clone(); // Start off the To location as the Players current location. ++ ++ // If the packet contains movement information then we update the To location with the correct XYZ. ++ if (packet.hasPos) { ++ to.setX(packet.x); ++ to.setY(packet.y); ++ to.setZ(packet.z); ++ } ++ ++ // If the packet contains look information then we update the To location with the correct Yaw & Pitch. ++ if (packet.hasRot) { ++ to.setYaw(packet.yRot); ++ to.setPitch(packet.xRot); ++ } ++ ++ // Prevent 40 event-calls for less than a single pixel of movement >.> ++ double delta = Math.pow(this.lastPosX - to.getX(), 2) + Math.pow(this.lastPosY - to.getY(), 2) + Math.pow(this.lastPosZ - to.getZ(), 2); ++ float deltaAngle = Math.abs(this.lastYaw - to.getYaw()) + Math.abs(this.lastPitch - to.getPitch()); ++ ++ if ((delta > 1f / 256 || deltaAngle > 10f) && !this.player.isImmobile()) { ++ this.lastPosX = to.getX(); ++ this.lastPosY = to.getY(); ++ this.lastPosZ = to.getZ(); ++ this.lastYaw = to.getYaw(); ++ this.lastPitch = to.getPitch(); ++ ++ // Skip the first time we do this ++ if (from.getX() != Double.MAX_VALUE) { ++ Location oldTo = to.clone(); ++ PlayerMoveEvent event = new PlayerMoveEvent(player, from, to); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ // If the event is cancelled we move the player back to their old location. ++ if (event.isCancelled()) { ++ teleport(from); ++ return; ++ } ++ ++ // If a Plugin has changed the To destination then we teleport the Player ++ // there to avoid any 'Moved wrongly' or 'Moved too quickly' errors. ++ // We only do this if the Event was not cancelled. ++ if (!oldTo.equals(event.getTo()) && !event.isCancelled()) { ++ this.player.getBukkitEntity().teleport(event.getTo(), PlayerTeleportEvent.TeleportCause.PLUGIN); ++ return; ++ } ++ ++ // Check to see if the Players Location has some how changed during the call of the event. ++ // This can happen due to a plugin teleporting the player instead of using .setTo() ++ if (!from.equals(this.getCraftPlayer().getLocation()) && this.justTeleported) { ++ this.justTeleported = false; ++ return; ++ } ++ } ++ } ++ // CraftBukkit end + this.player.absMoveTo(d0, d1, d2, f, f1); + this.clientIsFloating = d11 >= -0.03125D && !flag1 && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR && !this.server.isFlightAllowed() && !this.player.getAbilities().mayfly && !this.player.hasEffect(MobEffects.LEVITATION) && !this.player.isFallFlying() && !this.player.isAutoSpinAttack() && this.noBlocksAround(this.player); + this.player.serverLevel().getChunkSource().move(this.player); +- this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, serverboundmoveplayerpacket.isOnGround()); +- this.player.setOnGroundWithKnownMovement(serverboundmoveplayerpacket.isOnGround(), new Vec3(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5)); ++ this.player.doCheckFallDamage(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5, packet.isOnGround()); ++ this.player.setOnGroundWithKnownMovement(packet.isOnGround(), new Vec3(this.player.getX() - d3, this.player.getY() - d4, this.player.getZ() - d5)); + if (flag) { + this.player.resetFallDistance(); + } +@@ -1062,10 +1315,10 @@ + } + } + +- private boolean isPlayerCollidingWithAnythingNew(LevelReader levelreader, AABB aabb, double d0, double d1, double d2) { +- AABB aabb1 = this.player.getBoundingBox().move(d0 - this.player.getX(), d1 - this.player.getY(), d2 - this.player.getZ()); +- Iterable<VoxelShape> iterable = levelreader.getCollisions(this.player, aabb1.deflate(9.999999747378752E-6D)); +- VoxelShape voxelshape = Shapes.create(aabb.deflate(9.999999747378752E-6D)); ++ private boolean isPlayerCollidingWithAnythingNew(LevelReader level, AABB box, double x, double d1, double y) { ++ AABB axisalignedbb1 = this.player.getBoundingBox().move(x - this.player.getX(), d1 - this.player.getY(), y - this.player.getZ()); ++ Iterable<VoxelShape> iterable = level.getCollisions(this.player, axisalignedbb1.deflate(9.999999747378752E-6D)); ++ VoxelShape voxelshape = Shapes.create(box.deflate(9.999999747378752E-6D)); + Iterator iterator = iterable.iterator(); + + VoxelShape voxelshape1; +@@ -1081,11 +1334,68 @@ + return true; + } + +- public void teleport(double d0, double d1, double d2, float f, float f1) { +- this.teleport(d0, d1, d2, f, f1, Collections.emptySet()); ++ // CraftBukkit start - Delegate to teleport(Location) ++ public void teleport(double x, double d1, double y, float f, float z) { ++ this.teleport(x, d1, y, f, z, PlayerTeleportEvent.TeleportCause.UNKNOWN); + } + +- public void teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) { ++ public void teleport(double d0, double d1, double d2, float f, float f1, PlayerTeleportEvent.TeleportCause cause) { ++ this.teleport(d0, d1, d2, f, f1, Collections.emptySet(), cause); ++ } ++ ++ public void teleport(double x, double d1, double y, float f, float z, Set<RelativeMovement> set) { ++ this.teleport(x, d1, y, f, z, set, PlayerTeleportEvent.TeleportCause.UNKNOWN); ++ } ++ ++ public boolean teleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set, PlayerTeleportEvent.TeleportCause cause) { // CraftBukkit - Return event status ++ Player player = this.getCraftPlayer(); ++ Location from = player.getLocation(); ++ ++ double x = d0; ++ double y = d1; ++ double z = d2; ++ float yaw = f; ++ float pitch = f1; ++ ++ Location to = new Location(this.getCraftPlayer().getWorld(), x, y, z, yaw, pitch); ++ // SPIGOT-5171: Triggered on join ++ if (from.equals(to)) { ++ this.internalTeleport(d0, d1, d2, f, f1, set); ++ return false; // CraftBukkit - Return event status ++ } ++ ++ PlayerTeleportEvent event = new PlayerTeleportEvent(player, from.clone(), to.clone(), cause); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled() || !to.equals(event.getTo())) { ++ set.clear(); // Can't relative teleport ++ to = event.isCancelled() ? event.getFrom() : event.getTo(); ++ d0 = to.getX(); ++ d1 = to.getY(); ++ d2 = to.getZ(); ++ f = to.getYaw(); ++ f1 = to.getPitch(); ++ } ++ ++ this.internalTeleport(d0, d1, d2, f, f1, set); ++ return event.isCancelled(); // CraftBukkit - Return event status ++ } ++ ++ public void teleport(Location dest) { ++ internalTeleport(dest.getX(), dest.getY(), dest.getZ(), dest.getYaw(), dest.getPitch(), Collections.emptySet()); ++ } ++ ++ private void internalTeleport(double d0, double d1, double d2, float f, float f1, Set<RelativeMovement> set) { ++ // CraftBukkit start ++ if (Float.isNaN(f)) { ++ f = 0; ++ } ++ if (Float.isNaN(f1)) { ++ f1 = 0; ++ } ++ ++ this.justTeleported = true; ++ // CraftBukkit end + double d3 = set.contains(RelativeMovement.X) ? this.player.getX() : 0.0D; + double d4 = set.contains(RelativeMovement.Y) ? this.player.getY() : 0.0D; + double d5 = set.contains(RelativeMovement.Z) ? this.player.getZ() : 0.0D; +@@ -1097,33 +1407,73 @@ + this.awaitingTeleport = 0; + } + ++ // CraftBukkit start - update last location ++ this.lastPosX = this.awaitingPositionFromClient.x; ++ this.lastPosY = this.awaitingPositionFromClient.y; ++ this.lastPosZ = this.awaitingPositionFromClient.z; ++ this.lastYaw = f; ++ this.lastPitch = f1; ++ // CraftBukkit end ++ + this.awaitingTeleportTime = this.tickCount; + this.player.absMoveTo(d0, d1, d2, f, f1); + this.player.connection.send(new ClientboundPlayerPositionPacket(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.awaitingTeleport)); + } + + @Override +- @Override +- public void handlePlayerAction(ServerboundPlayerActionPacket serverboundplayeractionpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundplayeractionpacket, this, this.player.serverLevel()); +- BlockPos blockpos = serverboundplayeractionpacket.getPos(); ++ public void handlePlayerAction(ServerboundPlayerActionPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ BlockPos blockposition = packet.getPos(); + + this.player.resetLastActionTime(); +- ServerboundPlayerActionPacket.Action serverboundplayeractionpacket_action = serverboundplayeractionpacket.getAction(); ++ ServerboundPlayerActionPacket.EnumPlayerDigType packetplayinblockdig_enumplayerdigtype = packet.getAction(); + +- switch (serverboundplayeractionpacket_action) { ++ switch (packetplayinblockdig_enumplayerdigtype) { + case SWAP_ITEM_WITH_OFFHAND: + if (!this.player.isSpectator()) { +- ItemStack itemstack = this.player.getItemInHand(InteractionHand.OFF_HAND); ++ ItemStack itemstack = this.player.getItemInHand(EnumHand.OFF_HAND); + +- this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND)); +- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemstack); ++ // CraftBukkit start - inspiration taken from DispenserRegistry (See SpigotCraft#394) ++ CraftItemStack mainHand = CraftItemStack.asCraftMirror(itemstack); ++ CraftItemStack offHand = CraftItemStack.asCraftMirror(this.player.getItemInHand(EnumHand.MAIN_HAND)); ++ PlayerSwapHandItemsEvent swapItemsEvent = new PlayerSwapHandItemsEvent(getCraftPlayer(), mainHand.clone(), offHand.clone()); ++ this.cserver.getPluginManager().callEvent(swapItemsEvent); ++ if (swapItemsEvent.isCancelled()) { ++ return; ++ } ++ if (swapItemsEvent.getOffHandItem().equals(offHand)) { ++ this.player.setItemInHand(EnumHand.OFF_HAND, this.player.getItemInHand(EnumHand.MAIN_HAND)); ++ } else { ++ this.player.setItemInHand(EnumHand.OFF_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getOffHandItem())); ++ } ++ if (swapItemsEvent.getMainHandItem().equals(mainHand)) { ++ this.player.setItemInHand(EnumHand.MAIN_HAND, itemstack); ++ } else { ++ this.player.setItemInHand(EnumHand.MAIN_HAND, CraftItemStack.asNMSCopy(swapItemsEvent.getMainHandItem())); ++ } ++ // CraftBukkit end + this.player.stopUsingItem(); + } + + return; + case DROP_ITEM: + if (!this.player.isSpectator()) { ++ // limit how quickly items can be dropped ++ // If the ticks aren't the same then the count starts from 0 and we update the lastDropTick. ++ if (this.lastDropTick != MinecraftServer.currentTick) { ++ this.dropCount = 0; ++ this.lastDropTick = MinecraftServer.currentTick; ++ } else { ++ // Else we increment the drop count and check the amount. ++ this.dropCount++; ++ if (this.dropCount >= 20) { ++ LOGGER.warn(this.player.getScoreboardName() + " dropped their items too quickly!"); ++ this.disconnect("You dropped your items too quickly (Hacking?)"); ++ return; ++ } ++ } ++ // CraftBukkit end + this.player.drop(false); + } + +@@ -1140,109 +1490,152 @@ + case START_DESTROY_BLOCK: + case ABORT_DESTROY_BLOCK: + case STOP_DESTROY_BLOCK: +- this.player.gameMode.handleBlockBreakAction(blockpos, serverboundplayeractionpacket_action, serverboundplayeractionpacket.getDirection(), this.player.level().getMaxBuildHeight(), serverboundplayeractionpacket.getSequence()); +- this.player.connection.ackBlockChangesUpTo(serverboundplayeractionpacket.getSequence()); ++ this.player.gameMode.handleBlockBreakAction(blockposition, packetplayinblockdig_enumplayerdigtype, packet.getDirection(), this.player.level().getMaxBuildHeight(), packet.getSequence()); ++ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); + return; + default: + throw new IllegalArgumentException("Invalid player action"); + } + } + +- private static boolean wasBlockPlacementAttempt(ServerPlayer serverplayer, ItemStack itemstack) { +- if (itemstack.isEmpty()) { ++ private static boolean wasBlockPlacementAttempt(ServerPlayer player, ItemStack stack) { ++ if (stack.isEmpty()) { + return false; + } else { +- Item item = itemstack.getItem(); ++ Item item = stack.getItem(); + +- return (item instanceof BlockItem || item instanceof BucketItem) && !serverplayer.getCooldowns().isOnCooldown(item); ++ return (item instanceof BlockItem || item instanceof BucketItem) && !player.getCooldowns().isOnCooldown(item); + } + } + + @Override +- @Override +- public void handleUseItemOn(ServerboundUseItemOnPacket serverbounduseitemonpacket) { +- PacketUtils.ensureRunningOnSameThread(serverbounduseitemonpacket, this, this.player.serverLevel()); +- this.player.connection.ackBlockChangesUpTo(serverbounduseitemonpacket.getSequence()); +- ServerLevel serverlevel = this.player.serverLevel(); +- InteractionHand interactionhand = serverbounduseitemonpacket.getHand(); +- ItemStack itemstack = this.player.getItemInHand(interactionhand); ++ public void handleUseItemOn(ServerboundUseItemOnPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ this.player.connection.ackBlockChangesUpTo(packet.getSequence()); ++ ServerLevel worldserver = this.player.serverLevel(); ++ EnumHand enumhand = packet.getHand(); ++ ItemStack itemstack = this.player.getItemInHand(enumhand); + +- if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) { +- BlockHitResult blockhitresult = serverbounduseitemonpacket.getHitResult(); +- Vec3 vec3 = blockhitresult.getLocation(); +- BlockPos blockpos = blockhitresult.getBlockPos(); +- Vec3 vec31 = Vec3.atCenterOf(blockpos); ++ if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { ++ BlockHitResult movingobjectpositionblock = packet.getHitResult(); ++ Vec3 vec3d = movingobjectpositionblock.getLocation(); ++ BlockPos blockposition = movingobjectpositionblock.getBlockPos(); ++ Vec3 vec3d1 = Vec3.atCenterOf(blockposition); + +- if (this.player.getEyePosition().distanceToSqr(vec31) <= ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { +- Vec3 vec32 = vec3.subtract(vec31); ++ if (this.player.getEyePosition().distanceToSqr(vec3d1) <= ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { ++ Vec3 vec3d2 = vec3d.subtract(vec3d1); + double d0 = 1.0000001D; + +- if (Math.abs(vec32.x()) < 1.0000001D && Math.abs(vec32.y()) < 1.0000001D && Math.abs(vec32.z()) < 1.0000001D) { +- Direction direction = blockhitresult.getDirection(); ++ if (Math.abs(vec3d2.x()) < 1.0000001D && Math.abs(vec3d2.y()) < 1.0000001D && Math.abs(vec3d2.z()) < 1.0000001D) { ++ Direction enumdirection = movingobjectpositionblock.getDirection(); + + this.player.resetLastActionTime(); + int i = this.player.level().getMaxBuildHeight(); + +- if (blockpos.getY() < i) { +- if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockpos.getX() + 0.5D, (double) blockpos.getY() + 0.5D, (double) blockpos.getZ() + 0.5D) < 64.0D && serverlevel.mayInteract(this.player, blockpos)) { +- InteractionResult interactionresult = this.player.gameMode.useItemOn(this.player, serverlevel, itemstack, interactionhand, blockhitresult); ++ if (blockposition.getY() < i) { ++ if (this.awaitingPositionFromClient == null && this.player.distanceToSqr((double) blockposition.getX() + 0.5D, (double) blockposition.getY() + 0.5D, (double) blockposition.getZ() + 0.5D) < 64.0D && worldserver.mayInteract(this.player, blockposition)) { ++ this.player.stopUsingItem(); // CraftBukkit - SPIGOT-4706 ++ InteractionResult enuminteractionresult = this.player.gameMode.useItemOn(this.player, worldserver, itemstack, enumhand, movingobjectpositionblock); + +- if (direction == Direction.UP && !interactionresult.consumesAction() && blockpos.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) { +- MutableComponent mutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); ++ if (enumdirection == Direction.UP && !enuminteractionresult.consumesAction() && blockposition.getY() >= i - 1 && wasBlockPlacementAttempt(this.player, itemstack)) { ++ MutableComponent ichatmutablecomponent = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); + +- this.player.sendSystemMessage(mutablecomponent, true); +- } else if (interactionresult.shouldSwing()) { +- this.player.swing(interactionhand, true); ++ this.player.sendSystemMessage(ichatmutablecomponent, true); ++ } else if (enuminteractionresult.shouldSwing()) { ++ this.player.swing(enumhand, true); + } + } + } else { +- MutableComponent mutablecomponent1 = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); ++ MutableComponent ichatmutablecomponent1 = Component.translatable("build.tooHigh", i - 1).withStyle(ChatFormatting.RED); + +- this.player.sendSystemMessage(mutablecomponent1, true); ++ this.player.sendSystemMessage(ichatmutablecomponent1, true); + } + +- this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos)); +- this.player.connection.send(new ClientboundBlockUpdatePacket(serverlevel, blockpos.relative(direction))); ++ this.player.connection.send(new ClientboundBlockUpdatePacket(worldserver, blockposition)); ++ this.player.connection.send(new ClientboundBlockUpdatePacket(worldserver, blockposition.relative(enumdirection))); + } else { +- ServerGamePacketListenerImpl.LOGGER.warn("Rejecting UseItemOnPacket from {}: Location {} too far away from hit block {}.", new Object[]{this.player.getGameProfile().getName(), vec3, blockpos}); ++ ServerGamePacketListenerImpl.LOGGER.warn("Rejecting UseItemOnPacket from {}: Location {} too far away from hit block {}.", new Object[]{this.player.getGameProfile().getName(), vec3d, blockposition}); + } + } + } + } + + @Override +- @Override +- public void handleUseItem(ServerboundUseItemPacket serverbounduseitempacket) { +- PacketUtils.ensureRunningOnSameThread(serverbounduseitempacket, this, this.player.serverLevel()); +- this.ackBlockChangesUpTo(serverbounduseitempacket.getSequence()); +- ServerLevel serverlevel = this.player.serverLevel(); +- InteractionHand interactionhand = serverbounduseitempacket.getHand(); +- ItemStack itemstack = this.player.getItemInHand(interactionhand); ++ public void handleUseItem(ServerboundUseItemPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ this.ackBlockChangesUpTo(packet.getSequence()); ++ ServerLevel worldserver = this.player.serverLevel(); ++ EnumHand enumhand = packet.getHand(); ++ ItemStack itemstack = this.player.getItemInHand(enumhand); + + this.player.resetLastActionTime(); +- if (!itemstack.isEmpty() && itemstack.isItemEnabled(serverlevel.enabledFeatures())) { +- InteractionResult interactionresult = this.player.gameMode.useItem(this.player, serverlevel, itemstack, interactionhand); ++ if (!itemstack.isEmpty() && itemstack.isItemEnabled(worldserver.enabledFeatures())) { ++ // CraftBukkit start ++ // Raytrace to look for 'rogue armswings' ++ float f1 = this.player.getXRot(); ++ float f2 = this.player.getYRot(); ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Vec3 vec3d = new Vec3(d0, d1, d2); + +- if (interactionresult.shouldSwing()) { +- this.player.swing(interactionhand, true); ++ float f3 = Mth.cos(-f2 * 0.017453292F - 3.1415927F); ++ float f4 = Mth.sin(-f2 * 0.017453292F - 3.1415927F); ++ float f5 = -Mth.cos(-f1 * 0.017453292F); ++ float f6 = Mth.sin(-f1 * 0.017453292F); ++ float f7 = f4 * f5; ++ float f8 = f3 * f5; ++ double d3 = player.gameMode.getGameModeForPlayer()== GameType.CREATIVE ? 5.0D : 4.5D; ++ 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, player)); ++ ++ boolean cancelled; ++ if (movingobjectposition == null || movingobjectposition.getType() != HitResult.EnumMovingObjectType.BLOCK) { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.RIGHT_CLICK_AIR, itemstack, enumhand); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } else { ++ BlockHitResult movingobjectpositionblock = (BlockHitResult) movingobjectposition; ++ if (player.gameMode.firedInteract && player.gameMode.interactPosition.equals(movingobjectpositionblock.getBlockPos()) && player.gameMode.interactHand == enumhand && ItemStack.isSameItemSameTags(player.gameMode.interactItemStack, itemstack)) { ++ cancelled = player.gameMode.interactResult; ++ } else { ++ org.bukkit.event.player.PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, movingobjectpositionblock.getBlockPos(), movingobjectpositionblock.getDirection(), itemstack, true, enumhand, movingobjectpositionblock.getLocation()); ++ cancelled = event.useItemInHand() == Event.Result.DENY; ++ } ++ player.gameMode.firedInteract = false; + } + ++ if (cancelled) { ++ this.player.getBukkitEntity().updateInventory(); // SPIGOT-2524 ++ return; ++ } ++ itemstack = this.player.getItemInHand(enumhand); // Update in case it was changed in the event ++ if (itemstack.isEmpty()) { ++ return; ++ } ++ // CraftBukkit end ++ InteractionResult enuminteractionresult = this.player.gameMode.useItem(this.player, worldserver, itemstack, enumhand); ++ ++ if (enuminteractionresult.shouldSwing()) { ++ this.player.swing(enumhand, true); ++ } ++ + } + } + + @Override +- @Override +- public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket serverboundteleporttoentitypacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundteleporttoentitypacket, this, this.player.serverLevel()); ++ public void handleTeleportToEntityPacket(ServerboundTeleportToEntityPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.isSpectator()) { + Iterator iterator = this.server.getAllLevels().iterator(); + + while (iterator.hasNext()) { +- ServerLevel serverlevel = (ServerLevel) iterator.next(); +- Entity entity = serverboundteleporttoentitypacket.getEntity(serverlevel); ++ ServerLevel worldserver = (ServerLevel) iterator.next(); ++ Entity entity = packet.getEntity(worldserver); + + if (entity != null) { +- this.player.teleportTo(serverlevel, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot()); ++ this.player.teleportTo(worldserver, entity.getX(), entity.getY(), entity.getZ(), entity.getYRot(), entity.getXRot(), org.bukkit.event.player.PlayerTeleportEvent.TeleportCause.SPECTATE); // CraftBukkit + return; + } + } +@@ -1251,104 +1644,136 @@ + } + + @Override +- @Override +- public void handlePaddleBoat(ServerboundPaddleBoatPacket serverboundpaddleboatpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundpaddleboatpacket, this, this.player.serverLevel()); ++ public void handlePaddleBoat(ServerboundPaddleBoatPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + Entity entity = this.player.getControlledVehicle(); + + if (entity instanceof Boat) { +- Boat boat = (Boat) entity; ++ Boat entityboat = (Boat) entity; + +- boat.setPaddleState(serverboundpaddleboatpacket.getLeft(), serverboundpaddleboatpacket.getRight()); ++ entityboat.setPaddleState(packet.getLeft(), packet.getRight()); + } + + } + + @Override +- @Override +- public void onDisconnect(Component component) { +- ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), component.getString()); ++ public void onDisconnect(Component reason) { ++ // CraftBukkit start - Rarely it would send a disconnect line twice ++ if (this.processedDisconnect) { ++ return; ++ } else { ++ this.processedDisconnect = true; ++ } ++ // CraftBukkit end ++ ServerGamePacketListenerImpl.LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString()); + this.removePlayerFromWorld(); +- super.onDisconnect(component); ++ super.onDisconnect(reason); + } + + private void removePlayerFromWorld() { + this.chatMessageChain.close(); ++ // 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.player.disconnect(); +- this.server.getPlayerList().remove(this.player); ++ String quitMessage = this.server.getPlayerList().remove(this.player); ++ if ((quitMessage != null) && (quitMessage.length() > 0)) { ++ this.server.getPlayerList().broadcastMessage(CraftChatMessage.fromString(quitMessage)); ++ } ++ // CraftBukkit end + this.player.getTextFilter().leave(); + } + +- public void ackBlockChangesUpTo(int i) { +- if (i < 0) { ++ public void ackBlockChangesUpTo(int sequence) { ++ if (sequence < 0) { + throw new IllegalArgumentException("Expected packet sequence nr >= 0"); + } else { +- this.ackBlockChangesUpTo = Math.max(i, this.ackBlockChangesUpTo); ++ this.ackBlockChangesUpTo = Math.max(sequence, this.ackBlockChangesUpTo); + } + } + + @Override +- @Override +- public void handleSetCarriedItem(ServerboundSetCarriedItemPacket serverboundsetcarrieditempacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetcarrieditempacket, this, this.player.serverLevel()); +- if (serverboundsetcarrieditempacket.getSlot() >= 0 && serverboundsetcarrieditempacket.getSlot() < Inventory.getSelectionSize()) { +- if (this.player.getInventory().selected != serverboundsetcarrieditempacket.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) { ++ public void handleSetCarriedItem(ServerboundSetCarriedItemPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ if (packet.getSlot() >= 0 && packet.getSlot() < Inventory.getSelectionSize()) { ++ PlayerItemHeldEvent event = new PlayerItemHeldEvent(this.getCraftPlayer(), this.player.getInventory().selected, packet.getSlot()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (event.isCancelled()) { ++ this.send(new ClientboundSetCarriedItemPacket(this.player.getInventory().selected)); ++ this.player.resetLastActionTime(); ++ return; ++ } ++ // CraftBukkit end ++ if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == EnumHand.MAIN_HAND) { + this.player.stopUsingItem(); + } + +- this.player.getInventory().selected = serverboundsetcarrieditempacket.getSlot(); ++ this.player.getInventory().selected = packet.getSlot(); + this.player.resetLastActionTime(); + } else { + ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString()); ++ this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit + } + } + + @Override +- @Override +- public void handleChat(ServerboundChatPacket serverboundchatpacket) { +- if (isChatMessageIllegal(serverboundchatpacket.message())) { ++ public void handleChat(ServerboundChatPacket packet) { ++ // CraftBukkit start - async chat ++ // SPIGOT-3638 ++ if (this.server.isStopped()) { ++ return; ++ } ++ // CraftBukkit end ++ if (isChatMessageIllegal(packet.message())) { + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); + } else { +- Optional<LastSeenMessages> optional = this.tryHandleChat(serverboundchatpacket.lastSeenMessages()); ++ Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages()); + + if (optional.isPresent()) { +- this.server.submit(() -> { ++ // this.server.submit(() -> { // CraftBukkit - async chat + PlayerChatMessage playerchatmessage; + + try { +- playerchatmessage = this.getSignedMessage(serverboundchatpacket, (LastSeenMessages) optional.get()); +- } catch (SignedMessageChain.DecodeException signedmessagechain_decodeexception) { +- this.handleMessageDecodeFailure(signedmessagechain_decodeexception); ++ playerchatmessage = this.getSignedMessage(packet, (LastSeenMessages) optional.get()); ++ } catch (SignedMessageChain.DecodeException signedmessagechain_a) { ++ this.handleMessageDecodeFailure(signedmessagechain_a); + return; + } + +- CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()); +- Component component = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); ++ CompletableFuture<FilteredText> completablefuture = this.filterTextPacket(playerchatmessage.signedContent()).thenApplyAsync(Function.identity(), this.server.chatExecutor); // CraftBukkit - async chat ++ Component ichatbasecomponent = this.server.getChatDecorator().decorate(this.player, playerchatmessage.decoratedContent()); + + this.chatMessageChain.append(completablefuture, (filteredtext) -> { +- PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(component).filter(filteredtext.mask()); ++ PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(ichatbasecomponent).filter(filteredtext.mask()); + + this.broadcastChatMessage(playerchatmessage1); + }); +- }); ++ // }); // CraftBukkit - async chat + } + + } + } + + @Override +- @Override +- public void handleChatCommand(ServerboundChatCommandPacket serverboundchatcommandpacket) { +- if (isChatMessageIllegal(serverboundchatcommandpacket.command())) { ++ public void handleChatCommand(ServerboundChatCommandPacket packet) { ++ if (isChatMessageIllegal(packet.command())) { + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); + } else { +- Optional<LastSeenMessages> optional = this.tryHandleChat(serverboundchatcommandpacket.lastSeenMessages()); ++ Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages()); + + if (optional.isPresent()) { + this.server.submit(() -> { +- this.performChatCommand(serverboundchatcommandpacket, (LastSeenMessages) optional.get()); ++ // CraftBukkit start - SPIGOT-7346: Prevent disconnected players from executing commands ++ if (player.hasDisconnected()) { ++ return; ++ } ++ // CraftBukkit end ++ ++ this.performChatCommand(packet, (LastSeenMessages) optional.get()); + this.detectRateSpam(); + }); + } +@@ -1356,61 +1781,74 @@ + } + } + +- private void performChatCommand(ServerboundChatCommandPacket serverboundchatcommandpacket, LastSeenMessages lastseenmessages) { +- ParseResults parseresults = this.parseCommand(serverboundchatcommandpacket.command()); ++ private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) { ++ // CraftBukkit start ++ String command = "/" + packet.command(); ++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command); + ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(getCraftPlayer(), command, new LazyPlayerSet(server)); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ command = event.getMessage().substring(1); ++ ++ ParseResults parseresults = this.parseCommand(command); ++ // CraftBukkit end ++ + Map map; + + try { +- map = this.collectSignedArguments(serverboundchatcommandpacket, SignableCommand.of(parseresults), lastseenmessages); +- } catch (SignedMessageChain.DecodeException signedmessagechain_decodeexception) { +- this.handleMessageDecodeFailure(signedmessagechain_decodeexception); ++ map = (packet.command().equals(command)) ? this.collectSignedArguments(packet, SignableCommand.of(parseresults), lastSeenMessages) : Collections.emptyMap(); // CraftBukkit ++ } catch (SignedMessageChain.DecodeException signedmessagechain_a) { ++ this.handleMessageDecodeFailure(signedmessagechain_a); + return; + } + +- CommandSigningContext.SignedArguments commandsigningcontext_signedarguments = new CommandSigningContext.SignedArguments(map); ++ CommandSigningContext.a commandsigningcontext_a = new CommandSigningContext.a(map); + +- parseresults = Commands.mapSource(parseresults, (commandsourcestack) -> { +- return commandsourcestack.withSigningContext(commandsigningcontext_signedarguments, this.chatMessageChain); ++ parseresults = Commands.<CommandSourceStack>mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error ++ return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain); + }); +- this.server.getCommands().performCommand(parseresults, serverboundchatcommandpacket.command()); ++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit + } + +- private void handleMessageDecodeFailure(SignedMessageChain.DecodeException signedmessagechain_decodeexception) { +- ServerGamePacketListenerImpl.LOGGER.warn("Failed to update secure chat state for {}: '{}'", this.player.getGameProfile().getName(), signedmessagechain_decodeexception.getComponent().getString()); +- if (signedmessagechain_decodeexception.shouldDisconnect()) { +- this.disconnect(signedmessagechain_decodeexception.getComponent()); ++ private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) { ++ ServerGamePacketListenerImpl.LOGGER.warn("Failed to update secure chat state for {}: '{}'", this.player.getGameProfile().getName(), exception.getComponent().getString()); ++ if (exception.shouldDisconnect()) { ++ this.disconnect(exception.getComponent()); + } else { +- this.player.sendSystemMessage(signedmessagechain_decodeexception.getComponent().copy().withStyle(ChatFormatting.RED)); ++ this.player.sendSystemMessage(exception.getComponent().copy().withStyle(ChatFormatting.RED)); + } + + } + +- private Map<String, PlayerChatMessage> collectSignedArguments(ServerboundChatCommandPacket serverboundchatcommandpacket, SignableCommand<?> signablecommand, LastSeenMessages lastseenmessages) throws SignedMessageChain.DecodeException { ++ private Map<String, PlayerChatMessage> collectSignedArguments(ServerboundChatCommandPacket packet, SignableCommand<?> command, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException { + Map<String, PlayerChatMessage> map = new Object2ObjectOpenHashMap(); +- Iterator iterator = signablecommand.arguments().iterator(); ++ Iterator iterator = command.arguments().iterator(); + + while (iterator.hasNext()) { +- SignableCommand.Argument<?> signablecommand_argument = (SignableCommand.Argument) iterator.next(); +- MessageSignature messagesignature = serverboundchatcommandpacket.argumentSignatures().get(signablecommand_argument.name()); +- SignedMessageBody signedmessagebody = new SignedMessageBody(signablecommand_argument.value(), serverboundchatcommandpacket.timeStamp(), serverboundchatcommandpacket.salt(), lastseenmessages); ++ SignableCommand.a<?> signablecommand_a = (SignableCommand.a) iterator.next(); ++ MessageSignature messagesignature = packet.argumentSignatures().get(signablecommand_a.name()); ++ SignedMessageBody signedmessagebody = new SignedMessageBody(signablecommand_a.value(), packet.timeStamp(), packet.salt(), lastSeenMessages); + +- map.put(signablecommand_argument.name(), this.signedMessageDecoder.unpack(messagesignature, signedmessagebody)); ++ map.put(signablecommand_a.name(), this.signedMessageDecoder.unpack(messagesignature, signedmessagebody)); + } + + return map; + } + +- private ParseResults<CommandSourceStack> parseCommand(String s) { +- CommandDispatcher<CommandSourceStack> commanddispatcher = this.server.getCommands().getDispatcher(); ++ private ParseResults<CommandSourceStack> parseCommand(String command) { ++ com.mojang.brigadier.CommandDispatcher<CommandSourceStack> com_mojang_brigadier_commanddispatcher = this.server.getCommands().getDispatcher(); + +- return commanddispatcher.parse(s, this.player.createCommandSourceStack()); ++ return com_mojang_brigadier_commanddispatcher.parse(command, this.player.createCommandSourceStack()); + } + +- private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update lastseenmessages_update) { +- Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_update); ++ private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update lastseenmessages_b) { ++ Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_b); + +- if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { ++ 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)); + return Optional.empty(); + } else { +@@ -1419,11 +1857,11 @@ + } + } + +- private Optional<LastSeenMessages> unpackAndApplyLastSeen(LastSeenMessages.Update lastseenmessages_update) { ++ private Optional<LastSeenMessages> unpackAndApplyLastSeen(LastSeenMessages.Update update) { + LastSeenMessagesValidator lastseenmessagesvalidator = this.lastSeenMessages; + + synchronized (this.lastSeenMessages) { +- Optional<LastSeenMessages> optional = this.lastSeenMessages.applyUpdate(lastseenmessages_update); ++ Optional<LastSeenMessages> optional = this.lastSeenMessages.applyUpdate(update); + + if (optional.isEmpty()) { + ServerGamePacketListenerImpl.LOGGER.warn("Failed to validate message acknowledgements from {}", this.player.getName().getString()); +@@ -1434,9 +1872,9 @@ + } + } + +- private static boolean isChatMessageIllegal(String s) { +- for (int i = 0; i < s.length(); ++i) { +- if (!SharedConstants.isAllowedChatCharacter(s.charAt(i))) { ++ private static boolean isChatMessageIllegal(String message) { ++ for (int i = 0; i < message.length(); ++i) { ++ if (!SharedConstants.isAllowedChatCharacter(message.charAt(i))) { + return true; + } + } +@@ -1444,32 +1882,161 @@ + return false; + } + +- private PlayerChatMessage getSignedMessage(ServerboundChatPacket serverboundchatpacket, LastSeenMessages lastseenmessages) throws SignedMessageChain.DecodeException { +- SignedMessageBody signedmessagebody = new SignedMessageBody(serverboundchatpacket.message(), serverboundchatpacket.timeStamp(), serverboundchatpacket.salt(), lastseenmessages); ++ // CraftBukkit start - add method ++ public void chat(String s, PlayerChatMessage original, boolean async) { ++ if (s.isEmpty() || this.player.getChatVisibility() == ChatVisiblity.HIDDEN) { ++ return; ++ } ++ OutgoingChatMessage outgoing = OutgoingChatMessage.create(original); + +- return this.signedMessageDecoder.unpack(serverboundchatpacket.signature(), signedmessagebody); ++ if (!async && s.startsWith("/")) { ++ this.handleCommand(s); ++ } else if (this.player.getChatVisibility() == ChatVisiblity.SYSTEM) { ++ // Do nothing, this is coming from a plugin ++ } else { ++ Player player = this.getCraftPlayer(); ++ AsyncPlayerChatEvent event = new AsyncPlayerChatEvent(async, player, s, new LazyPlayerSet(server)); ++ String originalFormat = event.getFormat(), originalMessage = event.getMessage(); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (PlayerChatEvent.getHandlerList().getRegisteredListeners().length != 0) { ++ // Evil plugins still listening to deprecated event ++ final PlayerChatEvent queueEvent = new PlayerChatEvent(player, event.getMessage(), event.getFormat(), event.getRecipients()); ++ queueEvent.setCancelled(event.isCancelled()); ++ Waitable waitable = new Waitable() { ++ @Override ++ protected Object evaluate() { ++ org.bukkit.Bukkit.getPluginManager().callEvent(queueEvent); ++ ++ if (queueEvent.isCancelled()) { ++ return null; ++ } ++ ++ String message = String.format(queueEvent.getFormat(), queueEvent.getPlayer().getDisplayName(), queueEvent.getMessage()); ++ if (((LazyPlayerSet) queueEvent.getRecipients()).isLazy()) { ++ if (originalFormat.equals(queueEvent.getFormat()) && originalMessage.equals(queueEvent.getMessage()) && queueEvent.getPlayer().getName().equalsIgnoreCase(queueEvent.getPlayer().getDisplayName())) { ++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player)); ++ return null; ++ } ++ ++ for (ServerPlayer recipient : server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); ++ } ++ } else { ++ for (Player player : queueEvent.getRecipients()) { ++ player.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), message); ++ } ++ } ++ ServerGamePacketListenerImpl.this.server.console.sendMessage(message); ++ ++ return null; ++ }}; ++ if (async) { ++ server.processQueue.add(waitable); ++ } else { ++ waitable.run(); ++ } ++ try { ++ waitable.get(); ++ } catch (InterruptedException e) { ++ Thread.currentThread().interrupt(); // This is proper habit for java. If we aren't handling it, pass it on! ++ } catch (ExecutionException e) { ++ throw new RuntimeException("Exception processing chat event", e.getCause()); ++ } ++ } else { ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ s = String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()); ++ if (((LazyPlayerSet) event.getRecipients()).isLazy()) { ++ if (originalFormat.equals(event.getFormat()) && originalMessage.equals(event.getMessage()) && event.getPlayer().getName().equalsIgnoreCase(event.getPlayer().getDisplayName())) { ++ ServerGamePacketListenerImpl.this.server.getPlayerList().broadcastChatMessage(original, ServerGamePacketListenerImpl.this.player, ChatType.bind(ChatType.CHAT, (Entity) ServerGamePacketListenerImpl.this.player)); ++ return; ++ } ++ ++ for (ServerPlayer recipient : server.getPlayerList().players) { ++ recipient.getBukkitEntity().sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ } ++ } else { ++ for (Player recipient : event.getRecipients()) { ++ recipient.sendMessage(ServerGamePacketListenerImpl.this.player.getUUID(), s); ++ } ++ } ++ server.console.sendMessage(s); ++ } ++ } + } + +- private void broadcastChatMessage(PlayerChatMessage playerchatmessage) { +- this.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatType.bind(ChatType.CHAT, (Entity) this.player)); ++ private void handleCommand(String s) { ++ this.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + s); ++ ++ CraftPlayer player = this.getCraftPlayer(); ++ ++ PlayerCommandPreprocessEvent event = new PlayerCommandPreprocessEvent(player, s, new LazyPlayerSet(server)); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ ++ try { ++ if (this.cserver.dispatchCommand(event.getPlayer(), event.getMessage().substring(1))) { ++ return; ++ } ++ } catch (org.bukkit.command.CommandException ex) { ++ player.sendMessage(org.bukkit.ChatColor.RED + "An internal error occurred while attempting to perform this command"); ++ java.util.logging.Logger.getLogger(ServerGamePacketListenerImpl.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); ++ return; ++ } ++ } ++ // CraftBukkit end ++ ++ private PlayerChatMessage getSignedMessage(ServerboundChatPacket packet, LastSeenMessages lastSeenMessages) throws SignedMessageChain.DecodeException { ++ SignedMessageBody signedmessagebody = new SignedMessageBody(packet.message(), packet.timeStamp(), packet.salt(), lastSeenMessages); ++ ++ return this.signedMessageDecoder.unpack(packet.signature(), signedmessagebody); ++ } ++ ++ private void broadcastChatMessage(PlayerChatMessage message) { ++ // CraftBukkit start ++ String s = message.signedContent(); ++ if (s.isEmpty()) { ++ LOGGER.warn(this.player.getScoreboardName() + " tried to send an empty message"); ++ } else if (getCraftPlayer().isConversing()) { ++ final String conversationInput = s; ++ this.server.processQueue.add(new Runnable() { ++ @Override ++ public void run() { ++ getCraftPlayer().acceptConversationInput(conversationInput); ++ } ++ }); ++ } else if (this.player.getChatVisibility() == 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.server.getPlayerList().broadcastChatMessage(playerchatmessage, this.player, ChatMessageType.bind(ChatMessageType.CHAT, (Entity) this.player)); ++ // CraftBukkit end + this.detectRateSpam(); + } + + private void detectRateSpam() { +- this.chatSpamTickCount += 20; +- if (this.chatSpamTickCount > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ // CraftBukkit start - replaced with thread safe throttle ++ // this.chatSpamTickCount += 20; ++ if (this.chatSpamTickCount.addAndGet(20) > 200 && !this.server.getPlayerList().isOp(this.player.getGameProfile())) { ++ // CraftBukkit end + this.disconnect(Component.translatable("disconnect.spam")); + } + + } + + @Override +- @Override +- public void handleChatAck(ServerboundChatAckPacket serverboundchatackpacket) { ++ public void handleChatAck(ServerboundChatAckPacket packet) { + LastSeenMessagesValidator lastseenmessagesvalidator = this.lastSeenMessages; + + synchronized (this.lastSeenMessages) { +- if (!this.lastSeenMessages.applyOffset(serverboundchatackpacket.offset())) { ++ 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); + } +@@ -1478,22 +2045,69 @@ + } + + @Override +- @Override +- public void handleAnimate(ServerboundSwingPacket serverboundswingpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundswingpacket, this, this.player.serverLevel()); ++ public void handleAnimate(ServerboundSwingPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- this.player.swing(serverboundswingpacket.getHand()); ++ // CraftBukkit start - Raytrace to look for 'rogue armswings' ++ float f1 = this.player.getXRot(); ++ float f2 = this.player.getYRot(); ++ double d0 = this.player.getX(); ++ double d1 = this.player.getY() + (double) this.player.getEyeHeight(); ++ double d2 = this.player.getZ(); ++ Location origin = new Location(this.player.level().getWorld(), d0, d1, d2, f2, f1); ++ ++ double d3 = player.gameMode.getGameModeForPlayer() == GameType.CREATIVE ? 5.0D : 4.5D; ++ // SPIGOT-5607: Only call interact event if no block or entity is being clicked. Use bukkit ray trace method, because it handles blocks and entities at the same time ++ // SPIGOT-7429: Make sure to call PlayerInteractEvent for spectators and non-pickable entities ++ org.bukkit.util.RayTraceResult result = this.player.level().getWorld().rayTrace(origin, origin.getDirection(), d3, org.bukkit.FluidCollisionMode.NEVER, false, 0.1, entity -> { ++ Entity handle = ((CraftEntity) entity).getHandle(); ++ return entity != this.player.getBukkitEntity() && this.player.getBukkitEntity().canSee(entity) && !handle.isSpectator() && handle.isPickable() && !handle.isPassengerOfSameVehicle(player); ++ }); ++ if (result == null) { ++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_AIR, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND); ++ } ++ ++ // Arm swing animation ++ PlayerAnimationEvent event = new PlayerAnimationEvent(this.getCraftPlayer(), (packet.getHand() == EnumHand.MAIN_HAND) ? PlayerAnimationType.ARM_SWING : PlayerAnimationType.OFF_ARM_SWING); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) return; ++ // CraftBukkit end ++ this.player.swing(packet.getHand()); + } + + @Override +- @Override +- public void handlePlayerCommand(ServerboundPlayerCommandPacket serverboundplayercommandpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundplayercommandpacket, this, this.player.serverLevel()); ++ public void handlePlayerCommand(ServerboundPlayerCommandPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (this.player.isRemoved()) return; ++ switch (packet.getAction()) { ++ case PRESS_SHIFT_KEY: ++ case RELEASE_SHIFT_KEY: ++ PlayerToggleSneakEvent event = new PlayerToggleSneakEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.EnumPlayerAction.PRESS_SHIFT_KEY); ++ this.cserver.getPluginManager().callEvent(event); ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ break; ++ case START_SPRINTING: ++ case STOP_SPRINTING: ++ PlayerToggleSprintEvent e2 = new PlayerToggleSprintEvent(this.getCraftPlayer(), packet.getAction() == ServerboundPlayerCommandPacket.EnumPlayerAction.START_SPRINTING); ++ this.cserver.getPluginManager().callEvent(e2); ++ ++ if (e2.isCancelled()) { ++ return; ++ } ++ break; ++ } ++ // CraftBukkit end + this.player.resetLastActionTime(); + Entity entity; +- PlayerRideableJumping playerrideablejumping; ++ PlayerRideableJumping ijumpable; + +- switch (serverboundplayercommandpacket.getAction()) { ++ switch (packet.getAction()) { + case PRESS_SHIFT_KEY: + this.player.setShiftKeyDown(true); + break; +@@ -1515,19 +2129,19 @@ + case START_RIDING_JUMP: + entity = this.player.getControlledVehicle(); + if (entity instanceof PlayerRideableJumping) { +- playerrideablejumping = (PlayerRideableJumping) entity; +- int i = serverboundplayercommandpacket.getData(); ++ ijumpable = (PlayerRideableJumping) entity; ++ int i = packet.getData(); + +- if (playerrideablejumping.canJump() && i > 0) { +- playerrideablejumping.handleStartJump(i); ++ if (ijumpable.canJump() && i > 0) { ++ ijumpable.handleStartJump(i); + } + } + break; + case STOP_RIDING_JUMP: + entity = this.player.getControlledVehicle(); + if (entity instanceof PlayerRideableJumping) { +- playerrideablejumping = (PlayerRideableJumping) entity; +- playerrideablejumping.handleStopJump(); ++ ijumpable = (PlayerRideableJumping) entity; ++ ijumpable.handleStopJump(); + } + break; + case OPEN_INVENTORY: +@@ -1549,11 +2163,11 @@ + + } + +- public void addPendingMessage(PlayerChatMessage playerchatmessage) { +- MessageSignature messagesignature = playerchatmessage.signature(); ++ public void addPendingMessage(PlayerChatMessage message) { ++ MessageSignature messagesignature = message.signature(); + + if (messagesignature != null) { +- this.messageSignatureCache.push(playerchatmessage.signedBody(), playerchatmessage.signature()); ++ this.messageSignatureCache.push(message.signedBody(), message.signature()); + LastSeenMessagesValidator lastseenmessagesvalidator = this.lastSeenMessages; + int i; + +@@ -1569,13 +2183,19 @@ + } + } + +- public void sendPlayerChatMessage(PlayerChatMessage playerchatmessage, ChatType.Bound chattype_bound) { +- this.send(new ClientboundPlayerChatPacket(playerchatmessage.link().sender(), playerchatmessage.link().index(), playerchatmessage.signature(), playerchatmessage.signedBody().pack(this.messageSignatureCache), playerchatmessage.unsignedContent(), playerchatmessage.filterMask(), chattype_bound.toNetwork(this.player.level().registryAccess()))); +- this.addPendingMessage(playerchatmessage); ++ 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 (!getCraftPlayer().canSee(chatMessage.link().sender())) { ++ sendDisguisedChatMessage(chatMessage.decoratedContent(), boundType); ++ return; ++ } ++ // CraftBukkit end ++ this.send(new ClientboundPlayerChatPacket(chatMessage.link().sender(), chatMessage.link().index(), chatMessage.signature(), chatMessage.signedBody().pack(this.messageSignatureCache), chatMessage.unsignedContent(), chatMessage.filterMask(), boundType.toNetwork(this.player.level().registryAccess()))); ++ this.addPendingMessage(chatMessage); + } + +- public void sendDisguisedChatMessage(Component component, ChatType.Bound chattype_bound) { +- this.send(new ClientboundDisguisedChatPacket(component, chattype_bound.toNetwork(this.player.level().registryAccess()))); ++ public void sendDisguisedChatMessage(Component message, ChatType.Bound boundType) { ++ this.send(new ClientboundDisguisedChatPacket(message, boundType.toNetwork(this.player.level().registryAccess()))); + } + + public SocketAddress getRemoteAddress() { +@@ -1589,40 +2209,77 @@ + } + + @Override +- @Override +- public void handlePingRequest(ServerboundPingRequestPacket serverboundpingrequestpacket) { +- this.connection.send(new ClientboundPongResponsePacket(serverboundpingrequestpacket.getTime())); ++ public void handlePingRequest(ServerboundPingRequestPacket packetstatusinping) { ++ this.connection.send(new ClientboundPongResponsePacket(packetstatusinping.getTime())); + } + + @Override +- @Override +- public void handleInteract(ServerboundInteractPacket serverboundinteractpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundinteractpacket, this, this.player.serverLevel()); +- final ServerLevel serverlevel = this.player.serverLevel(); +- final Entity entity = serverboundinteractpacket.getTarget(serverlevel); ++ public void handleInteract(ServerboundInteractPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit ++ final ServerLevel worldserver = this.player.serverLevel(); ++ final Entity entity = packet.getTarget(worldserver); + + this.player.resetLastActionTime(); +- this.player.setShiftKeyDown(serverboundinteractpacket.isUsingSecondaryAction()); ++ this.player.setShiftKeyDown(packet.isUsingSecondaryAction()); + if (entity != null) { +- if (!serverlevel.getWorldBorder().isWithinBounds(entity.blockPosition())) { ++ if (!worldserver.getWorldBorder().isWithinBounds(entity.blockPosition())) { + return; + } + +- AABB aabb = entity.getBoundingBox(); ++ AABB axisalignedbb = entity.getBoundingBox(); + +- if (aabb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { +- serverboundinteractpacket.dispatch(new ServerboundInteractPacket.Handler() { +- private void performInteraction(InteractionHand interactionhand, ServerGamePacketListenerImpl.EntityInteraction servergamepacketlistenerimpl_entityinteraction) { +- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(interactionhand); ++ if (axisalignedbb.distanceToSqr(this.player.getEyePosition()) < ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) { ++ packet.dispatch(new ServerboundInteractPacket.Handler() { ++ private void performInteraction(EnumHand enumhand, ServerGamePacketListenerImpl.EntityInteraction playerconnection_a, PlayerInteractEntityEvent event) { // CraftBukkit ++ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); + +- if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) { ++ if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + ItemStack itemstack1 = itemstack.copy(); +- InteractionResult interactionresult = servergamepacketlistenerimpl_entityinteraction.run(ServerGamePacketListenerImpl.this.player, entity, interactionhand); ++ // CraftBukkit start ++ ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(enumhand); ++ boolean triggerLeashUpdate = itemInHand != null && itemInHand.getItem() == Items.LEAD && entity instanceof Mob; ++ Item origItem = player.getInventory().getSelected() == null ? null : player.getInventory().getSelected().getItem(); + +- if (interactionresult.consumesAction()) { ++ cserver.getPluginManager().callEvent(event); ++ ++ // Entity in bucket - SPIGOT-4048 and SPIGOT-6859a ++ if ((entity instanceof Bucketable && entity instanceof LivingEntity && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) { ++ send(new ClientboundAddEntityPacket(entity)); ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ ++ if (triggerLeashUpdate && (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem)) { ++ // Refresh the current leash state ++ send(new ClientboundSetEntityLinkPacket(entity, ((Mob) entity).getLeashHolder())); ++ } ++ ++ if (event.isCancelled() || player.getInventory().getSelected() == null || player.getInventory().getSelected().getItem() != origItem) { ++ // Refresh the current entity metadata ++ entity.getEntityData().refresh(player); ++ // SPIGOT-7136 - Allays ++ if (entity instanceof Allay) { ++ send(new ClientboundSetEquipmentPacket(entity.getId(), Arrays.stream(net.minecraft.world.entity.EquipmentSlot.values()).map((slot) -> Pair.of(slot, ((LivingEntity) entity).getItemBySlot(slot).copy())).collect(Collectors.toList()))); ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ } ++ ++ if (event.isCancelled()) { ++ return; ++ } ++ // CraftBukkit end ++ InteractionResult enuminteractionresult = playerconnection_a.run(ServerGamePacketListenerImpl.this.player, entity, enumhand); ++ ++ // CraftBukkit start ++ if (!itemInHand.isEmpty() && itemInHand.getCount() <= -1) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end ++ ++ if (enuminteractionresult.consumesAction()) { + CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemstack1, entity); +- if (interactionresult.shouldSwing()) { +- ServerGamePacketListenerImpl.this.player.swing(interactionhand, true); ++ if (enuminteractionresult.shouldSwing()) { ++ ServerGamePacketListenerImpl.this.player.swing(enumhand, true); + } + } + +@@ -1630,27 +2287,30 @@ + } + + @Override +- @Override +- public void onInteraction(InteractionHand interactionhand) { +- this.performInteraction(interactionhand, Player::interactOn); ++ public void onInteraction(EnumHand hand) { ++ this.performInteraction(hand, net.minecraft.world.entity.player.Player::interactOn, new PlayerInteractEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), (hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override +- @Override +- public void onInteraction(InteractionHand interactionhand, Vec3 vec3) { +- this.performInteraction(interactionhand, (serverplayer, entity1, interactionhand1) -> { +- return entity1.interactAt(serverplayer, vec3, interactionhand1); +- }); ++ public void onInteraction(EnumHand hand, Vec3 interactionLocation) { ++ this.performInteraction(hand, (entityplayer, entity1, enumhand1) -> { ++ return entity1.interactAt(entityplayer, interactionLocation, enumhand1); ++ }, new PlayerInteractAtEntityEvent(getCraftPlayer(), entity.getBukkitEntity(), new org.bukkit.util.Vector(interactionLocation.x, interactionLocation.y, interactionLocation.z), (hand == EnumHand.OFF_HAND) ? EquipmentSlot.OFF_HAND : EquipmentSlot.HAND)); // CraftBukkit + } + + @Override +- @Override + public void onAttack() { +- if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && !(entity instanceof AbstractArrow) && entity != ServerGamePacketListenerImpl.this.player) { +- ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND); ++ // CraftBukkit ++ if (!(entity instanceof ItemEntity) && !(entity instanceof ExperienceOrb) && !(entity instanceof AbstractArrow) && (entity != ServerGamePacketListenerImpl.this.player || player.isSpectator())) { ++ ItemStack itemstack = ServerGamePacketListenerImpl.this.player.getItemInHand(EnumHand.MAIN_HAND); + +- if (itemstack.isItemEnabled(serverlevel.enabledFeatures())) { ++ if (itemstack.isItemEnabled(worldserver.enabledFeatures())) { + ServerGamePacketListenerImpl.this.player.attack(entity); ++ // CraftBukkit start ++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) { ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ // CraftBukkit end + } + } else { + ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked")); +@@ -1664,24 +2324,23 @@ + } + + @Override +- @Override +- public void handleClientCommand(ServerboundClientCommandPacket serverboundclientcommandpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundclientcommandpacket, this, this.player.serverLevel()); ++ public void handleClientCommand(ServerboundClientCommandPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.resetLastActionTime(); +- ServerboundClientCommandPacket.Action serverboundclientcommandpacket_action = serverboundclientcommandpacket.getAction(); ++ ServerboundClientCommandPacket.EnumClientCommand packetplayinclientcommand_enumclientcommand = packet.getAction(); + +- switch (serverboundclientcommandpacket_action) { ++ switch (packetplayinclientcommand_enumclientcommand) { + case PERFORM_RESPAWN: + if (this.player.wonGame) { + this.player.wonGame = false; +- this.player = this.server.getPlayerList().respawn(this.player, true); ++ this.player = this.server.getPlayerList().respawn(this.player, true, RespawnReason.END_PORTAL); + CriteriaTriggers.CHANGED_DIMENSION.trigger(this.player, Level.END, Level.OVERWORLD); + } else { + if (this.player.getHealth() > 0.0F) { + return; + } + +- this.player = this.server.getPlayerList().respawn(this.player, false); ++ this.player = this.server.getPlayerList().respawn(this.player, false, RespawnReason.DEATH); + if (this.server.isHardcore()) { + this.player.setGameMode(GameType.SPECTATOR); + ((GameRules.BooleanValue) this.player.level().getGameRules().getRule(GameRules.RULE_SPECTATORSGENERATECHUNKS)).set(false, this.server); +@@ -1695,41 +2354,322 @@ + } + + @Override +- @Override +- public void handleContainerClose(ServerboundContainerClosePacket serverboundcontainerclosepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundcontainerclosepacket, this, this.player.serverLevel()); ++ public void handleContainerClose(ServerboundContainerClosePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ ++ if (this.player.isImmobile()) return; // CraftBukkit ++ CraftEventFactory.handleInventoryCloseEvent(this.player); // CraftBukkit ++ + this.player.doCloseContainer(); + } + + @Override +- @Override +- public void handleContainerClick(ServerboundContainerClickPacket serverboundcontainerclickpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundcontainerclickpacket, this, this.player.serverLevel()); ++ public void handleContainerClick(ServerboundContainerClickPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- if (this.player.containerMenu.containerId == serverboundcontainerclickpacket.getContainerId()) { +- if (this.player.isSpectator()) { ++ if (this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu.stillValid(this.player)) { // CraftBukkit ++ boolean cancelled = this.player.isSpectator(); // CraftBukkit - see below if ++ if (false/*this.player.isSpectator()*/) { // CraftBukkit + this.player.containerMenu.sendAllDataToRemote(); + } else if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { +- int i = serverboundcontainerclickpacket.getSlotNum(); ++ int i = packet.getSlotNum(); + + if (!this.player.containerMenu.isValidSlotIndex(i)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} clicked invalid slot index: {}, available slots: {}", new Object[]{this.player.getName(), i, this.player.containerMenu.slots.size()}); + } else { +- boolean flag = serverboundcontainerclickpacket.getStateId() != this.player.containerMenu.getStateId(); ++ boolean flag = packet.getStateId() != this.player.containerMenu.getStateId(); + + this.player.containerMenu.suppressRemoteUpdates(); +- this.player.containerMenu.clicked(i, serverboundcontainerclickpacket.getButtonNum(), serverboundcontainerclickpacket.getClickType(), this.player); +- ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(serverboundcontainerclickpacket.getChangedSlots()).iterator(); ++ // CraftBukkit start - Call InventoryClickEvent ++ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) { ++ return; ++ } + ++ InventoryView inventory = this.player.containerMenu.getBukkitView(); ++ SlotType type = inventory.getSlotType(packet.getSlotNum()); ++ ++ InventoryClickEvent event; ++ ClickType click = ClickType.UNKNOWN; ++ InventoryAction action = InventoryAction.UNKNOWN; ++ ++ ItemStack itemstack = ItemStack.EMPTY; ++ ++ switch (packet.getClickType()) { ++ case PICKUP: ++ if (packet.getButtonNum() == 0) { ++ click = ClickType.LEFT; ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.RIGHT; ++ } ++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { ++ action = InventoryAction.NOTHING; // Don't want to repeat ourselves ++ if (packet.getSlotNum() == -999) { ++ if (!player.containerMenu.getCarried().isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.DROP_ALL_CURSOR : InventoryAction.DROP_ONE_CURSOR; ++ } ++ } else if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null) { ++ ItemStack clickedItem = slot.getItem(); ++ ItemStack cursor = player.containerMenu.getCarried(); ++ if (clickedItem.isEmpty()) { ++ if (!cursor.isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.PLACE_ALL : InventoryAction.PLACE_ONE; ++ } ++ } else if (slot.mayPickup(player)) { ++ if (cursor.isEmpty()) { ++ action = packet.getButtonNum() == 0 ? InventoryAction.PICKUP_ALL : InventoryAction.PICKUP_HALF; ++ } else if (slot.mayPlace(cursor)) { ++ if (ItemStack.isSameItemSameTags(clickedItem, cursor)) { ++ int toPlace = packet.getButtonNum() == 0 ? cursor.getCount() : 1; ++ toPlace = Math.min(toPlace, clickedItem.getMaxStackSize() - clickedItem.getCount()); ++ toPlace = Math.min(toPlace, slot.container.getMaxStackSize() - clickedItem.getCount()); ++ if (toPlace == 1) { ++ action = InventoryAction.PLACE_ONE; ++ } else if (toPlace == cursor.getCount()) { ++ action = InventoryAction.PLACE_ALL; ++ } else if (toPlace < 0) { ++ action = toPlace != -1 ? InventoryAction.PICKUP_SOME : InventoryAction.PICKUP_ONE; // this happens with oversized stacks ++ } else if (toPlace != 0) { ++ action = InventoryAction.PLACE_SOME; ++ } ++ } else if (cursor.getCount() <= slot.getMaxStackSize()) { ++ action = InventoryAction.SWAP_WITH_CURSOR; ++ } ++ } else if (ItemStack.isSameItemSameTags(cursor, clickedItem)) { ++ if (clickedItem.getCount() >= 0) { ++ if (clickedItem.getCount() + cursor.getCount() <= cursor.getMaxStackSize()) { ++ // As of 1.5, this is result slots only ++ action = InventoryAction.PICKUP_ALL; ++ } ++ } ++ } ++ } ++ } ++ } ++ } ++ break; ++ // TODO check on updates ++ case QUICK_MOVE: ++ if (packet.getButtonNum() == 0) { ++ click = ClickType.SHIFT_LEFT; ++ } else if (packet.getButtonNum() == 1) { ++ click = ClickType.SHIFT_RIGHT; ++ } ++ if (packet.getButtonNum() == 0 || packet.getButtonNum() == 1) { ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.mayPickup(this.player) && slot.hasItem()) { ++ action = InventoryAction.MOVE_TO_OTHER_INVENTORY; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } ++ break; ++ case SWAP: ++ if ((packet.getButtonNum() >= 0 && packet.getButtonNum() < 9) || packet.getButtonNum() == 40) { ++ click = (packet.getButtonNum() == 40) ? ClickType.SWAP_OFFHAND : ClickType.NUMBER_KEY; ++ Slot clickedSlot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (clickedSlot.mayPickup(player)) { ++ ItemStack hotbar = this.player.getInventory().getItem(packet.getButtonNum()); ++ boolean canCleanSwap = hotbar.isEmpty() || (clickedSlot.container == player.getInventory() && clickedSlot.mayPlace(hotbar)); // the slot will accept the hotbar item ++ if (clickedSlot.hasItem()) { ++ if (canCleanSwap) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.HOTBAR_MOVE_AND_READD; ++ } ++ } else if (!clickedSlot.hasItem() && !hotbar.isEmpty() && clickedSlot.mayPlace(hotbar)) { ++ action = InventoryAction.HOTBAR_SWAP; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ break; ++ case CLONE: ++ if (packet.getButtonNum() == 2) { ++ click = ClickType.MIDDLE; ++ if (packet.getSlotNum() < 0) { ++ action = InventoryAction.NOTHING; ++ } else { ++ Slot slot = this.player.containerMenu.getSlot(packet.getSlotNum()); ++ if (slot != null && slot.hasItem() && player.getAbilities().instabuild && player.containerMenu.getCarried().isEmpty()) { ++ action = InventoryAction.CLONE_STACK; ++ } else { ++ action = InventoryAction.NOTHING; ++ } ++ } ++ } else { ++ click = ClickType.UNKNOWN; ++ action = InventoryAction.UNKNOWN; ++ } ++ break; ++ case THROW: ++ if (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(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(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; ++ } ++ action = InventoryAction.NOTHING; ++ } ++ break; ++ case QUICK_CRAFT: ++ 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(CraftMagicNumbers.getMaterial(cursor.getItem())) || inventory.getBottomInventory().contains(CraftMagicNumbers.getMaterial(cursor.getItem()))) { ++ action = InventoryAction.COLLECT_TO_CURSOR; ++ } ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (packet.getClickType() != InventoryClickType.QUICK_CRAFT) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new InventoryClickEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ ++ org.bukkit.inventory.Inventory top = inventory.getTopInventory(); ++ if (packet.getSlotNum() == 0 && top instanceof CraftingInventory) { ++ org.bukkit.inventory.Recipe recipe = ((CraftingInventory) top).getRecipe(); ++ if (recipe != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new CraftItemEvent(recipe, inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ if (packet.getSlotNum() == 3 && top instanceof SmithingInventory) { ++ org.bukkit.inventory.ItemStack result = ((SmithingInventory) top).getResult(); ++ if (result != null) { ++ if (click == ClickType.NUMBER_KEY) { ++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action, packet.getButtonNum()); ++ } else { ++ event = new SmithItemEvent(inventory, type, packet.getSlotNum(), click, action); ++ } ++ } ++ } ++ ++ event.setCancelled(cancelled); ++ AbstractContainerMenu oldContainer = this.player.containerMenu; // SPIGOT-1224 ++ cserver.getPluginManager().callEvent(event); ++ if (this.player.containerMenu != oldContainer) { ++ return; ++ } ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ case DEFAULT: ++ this.player.containerMenu.clicked(i, packet.getButtonNum(), packet.getClickType(), this.player); ++ break; ++ case DENY: ++ /* Needs enum constructor in InventoryAction ++ if (action.modifiesOtherSlots()) { ++ ++ } else { ++ if (action.modifiesCursor()) { ++ this.player.playerConnection.sendPacket(new Packet103SetSlot(-1, -1, this.player.inventory.getCarried())); ++ } ++ if (action.modifiesClicked()) { ++ this.player.playerConnection.sendPacket(new Packet103SetSlot(this.player.activeContainer.windowId, packet102windowclick.slot, this.player.activeContainer.getSlot(packet102windowclick.slot).getItem())); ++ } ++ }*/ ++ switch (action) { ++ // Modified other slots ++ case PICKUP_ALL: ++ case MOVE_TO_OTHER_INVENTORY: ++ case HOTBAR_MOVE_AND_READD: ++ case HOTBAR_SWAP: ++ case COLLECT_TO_CURSOR: ++ case UNKNOWN: ++ this.player.containerMenu.sendAllDataToRemote(); ++ break; ++ // Modified cursor and clicked ++ case PICKUP_SOME: ++ case PICKUP_HALF: ++ case PICKUP_ONE: ++ case PLACE_ALL: ++ case PLACE_SOME: ++ case PLACE_ONE: ++ case SWAP_WITH_CURSOR: ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried())); ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.containerMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.containerMenu.getSlot(packet.getSlotNum()).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())); ++ break; ++ // Modified cursor only ++ case DROP_ALL_CURSOR: ++ case DROP_ONE_CURSOR: ++ case CLONE_STACK: ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, -1, this.player.inventoryMenu.incrementStateId(), this.player.containerMenu.getCarried())); ++ break; ++ // Nothing ++ case NOTHING: ++ break; ++ } ++ } ++ ++ if (event instanceof CraftItemEvent || event instanceof SmithItemEvent) { ++ // Need to update the inventory on crafting to ++ // correctly support custom recipes ++ player.containerMenu.sendAllDataToRemote(); ++ } ++ } ++ // CraftBukkit end ++ ObjectIterator objectiterator = Int2ObjectMaps.fastIterable(packet.getChangedSlots()).iterator(); ++ + while (objectiterator.hasNext()) { + Entry<ItemStack> entry = (Entry) objectiterator.next(); + + this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), (ItemStack) entry.getValue()); + } + +- this.player.containerMenu.setRemoteCarried(serverboundcontainerclickpacket.getCarriedItem()); ++ this.player.containerMenu.setRemoteCarried(packet.getCarriedItem()); + this.player.containerMenu.resumeRemoteUpdates(); + if (flag) { + this.player.containerMenu.broadcastFullState(); +@@ -1743,31 +2683,39 @@ + } + + @Override +- @Override +- public void handlePlaceRecipe(ServerboundPlaceRecipePacket serverboundplacerecipepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundplacerecipepacket, this, this.player.serverLevel()); ++ public void handlePlaceRecipe(ServerboundPlaceRecipePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + this.player.resetLastActionTime(); +- if (!this.player.isSpectator() && this.player.containerMenu.containerId == serverboundplacerecipepacket.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { ++ if (!this.player.isSpectator() && this.player.containerMenu.containerId == packet.getContainerId() && this.player.containerMenu instanceof RecipeBookMenu) { + if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { +- this.server.getRecipeManager().byKey(serverboundplacerecipepacket.getRecipe()).ifPresent((recipeholder) -> { +- ((RecipeBookMenu) this.player.containerMenu).handlePlacement(serverboundplacerecipepacket.isShiftDown(), recipeholder, this.player); ++ // CraftBukkit start - implement PlayerRecipeBookClickEvent ++ org.bukkit.inventory.Recipe recipe = this.cserver.getRecipe(CraftNamespacedKey.fromMinecraft(packet.getRecipe())); ++ if (recipe == null) { ++ return; ++ } ++ org.bukkit.event.player.PlayerRecipeBookClickEvent event = CraftEventFactory.callRecipeBookClickEvent(this.player, recipe, packet.isShiftDown()); ++ ++ // Cast to keyed should be safe as the recipe will never be a MerchantRecipe. ++ this.server.getRecipeManager().byKey(CraftNamespacedKey.toMinecraft(((org.bukkit.Keyed) event.getRecipe()).getKey())).ifPresent((recipeholder) -> { ++ ((RecipeBookMenu) this.player.containerMenu).handlePlacement(event.isShiftClick(), recipeholder, this.player); + }); ++ // CraftBukkit end + } + } + } + + @Override +- @Override +- public void handleContainerButtonClick(ServerboundContainerButtonClickPacket serverboundcontainerbuttonclickpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundcontainerbuttonclickpacket, this, this.player.serverLevel()); ++ public void handleContainerButtonClick(ServerboundContainerButtonClickPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- if (this.player.containerMenu.containerId == serverboundcontainerbuttonclickpacket.getContainerId() && !this.player.isSpectator()) { ++ if (this.player.containerMenu.containerId == packet.getContainerId() && !this.player.isSpectator()) { + if (!this.player.containerMenu.stillValid(this.player)) { + ServerGamePacketListenerImpl.LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu); + } else { +- boolean flag = this.player.containerMenu.clickMenuButton(this.player, serverboundcontainerbuttonclickpacket.getButtonId()); ++ boolean flag = this.player.containerMenu.clickMenuButton(this.player, packet.getButtonId()); + + if (flag) { + this.player.containerMenu.broadcastChanges(); +@@ -1778,36 +2726,72 @@ + } + + @Override +- @Override +- public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket serverboundsetcreativemodeslotpacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundsetcreativemodeslotpacket, this, this.player.serverLevel()); ++ public void handleSetCreativeModeSlot(ServerboundSetCreativeModeSlotPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.gameMode.isCreative()) { +- boolean flag = serverboundsetcreativemodeslotpacket.getSlotNum() < 0; +- ItemStack itemstack = serverboundsetcreativemodeslotpacket.getItem(); ++ boolean flag = packet.getSlotNum() < 0; ++ ItemStack itemstack = packet.getItem(); + + if (!itemstack.isItemEnabled(this.player.level().enabledFeatures())) { + return; + } + +- CompoundTag compoundtag = BlockItem.getBlockEntityData(itemstack); ++ CompoundTag nbttagcompound = BlockItem.getBlockEntityData(itemstack); + +- if (!itemstack.isEmpty() && compoundtag != null && compoundtag.contains("x") && compoundtag.contains("y") && compoundtag.contains("z")) { +- BlockPos blockpos = BlockEntity.getPosFromTag(compoundtag); ++ if (!itemstack.isEmpty() && nbttagcompound != null && nbttagcompound.contains("x") && nbttagcompound.contains("y") && nbttagcompound.contains("z")) { ++ BlockPos blockposition = BlockEntity.getPosFromTag(nbttagcompound); + +- if (this.player.level().isLoaded(blockpos)) { +- BlockEntity blockentity = this.player.level().getBlockEntity(blockpos); ++ if (this.player.level().isLoaded(blockposition)) { ++ BlockEntity tileentity = this.player.level().getBlockEntity(blockposition); + +- if (blockentity != null) { +- blockentity.saveToItem(itemstack); ++ if (tileentity != null) { ++ tileentity.saveToItem(itemstack); + } + } + } + +- boolean flag1 = serverboundsetcreativemodeslotpacket.getSlotNum() >= 1 && serverboundsetcreativemodeslotpacket.getSlotNum() <= 45; ++ boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45; + boolean flag2 = itemstack.isEmpty() || itemstack.getDamageValue() >= 0 && itemstack.getCount() <= 64 && !itemstack.isEmpty(); ++ if (flag || (flag1 && !ItemStack.matches(this.player.inventoryMenu.getSlot(packet.getSlotNum()).getItem(), packet.getItem()))) { // Insist on valid slot ++ // CraftBukkit start - Call click event ++ InventoryView inventory = this.player.inventoryMenu.getBukkitView(); ++ org.bukkit.inventory.ItemStack item = CraftItemStack.asBukkitCopy(packet.getItem()); + ++ SlotType type = SlotType.QUICKBAR; ++ if (flag) { ++ type = SlotType.OUTSIDE; ++ } else if (packet.getSlotNum() < 36) { ++ if (packet.getSlotNum() >= 5 && packet.getSlotNum() < 9) { ++ type = SlotType.ARMOR; ++ } else { ++ type = SlotType.CONTAINER; ++ } ++ } ++ InventoryCreativeEvent event = new InventoryCreativeEvent(inventory, type, flag ? -999 : packet.getSlotNum(), item); ++ cserver.getPluginManager().callEvent(event); ++ ++ itemstack = CraftItemStack.asNMSCopy(event.getCursor()); ++ ++ switch (event.getResult()) { ++ case ALLOW: ++ // Plugin cleared the id / stacksize checks ++ flag2 = true; ++ break; ++ case DEFAULT: ++ break; ++ case DENY: ++ // Reset the slot ++ if (packet.getSlotNum() >= 0) { ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(this.player.inventoryMenu.containerId, this.player.inventoryMenu.incrementStateId(), packet.getSlotNum(), this.player.inventoryMenu.getSlot(packet.getSlotNum()).getItem())); ++ this.player.connection.send(new ClientboundContainerSetSlotPacket(-1, this.player.inventoryMenu.incrementStateId(), -1, ItemStack.EMPTY)); ++ } ++ return; ++ } ++ } ++ // CraftBukkit end ++ + if (flag1 && flag2) { +- this.player.inventoryMenu.getSlot(serverboundsetcreativemodeslotpacket.getSlotNum()).setByPlayer(itemstack); ++ this.player.inventoryMenu.getSlot(packet.getSlotNum()).setByPlayer(itemstack); + this.player.inventoryMenu.broadcastChanges(); + } else if (flag && flag2 && this.dropSpamTickCount < 200) { + this.dropSpamTickCount += 20; +@@ -1818,76 +2802,81 @@ + } + + @Override +- @Override +- public void handleSignUpdate(ServerboundSignUpdatePacket serverboundsignupdatepacket) { +- List<String> list = (List) Stream.of(serverboundsignupdatepacket.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); ++ public void handleSignUpdate(ServerboundSignUpdatePacket packet) { ++ List<String> list = (List) Stream.of(packet.getLines()).map(ChatFormatting::stripFormatting).collect(Collectors.toList()); + + this.filterTextPacket(list).thenAcceptAsync((list1) -> { +- this.updateSignText(serverboundsignupdatepacket, list1); ++ this.updateSignText(packet, list1); + }, this.server); + } + +- private void updateSignText(ServerboundSignUpdatePacket serverboundsignupdatepacket, List<FilteredText> list) { ++ private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) { ++ if (this.player.isImmobile()) return; // CraftBukkit + this.player.resetLastActionTime(); +- ServerLevel serverlevel = this.player.serverLevel(); +- BlockPos blockpos = serverboundsignupdatepacket.getPos(); ++ ServerLevel worldserver = this.player.serverLevel(); ++ BlockPos blockposition = packet.getPos(); + +- if (serverlevel.hasChunkAt(blockpos)) { +- BlockEntity blockentity = serverlevel.getBlockEntity(blockpos); ++ if (worldserver.hasChunkAt(blockposition)) { ++ BlockEntity tileentity = worldserver.getBlockEntity(blockposition); + +- if (!(blockentity instanceof SignBlockEntity)) { ++ if (!(tileentity instanceof SignBlockEntity)) { + return; + } + +- SignBlockEntity signblockentity = (SignBlockEntity) blockentity; ++ SignBlockEntity tileentitysign = (SignBlockEntity) tileentity; + +- signblockentity.updateSignText(this.player, serverboundsignupdatepacket.isFrontText(), list); ++ tileentitysign.updateSignText(this.player, packet.isFrontText(), filteredText); + } + + } + + @Override +- @Override +- public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket serverboundplayerabilitiespacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundplayerabilitiespacket, this, this.player.serverLevel()); +- this.player.getAbilities().flying = serverboundplayerabilitiespacket.isFlying() && this.player.getAbilities().mayfly; ++ public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ // CraftBukkit start ++ if (this.player.getAbilities().mayfly && this.player.getAbilities().flying != packet.isFlying()) { ++ PlayerToggleFlightEvent event = new PlayerToggleFlightEvent(this.player.getBukkitEntity(), packet.isFlying()); ++ this.cserver.getPluginManager().callEvent(event); ++ if (!event.isCancelled()) { ++ this.player.getAbilities().flying = packet.isFlying(); // Actually set the player's flying status ++ } else { ++ this.player.onUpdateAbilities(); // Tell the player their ability was reverted ++ } ++ } ++ // CraftBukkit end + } + + @Override +- @Override + public void handleClientInformation(ServerboundClientInformationPacket serverboundclientinformationpacket) { + PacketUtils.ensureRunningOnSameThread(serverboundclientinformationpacket, this, this.player.serverLevel()); + this.player.updateOptions(serverboundclientinformationpacket.information()); + } + + @Override +- @Override +- public void handleChangeDifficulty(ServerboundChangeDifficultyPacket serverboundchangedifficultypacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundchangedifficultypacket, this, this.player.serverLevel()); ++ public void handleChangeDifficulty(ServerboundChangeDifficultyPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { +- this.server.setDifficulty(serverboundchangedifficultypacket.getDifficulty(), false); ++ this.server.setDifficulty(packet.getDifficulty(), false); + } + } + + @Override +- @Override +- public void handleLockDifficulty(ServerboundLockDifficultyPacket serverboundlockdifficultypacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundlockdifficultypacket, this, this.player.serverLevel()); ++ public void handleLockDifficulty(ServerboundLockDifficultyPacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); + if (this.player.hasPermissions(2) || this.isSingleplayerOwner()) { +- this.server.setDifficultyLocked(serverboundlockdifficultypacket.isLocked()); ++ this.server.setDifficultyLocked(packet.isLocked()); + } + } + + @Override +- @Override +- public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket serverboundchatsessionupdatepacket) { +- PacketUtils.ensureRunningOnSameThread(serverboundchatsessionupdatepacket, this, this.player.serverLevel()); +- RemoteChatSession.Data remotechatsession_data = serverboundchatsessionupdatepacket.chatSession(); +- ProfilePublicKey.Data profilepublickey_data = this.chatSession != null ? this.chatSession.profilePublicKey().data() : null; +- ProfilePublicKey.Data profilepublickey_data1 = remotechatsession_data.profilePublicKey(); ++ public void handleChatSessionUpdate(ServerboundChatSessionUpdatePacket packet) { ++ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel()); ++ RemoteChatSession.Data remotechatsession_a = packet.chatSession(); ++ ProfilePublicKey.Data profilepublickey_a = this.chatSession != null ? this.chatSession.profilePublicKey().data() : null; ++ ProfilePublicKey.Data profilepublickey_a1 = remotechatsession_a.profilePublicKey(); + +- if (!Objects.equals(profilepublickey_data, profilepublickey_data1)) { +- if (profilepublickey_data != null && profilepublickey_data1.expiresAt().isBefore(profilepublickey_data.expiresAt())) { ++ if (!Objects.equals(profilepublickey_a, profilepublickey_a1)) { ++ if (profilepublickey_a != null && profilepublickey_a1.expiresAt().isBefore(profilepublickey_a.expiresAt())) { + this.disconnect(ProfilePublicKey.EXPIRED_PROFILE_PUBLIC_KEY); + } else { + try { +@@ -1898,10 +2887,10 @@ + return; + } + +- this.resetPlayerChatState(remotechatsession_data.validate(this.player.getGameProfile(), signaturevalidator)); +- } catch (ProfilePublicKey.ValidationException profilepublickey_validationexception) { +- ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_validationexception.getMessage()); +- this.disconnect(profilepublickey_validationexception.getComponent()); ++ this.resetPlayerChatState(remotechatsession_a.validate(this.player.getGameProfile(), signaturevalidator)); ++ } catch (ProfilePublicKey.b profilepublickey_b) { ++ ServerGamePacketListenerImpl.LOGGER.error("Failed to validate profile key: {}", profilepublickey_b.getMessage()); ++ this.disconnect(profilepublickey_b.getComponent()); + } + + } +@@ -1909,33 +2898,30 @@ + } + + @Override +- @Override + public void handleConfigurationAcknowledged(ServerboundConfigurationAcknowledgedPacket serverboundconfigurationacknowledgedpacket) { + if (!this.waitingForSwitchToConfig) { + throw new IllegalStateException("Client acknowledged config, but none was requested"); + } else { +- this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))); ++ this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit + } + } + + @Override +- @Override + public void handleChunkBatchReceived(ServerboundChunkBatchReceivedPacket serverboundchunkbatchreceivedpacket) { + PacketUtils.ensureRunningOnSameThread(serverboundchunkbatchreceivedpacket, this, this.player.serverLevel()); + this.chunkSender.onChunkBatchReceivedByClient(serverboundchunkbatchreceivedpacket.desiredChunksPerTick()); + } + +- private void resetPlayerChatState(RemoteChatSession remotechatsession) { +- this.chatSession = remotechatsession; +- this.signedMessageDecoder = remotechatsession.createMessageDecoder(this.player.getUUID()); ++ private void resetPlayerChatState(RemoteChatSession chatSession) { ++ this.chatSession = chatSession; ++ this.signedMessageDecoder = chatSession.createMessageDecoder(this.player.getUUID()); + this.chatMessageChain.append(() -> { +- this.player.setChatSession(remotechatsession); ++ this.player.setChatSession(chatSession); + this.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.INITIALIZE_CHAT), List.of(this.player))); + }); + } + + @Override +- @Override + public ServerPlayer getPlayer() { + return this.player; + } +@@ -1943,6 +2929,6 @@ + @FunctionalInterface + private interface EntityInteraction { + +- InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); ++ InteractionResult run(ServerPlayer player, Entity entity, EnumHand hand); + } + } diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch new file mode 100644 index 0000000000..9e2351225b --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch @@ -0,0 +1,123 @@ +--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java +@@ -11,60 +11,101 @@ + import net.minecraft.network.protocol.status.ServerStatus; + import net.minecraft.server.MinecraftServer; + ++// CraftBukkit start ++import java.net.InetAddress; ++import java.util.HashMap; ++// CraftBukkit end ++ + public class ServerHandshakePacketListenerImpl implements ServerHandshakePacketListener { + ++ // CraftBukkit start - add fields ++ private static final HashMap<InetAddress, Long> throttleTracker = new HashMap<InetAddress, Long>(); ++ private static int throttleCounter = 0; ++ // CraftBukkit end + private static final Component IGNORE_STATUS_REASON = Component.translatable("disconnect.ignoring_status_request"); + private final MinecraftServer server; + private final Connection connection; + +- public ServerHandshakePacketListenerImpl(MinecraftServer minecraftserver, Connection connection) { +- this.server = minecraftserver; ++ public ServerHandshakePacketListenerImpl(MinecraftServer server, Connection connection) { ++ this.server = server; + this.connection = connection; + } + + @Override +- @Override +- public void handleIntention(ClientIntentionPacket clientintentionpacket) { +- switch (clientintentionpacket.intention()) { ++ public void handleIntention(ClientIntentionPacket packet) { ++ this.connection.hostname = packet.hostName() + ":" + packet.port(); // CraftBukkit - set hostname ++ switch (packet.intention()) { + case LOGIN: + this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); +- if (clientintentionpacket.protocolVersion() != SharedConstants.getCurrentVersion().getProtocolVersion()) { +- MutableComponent mutablecomponent; ++ // CraftBukkit start - Connection throttle ++ try { ++ long currentTime = System.currentTimeMillis(); ++ long connectionThrottle = this.server.server.getConnectionThrottle(); ++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress(); + +- if (clientintentionpacket.protocolVersion() < 754) { +- mutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName()); ++ synchronized (throttleTracker) { ++ if (throttleTracker.containsKey(address) && !"127.0.0.1".equals(address.getHostAddress()) && currentTime - throttleTracker.get(address) < connectionThrottle) { ++ throttleTracker.put(address, currentTime); ++ MutableComponent chatmessage = Component.literal("Connection throttled! Please wait before reconnecting."); ++ this.connection.send(new ClientboundLoginDisconnectPacket(chatmessage)); ++ this.connection.disconnect(chatmessage); ++ return; ++ } ++ ++ throttleTracker.put(address, currentTime); ++ throttleCounter++; ++ if (throttleCounter > 200) { ++ throttleCounter = 0; ++ ++ // Cleanup stale entries ++ java.util.Iterator iter = throttleTracker.entrySet().iterator(); ++ while (iter.hasNext()) { ++ java.util.Map.Entry<InetAddress, Long> entry = (java.util.Map.Entry) iter.next(); ++ if (entry.getValue() > connectionThrottle) { ++ iter.remove(); ++ } ++ } ++ } ++ } ++ } 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; ++ ++ if (packet.protocolVersion() < 754) { ++ ichatmutablecomponent = Component.translatable("multiplayer.disconnect.outdated_client", SharedConstants.getCurrentVersion().getName()); + } else { +- mutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName()); ++ ichatmutablecomponent = Component.translatable("multiplayer.disconnect.incompatible", SharedConstants.getCurrentVersion().getName()); + } + +- this.connection.send(new ClientboundLoginDisconnectPacket(mutablecomponent)); +- this.connection.disconnect(mutablecomponent); ++ this.connection.send(new ClientboundLoginDisconnectPacket(ichatmutablecomponent)); ++ this.connection.disconnect(ichatmutablecomponent); + } else { + this.connection.setListener(new ServerLoginPacketListenerImpl(this.server, this.connection)); + } + break; + case STATUS: +- ServerStatus serverstatus = this.server.getStatus(); ++ ServerStatus serverping = this.server.getStatus(); + +- if (this.server.repliesToStatus() && serverstatus != null) { ++ if (this.server.repliesToStatus() && serverping != null) { + this.connection.setClientboundProtocolAfterHandshake(ClientIntent.STATUS); +- this.connection.setListener(new ServerStatusPacketListenerImpl(serverstatus, this.connection)); ++ this.connection.setListener(new ServerStatusPacketListenerImpl(serverping, this.connection)); + } else { + this.connection.disconnect(ServerHandshakePacketListenerImpl.IGNORE_STATUS_REASON); + } + break; + default: +- throw new UnsupportedOperationException("Invalid intention " + clientintentionpacket.intention()); ++ throw new UnsupportedOperationException("Invalid intention " + packet.intention()); + } + + } + + @Override +- @Override +- public void onDisconnect(Component component) {} ++ public void onDisconnect(Component reason) {} + + @Override +- @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected(); + } diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch new file mode 100644 index 0000000000..54daae88c3 --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch @@ -0,0 +1,306 @@ +--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +@@ -32,6 +32,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; +@@ -39,6 +40,10 @@ + import net.minecraft.world.entity.player.Player; + import org.apache.commons.lang3.Validate; + import org.slf4j.Logger; ++import org.bukkit.craftbukkit.util.Waitable; ++import org.bukkit.event.player.AsyncPlayerPreLoginEvent; ++import org.bukkit.event.player.PlayerPreLoginEvent; ++// CraftBukkit end + + public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { + +@@ -48,31 +53,31 @@ + private static final Component DISCONNECT_UNEXPECTED_QUERY = Component.translatable("multiplayer.disconnect.unexpected_query_response"); + private final byte[] challenge; + final MinecraftServer server; +- final Connection connection; +- private volatile ServerLoginPacketListenerImpl.State state; ++ public final Connection connection; ++ private volatile ServerLoginPacketListenerImpl.EnumProtocolState state; + private int tick; + @Nullable + String requestedUsername; + @Nullable + private GameProfile authenticatedProfile; + private final String serverId; ++ private ServerPlayer player; // CraftBukkit + +- public ServerLoginPacketListenerImpl(MinecraftServer minecraftserver, Connection connection) { +- this.state = ServerLoginPacketListenerImpl.State.HELLO; ++ public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) { ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.HELLO; + this.serverId = ""; +- this.server = minecraftserver; ++ this.server = server; + this.connection = connection; + this.challenge = Ints.toByteArray(RandomSource.create().nextInt()); + } + + @Override +- @Override + public void tick() { +- if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { ++ if (this.state == ServerLoginPacketListenerImpl.EnumProtocolState.VERIFYING) { + this.verifyLoginAndFinishConnectionSetup((GameProfile) Objects.requireNonNull(this.authenticatedProfile)); + } + +- if (this.state == ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) { ++ if (this.state == ServerLoginPacketListenerImpl.EnumProtocolState.WAITING_FOR_DUPE_DISCONNECT && !this.isPlayerAlreadyInWorld((GameProfile) Objects.requireNonNull(this.authenticatedProfile))) { + this.finishLoginAndWaitForClient(this.authenticatedProfile); + } + +@@ -82,17 +87,23 @@ + + } + ++ // CraftBukkit start ++ @Deprecated ++ public void disconnect(String s) { ++ disconnect(Component.literal(s)); ++ } ++ // CraftBukkit end ++ + @Override +- @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected(); + } + +- public void disconnect(Component component) { ++ public void disconnect(Component reason) { + try { +- ServerLoginPacketListenerImpl.LOGGER.info("Disconnecting {}: {}", this.getUserName(), component.getString()); +- this.connection.send(new ClientboundLoginDisconnectPacket(component)); +- this.connection.disconnect(component); ++ ServerLoginPacketListenerImpl.LOGGER.info("Disconnecting {}: {}", this.getUserName(), reason.getString()); ++ this.connection.send(new ClientboundLoginDisconnectPacket(reason)); ++ this.connection.disconnect(reason); + } catch (Exception exception) { + ServerLoginPacketListenerImpl.LOGGER.error("Error whilst disconnecting player", exception); + } +@@ -104,9 +115,8 @@ + } + + @Override +- @Override +- public void onDisconnect(Component component) { +- ServerLoginPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.getUserName(), component.getString()); ++ public void onDisconnect(Component reason) { ++ ServerLoginPacketListenerImpl.LOGGER.info("{} lost connection: {}", this.getUserName(), reason.getString()); + } + + public String getUserName() { +@@ -116,18 +126,17 @@ + } + + @Override +- @Override +- public void handleHello(ServerboundHelloPacket serverboundhellopacket) { +- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); +- Validate.validState(Player.isValidUsername(serverboundhellopacket.name()), "Invalid characters in username", new Object[0]); +- this.requestedUsername = serverboundhellopacket.name(); ++ public void handleHello(ServerboundHelloPacket packet) { ++ Validate.validState(this.state == ServerLoginPacketListenerImpl.EnumProtocolState.HELLO, "Unexpected hello packet", new Object[0]); ++ Validate.validState(Player.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]); ++ this.requestedUsername = packet.name(); + GameProfile gameprofile = this.server.getSingleplayerProfile(); + + if (gameprofile != null && this.requestedUsername.equalsIgnoreCase(gameprofile.getName())) { + this.startClientVerification(gameprofile); + } else { + if (this.server.usesAuthentication() && !this.connection.isMemoryConnection()) { +- this.state = ServerLoginPacketListenerImpl.State.KEY; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.KEY; + this.connection.send(new ClientboundHelloPacket("", this.server.getKeyPair().getPublic().getEncoded(), this.challenge)); + } else { + this.startClientVerification(UUIDUtil.createOfflineProfile(this.requestedUsername)); +@@ -138,15 +147,17 @@ + + void startClientVerification(GameProfile gameprofile) { + this.authenticatedProfile = gameprofile; +- this.state = ServerLoginPacketListenerImpl.State.VERIFYING; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.VERIFYING; + } + + private void verifyLoginAndFinishConnectionSetup(GameProfile gameprofile) { + PlayerList playerlist = this.server.getPlayerList(); +- Component component = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), gameprofile); ++ // CraftBukkit start - fire PlayerLoginEvent ++ this.player = playerlist.canPlayerLogin(this, gameprofile); // CraftBukkit + +- if (component != null) { +- this.disconnect(component); ++ if (this.player == null) { ++ // this.disconnect(ichatbasecomponent); ++ // CraftBukkit end + } else { + if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) { + this.connection.send(new ClientboundLoginCompressionPacket(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> { +@@ -154,10 +165,10 @@ + })); + } + +- boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile); ++ boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference + + if (flag) { +- this.state = ServerLoginPacketListenerImpl.State.WAITING_FOR_DUPE_DISCONNECT; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.WAITING_FOR_DUPE_DISCONNECT; + } else { + this.finishLoginAndWaitForClient(gameprofile); + } +@@ -166,37 +177,35 @@ + } + + private void finishLoginAndWaitForClient(GameProfile gameprofile) { +- this.state = ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.PROTOCOL_SWITCHING; + this.connection.send(new ClientboundGameProfilePacket(gameprofile)); + } + + @Override +- @Override +- public void handleKey(ServerboundKeyPacket serverboundkeypacket) { +- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.KEY, "Unexpected key packet", new Object[0]); ++ public void handleKey(ServerboundKeyPacket packet) { ++ Validate.validState(this.state == ServerLoginPacketListenerImpl.EnumProtocolState.KEY, "Unexpected key packet", new Object[0]); + + final String s; + + try { + PrivateKey privatekey = this.server.getKeyPair().getPrivate(); + +- if (!serverboundkeypacket.isChallengeValid(this.challenge, privatekey)) { ++ if (!packet.isChallengeValid(this.challenge, privatekey)) { + throw new IllegalStateException("Protocol error"); + } + +- SecretKey secretkey = serverboundkeypacket.getSecretKey(privatekey); ++ SecretKey secretkey = packet.getSecretKey(privatekey); + Cipher cipher = Crypt.getCipher(2, secretkey); + Cipher cipher1 = Crypt.getCipher(1, secretkey); + + s = (new BigInteger(Crypt.digestData("", this.server.getKeyPair().getPublic(), secretkey))).toString(16); +- this.state = ServerLoginPacketListenerImpl.State.AUTHENTICATING; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.AUTHENTICATING; + this.connection.setEncryptionKey(cipher, cipher1); +- } catch (CryptException cryptexception) { +- throw new IllegalStateException("Protocol error", cryptexception); ++ } catch (CryptException cryptographyexception) { ++ throw new IllegalStateException("Protocol error", cryptographyexception); + } + + Thread thread = new Thread("User Authenticator #" + ServerLoginPacketListenerImpl.UNIQUE_THREAD_ID.incrementAndGet()) { +- @Override + public void run() { + String s1 = (String) Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); + +@@ -206,6 +215,43 @@ + if (profileresult != null) { + GameProfile gameprofile = profileresult.profile(); + ++ // CraftBukkit start - fire PlayerPreLoginEvent ++ if (!connection.isConnected()) { ++ return; ++ } ++ ++ String playerName = gameprofile.getName(); ++ java.net.InetAddress address = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress(); ++ java.util.UUID uniqueId = gameprofile.getId(); ++ final org.bukkit.craftbukkit.CraftServer server = ServerLoginPacketListenerImpl.this.server.server; ++ ++ AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId); ++ server.getPluginManager().callEvent(asyncEvent); ++ ++ if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) { ++ final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId); ++ if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { ++ event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage()); ++ } ++ Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() { ++ @Override ++ protected PlayerPreLoginEvent.Result evaluate() { ++ server.getPluginManager().callEvent(event); ++ return event.getResult(); ++ }}; ++ ++ ServerLoginPacketListenerImpl.this.server.processQueue.add(waitable); ++ if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) { ++ disconnect(event.getKickMessage()); ++ return; ++ } ++ } else { ++ if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) { ++ disconnect(asyncEvent.getKickMessage()); ++ return; ++ } ++ } ++ // CraftBukkit end + ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId()); + ServerLoginPacketListenerImpl.this.startClientVerification(gameprofile); + } else if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { +@@ -223,6 +269,11 @@ + 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) { ++ disconnect("Failed to verify username!"); ++ server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception); ++ // CraftBukkit end + } + + } +@@ -240,35 +291,32 @@ + } + + @Override +- @Override + public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket serverboundcustomqueryanswerpacket) { + this.disconnect(ServerLoginPacketListenerImpl.DISCONNECT_UNEXPECTED_QUERY); + } + + @Override +- @Override + public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundloginacknowledgedpacket) { +- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]); ++ Validate.validState(this.state == ServerLoginPacketListenerImpl.EnumProtocolState.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]); + CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile)); +- ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie); ++ ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit + + this.connection.setListener(serverconfigurationpacketlistenerimpl); + serverconfigurationpacketlistenerimpl.startConfiguration(); +- this.state = ServerLoginPacketListenerImpl.State.ACCEPTED; ++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.ACCEPTED; + } + + @Override +- @Override +- public void fillListenerSpecificCrashDetails(CrashReportCategory crashreportcategory) { +- crashreportcategory.setDetail("Login phase", () -> { ++ public void fillListenerSpecificCrashDetails(CrashReportCategory crashreportsystemdetails) { ++ crashreportsystemdetails.setDetail("Login phase", () -> { + return this.state.toString(); + }); + } + +- private static enum State { ++ private static enum EnumProtocolState { + + HELLO, KEY, AUTHENTICATING, NEGOTIATING, VERIFYING, WAITING_FOR_DUPE_DISCONNECT, PROTOCOL_SWITCHING, ACCEPTED; + +- private State() {} ++ private EnumProtocolState() {} + } + } diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch new file mode 100644 index 0000000000..540cc32d8a --- /dev/null +++ b/patch-remap/mache-spigotflower/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch @@ -0,0 +1,166 @@ +--- a/net/minecraft/server/network/ServerStatusPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +@@ -1,5 +1,12 @@ + package net.minecraft.server.network; + ++// 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.network.Connection; + import net.minecraft.network.chat.Component; + import net.minecraft.network.protocol.status.ClientboundPongResponsePacket; +@@ -8,6 +15,12 @@ + import net.minecraft.network.protocol.status.ServerStatusPacketListener; + import net.minecraft.network.protocol.status.ServerboundPingRequestPacket; + import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket; ++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 { + +@@ -16,36 +29,126 @@ + private final Connection connection; + private boolean hasRequestedStatus; + +- public ServerStatusPacketListenerImpl(ServerStatus serverstatus, Connection connection) { +- this.status = serverstatus; ++ public ServerStatusPacketListenerImpl(ServerStatus status, Connection connection) { ++ this.status = status; + this.connection = connection; + } + + @Override +- @Override +- public void onDisconnect(Component component) {} ++ public void onDisconnect(Component reason) {} + + @Override +- @Override + public boolean isAcceptingMessages() { + return this.connection.isConnected(); + } + + @Override +- @Override +- public void handleStatusRequest(ServerboundStatusRequestPacket serverboundstatusrequestpacket) { ++ public void handleStatusRequest(ServerboundStatusRequestPacket packet) { + if (this.hasRequestedStatus) { + this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON); + } else { + this.hasRequestedStatus = true; +- this.connection.send(new ClientboundStatusResponsePacket(this.status)); ++ // 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(connection.hostname, ((InetSocketAddress) connection.getRemoteAddress()).getAddress(), server.getMotd(), server.getPlayerList().getMaxPlayers()); ++ } ++ ++ @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<Player> iterator() throws UnsupportedOperationException { ++ return new Iterator<Player>() { ++ int i; ++ int ret = Integer.MIN_VALUE; ++ ServerPlayer player; ++ ++ @Override ++ public boolean hasNext() { ++ if (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 (!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<GameProfile> profiles = new java.util.ArrayList<GameProfile>(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); ++ } ++ } ++ } ++ ++ ServerStatus.ServerPingPlayerSample playerSample = new ServerStatus.ServerPingPlayerSample(event.getMaxPlayers(), profiles.size(), (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.a(event.icon.value)) : Optional.empty(), ++ server.enforceSecureProfile() ++ ); ++ ++ this.connection.send(new ClientboundStatusResponsePacket(ping)); ++ // CraftBukkit end + } + } + + @Override +- @Override +- public void handlePingRequest(ServerboundPingRequestPacket serverboundpingrequestpacket) { +- this.connection.send(new ClientboundPongResponsePacket(serverboundpingrequestpacket.getTime())); ++ public void handlePingRequest(ServerboundPingRequestPacket packet) { ++ this.connection.send(new ClientboundPongResponsePacket(packet.getTime())); + this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON); + } + } |