aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-spigotflower/net/minecraft/server/network
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-spigotflower/net/minecraft/server/network')
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/LegacyQueryHandler.java.patch59
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch258
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch103
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerConnectionListener.java.patch151
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch3151
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch123
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch306
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch166
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);
+ }
+ }