From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Mark Vainomaa 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..8edc33bde29e967cec488d0f5e2f1097291978a6 --- /dev/null +++ b/src/main/java/com/destroystokyo/paper/event/server/GS4QueryEvent.java @@ -0,0 +1,424 @@ +package com.destroystokyo.paper.event.server; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import org.bukkit.Server; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +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 HANDLER_LIST = new HandlerList(); + + private final QueryType queryType; + private final InetAddress querierAddress; + private QueryResponse response; + + @ApiStatus.Internal + public GS4QueryEvent(@NotNull QueryType queryType, @NotNull InetAddress querierAddress, @NotNull QueryResponse response) { + super(true); // should always be called async + this.queryType = queryType; + this.querierAddress = querierAddress; + this.response = response; + } + + /** + * Get query type + * + * @return query type + */ + @NotNull + public QueryType getQueryType() { + return this.queryType; + } + + /** + * Get querier address + * + * @return querier address + */ + @NotNull + public InetAddress getQuerierAddress() { + return this.querierAddress; + } + + /** + * Get query response + * + * @return query response + */ + @NotNull + public QueryResponse getResponse() { + return this.response; + } + + /** + * Set query response + * + * @param response query response + */ + public void setResponse(@NotNull QueryResponse response) { + this.response = Preconditions.checkNotNull(response, "response"); + } + + @NotNull + @Override + public HandlerList getHandlers() { + return HANDLER_LIST; + } + + @NotNull + 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
MINECRAFT
), 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 players; + private final String serverVersion; + private final Collection plugins; + + private QueryResponse(String motd, String gameVersion, String map, int currentPlayers, int maxPlayers, String hostname, int port, Collection players, String serverVersion, Collection 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 + */ + @NotNull + 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 + */ + @NotNull + 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 + */ + @NotNull + 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 + */ + @NotNull + 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 + */ + @NotNull + public Collection getPlayers() { + return this.players; + } + + /** + * Get server software (name and version) which will be used to reply to the query. + * + * @return server software + */ + @NotNull + public String getServerVersion() { + return this.serverVersion; + } + + /** + * Get list of plugins which will be used to reply to the query. + * + * @return collection of plugins + */ + @NotNull + public Collection getPlugins() { + return this.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 final List players = new ArrayList<>(); + private final List 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 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 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 this.name; + } + + public void setName(@NotNull String name) { + this.name = name; + } + + public void setVersion(@NotNull String version) { + this.version = version; + } + + @NotNull + public String getVersion() { + return this.version; + } + + @NotNull + public static PluginInformation of(@NotNull String name, @NotNull String version) { + return new PluginInformation(name, version); + } + } + } +}