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..c1a8e295b66501e0580bcfc041bca51e69109062
--- /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.EntityPlayer;
+import net.minecraft.server.MinecraftServer;
+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.getMotd(), server.getPlayerCount(), server.getMaxPlayers(),
+                server.getServerModName() + ' ' + server.getVersion(), protocolVersion, icon);
+        this.server = server;
+    }
+
+    @Override
+    protected final Object[] getOnlinePlayers() {
+        return this.server.getPlayerList().players.toArray();
+    }
+
+    @Override
+    protected final Player getBukkitPlayer(Object player) {
+        return ((EntityPlayer) 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..a2a409e635dde08f7c53e67164b967a000511e94
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/network/PaperStatusClient.java
@@ -0,0 +1,11 @@
+package com.destroystokyo.paper.network;
+
+import net.minecraft.server.NetworkManager;
+
+class PaperStatusClient extends PaperNetworkClient implements StatusClient {
+
+    PaperStatusClient(NetworkManager 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..26ddf92f86fccc2b2562f7abf4788d1ed6b73205
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/network/StandardPaperServerListPingEventImpl.java
@@ -0,0 +1,112 @@
+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 net.minecraft.server.MinecraftServer;
+import net.minecraft.server.NetworkManager;
+import net.minecraft.server.PacketStatusOutServerInfo;
+import net.minecraft.server.ServerPing;
+import org.bukkit.craftbukkit.util.CraftChatMessage;
+
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nonnull;
+
+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 GameProfile[] originalSample;
+
+    private StandardPaperServerListPingEventImpl(MinecraftServer server, NetworkManager networkManager, ServerPing ping) {
+        super(server, new PaperStatusClient(networkManager), ping.getServerData() != null ? ping.getServerData().getProtocolVersion() : -1, server.server.getServerIcon());
+        this.originalSample = ping.getPlayers() == null ? null : ping.getPlayers().getSample(); // 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 GameProfile[] getPlayerSampleHandle() {
+        if (this.originalSample != null) {
+            return this.originalSample;
+        }
+
+        List<PlayerProfile> entries = super.getPlayerSample();
+        if (entries.isEmpty()) {
+            return EMPTY_PROFILES;
+        }
+
+        GameProfile[] profiles = new GameProfile[entries.size()];
+        for (int i = 0; i < profiles.length; i++) {
+            /*
+             * 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.
+             */
+
+            PlayerProfile profile = entries.get(i);
+            if (profile.getId() != null && profile.getName() != null) {
+                profiles[i] = CraftPlayerProfile.asAuthlib(profile);
+            } else {
+                profiles[i] = new GameProfile(MoreObjects.firstNonNull(profile.getId(), FAKE_UUID), Strings.nullToEmpty(profile.getName()));
+            }
+        }
+
+        return profiles;
+    }
+
+    @SuppressWarnings("deprecation")
+    public static void processRequest(MinecraftServer server, NetworkManager networkManager) {
+        StandardPaperServerListPingEventImpl event = new StandardPaperServerListPingEventImpl(server, networkManager, server.getServerPing());
+        server.server.getPluginManager().callEvent(event);
+
+        // Close connection immediately if event is cancelled
+        if (event.isCancelled()) {
+            networkManager.close(null);
+            return;
+        }
+
+        // Setup response
+        ServerPing ping = new ServerPing();
+
+        // Description
+        ping.setMOTD(CraftChatMessage.fromString(event.getMotd(), true)[0]);
+
+        // Players
+        if (!event.shouldHidePlayers()) {
+            ping.setPlayerSample(new ServerPing.ServerPingPlayerSample(event.getMaxPlayers(), event.getNumPlayers()));
+            ping.getPlayers().setSample(event.getPlayerSampleHandle());
+        }
+
+        // Version
+        ping.setServerInfo(new ServerPing.ServerData(event.getVersion(), event.getProtocolVersion()));
+
+        // Favicon
+        if (event.getServerIcon() != null) {
+            ping.setFavicon(event.getServerIcon().getData());
+        }
+
+        // Send response
+        networkManager.sendPacket(new PacketStatusOutServerInfo(ping));
+    }
+
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index b14381fd33b19a511106046d0c66d58353c57e7d..983868ef2b2d9bbec19ba8a23cb6376de015b53e 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -2,6 +2,9 @@ package net.minecraft.server;
 
 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;
@@ -1119,7 +1122,7 @@ public abstract class MinecraftServer extends IAsyncTaskHandlerReentrant<TickTas
         if (i - this.T >= 5000000000L) {
             this.T = i;
             this.serverPing.setPlayerSample(new ServerPing.ServerPingPlayerSample(this.getMaxPlayers(), this.getPlayerCount()));
-            GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), 12)];
+            GameProfile[] agameprofile = new GameProfile[Math.min(this.getPlayerCount(), org.spigotmc.SpigotConfig.playerSample)]; // Paper
             int j = MathHelper.nextInt(this.r, 0, this.getPlayerCount() - agameprofile.length);
 
             for (int k = 0; k < agameprofile.length; ++k) {
diff --git a/src/main/java/net/minecraft/server/PacketStatusListener.java b/src/main/java/net/minecraft/server/PacketStatusListener.java
index 055555cb5ce63d41cb9a7f4114341b0685879b9e..9beb6fad228ccb3081b661c845836f4f29404d86 100644
--- a/src/main/java/net/minecraft/server/PacketStatusListener.java
+++ b/src/main/java/net/minecraft/server/PacketStatusListener.java
@@ -35,6 +35,8 @@ public class PacketStatusListener implements PacketStatusInListener {
             this.networkManager.close(PacketStatusListener.a);
         } else {
             this.d = true;
+            // Paper start - Replace everything
+            /*
             // CraftBukkit start
             // this.networkManager.sendPacket(new PacketStatusOutServerInfo(this.minecraftServer.getServerPing()));
             final Object[] players = minecraftServer.getPlayerList().players.toArray();
@@ -130,6 +132,9 @@ public class PacketStatusListener implements PacketStatusInListener {
             ping.setServerInfo(new ServerPing.ServerData(minecraftServer.getServerModName() + " " + minecraftServer.getVersion(), version));
 
             this.networkManager.sendPacket(new PacketStatusOutServerInfo(ping));
+            */
+            com.destroystokyo.paper.network.StandardPaperServerListPingEventImpl.processRequest(this.minecraftServer, this.networkManager);
+            // Paper end
         }
         // CraftBukkit end
     }
diff --git a/src/main/java/net/minecraft/server/ServerPing.java b/src/main/java/net/minecraft/server/ServerPing.java
index aa125a52dcdf4f137cfe17c5dc808b17d5e9de0b..ea52e89bd965afbd74f15b0e2974657319c28e4a 100644
--- a/src/main/java/net/minecraft/server/ServerPing.java
+++ b/src/main/java/net/minecraft/server/ServerPing.java
@@ -29,6 +29,7 @@ public class ServerPing {
         this.a = ichatbasecomponent;
     }
 
+    public ServerPingPlayerSample getPlayers() { return b(); } // Paper - OBFHELPER
     public ServerPing.ServerPingPlayerSample b() {
         return this.b;
     }
@@ -160,10 +161,12 @@ public class ServerPing {
             return this.b;
         }
 
+        public GameProfile[] getSample() { return c(); } // Paper - OBFHELPER
         public GameProfile[] c() {
             return this.c;
         }
 
+        public void setSample(GameProfile[] sample) { a(sample); } // Paper - OBFHELPER
         public void a(GameProfile[] agameprofile) {
             this.c = agameprofile;
         }
diff --git a/src/main/java/org/spigotmc/SpigotConfig.java b/src/main/java/org/spigotmc/SpigotConfig.java
index bf00d6d263b692e8a597694c689768f177835fde..b830bfefd2984f012de0e3877592e5198a2a02ea 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()
     {
-        playerSample = getInt( "settings.sample-count", 12 );
+        playerSample = Math.max( 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
     }
 
