From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Minecrell <minecrell@minecrell.net>
Date: Wed, 11 Oct 2017 15:56:26 +0200
Subject: [PATCH] Implement extended PaperServerListPingEvent


diff --git a/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d7110a33957a597592cacb864c947eb053e8563
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/network/PaperServerListPingEventImpl.java
@@ -0,0 +1,31 @@
+package com.destroystokyo.paper.network;
+
+import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
+import net.minecraft.server.MinecraftServer;
+import net.minecraft.server.level.ServerPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.util.CachedServerIcon;
+
+import javax.annotation.Nullable;
+
+class PaperServerListPingEventImpl extends PaperServerListPingEvent {
+
+    private final MinecraftServer server;
+
+    PaperServerListPingEventImpl(MinecraftServer server, StatusClient client, int protocolVersion, @Nullable CachedServerIcon icon) {
+        super(client, server.server.motd(), server.getPlayerCount(), server.getMaxPlayers(),
+                server.getServerModName() + ' ' + server.getServerVersion(), protocolVersion, icon);
+        this.server = server;
+    }
+
+    @Override
+    protected final Object[] getOnlinePlayers() {
+        return this.server.getPlayerList().players.toArray();
+    }
+
+    @Override
+    protected final Player getBukkitPlayer(Object player) {
+        return ((ServerPlayer) player).getBukkitEntity();
+    }
+
+}
diff --git a/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..d926ad804355ee2fdc5910b2505e8671602acdab
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java
@@ -0,0 +1,11 @@
+package com.destroystokyo.paper.network;
+
+import net.minecraft.network.Connection;
+
+class PaperStatusClient extends PaperNetworkClient implements StatusClient {
+
+    PaperStatusClient(Connection networkManager) {
+        super(networkManager);
+    }
+
+}
diff --git a/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b0bdc266109cdfb874f08bf74323603921d2260
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java
@@ -0,0 +1,116 @@
+package com.destroystokyo.paper.network;
+
+import com.destroystokyo.paper.profile.CraftPlayerProfile;
+import com.destroystokyo.paper.profile.PlayerProfile;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.mojang.authlib.GameProfile;
+import io.papermc.paper.adventure.AdventureComponent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.UUID;
+import javax.annotation.Nonnull;
+import net.minecraft.network.Connection;
+import net.minecraft.network.chat.Component;
+import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
+import net.minecraft.network.protocol.status.ServerStatus;
+import net.minecraft.server.MinecraftServer;
+import org.bukkit.craftbukkit.util.CraftIconCache;
+
+public final class StandardPaperServerListPingEventImpl extends PaperServerListPingEventImpl {
+
+    // private static final GameProfile[] EMPTY_PROFILES = new GameProfile[0];
+    private static final UUID FAKE_UUID = new UUID(0, 0);
+
+    private List<GameProfile> originalSample;
+
+    private StandardPaperServerListPingEventImpl(MinecraftServer server, Connection networkManager, ServerStatus ping) {
+        super(server, new PaperStatusClient(networkManager), ping.version().map(ServerStatus.Version::protocol).orElse(-1), server.server.getServerIcon());
+        this.originalSample = ping.players().map(ServerStatus.Players::sample).orElse(null); // GH-1473 - pre-tick race condition NPE
+    }
+
+    @Nonnull
+    @Override
+    public List<PlayerProfile> getPlayerSample() {
+        List<PlayerProfile> sample = super.getPlayerSample();
+
+        if (this.originalSample != null) {
+            for (GameProfile profile : this.originalSample) {
+                sample.add(CraftPlayerProfile.asBukkitCopy(profile));
+            }
+            this.originalSample = null;
+        }
+
+        return sample;
+    }
+
+    private List<GameProfile> getPlayerSampleHandle() {
+        if (this.originalSample != null) {
+            return this.originalSample;
+        }
+
+        List<PlayerProfile> entries = super.getPlayerSample();
+        if (entries.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        final List<GameProfile> profiles = new ArrayList<>();
+        for (PlayerProfile profile : entries) {
+            /*
+             * Avoid null UUIDs/names since that will make the response invalid
+             * on the client.
+             * Instead, fall back to a fake/empty UUID and an empty string as name.
+             * This can be used to create custom lines in the player list that do not
+             * refer to a specific player.
+             */
+            if (profile.getId() != null && profile.getName() != null) {
+                profiles.add(CraftPlayerProfile.asAuthlib(profile));
+            } else {
+                profiles.add(new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName())));
+            }
+        }
+        return profiles;
+    }
+
+    public static void processRequest(MinecraftServer server, Connection networkManager) {
+        StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getStatus());
+        server.server.getPluginManager().callEvent(event);
+
+        // Close connection immediately if event is cancelled
+        if (event.isCancelled()) {
+            networkManager.disconnect(null);
+            return;
+        }
+
+        // Setup response
+
+        // Description
+        final Component description = new AdventureComponent(event.motd());
+
+        // Players
+        final Optional<ServerStatus.Players> players;
+        if (!event.shouldHidePlayers()) {
+            players = Optional.of(new ServerStatus.Players(event.getMaxPlayers(), event.getNumPlayers(), event.getPlayerSampleHandle()));
+        } else {
+            players = Optional.empty();
+        }
+
+        // Version
+        final ServerStatus.Version version = new ServerStatus.Version(event.getVersion(), event.getProtocolVersion());
+
+        // Favicon
+        final Optional<ServerStatus.Favicon> favicon;
+        if (event.getServerIcon() != null) {
+            favicon = Optional.of(new ServerStatus.Favicon(((CraftIconCache) event.getServerIcon()).value));
+        } else {
+            favicon = Optional.empty();
+        }
+        final ServerStatus ping = new ServerStatus(description, players, Optional.of(version), favicon, server.enforceSecureProfile());
+
+        // Send response
+        networkManager.send(new ClientboundStatusResponsePacket(ping));
+    }
+
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index ee27251b9decc00a0094611cf79644bd83bce5c7..52f5c91d71a8fc86d14c3c4c9e258b9cf70ad57b 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -3,6 +3,9 @@ package net.minecraft.server;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
+import co.aikar.timings.Timings;
+import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
@@ -1328,7 +1331,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
         if (this.hidesOnlinePlayers()) {
             return new ServerStatus.Players(i, list.size(), List.of());
         } else {
-            int j = Math.min(list.size(), 12);
+            int j = Math.min(list.size(), org.spigotmc.SpigotConfig.playerSample); // Paper
             ObjectArrayList<GameProfile> objectarraylist = new ObjectArrayList(j);
             int k = Mth.nextInt(this.random, 0, list.size() - j);
 
diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
index d99b2235038eb1aba8cda474c4aa51e207149ef4..2c13147bc063a09bb7907d6f90c3a1e811a09eb1 100644
--- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
+++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java
@@ -48,6 +48,8 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene
             this.connection.disconnect(ServerStatusPacketListenerImpl.DISCONNECT_REASON);
         } else {
             this.hasRequestedStatus = true;
+            // Paper start - Replace everything
+            /*
             // CraftBukkit start
             // this.connection.send(new PacketStatusOutServerInfo(this.status));
             MinecraftServer server = MinecraftServer.getServer();
@@ -150,6 +152,9 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene
 
             this.connection.send(new ClientboundStatusResponsePacket(ping));
             // CraftBukkit end
+            */
+            com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(MinecraftServer.getServer(), this.connection);
+            // Paper end
         }
     }
 
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index 489ce6f439778b26eb33ede9432681d4bf9e0116..ddc5f2d9aa7dda6aff132392927e3d7e3674dbff 100644
--- a/src/main/java/org/spigotmc/SpigotConfig.java
+++ b/src/main/java/org/spigotmc/SpigotConfig.java
@@ -289,7 +289,7 @@ public class SpigotConfig
     public static int playerSample;
     private static void playerSample()
     {
-        SpigotConfig.playerSample = SpigotConfig.getInt( "settings.sample-count", 12 );
+        SpigotConfig.playerSample = Math.max( SpigotConfig.getInt( "settings.sample-count", 12 ), 0 ); // Paper - Avoid negative counts
         Bukkit.getLogger().log( Level.INFO, "Server Ping Player Sample Count: {0}", playerSample ); // Paper - Use logger
     }