aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower-stripped/net/minecraft/server/network
diff options
context:
space:
mode:
authorMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
committerMiniDigger | Martin <[email protected]>2024-01-14 11:04:49 +0100
commitbee74680e607c2e29b038329f62181238911cd83 (patch)
tree708fd1a4a0227d9071243adf2a42d5e9e96cde4a /patch-remap/mache-vineflower-stripped/net/minecraft/server/network
parent0a44692ef6ff6e255d48eb3ba1bb114166eafda9 (diff)
downloadPaper-softspoon.tar.gz
Paper-softspoon.zip
add remapped patches as a testsoftspoon
Diffstat (limited to 'patch-remap/mache-vineflower-stripped/net/minecraft/server/network')
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch63
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch187
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch39
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch79
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch1729
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch65
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch161
-rw-r--r--patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch131
8 files changed, 2454 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
new file mode 100644
index 0000000000..e985c493ff
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/LegacyQueryHandler.java.patch
@@ -0,0 +1,63 @@
+--- a/net/minecraft/server/network/LegacyQueryHandler.java
++++ b/net/minecraft/server/network/LegacyQueryHandler.java
+@@ -31,12 +32,15 @@
+ return;
+ }
+
+- SocketAddress socketAddress = context.channel().remoteAddress();
+- int i = byteBuf.readableBytes();
++ 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) {
+- LOGGER.debug("Ping: (<1.3.x) from {}", socketAddress);
+- String string = createVersion0Response(this.server);
+- sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
++ LegacyQueryHandler.LOGGER.debug("Ping: (<1.3.x) from {}", socketaddress);
++ s = createVersion0Response(this.server, event); // CraftBukkit
++ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ } else {
+ if (byteBuf.readUnsignedByte() != 1) {
+ return;
+@@ -52,8 +56,8 @@
+ LOGGER.debug("Ping: (1.4-1.5.x) from {}", socketAddress);
+ }
+
+- String string = createVersion1Response(this.server);
+- sendFlushAndClose(context, createLegacyDisconnectPacket(context.alloc(), string));
++ s = createVersion1Response(this.server, event); // CraftBukkit
++ sendFlushAndClose(channelhandlercontext, createLegacyDisconnectPacket(channelhandlercontext.alloc(), s));
+ }
+
+ byteBuf.release();
+@@ -95,20 +107,16 @@
+ }
+ }
+
+- private static String createVersion0Response(ServerInfo serverInfo) {
+- return String.format(Locale.ROOT, "%s§%d§%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,
+- "§1\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 context) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..3575f2c830
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch
@@ -0,0 +1,187 @@
+--- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java
+@@ -24,6 +30,17 @@
+ import net.minecraft.util.VisibleForDebug;
+ 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();
+ public static final int LATENCY_CHECK_INTERVAL = 15000;
+@@ -36,13 +54,21 @@
+ private int latency;
+ private volatile boolean suspendFlushingOnServerThread = false;
+
+- public ServerCommonPacketListenerImpl(MinecraftServer minecraftServer, Connection connection, CommonListenerCookie commonListenerCookie) {
+- this.server = minecraftServer;
+- this.connection = connection;
++ public ServerCommonPacketListenerImpl(MinecraftServer minecraftserver, Connection networkmanager, CommonListenerCookie commonlistenercookie, ServerPlayer player) { // CraftBukkit
++ this.server = minecraftserver;
++ this.connection = networkmanager;
+ this.keepAliveTime = Util.getMillis();
+- this.latency = commonListenerCookie.latency();
++ this.latency = commonlistenercookie.latency();
++ // CraftBukkit start - add fields and methods
++ this.player = player;
++ this.cserver = minecraftserver.server;
+ }
+
++ public CraftPlayer getCraftPlayer() {
++ return (this.player == null) ? null : (CraftPlayer) this.player.getBukkitEntity();
++ // CraftBukkit end
++ }
++
+ @Override
+ public void onDisconnect(Component reason) {
+ if (this.isSingleplayerOwner()) {
+@@ -52,9 +82,11 @@
+ }
+
+ @Override
+- public void handleKeepAlive(ServerboundKeepAlivePacket serverboundKeepAlivePacket) {
+- if (this.keepAlivePending && serverboundKeepAlivePacket.getId() == this.keepAliveChallenge) {
+- int i = (int)(Util.getMillis() - this.keepAliveTime);
++ 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);
++
+ this.latency = (this.latency * 3 + i) / 4;
+ this.keepAlivePending = false;
+ } else if (!this.isSingleplayerOwner()) {
+@@ -66,10 +98,19 @@
+ 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
+ public void handleCustomPayload(ServerboundCustomPayloadPacket serverboundCustomPayloadPacket) {
+ }
+
++ public final boolean isDisconnected() {
++ return !this.player.joining && !this.connection.isConnected();
++ }
++ // CraftBukkit end
++
+ @Override
+ public void handleResourcePackResponse(ServerboundResourcePackPacket serverboundResourcePackPacket) {
+ PacketUtils.ensureRunningOnSameThread(serverboundResourcePackPacket, this, this.server);
+@@ -77,12 +156,15 @@
+ 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
++
+ }
+
+ protected void keepConnectionAlive() {
+ this.server.getProfiler().push("keepAlive");
+- long millis = Util.getMillis();
+- if (millis - this.keepAliveTime >= 15000L) {
++ long i = Util.getMillis();
++
++ if (i - this.keepAliveTime >= 25000L) { // CraftBukkit
+ if (this.keepAlivePending) {
+ this.disconnect(TIMEOUT_DISCONNECTION_MESSAGE);
+ } else {
+@@ -109,7 +191,15 @@
+ this.send(packet, null);
+ }
+
+- public void send(Packet<?> packet, @Nullable PacketSendListener packetSendListener) {
++ 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 {
+@@ -122,10 +215,67 @@
+ }
+ }
+
+- 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();
+- this.server.executeBlocking(this.connection::handleDisconnection);
++ MinecraftServer minecraftserver = this.server;
++ Connection networkmanager = this.connection;
++
++ Objects.requireNonNull(this.connection);
++ // CraftBukkit - Don't wait
++ minecraftserver.wrapRunnable(networkmanager::handleDisconnection);
+ }
+
+ protected boolean isSingleplayerOwner() {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..f007de6d2c
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java
+@@ -42,10 +44,10 @@
+ private ConfigurationTask currentTask;
+ private ClientInformation clientInformation;
+
+- public ServerConfigurationPacketListenerImpl(MinecraftServer minecraftServer, Connection connection, CommonListenerCookie commonListenerCookie) {
+- super(minecraftServer, connection, commonListenerCookie);
+- this.gameProfile = commonListenerCookie.gameProfile();
+- this.clientInformation = commonListenerCookie.clientInformation();
++ 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
+@@ -116,14 +117,16 @@
+ return;
+ }
+
+- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), this.gameProfile);
+- if (component != null) {
+- this.disconnect(component);
++ Component ichatbasecomponent = null; // CraftBukkit - login checks already completed
++
++ if (ichatbasecomponent != null) {
++ this.disconnect(ichatbasecomponent);
+ return;
+ }
+
+- ServerPlayer playerForLogin = playerList.getPlayerForLogin(this.gameProfile, this.clientInformation);
+- playerList.placeNewPlayer(this.connection, playerForLogin, this.createCookie(this.clientInformation));
++ ServerPlayer entityplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation, this.player); // CraftBukkit
++
++ playerlist.placeNewPlayer(this.connection, entityplayer, this.createCookie(this.clientInformation));
+ this.connection.resumeInboundAfterProtocolChange();
+ } catch (Exception var5) {
+ LOGGER.error("Couldn't place player in world", (Throwable)var5);
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
new file mode 100644
index 0000000000..27a9942ae4
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerConnectionListener.java.patch
@@ -0,0 +1,79 @@
+--- a/net/minecraft/server/network/ServerConnectionListener.java
++++ b/net/minecraft/server/network/ServerConnectionListener.java
+@@ -78,45 +84,40 @@
+ LOGGER.info("Using default channel type");
+ }
+
+- this.channels
+- .add(
+- new ServerBootstrap()
+- .channel(clazz)
+- .childHandler(
+- new ChannelInitializer<Channel>() {
+- @Override
+- protected void initChannel(Channel channel) {
+- Connection.setInitialProtocolAttributes(channel);
+-
+- try {
+- channel.config().setOption(ChannelOption.TCP_NODELAY, true);
+- } catch (ChannelException var5) {
+- }
+-
+- ChannelPipeline channelPipeline = channel.pipeline()
+- .addLast("timeout", new ReadTimeoutHandler(30))
+- .addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
+- Connection.configureSerialization(channelPipeline, PacketFlow.SERVERBOUND, null);
+- int rateLimitPacketsPerSecond = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
+- Connection connection = (Connection)(rateLimitPacketsPerSecond > 0
+- ? new RateKickingConnection(rateLimitPacketsPerSecond)
+- : new Connection(PacketFlow.SERVERBOUND));
+- ServerConnectionListener.this.connections.add(connection);
+- connection.configurePacketHandler(channelPipeline);
+- connection.setListenerForServerboundHandshake(
+- new ServerHandshakePacketListenerImpl(ServerConnectionListener.this.server, connection)
+- );
+- }
+- }
+- )
+- .group(eventLoopGroup)
+- .localAddress(address, port)
+- .bind()
+- .syncUninterruptibly()
+- );
++ this.channels.add(((ServerBootstrap) ((ServerBootstrap) (new ServerBootstrap()).channel(oclass)).childHandler(new ChannelInitializer<Channel>() {
++ protected void initChannel(Channel channel) {
++ Connection.setInitialProtocolAttributes(channel);
++
++ try {
++ channel.config().setOption(ChannelOption.TCP_NODELAY, true);
++ } catch (ChannelException channelexception) {
++ ;
++ }
++
++ ChannelPipeline channelpipeline = channel.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("legacy_query", new LegacyQueryHandler(ServerConnectionListener.this.getServer()));
++
++ Connection.configureSerialization(channelpipeline, PacketFlow.SERVERBOUND, (BandwidthDebugMonitor) null);
++ int j = ServerConnectionListener.this.server.getRateLimitPacketsPerSecond();
++ 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(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() {
+ ChannelFuture channelFuture;
+ synchronized (this.channels) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..1ce9adc6bb
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch
@@ -0,0 +1,1729 @@
+--- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java
+@@ -179,11 +185,64 @@
+ import net.minecraft.world.phys.shapes.VoxelShape;
+ import org.slf4j.Logger;
+
+-public class ServerGamePacketListenerImpl
+- extends ServerCommonPacketListenerImpl
+- implements ServerGamePacketListener,
+- ServerPlayerConnection,
+- TickablePacketListener {
++// 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();
+ public static final double MAX_INTERACTION_DISTANCE = Mth.square(6.0);
+ private static final int NO_BLOCK_UPDATES_TO_ACK = -1;
+@@ -193,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;
+@@ -227,19 +288,36 @@
+ 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();
+- this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(serverPlayer.getUUID(), minecraftServer::enforceSecureProfile);
+- this.chatMessageChain = new FutureChain(minecraftServer);
++ 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.chatExecutor); // CraftBukkit - async chat
+ }
+
++ // CraftBukkit start - add fields
++ private int lastTick = MinecraftServer.currentTick;
++ private int allowedPlayerTicks = 1;
++ private int lastDropTick = MinecraftServer.currentTick;
++ private int lastBookTick = MinecraftServer.currentTick;
++ private int dropCount = 0;
++
++ // Get position of last block hit for BlockDamageLevel.STOPPED
++ private double lastPosX = Double.MAX_VALUE;
++ private double lastPosY = Double.MAX_VALUE;
++ private double lastPosZ = Double.MAX_VALUE;
++ private float lastPitch = Float.MAX_VALUE;
++ private float lastYaw = Float.MAX_VALUE;
++ private boolean justTeleported = false;
++ // CraftBukkit end
++
+ @Override
+ public void tick() {
+ if (this.ackBlockChangesUpTo > -1) {
+@@ -291,17 +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) {
++ 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"));
+ }
+ }
+@@ -385,16 +468,42 @@
+ double d2 = clampHorizontal(packet.getZ());
+ float f = Mth.wrapDegrees(packet.getYRot());
+ float f1 = Mth.wrapDegrees(packet.getXRot());
+- double d3 = d - this.vehicleFirstGoodX;
+- double d4 = d1 - this.vehicleFirstGoodY;
+- double d5 = d2 - this.vehicleFirstGoodZ;
+- double d6 = rootVehicle.getDeltaMovement().lengthSqr();
+- double d7 = d3 * d3 + d4 * d4 + d5 * d5;
+- if (d7 - d6 > 100.0 && !this.isSingleplayerOwner()) {
+- LOGGER.warn(
+- "{} (vehicle of {}) moved too quickly! {},{},{}", rootVehicle.getName().getString(), this.player.getName().getString(), d3, d4, d5
+- );
+- this.send(new ClientboundMoveVehiclePacket(rootVehicle));
++ 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;
++
++
++ // 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;
+ }
+
+@@ -422,14 +540,73 @@
+ LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", rootVehicle.getName().getString(), this.player.getName().getString(), Math.sqrt(d7));
+ }
+
+- rootVehicle.absMoveTo(d, d1, d2, f, f1);
+- boolean flag3 = serverLevel.noCollision(rootVehicle, rootVehicle.getBoundingBox().deflate(0.0625));
++ entity.absMoveTo(d3, d4, d5, f, f1);
++ player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit
++ boolean flag3 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D));
++
+ if (flag && (flag2 || !flag3)) {
+- rootVehicle.absMoveTo(x, y, z, f, f1);
+- this.send(new ClientboundMoveVehiclePacket(rootVehicle));
++ 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() - x, this.player.getY() - y, this.player.getZ() - z);
+ this.clientVehicleIsFloating = d4 >= -0.03125
+@@ -475,6 +640,7 @@
+ }
+
+ this.awaitingPositionFromClient = null;
++ this.player.serverLevel().getChunkSource().move(this.player); // CraftBukkit
+ }
+ }
+
+@@ -487,6 +658,7 @@
+ @Override
+ 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());
+ }
+
+@@ -505,17 +679,24 @@
+ @Override
+ public void handleCustomCommandSuggestions(ServerboundCommandSuggestionPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- StringReader stringReader = new StringReader(packet.getCommand());
+- if (stringReader.canRead() && stringReader.peek() == '/') {
+- stringReader.skip();
++ // 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());
+
+- ParseResults<CommandSourceStack> parseResults = this.server.getCommands().getDispatcher().parse(stringReader, this.player.createCommandSourceStack());
+- this.server
+- .getCommands()
+- .getDispatcher()
+- .getCompletionSuggestions(parseResults)
+- .thenAccept(suggestions -> this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions)));
++ if (stringreader.canRead() && stringreader.peek() == '/') {
++ stringreader.skip();
++ }
++
++ ParseResults<CommandSourceStack> parseresults = this.server.getCommands().getDispatcher().parse(stringreader, this.player.createCommandSourceStack());
++
++ this.server.getCommands().getDispatcher().getCompletionSuggestions(parseresults).thenAccept((suggestions) -> {
++ if (suggestions.isEmpty()) return; // CraftBukkit - don't send through empty suggestions - prevents [<args>] from showing for plugins with nothing more to offer
++ this.send(new ClientboundCommandSuggestionsPacket(packet.getId(), suggestions));
++ });
+ }
+
+ @Override
+@@ -721,12 +937,18 @@
+ @Override
+ public void handleSelectTrade(ServerboundSelectTradePacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- int item = packet.getItem();
+- if (this.player.containerMenu instanceof MerchantMenu merchantMenu) {
+- if (!merchantMenu.stillValid(this.player)) {
+- LOGGER.debug("Player {} interacted with invalid menu {}", this.player, merchantMenu);
++ int i = packet.getItem();
++ AbstractContainerMenu container = this.player.containerMenu;
++
++ 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
+
+ merchantMenu.setSelectionHint(item);
+ merchantMenu.tryMoveItems(item);
+@@ -735,8 +963,16 @@
+
+ @Override
+ public void handleEditBook(ServerboundEditBookPacket packet) {
+- int slot = packet.getSlot();
+- if (Inventory.isHotbarSlot(slot) || slot == 40) {
++ // 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> title = packet.getTitle();
+ title.ifPresent(list::add);
+@@ -749,9 +993,10 @@
+ }
+
+ private void updateBookContents(List<FilteredText> pages, int index) {
+- ItemStack item = this.player.getInventory().getItem(index);
+- if (item.is(Items.WRITABLE_BOOK)) {
+- this.updateBookPages(pages, UnaryOperator.identity(), item);
++ ItemStack itemstack = this.player.getInventory().getItem(index);
++
++ if (itemstack.is(Items.WRITABLE_BOOK)) {
++ this.updateBookPages(pages, UnaryOperator.identity(), itemstack.copy(), index, itemstack); // CraftBukkit
+ }
+ }
+
+@@ -772,13 +1019,16 @@
+ itemStack.addTagElement("title", StringTag.valueOf(title.raw()));
+ }
+
+- this.updateBookPages(pages, string -> Component.Serializer.toJson(Component.literal(string)), itemStack);
+- this.player.getInventory().setItem(index, itemStack);
++ this.updateBookPages(pages, (s) -> {
++ return Component.Serializer.toJson(Component.literal(s));
++ }, itemstack1, index, itemstack); // CraftBukkit
++ this.player.getInventory().setItem(index, itemstack); // CraftBukkit - event factory updates the hand book
+ }
+ }
+
+- private void updateBookPages(List<FilteredText> pages, UnaryOperator<String> updater, ItemStack book) {
+- ListTag list = 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()) {
+ pages.stream().map(filteredText1 -> StringTag.valueOf(updater.apply(filteredText1.filteredOrEmpty()))).forEach(list::add);
+ } else {
+@@ -799,7 +1055,8 @@
+ }
+ }
+
+- book.addTagElement("pages", list);
++ itemstack.addTagElement("pages", nbttaglist);
++ CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack); // CraftBukkit
+ }
+
+ @Override
+@@ -840,8 +1111,9 @@
+ if (containsInvalidValues(packet.getX(0.0), packet.getY(0.0), packet.getZ(0.0), packet.getYRot(0.0F), packet.getXRot(0.0F))) {
+ this.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_movement"));
+ } else {
+- ServerLevel serverLevel = this.player.serverLevel();
+- if (!this.player.wonGame) {
++ ServerLevel worldserver = this.player.serverLevel();
++
++ if (!this.player.wonGame && !this.player.isImmobile()) { // CraftBukkit
+ if (this.tickCount == 0) {
+ this.resetPosition();
+ }
+@@ -857,6 +1123,7 @@
+ this.player.getXRot()
+ );
+ }
++ this.allowedPlayerTicks = 20; // CraftBukkit
+ } else {
+ this.awaitingTeleportTime = this.tickCount;
+ double d = clampHorizontal(packet.getX(this.player.getX()));
+@@ -867,15 +1135,24 @@
+ 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 {
+- double x = this.player.getX();
+- double y = this.player.getY();
+- double z = this.player.getZ();
+- double d3 = d - this.firstGoodX;
+- double d4 = d1 - this.firstGoodY;
+- double d5 = d2 - this.firstGoodZ;
+- double d6 = this.player.getDeltaMovement().lengthSqr();
+- double d7 = d3 * d3 + d4 * d4 + d5 * d5;
++ // 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();
++ double d6 = d0 - this.firstGoodX;
++ double d7 = d1 - this.firstGoodY;
++ double d8 = d2 - this.firstGoodZ;
++ double d9 = this.player.getDeltaMovement().lengthSqr();
++ double d10 = d6 * d6 + d7 * d7 + d8 * d8;
++
+ if (this.player.isSleeping()) {
+ if (d7 > 1.0) {
+ this.teleport(this.player.getX(), this.player.getY(), this.player.getZ(), f, f1);
+@@ -884,8 +1162,14 @@
+ if (serverLevel.tickRateManager().runsNormally()) {
+ this.receivedMovePacketCount++;
+ int i = this.receivedMovePacketCount - this.knownMovePacketCount;
+- if (i > 5) {
+- LOGGER.debug("{} is sending move packets too frequently ({} packets since last tick)", this.player.getName().getString(), i);
++
++ // 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;
+ }
+
+@@ -895,8 +1187,10 @@
+ || !this.player.isFallFlying()
+ )) {
+ float f2 = this.player.isFallFlying() ? 300.0F : 100.0F;
+- if (d7 - d6 > (double)(f2 * (float)i) && !this.isSingleplayerOwner()) {
+- LOGGER.warn("{} moved too quickly! {},{},{}", this.player.getName().getString(), d3, d4, d5);
++
++ 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;
+ }
+@@ -913,11 +1209,15 @@
+ }
+
+ boolean flag1 = this.player.verticalCollisionBelow;
+- this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5));
+- d3 = d - this.player.getX();
+- double var36 = d1 - this.player.getY();
+- if (var36 > -0.5 || var36 < 0.5) {
+- var36 = 0.0;
++
++ 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();
++ d7 = d1 - this.player.getY();
++ if (d7 > -0.5D || d7 < 0.5D) {
++ d7 = 0.0D;
+ }
+
+ d5 = d2 - this.player.getZ();
+@@ -932,20 +1229,73 @@
+ LOGGER.warn("{} moved wrongly!", this.player.getName().getString());
+ }
+
+- if (this.player.noPhysics
+- || this.player.isSleeping()
+- || (!flag2 || !serverLevel.noCollision(this.player, boundingBox))
+- && !this.isPlayerCollidingWithAnythingNew(serverLevel, boundingBox, d, d1, d2)) {
+- this.player.absMoveTo(d, d1, d2, f, f1);
+- this.clientIsFloating = d4 >= -0.03125
+- && !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);
++ 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() - x, this.player.getY() - y, this.player.getZ() - z, packet.isOnGround());
+ this.player
+@@ -980,26 +1327,94 @@
+ if (!Shapes.joinIsNotEmpty(voxelShape1, voxelShape, BooleanOp.AND)) {
+ return true;
+ }
++
++ voxelshape1 = (VoxelShape) iterator.next();
++ } while (Shapes.joinIsNotEmpty(voxelshape1, voxelshape, BooleanOp.AND));
++
++ return true;
++ }
++
++ // 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, 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
+ }
+
+- return false;
++ 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(double x, double y, double z, float yaw, float pitch) {
+ this.teleport(x, y, z, yaw, pitch, Collections.emptySet());
+ }
+
+- public void teleport(double x, double y, double z, float yaw, float pitch, Set<RelativeMovement> relativeSet) {
+- double d = relativeSet.contains(RelativeMovement.X) ? this.player.getX() : 0.0;
+- double d1 = relativeSet.contains(RelativeMovement.Y) ? this.player.getY() : 0.0;
+- double d2 = relativeSet.contains(RelativeMovement.Z) ? this.player.getZ() : 0.0;
+- float f = relativeSet.contains(RelativeMovement.Y_ROT) ? this.player.getYRot() : 0.0F;
+- float f1 = relativeSet.contains(RelativeMovement.X_ROT) ? this.player.getXRot() : 0.0F;
+- this.awaitingPositionFromClient = new Vec3(x, y, z);
++ 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;
++ float f2 = set.contains(RelativeMovement.Y_ROT) ? this.player.getYRot() : 0.0F;
++ float f3 = set.contains(RelativeMovement.X_ROT) ? this.player.getXRot() : 0.0F;
++
++ this.awaitingPositionFromClient = new Vec3(d0, d1, d2);
+ if (++this.awaitingTeleport == Integer.MAX_VALUE) {
+ 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(x, y, z, yaw, pitch);
+ this.player.connection.send(new ClientboundPlayerPositionPacket(x - d, y - d1, z - d2, yaw - f, pitch - f1, relativeSet, this.awaitingTeleport));
+@@ -1008,21 +1423,56 @@
+ @Override
+ public void handlePlayerAction(ServerboundPlayerActionPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- BlockPos pos = packet.getPos();
++ if (this.player.isImmobile()) return; // CraftBukkit
++ BlockPos blockposition = packet.getPos();
++
+ this.player.resetLastActionTime();
+ ServerboundPlayerActionPacket.Action action = packet.getAction();
+ switch (action) {
+ case SWAP_ITEM_WITH_OFFHAND:
+ if (!this.player.isSpectator()) {
+- ItemStack itemInHand = this.player.getItemInHand(InteractionHand.OFF_HAND);
+- this.player.setItemInHand(InteractionHand.OFF_HAND, this.player.getItemInHand(InteractionHand.MAIN_HAND));
+- this.player.setItemInHand(InteractionHand.MAIN_HAND, itemInHand);
++ ItemStack itemstack = this.player.getItemInHand(EnumHand.OFF_HAND);
++
++ // 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);
+ }
+
+@@ -1059,6 +1511,7 @@
+ @Override
+ 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 serverLevel = this.player.serverLevel();
+ InteractionHand hand = packet.getHand();
+@@ -1074,20 +1531,19 @@
+ if (Math.abs(vec31.x()) < 1.0000001 && Math.abs(vec31.y()) < 1.0000001 && Math.abs(vec31.z()) < 1.0000001) {
+ Direction direction = hitResult.getDirection();
+ this.player.resetLastActionTime();
+- int maxBuildHeight = this.player.level().getMaxBuildHeight();
+- if (blockPos.getY() < maxBuildHeight) {
+- if (this.awaitingPositionFromClient == null
+- && this.player.distanceToSqr((double)blockPos.getX() + 0.5, (double)blockPos.getY() + 0.5, (double)blockPos.getZ() + 0.5) < 64.0
+- && serverLevel.mayInteract(this.player, blockPos)) {
+- InteractionResult interactionResult = this.player.gameMode.useItemOn(this.player, serverLevel, itemInHand, hand, hitResult);
+- if (direction == Direction.UP
+- && !interactionResult.consumesAction()
+- && blockPos.getY() >= maxBuildHeight - 1
+- && wasBlockPlacementAttempt(this.player, itemInHand)) {
+- Component component = Component.translatable("build.tooHigh", maxBuildHeight - 1).withStyle(ChatFormatting.RED);
+- this.player.sendSystemMessage(component, true);
+- } else if (interactionResult.shouldSwing()) {
+- this.player.swing(hand, true);
++ int i = this.player.level().getMaxBuildHeight();
++
++ 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 (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(ichatmutablecomponent, true);
++ } else if (enuminteractionresult.shouldSwing()) {
++ this.player.swing(enumhand, true);
+ }
+ }
+ } else {
+@@ -1112,16 +1564,62 @@
+ @Override
+ public void handleUseItem(ServerboundUseItemPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.ackBlockChangesUpTo(packet.getSequence());
+ ServerLevel serverLevel = this.player.serverLevel();
+ InteractionHand hand = packet.getHand();
+ ItemStack itemInHand = this.player.getItemInHand(hand);
+ this.player.resetLastActionTime();
+- if (!itemInHand.isEmpty() && itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- InteractionResult interactionResult = this.player.gameMode.useItem(this.player, serverLevel, itemInHand, hand);
+- if (interactionResult.shouldSwing()) {
+- this.player.swing(hand, true);
++ 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);
++
++ 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);
++ }
++
+ }
+ }
+
+@@ -1132,7 +1635,7 @@
+ for (ServerLevel serverLevel : this.server.getAllLevels()) {
+ Entity entity = packet.getEntity(serverLevel);
+ 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;
+ }
+ }
+@@ -1149,19 +1658,32 @@
+
+ @Override
+ public void onDisconnect(Component reason) {
+- LOGGER.info("{} lost connection: {}", this.player.getName().getString(), reason.getString());
++ // 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(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.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();
+ }
+
+@@ -1176,27 +1698,44 @@
+ @Override
+ 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()) {
+- if (this.player.getInventory().selected != packet.getSlot() && this.player.getUsedItemHand() == InteractionHand.MAIN_HAND) {
++ 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 = packet.getSlot();
+ this.player.resetLastActionTime();
+ } else {
+- LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
++ ServerGamePacketListenerImpl.LOGGER.warn("{} tried to set an invalid carried item", this.player.getName().getString());
++ this.disconnect("Invalid hotbar selection (Hacking?)"); // CraftBukkit
+ }
+ }
+
+ @Override
+ 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(packet.lastSeenMessages());
+ if (optional.isPresent()) {
+- this.server.submit(() -> {
+- PlayerChatMessage signedMessage;
++ // this.server.submit(() -> { // CraftBukkit - async chat
++ PlayerChatMessage playerchatmessage;
++
+ try {
+ signedMessage = this.getSignedMessage(packet, optional.get());
+ } catch (SignedMessageChain.DecodeException var6) {
+@@ -1204,13 +1744,15 @@
+ return;
+ }
+
+- CompletableFuture<FilteredText> completableFuture = this.filterTextPacket(signedMessage.signedContent());
+- Component component = this.server.getChatDecorator().decorate(this.player, signedMessage.decoratedContent());
+- this.chatMessageChain.append(completableFuture, filteredText -> {
+- PlayerChatMessage playerChatMessage = signedMessage.withUnsignedContent(component).filter(filteredText.mask());
+- this.broadcastChatMessage(playerChatMessage);
++ 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(ichatbasecomponent).filter(filteredtext.mask());
++
++ this.broadcastChatMessage(playerchatmessage1);
+ });
+- });
++ // }); // CraftBukkit - async chat
+ }
+ }
+ }
+@@ -1223,7 +1767,13 @@
+ Optional<LastSeenMessages> optional = this.tryHandleChat(packet.lastSeenMessages());
+ if (optional.isPresent()) {
+ this.server.submit(() -> {
+- this.performChatCommand(packet, 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();
+ });
+ }
+@@ -1231,21 +1782,36 @@
+ }
+
+ private void performChatCommand(ServerboundChatCommandPacket packet, LastSeenMessages lastSeenMessages) {
+- ParseResults<CommandSourceStack> parseResults = this.parseCommand(packet.command());
++ // CraftBukkit start
++ String command = "/" + packet.command();
++ ServerGamePacketListenerImpl.LOGGER.info(this.player.getScoreboardName() + " issued server command: " + command);
+
+- Map<String, PlayerChatMessage> map;
++ 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(packet, SignableCommand.of(parseResults), lastSeenMessages);
+- } catch (SignedMessageChain.DecodeException var6) {
+- this.handleMessageDecodeFailure(var6);
++ 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 commandSigningContext = new CommandSigningContext.SignedArguments(map);
+- parseResults = Commands.mapSource(
+- parseResults, commandSourceStack -> commandSourceStack.withSigningContext(commandSigningContext, this.chatMessageChain)
+- );
+- this.server.getCommands().performCommand(parseResults, packet.command());
++ CommandSigningContext.a commandsigningcontext_a = new CommandSigningContext.a(map);
++
++ parseresults = Commands.<CommandSourceStack>mapSource(parseresults, (commandlistenerwrapper) -> { // CraftBukkit - decompile error
++ return commandlistenerwrapper.withSigningContext(commandsigningcontext_a, this.chatMessageChain);
++ });
++ this.server.getCommands().performCommand(parseresults, command); // CraftBukkit
+ }
+
+ private void handleMessageDecodeFailure(SignedMessageChain.DecodeException exception) {
+@@ -1276,9 +1845,10 @@
+ return dispatcher.parse(command, this.player.createCommandSourceStack());
+ }
+
+- private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update update) {
+- Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(update);
+- if (this.player.getChatVisibility() == ChatVisiblity.HIDDEN) {
++ private Optional<LastSeenMessages> tryHandleChat(LastSeenMessages.Update lastseenmessages_b) {
++ Optional<LastSeenMessages> optional = this.unpackAndApplyLastSeen(lastseenmessages_b);
++
++ 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 {
+@@ -1309,19 +1882,149 @@
+ return false;
+ }
+
++ // 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);
++
++ 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 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) {
+- this.server.getPlayerList().broadcastChatMessage(message, this.player, ChatType.bind(ChatType.CHAT, this.player));
++ // 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"));
+ }
+ }
+@@ -1339,13 +2047,62 @@
+ @Override
+ public void handleAnimate(ServerboundSwingPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
++ // CraftBukkit start - Raytrace to look for 'rogue armswings'
++ float f1 = this.player.getXRot();
++ float f2 = this.player.getYRot();
++ double d0 = this.player.getX();
++ double d1 = this.player.getY() + (double) this.player.getEyeHeight();
++ double d2 = this.player.getZ();
++ Location origin = new Location(this.player.level().getWorld(), d0, d1, d2, f2, f1);
++
++ double d3 = player.gameMode.getGameModeForPlayer() == 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
+ 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();
+ switch (packet.getAction()) {
+ case PRESS_SHIFT_KEY:
+@@ -1411,17 +2184,13 @@
+ }
+
+ public void sendPlayerChatMessage(PlayerChatMessage chatMessage, ChatType.Bound boundType) {
+- 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())
+- )
+- );
++ // 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);
+ }
+
+@@ -1447,8 +2216,10 @@
+ @Override
+ public void handleInteract(ServerboundInteractPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- final ServerLevel serverLevel = this.player.serverLevel();
+- final Entity target = packet.getTarget(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(packet.isUsingSecondaryAction());
+ if (target != null) {
+@@ -1456,48 +2227,90 @@
+ return;
+ }
+
+- AABB boundingBox = target.getBoundingBox();
+- if (boundingBox.distanceToSqr(this.player.getEyePosition()) < MAX_INTERACTION_DISTANCE) {
+- packet.dispatch(
+- new ServerboundInteractPacket.Handler() {
+- private void performInteraction(InteractionHand hand, ServerGamePacketListenerImpl.EntityInteraction entityInteraction) {
+- ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(hand);
+- if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- ItemStack itemStack = itemInHand.copy();
+- InteractionResult interactionResult = entityInteraction.run(ServerGamePacketListenerImpl.this.player, target, hand);
+- if (interactionResult.consumesAction()) {
+- CriteriaTriggers.PLAYER_INTERACTED_WITH_ENTITY.trigger(ServerGamePacketListenerImpl.this.player, itemStack, target);
+- if (interactionResult.shouldSwing()) {
+- ServerGamePacketListenerImpl.this.player.swing(hand, true);
+- }
++ AABB axisalignedbb = entity.getBoundingBox();
++
++ 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(worldserver.enabledFeatures())) {
++ ItemStack itemstack1 = itemstack.copy();
++ // 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();
++
++ 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 (enuminteractionresult.shouldSwing()) {
++ ServerGamePacketListenerImpl.this.player.swing(enumhand, true);
++ }
++ }
++
+ }
+-
+- @Override
+- public void onInteraction(InteractionHand hand) {
+- this.performInteraction(hand, Player::interactOn);
+- }
+-
+- @Override
+- public void onInteraction(InteractionHand hand, Vec3 interactionLocation) {
+- this.performInteraction(hand, (player, entity, hand1) -> entity.interactAt(player, interactionLocation, hand1));
+- }
+-
+- @Override
+- public void onAttack() {
+- if (!(target instanceof ItemEntity)
+- && !(target instanceof ExperienceOrb)
+- && !(target instanceof AbstractArrow)
+- && target != ServerGamePacketListenerImpl.this.player) {
+- ItemStack itemInHand = ServerGamePacketListenerImpl.this.player.getItemInHand(InteractionHand.MAIN_HAND);
+- if (itemInHand.isItemEnabled(serverLevel.enabledFeatures())) {
+- ServerGamePacketListenerImpl.this.player.attack(target);
++ }
++
++ @Override
++ 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
++ 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
++ public void onAttack() {
++ // 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(worldserver.enabledFeatures())) {
++ ServerGamePacketListenerImpl.this.player.attack(entity);
++ // CraftBukkit start
++ if (!itemstack.isEmpty() && itemstack.getCount() <= -1) {
++ player.containerMenu.sendAllDataToRemote();
+ }
+- } else {
+- ServerGamePacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.invalid_entity_attacked"));
+- ServerGamePacketListenerImpl.LOGGER
+- .warn("Player {} tried to attack an invalid entity", ServerGamePacketListenerImpl.this.player.getName().getString());
++ // CraftBukkit end
+ }
+ }
+ }
+@@ -1537,15 +2356,21 @@
+ @Override
+ 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
+ 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 == packet.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)) {
+ LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+@@ -1558,12 +2383,122 @@
+ } else {
+ boolean flag = packet.getStateId() != this.player.containerMenu.getStateId();
+ this.player.containerMenu.suppressRemoteUpdates();
+- this.player.containerMenu.clicked(slotNum, packet.getButtonNum(), packet.getClickType(), this.player);
++ // CraftBukkit start - Call InventoryClickEvent
++ if (packet.getSlotNum() < -1 && packet.getSlotNum() != -999) {
++ return;
++ }
+
+ for (Entry<ItemStack> entry : Int2ObjectMaps.fastIterable(packet.getChangedSlots())) {
+ this.player.containerMenu.setRemoteSlotNoCopy(entry.getIntKey(), entry.getValue());
+ }
+
++ 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(packet.getCarriedItem());
+ this.player.containerMenu.resumeRemoteUpdates();
+ if (flag) {
+@@ -1586,13 +2690,18 @@
+ if (!this.player.containerMenu.stillValid(this.player)) {
+ LOGGER.debug("Player {} interacted with invalid menu {}", this.player, this.player.containerMenu);
+ } else {
+- this.server
+- .getRecipeManager()
+- .byKey(packet.getRecipe())
+- .ifPresent(
+- recipeHolder -> ((RecipeBookMenu)this.player.containerMenu)
+- .handlePlacement(packet.isShiftDown(), (RecipeHolder<?>)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
+ }
+ }
+ }
+@@ -1600,6 +2709,7 @@
+ @Override
+ 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 == packet.getContainerId() && !this.player.isSpectator()) {
+ if (!this.player.containerMenu.stillValid(this.player)) {
+@@ -1635,7 +2751,45 @@
+ }
+
+ boolean flag1 = packet.getSlotNum() >= 1 && packet.getSlotNum() <= 45;
+- boolean flag2 = item.isEmpty() || item.getDamageValue() >= 0 && item.getCount() <= 64 && !item.isEmpty();
++ 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(packet.getSlotNum()).setByPlayer(item);
+ this.player.inventoryMenu.broadcastChanges();
+@@ -1653,6 +2811,7 @@
+ }
+
+ private void updateSignText(ServerboundSignUpdatePacket packet, List<FilteredText> filteredText) {
++ if (this.player.isImmobile()) return; // CraftBukkit
+ this.player.resetLastActionTime();
+ ServerLevel serverLevel = this.player.serverLevel();
+ BlockPos pos = packet.getPos();
+@@ -1668,7 +2833,17 @@
+ @Override
+ public void handlePlayerAbilities(ServerboundPlayerAbilitiesPacket packet) {
+ PacketUtils.ensureRunningOnSameThread(packet, this, this.player.serverLevel());
+- this.player.getAbilities().flying = packet.isFlying() && this.player.getAbilities().mayfly;
++ // 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
+@@ -1724,8 +2902,7 @@
+ if (!this.waitingForSwitchToConfig) {
+ throw new IllegalStateException("Client acknowledged config, but none was requested");
+ } else {
+- this.connection
+- .setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation())));
++ this.connection.setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()), this.player)); // CraftBukkit
+ }
+ }
+
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
new file mode 100644
index 0000000000..b4887ad7c6
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch
@@ -0,0 +1,65 @@
+--- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java
+@@ -10,7 +11,17 @@
+ 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;
+@@ -22,9 +33,44 @@
+
+ @Override
+ 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);
++ // CraftBukkit start - Connection throttle
++ try {
++ long currentTime = System.currentTimeMillis();
++ long connectionThrottle = this.server.server.getConnectionThrottle();
++ InetAddress address = ((java.net.InetSocketAddress) this.connection.getRemoteAddress()).getAddress();
++
++ 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()) {
+ Component component;
+ if (packet.protocolVersion() < 754) {
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..4f1171a127
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch
@@ -0,0 +1,161 @@
+--- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
++++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java
+@@ -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 {
+ private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
+@@ -54,7 +60,8 @@
+ String requestedUsername;
+ @Nullable
+ private GameProfile authenticatedProfile;
+- private final String serverId = "";
++ private final String serverId;
++ private ServerPlayer player; // CraftBukkit
+
+ public ServerLoginPacketListenerImpl(MinecraftServer server, Connection connection) {
+ this.server = server;
+@@ -78,6 +87,13 @@
+ }
+ }
+
++ // CraftBukkit start
++ @Deprecated
++ public void disconnect(String s) {
++ disconnect(Component.literal(s));
++ }
++ // CraftBukkit end
++
+ @Override
+ public boolean isAcceptingMessages() {
+ return this.connection.isConnected();
+@@ -130,11 +150,14 @@
+ this.state = ServerLoginPacketListenerImpl.State.VERIFYING;
+ }
+
+- private void verifyLoginAndFinishConnectionSetup(GameProfile gameProfile) {
+- PlayerList playerList = this.server.getPlayerList();
+- Component component = playerList.canPlayerLogin(this.connection.getRemoteAddress(), gameProfile);
+- if (component != null) {
+- this.disconnect(component);
++ private void verifyLoginAndFinishConnectionSetup(GameProfile gameprofile) {
++ PlayerList playerlist = this.server.getPlayerList();
++ // CraftBukkit start - fire PlayerLoginEvent
++ this.player = playerlist.canPlayerLogin(this, gameprofile); // CraftBukkit
++
++ if (this.player == null) {
++ // this.disconnect(ichatbasecomponent);
++ // CraftBukkit end
+ } else {
+ if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
+ this.connection
+@@ -144,7 +165,8 @@
+ );
+ }
+
+- 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;
+ } else {
+@@ -185,13 +210,50 @@
+ String string1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized");
+
+ try {
+- ProfileResult profileResult = ServerLoginPacketListenerImpl.this.server
+- .getSessionService()
+- .hasJoinedServer(string1, string, this.getAddress());
+- if (profileResult != null) {
+- GameProfile gameProfile = profileResult.profile();
+- ServerLoginPacketListenerImpl.LOGGER.info("UUID of player {} is {}", gameProfile.getName(), gameProfile.getId());
+- ServerLoginPacketListenerImpl.this.startClientVerification(gameProfile);
++ ProfileResult profileresult = ServerLoginPacketListenerImpl.this.server.getSessionService().hasJoinedServer(s1, s, this.getAddress());
++
++ 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()) {
+ ServerLoginPacketListenerImpl.LOGGER.warn("Failed to verify username but will let them in anyway!");
+ ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(string1));
+@@ -207,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
+ }
+ }
+
+@@ -228,15 +296,14 @@
+ }
+
+ @Override
+- public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundLoginAcknowledgedPacket) {
+- Validate.validState(this.state == ServerLoginPacketListenerImpl.State.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet");
+- CommonListenerCookie commonListenerCookie = CommonListenerCookie.createInitial(Objects.requireNonNull(this.authenticatedProfile));
+- ServerConfigurationPacketListenerImpl serverConfigurationPacketListenerImpl = new ServerConfigurationPacketListenerImpl(
+- this.server, this.connection, commonListenerCookie
+- );
+- this.connection.setListener(serverConfigurationPacketListenerImpl);
+- serverConfigurationPacketListenerImpl.startConfiguration();
+- this.state = ServerLoginPacketListenerImpl.State.ACCEPTED;
++ public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundloginacknowledgedpacket) {
++ 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, this.player); // CraftBukkit
++
++ this.connection.setListener(serverconfigurationpacketlistenerimpl);
++ serverconfigurationpacketlistenerimpl.startConfiguration();
++ this.state = ServerLoginPacketListenerImpl.EnumProtocolState.ACCEPTED;
+ }
+
+ @Override
diff --git a/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
new file mode 100644
index 0000000000..706bb740d5
--- /dev/null
+++ b/patch-remap/mache-vineflower-stripped/net/minecraft/server/network/ServerStatusPacketListenerImpl.java.patch
@@ -0,0 +1,131 @@
+--- 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 {
+ private static final Component DISCONNECT_REASON = Component.translatable("multiplayer.status.request_handled");
+@@ -35,7 +48,101 @@
+ this.connection.disconnect(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
+ }
+ }
+