diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java b/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java new file mode 100644 index 000000000..51be358e4 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/PingOptions.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2018-2023 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.proxy.server; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.velocitypowered.api.network.ProtocolVersion; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import net.kyori.adventure.builder.AbstractBuilder; +import org.jetbrains.annotations.NotNull; + +/** + * Contains the parameters used to ping a {@link RegisteredServer}. + * This class is immutable. + * + * @since 3.2.0 + * @see RegisteredServer#ping(PingOptions) + */ +public final class PingOptions { + /** + * Default PingOptions. + */ + public static final PingOptions DEFAULT = PingOptions.builder().build(); + private final ProtocolVersion protocolVersion; + private final long timeout; + + private PingOptions(final Builder builder) { + this.protocolVersion = builder.protocolVersion; + this.timeout = builder.timeout; + } + + /** + * The protocol version used to ping the server. + * + * @return the emulated Minecraft version + */ + public ProtocolVersion getProtocolVersion() { + return this.protocolVersion; + } + + /** + * The maximum period of time to wait for a response from the remote server. + * + * @return the server ping timeout in milliseconds + */ + public long getTimeout() { + return this.timeout; + } + + /** + * Create a new builder to assign values to a new PingOptions. + * + * @return a new {@link PingOptions.Builder} + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (!(o instanceof PingOptions)) { + return false; + } + final PingOptions other = (PingOptions) o; + return Objects.equals(this.protocolVersion, other.protocolVersion) + && Objects.equals(this.timeout, other.timeout); + } + + @Override + public int hashCode() { + return Objects.hash(this.protocolVersion, this.timeout); + } + + @Override + public String toString() { + return "PingOptions{" + + "protocolVersion=" + protocolVersion + + ", timeout=" + timeout + + '}'; + } + + /** + * A builder for {@link PingOptions} objects. + * + * @since 3.2.0 + */ + public static final class Builder implements AbstractBuilder { + private ProtocolVersion protocolVersion = ProtocolVersion.UNKNOWN; + private long timeout = 0; + + private Builder() { + } + + /** + * Sets the protocol with which the server is to be pinged. + * + * @param protocolVersion the specified protocol + * @return this builder + */ + public Builder version(final @NotNull ProtocolVersion protocolVersion) { + checkNotNull(protocolVersion, "protocolVersion cannot be null"); + this.protocolVersion = protocolVersion; + return this; + } + + /** + * Sets the maximum time to wait to get the required {@link ServerPing}. + * + * @param timeout the timeout duration + * A value of 0 means that the read-timeout value + * from the Velocity configuration will be used, + * while a negative value means that there will + * be no timeout. + * @return this builder + */ + public Builder timeout(final @NotNull Duration timeout) { + checkNotNull(timeout, "timeout cannot be null"); + this.timeout = timeout.toMillis(); + return this; + } + + /** + * Sets the maximum time to wait to get the required {@link ServerPing}. + * + * @param time the timeout duration + * A value of 0 means that the read-timeout value + * from the Velocity configuration will be used, + * while a negative value means that there will + * be no timeout. + * @param timeunit the unit of time to be used to provide the timeout duration + * @return this builder + */ + public Builder timeout(final long time, final @NotNull TimeUnit timeunit) { + checkNotNull(timeunit, "timeunit cannot be null"); + this.timeout = timeunit.toMillis(time); + return this; + } + + /** + * Create a new {@link PingOptions} with the values of this Builder. + * + * @return a new PingOptions object + */ + @Override + public @NotNull PingOptions build() { + return new PingOptions(this); + } + } +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java index f2def1386..1766833ec 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/server/RegisteredServer.java @@ -40,4 +40,14 @@ public interface RegisteredServer extends ChannelMessageSink, Audience { * @return the server ping result from the server */ CompletableFuture ping(); + + /** + * Attempts to ping the remote server and return the server list ping result + * according to the options provided. + * + * @param pingOptions the options provided for pinging the server + * @return the server ping result from the server + * @since 3.2.0 + */ + CompletableFuture ping(PingOptions pingOptions); } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java index 610010c13..760ef2fea 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/util/ServerListPingHandler.java @@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection.util; import com.google.common.collect.ImmutableList; import com.spotify.futures.CompletableFutures; import com.velocitypowered.api.network.ProtocolVersion; +import com.velocitypowered.api.proxy.server.PingOptions; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerPing; import com.velocitypowered.api.util.ModInfo; @@ -64,11 +65,12 @@ public class ServerListPingHandler { List> pings = new ArrayList<>(); for (String s : servers) { Optional rs = server.getServer(s); - if (!rs.isPresent()) { + if (rs.isEmpty()) { continue; } VelocityRegisteredServer vrs = (VelocityRegisteredServer) rs.get(); - pings.add(vrs.ping(connection.getConnection().eventLoop(), responseProtocolVersion)); + pings.add(vrs.ping(connection.getConnection().eventLoop(), PingOptions.builder() + .version(responseProtocolVersion).build())); } if (pings.isEmpty()) { return CompletableFuture.completedFuture(fallback); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java index fe28ae00e..57780110b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/server/VelocityRegisteredServer.java @@ -26,9 +26,9 @@ import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; +import com.velocitypowered.api.proxy.server.PingOptions; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerPing; @@ -83,9 +83,14 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud return ImmutableList.copyOf(players.values()); } + @Override + public CompletableFuture ping(PingOptions pingOptions) { + return ping(null, pingOptions); + } + @Override public CompletableFuture ping() { - return ping(null, ProtocolVersion.UNKNOWN); + return ping(null, PingOptions.DEFAULT); } /** @@ -93,10 +98,10 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud * {@code version}. * * @param loop the event loop to use - * @param version the version to report + * @param pingOptions the options to apply to this ping * @return the server list ping response */ - public CompletableFuture ping(@Nullable EventLoop loop, ProtocolVersion version) { + public CompletableFuture ping(@Nullable EventLoop loop, PingOptions pingOptions) { if (server == null) { throw new IllegalStateException("No Velocity proxy instance available"); } @@ -108,7 +113,8 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud ch.pipeline() .addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder()) .addLast(READ_TIMEOUT, - new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), + new ReadTimeoutHandler(pingOptions.getTimeout() == 0 + ? server.getConfiguration().getReadTimeout() : pingOptions.getTimeout(), TimeUnit.MILLISECONDS)) .addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE) .addLast(MINECRAFT_DECODER, @@ -124,7 +130,7 @@ public class VelocityRegisteredServer implements RegisteredServer, ForwardingAud if (future.isSuccess()) { MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class); conn.setSessionHandler(new PingSessionHandler( - pingFuture, VelocityRegisteredServer.this, conn, version)); + pingFuture, VelocityRegisteredServer.this, conn, pingOptions.getProtocolVersion())); } else { pingFuture.completeExceptionally(future.cause()); }