diff options
author | MiniDigger | Martin <[email protected]> | 2024-01-14 11:04:49 +0100 |
---|---|---|
committer | MiniDigger | Martin <[email protected]> | 2024-01-14 11:04:49 +0100 |
commit | bee74680e607c2e29b038329f62181238911cd83 (patch) | |
tree | 708fd1a4a0227d9071243adf2a42d5e9e96cde4a /patch-remap/mache-vineflower-stripped/net/minecraft/server/network | |
parent | 0a44692ef6ff6e255d48eb3ba1bb114166eafda9 (diff) | |
download | Paper-softspoon.tar.gz Paper-softspoon.zip |
add remapped patches as a testsoftspoon
Diffstat (limited to 'patch-remap/mache-vineflower-stripped/net/minecraft/server/network')
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 + } + } + |