From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mark Vainomaa <mikroskeem@mikroskeem.eu>
Date: Sun, 17 Mar 2019 21:46:27 +0200
Subject: [PATCH] Add GS4 Query event


diff --git a/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
new file mode 100644
index 0000000000000000000000000000000000000000..77a19995f6792a182c5a43d6714e7bda0f42df5b
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
@@ -0,0 +1,412 @@
+package com.destroystokyo.paper.event.server;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This event is fired if server is getting queried over GS4 Query protocol
+ *
+ * Adapted from Velocity's ProxyQueryEvent
+ *
+ * @author Mark Vainomaa
+ */
+public final class GS4QueryEvent extends Event {
+    private static final HandlerList handlers = new HandlerList();
+
+    private final QueryType queryType;
+    private final InetAddress querierAddress;
+    private QueryResponse response;
+
+    public GS4QueryEvent(@NotNull QueryType queryType, @NotNull InetAddress querierAddress, @NotNull QueryResponse response) {
+        super(true); // should always be called async
+        this.queryType = Preconditions.checkNotNull(queryType, "queryType");
+        this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress");
+        this.response = Preconditions.checkNotNull(response, "response");
+    }
+
+    /**
+     * Get query type
+     * @return query type
+     */
+    @NotNull
+    public QueryType getQueryType() {
+        return queryType;
+    }
+
+    /**
+     * Get querier address
+     * @return querier address
+     */
+    @NotNull
+    public InetAddress getQuerierAddress() {
+        return querierAddress;
+    }
+
+    /**
+     * Get query response
+     * @return query response
+     */
+    @NotNull
+    public QueryResponse getResponse() {
+        return response;
+    }
+
+    /**
+     * Set query response
+     * @param response query response
+     */
+    public void setResponse(@NotNull QueryResponse response) {
+        this.response = Preconditions.checkNotNull(response, "response");
+    }
+
+    @Override
+    public String toString() {
+        return "GS4QueryEvent{" +
+            "queryType=" + queryType +
+            ", querierAddress=" + querierAddress +
+            ", response=" + response +
+            '}';
+    }
+
+    @NotNull
+    @Override
+    public HandlerList getHandlers() {
+        return handlers;
+    }
+
+    @NotNull
+    public static HandlerList getHandlerList() {
+        return handlers;
+    }
+
+    /**
+     * The type of query
+     */
+    public enum QueryType {
+        /**
+         * Basic query asks only a subset of information, such as motd, game type (hardcoded to <pre>MINECRAFT</pre>), map,
+         * current players, max players, server port and server motd
+         */
+        BASIC,
+
+        /**
+         * Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here).
+         */
+        FULL
+        ;
+    }
+
+    public final static class QueryResponse {
+        private final String motd;
+        private final String gameVersion;
+        private final String map;
+        private final int currentPlayers;
+        private final int maxPlayers;
+        private final String hostname;
+        private final int port;
+        private final Collection<String> players;
+        private final String serverVersion;
+        private final Collection<PluginInformation> plugins;
+
+        private QueryResponse(String motd, String gameVersion, String map, int currentPlayers, int maxPlayers, String hostname, int port, Collection<String> players, String serverVersion, Collection<PluginInformation> plugins) {
+            this.motd = motd;
+            this.gameVersion = gameVersion;
+            this.map = map;
+            this.currentPlayers = currentPlayers;
+            this.maxPlayers = maxPlayers;
+            this.hostname = hostname;
+            this.port = port;
+            this.players = players;
+            this.serverVersion = serverVersion;
+            this.plugins = plugins;
+        }
+
+        /**
+         * Get motd which will be used to reply to the query. By default it is {@link org.bukkit.Server#getMotd()}.
+         * @return motd
+         */
+        @NotNull
+        public String getMotd() {
+            return motd;
+        }
+
+        /**
+         * Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent.
+         * @return game version
+         */
+        @NotNull
+        public String getGameVersion() {
+            return gameVersion;
+        }
+
+        /**
+         * Get map name which will be used to reply to the query. By default {@code world} is sent.
+         * @return map name
+         */
+        @NotNull
+        public String getMap() {
+            return map;
+        }
+
+        /**
+         * Get current online player count which will be used to reply to the query.
+         * @return online player count
+         */
+        public int getCurrentPlayers() {
+            return currentPlayers;
+        }
+
+        /**
+         * Get max player count which will be used to reply to the query.
+         * @return max player count
+         */
+        public int getMaxPlayers() {
+            return maxPlayers;
+        }
+
+        /**
+         * Get server (public facing) hostname
+         * @return server hostname
+         */
+        @NotNull
+        public String getHostname() {
+            return hostname;
+        }
+
+        /**
+         * Get server (public facing) port
+         * @return server port
+         */
+        public int getPort() {
+            return port;
+        }
+
+        /**
+         * Get collection of players which will be used to reply to the query.
+         * @return collection of players
+         */
+        @NotNull
+        public Collection<String> getPlayers() {
+            return players;
+        }
+
+        /**
+         * Get server software (name and version) which will be used to reply to the query.
+         * @return server software
+         */
+        @NotNull
+        public String getServerVersion() {
+            return serverVersion;
+        }
+
+        /**
+         * Get list of plugins which will be used to reply to the query.
+         * @return collection of plugins
+         */
+        @NotNull
+        public Collection<PluginInformation> getPlugins() {
+            return plugins;
+        }
+
+
+        /**
+         * Creates a new {@link Builder} instance from data represented by this response
+         * @return {@link QueryResponse} builder
+         */
+        @NotNull
+        public Builder toBuilder() {
+            return QueryResponse.builder()
+                .motd(getMotd())
+                .gameVersion(getGameVersion())
+                .map(getMap())
+                .currentPlayers(getCurrentPlayers())
+                .maxPlayers(getMaxPlayers())
+                .hostname(getHostname())
+                .port(getPort())
+                .players(getPlayers())
+                .serverVersion(getServerVersion())
+                .plugins(getPlugins());
+        }
+
+        /**
+         * Creates a new {@link Builder} instance
+         * @return {@link QueryResponse} builder
+         */
+        @NotNull
+        public static Builder builder() {
+            return new Builder();
+        }
+
+        /**
+         * A builder for {@link QueryResponse} objects.
+         */
+        public static final class Builder {
+            private String motd;
+            private String gameVersion;
+            private String map;
+            private String hostname;
+            private String serverVersion;
+
+            private int currentPlayers;
+            private int maxPlayers;
+            private int port;
+
+            private List<String> players = new ArrayList<>();
+            private List<PluginInformation> plugins = new ArrayList<>();
+
+            private Builder() {}
+
+            @NotNull
+            public Builder motd(@NotNull String motd) {
+                this.motd = Preconditions.checkNotNull(motd, "motd");
+                return this;
+            }
+
+            @NotNull
+            public Builder gameVersion(@NotNull String gameVersion) {
+                this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
+                return this;
+            }
+
+            @NotNull
+            public Builder map(@NotNull String map) {
+                this.map = Preconditions.checkNotNull(map, "map");
+                return this;
+            }
+
+            @NotNull
+            public Builder currentPlayers(int currentPlayers) {
+                Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
+                this.currentPlayers = currentPlayers;
+                return this;
+            }
+
+            @NotNull
+            public Builder maxPlayers(int maxPlayers) {
+                Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
+                this.maxPlayers = maxPlayers;
+                return this;
+            }
+
+            @NotNull
+            public Builder hostname(@NotNull String hostname) {
+                this.hostname = Preconditions.checkNotNull(hostname, "hostname");
+                return this;
+            }
+
+            @NotNull
+            public Builder port(int port) {
+                Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be between 1-65535");
+                this.port = port;
+                return this;
+            }
+
+            @NotNull
+            public Builder players(@NotNull Collection<String> players) {
+                this.players.addAll(Preconditions.checkNotNull(players, "players"));
+                return this;
+            }
+
+            @NotNull
+            public Builder players(@NotNull String... players) {
+                this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
+                return this;
+            }
+
+            @NotNull
+            public Builder clearPlayers() {
+                this.players.clear();
+                return this;
+            }
+
+            @NotNull
+            public Builder serverVersion(@NotNull String serverVersion) {
+                this.serverVersion = Preconditions.checkNotNull(serverVersion, "serverVersion");
+                return this;
+            }
+
+            @NotNull
+            public Builder plugins(@NotNull Collection<PluginInformation> plugins) {
+                this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
+                return this;
+            }
+
+            @NotNull
+            public Builder plugins(@NotNull PluginInformation... plugins) {
+                this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins")));
+                return this;
+            }
+
+            @NotNull
+            public Builder clearPlugins() {
+                this.plugins.clear();
+                return this;
+            }
+
+            /**
+             * Builds new {@link QueryResponse} with supplied data
+             * @return response
+             */
+            @NotNull
+            public QueryResponse build() {
+                return new QueryResponse(
+                    Preconditions.checkNotNull(motd, "motd"),
+                    Preconditions.checkNotNull(gameVersion, "gameVersion"),
+                    Preconditions.checkNotNull(map, "map"),
+                    currentPlayers,
+                    maxPlayers,
+                    Preconditions.checkNotNull(hostname, "hostname"),
+                    port,
+                    ImmutableList.copyOf(players),
+                    Preconditions.checkNotNull(serverVersion, "serverVersion"),
+                    ImmutableList.copyOf(plugins)
+                );
+            }
+        }
+
+        /**
+         * Plugin information
+         */
+        public static class PluginInformation {
+            private String name;
+            private String version;
+
+            public PluginInformation(@NotNull String name, @NotNull String version) {
+                this.name = Preconditions.checkNotNull(name, "name");
+                this.version = Preconditions.checkNotNull(version, "version");
+            }
+
+            @NotNull
+            public String getName() {
+                return name;
+            }
+
+            public void setName(@NotNull String name) {
+                this.name = name;
+            }
+
+            public void setVersion(@NotNull String version) {
+                this.version = version;
+            }
+
+            @NotNull
+            public String getVersion() {
+                return version;
+            }
+
+            @NotNull
+            public static PluginInformation of(@NotNull String name, @NotNull String version) {
+                return new PluginInformation(name, version);
+            }
+        }
+    }
+}