Paper/patches/api/0163-Add-GS4-Query-event.patch
Jake Potrebic f9c7f2a5c1
Begin switching to JSpecify annotations (#11448)
* Begin switching to JSpecify annotations

* more

* fixes
2024-09-29 12:52:13 -07:00

408 lines
13 KiB
Diff

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..5a8813c80e56d17ccaa35a7eef2f6bd70c7a3d96
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java
@@ -0,0 +1,395 @@
+package com.destroystokyo.paper.event.server;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import org.bukkit.Server;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
+import org.jetbrains.annotations.ApiStatus;
+import org.jspecify.annotations.NullMarked;
+
+/**
+ * This event is fired if server is getting queried over GS4 Query protocol.
+ * <br>
+ * Adapted from Velocity's ProxyQueryEvent
+ *
+ * @author Mark Vainomaa
+ */
+@NullMarked
+public final class GS4QueryEvent extends Event {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+
+ private final QueryType queryType;
+ private final InetAddress querierAddress;
+ private QueryResponse response;
+
+ @ApiStatus.Internal
+ public GS4QueryEvent(final QueryType queryType, final InetAddress querierAddress, final QueryResponse response) {
+ super(true); // should always be called async
+ this.queryType = queryType;
+ this.querierAddress = querierAddress;
+ this.response = response;
+ }
+
+ /**
+ * Get query type
+ *
+ * @return query type
+ */
+ public QueryType getQueryType() {
+ return this.queryType;
+ }
+
+ /**
+ * Get querier address
+ *
+ * @return querier address
+ */
+ public InetAddress getQuerierAddress() {
+ return this.querierAddress;
+ }
+
+ /**
+ * Get query response
+ *
+ * @return query response
+ */
+ public QueryResponse getResponse() {
+ return this.response;
+ }
+
+ /**
+ * Set query response
+ *
+ * @param response query response
+ */
+ public void setResponse(final QueryResponse response) {
+ this.response = Preconditions.checkNotNull(response, "response");
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+
+ public static HandlerList getHandlerList() {
+ return HANDLER_LIST;
+ }
+
+ /**
+ * 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 static final 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(final String motd, final String gameVersion, final String map, final int currentPlayers, final int maxPlayers, final String hostname, final int port, final Collection<String> players, final String serverVersion, final 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 Server#getMotd()}.
+ *
+ * @return motd
+ */
+ public String getMotd() {
+ return this.motd;
+ }
+
+ /**
+ * Get game version which will be used to reply to the query. By default, supported Minecraft versions range is sent.
+ *
+ * @return game version
+ */
+ public String getGameVersion() {
+ return this.gameVersion;
+ }
+
+ /**
+ * Get map name which will be used to reply to the query. By default {@code world} is sent.
+ *
+ * @return map name
+ */
+ public String getMap() {
+ return this.map;
+ }
+
+ /**
+ * Get current online player count which will be used to reply to the query.
+ *
+ * @return online player count
+ */
+ public int getCurrentPlayers() {
+ return this.currentPlayers;
+ }
+
+ /**
+ * Get max player count which will be used to reply to the query.
+ *
+ * @return max player count
+ */
+ public int getMaxPlayers() {
+ return this.maxPlayers;
+ }
+
+ /**
+ * Get server (public facing) hostname.
+ *
+ * @return server hostname
+ */
+ public String getHostname() {
+ return this.hostname;
+ }
+
+ /**
+ * Get server (public facing) port.
+ *
+ * @return server port
+ */
+ public int getPort() {
+ return this.port;
+ }
+
+ /**
+ * Get collection of players which will be used to reply to the query.
+ *
+ * @return collection of players
+ */
+ public Collection<String> getPlayers() {
+ return this.players;
+ }
+
+ /**
+ * Get server software (name and version) which will be used to reply to the query.
+ *
+ * @return server software
+ */
+ public String getServerVersion() {
+ return this.serverVersion;
+ }
+
+ /**
+ * Get list of plugins which will be used to reply to the query.
+ *
+ * @return collection of plugins
+ */
+ public Collection<PluginInformation> getPlugins() {
+ return this.plugins;
+ }
+
+ /**
+ * Creates a new {@link Builder} instance from data represented by this response.
+ *
+ * @return {@link QueryResponse} builder
+ */
+ public Builder toBuilder() {
+ return QueryResponse.builder()
+ .motd(this.getMotd())
+ .gameVersion(this.getGameVersion())
+ .map(this.getMap())
+ .currentPlayers(this.getCurrentPlayers())
+ .maxPlayers(this.getMaxPlayers())
+ .hostname(this.getHostname())
+ .port(this.getPort())
+ .players(this.getPlayers())
+ .serverVersion(this.getServerVersion())
+ .plugins(this.getPlugins());
+ }
+
+ /**
+ * Creates a new {@link Builder} instance.
+ *
+ * @return {@link QueryResponse} builder
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * A builder for {@link QueryResponse} objects.
+ */
+ public static final class Builder {
+
+ private @MonotonicNonNull String motd;
+ private @MonotonicNonNull String gameVersion;
+ private @MonotonicNonNull String map;
+ private @MonotonicNonNull String hostname;
+ private @MonotonicNonNull String serverVersion;
+
+ private int currentPlayers;
+ private int maxPlayers;
+ private int port;
+
+ private final List<String> players = new ArrayList<>();
+ private final List<PluginInformation> plugins = new ArrayList<>();
+
+ private Builder() {
+ }
+
+ public Builder motd(final String motd) {
+ this.motd = Preconditions.checkNotNull(motd, "motd");
+ return this;
+ }
+
+ public Builder gameVersion(final String gameVersion) {
+ this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
+ return this;
+ }
+
+ public Builder map(final String map) {
+ this.map = Preconditions.checkNotNull(map, "map");
+ return this;
+ }
+
+ public Builder currentPlayers(final int currentPlayers) {
+ Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
+ this.currentPlayers = currentPlayers;
+ return this;
+ }
+
+ public Builder maxPlayers(final int maxPlayers) {
+ Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
+ this.maxPlayers = maxPlayers;
+ return this;
+ }
+
+ public Builder hostname(final String hostname) {
+ this.hostname = Preconditions.checkNotNull(hostname, "hostname");
+ return this;
+ }
+
+ public Builder port(final int port) {
+ Preconditions.checkArgument(port >= 1 && port <= 65535, "port must be between 1-65535");
+ this.port = port;
+ return this;
+ }
+
+ public Builder players(final Collection<String> players) {
+ this.players.addAll(Preconditions.checkNotNull(players, "players"));
+ return this;
+ }
+
+ public Builder players(final String... players) {
+ this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
+ return this;
+ }
+
+ public Builder clearPlayers() {
+ this.players.clear();
+ return this;
+ }
+
+ public Builder serverVersion(final String serverVersion) {
+ this.serverVersion = Preconditions.checkNotNull(serverVersion, "serverVersion");
+ return this;
+ }
+
+ public Builder plugins(final Collection<PluginInformation> plugins) {
+ this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
+ return this;
+ }
+
+ public Builder plugins(final PluginInformation... plugins) {
+ this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins")));
+ return this;
+ }
+
+ public Builder clearPlugins() {
+ this.plugins.clear();
+ return this;
+ }
+
+ /**
+ * Builds new {@link QueryResponse} with supplied data.
+ *
+ * @return response
+ */
+ public QueryResponse build() {
+ return new QueryResponse(
+ Preconditions.checkNotNull(this.motd, "motd"),
+ Preconditions.checkNotNull(this.gameVersion, "gameVersion"),
+ Preconditions.checkNotNull(this.map, "map"),
+ this.currentPlayers,
+ this.maxPlayers,
+ Preconditions.checkNotNull(this.hostname, "hostname"),
+ this.port,
+ ImmutableList.copyOf(this.players),
+ Preconditions.checkNotNull(this.serverVersion, "serverVersion"),
+ ImmutableList.copyOf(this.plugins)
+ );
+ }
+ }
+
+ /**
+ * Plugin information
+ */
+ public static class PluginInformation {
+
+ private String name;
+ private String version;
+
+ public PluginInformation(final String name, final String version) {
+ this.name = Preconditions.checkNotNull(name, "name");
+ this.version = Preconditions.checkNotNull(version, "version");
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setVersion(final String version) {
+ this.version = version;
+ }
+
+ public String getVersion() {
+ return this.version;
+ }
+
+ public static PluginInformation of(final String name, final String version) {
+ return new PluginInformation(name, version);
+ }
+ }
+ }
+}