merge upstream

This commit is contained in:
Leymooo
2018-09-24 12:40:48 +03:00
100 changed files with 1825 additions and 1568 deletions

View File

@@ -19,7 +19,8 @@ import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor {

View File

@@ -26,4 +26,19 @@ public interface Command {
default List<String> suggest(@NonNull CommandSource source, @NonNull String[] currentArgs) {
return ImmutableList.of();
}
/**
* Tests to check if the {@code source} has permission to use this command
* with the provided {@code args}.
*
* <p>If this method returns false, the handling will be forwarded onto
* the players current server.</p>
*
* @param source the source of the command
* @param args the arguments for this command
* @return whether the source has permission
*/
default boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
return true;
}
}

View File

@@ -0,0 +1,105 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays;
/**
* This event is fired when a plugin message is sent to the proxy, either from a client ({@link com.velocitypowered.api.proxy.Player})
* or a server ({@link com.velocitypowered.api.proxy.ServerConnection}).
*/
public class PluginMessageEvent implements ResultedEvent<PluginMessageEvent.ForwardResult> {
private final ChannelMessageSource source;
private final ChannelMessageSink target;
private final ChannelIdentifier identifier;
private final byte[] data;
private ForwardResult result;
public PluginMessageEvent(ChannelMessageSource source, ChannelMessageSink target, ChannelIdentifier identifier, byte[] data) {
this.source = Preconditions.checkNotNull(source, "source");
this.target = Preconditions.checkNotNull(target, "target");
this.identifier = Preconditions.checkNotNull(identifier, "identifier");
this.data = Preconditions.checkNotNull(data, "data");
this.result = ForwardResult.forward();
}
@Override
public ForwardResult getResult() {
return result;
}
@Override
public void setResult(@NonNull ForwardResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public ChannelMessageSource getSource() {
return source;
}
public ChannelMessageSink getTarget() {
return target;
}
public ChannelIdentifier getIdentifier() {
return identifier;
}
public byte[] getData() {
return Arrays.copyOf(data, data.length);
}
public ByteArrayDataInput dataAsDataStream() {
return ByteStreams.newDataInput(data);
}
@Override
public String toString() {
return "PluginMessageEvent{" +
"source=" + source +
", target=" + target +
", identifier=" + identifier +
", data=" + Arrays.toString(data) +
", result=" + result +
'}';
}
/**
* A result determining whether or not to forward this message on.
*/
public static class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean allowed;
private ForwardResult(boolean b) {
this.allowed = b;
}
@Override
public boolean isAllowed() {
return allowed;
}
@Override
public String toString() {
return allowed ? "forward to sink" : "handled message at proxy";
}
public static ForwardResult forward() {
return ALLOWED;
}
public static ForwardResult handled() {
return DENIED;
}
}
}

View File

@@ -0,0 +1,28 @@
package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
/**
* This event is fired once the player has been successfully authenticated and
* fully initialized and player will be connected to server after this event
*/
public class PostLoginEvent {
private final Player player;
public PostLoginEvent(Player player) {
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() {
return player;
}
@Override
public String toString() {
return "PostLoginEvent{"
+ "player=" + player
+ '}';
}
}

View File

@@ -3,12 +3,13 @@ package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/**
* This event is fired when a player has initiated a connection with the proxy but before the proxy authenticates the
* player with Mojang or before the player's proxy connection is fully established (for offline mode).
@@ -52,44 +53,59 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* Represents an "allowed/allowed with online mode/denied" result with a reason allowed for denial.
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial.
*/
public static class PreLoginComponentResult extends ResultedEvent.ComponentResult {
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult((Component) null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(true);
public static class PreLoginComponentResult implements ResultedEvent.Result {
private final boolean onlineMode;
private static final PreLoginComponentResult ALLOWED = new PreLoginComponentResult(Result.ALLOWED, null);
private static final PreLoginComponentResult FORCE_ONLINEMODE = new PreLoginComponentResult(Result.FORCE_ONLINE, null);
private static final PreLoginComponentResult FORCE_OFFLINEMODE = new PreLoginComponentResult(Result.FORCE_OFFLINE, null);
/**
* Allows online mode to be enabled for the player connection, if Velocity is running in offline mode.
* @param allowedOnlineMode if true, online mode will be used for the connection
*/
private PreLoginComponentResult(boolean allowedOnlineMode) {
super(true, null);
this.onlineMode = allowedOnlineMode;
private final Result result;
private final Optional<Component> reason;
private PreLoginComponentResult(Result result, @Nullable Component reason) {
this.result = result;
this.reason = Optional.ofNullable(reason);
}
private PreLoginComponentResult(@Nullable Component reason) {
super(reason == null, reason);
// Don't care about this
this.onlineMode = false;
@Override
public boolean isAllowed() {
return result != Result.DISALLOWED;
}
public Optional<Component> getReason() {
return reason;
}
public boolean isOnlineModeAllowed() {
return this.onlineMode;
return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
}
@Override
public String toString() {
if (isForceOfflineMode()) {
return "allowed with force offline mode";
}
if (isOnlineModeAllowed()) {
return "allowed with online mode";
}
return super.toString();
if (isAllowed()) {
return "allowed";
}
if (reason.isPresent()) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason.get());
}
return "denied";
}
/**
* Returns a result indicating the connection will be allowed through the proxy.
* Returns a result indicating the connection will be allowed through
* the proxy.
* @return the allowed result
*/
public static PreLoginComponentResult allowed() {
@@ -97,23 +113,41 @@ public class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginCompon
}
/**
* Returns a result indicating the connection will be allowed through the proxy, but the connection will be
* forced to use online mode provided that the proxy is in offline mode. This acts similarly to {@link #allowed()}
* on an online-mode proxy.
* Returns a result indicating the connection will be allowed through
* the proxy, but the connection will be forced to use online mode
* provided that the proxy is in offline mode. This acts similarly to
* {@link #allowed()} on an online-mode proxy.
* @return the result
*/
public static PreLoginComponentResult forceOnlineMode() {
return FORCE_ONLINEMODE;
}
/**
* Returns a result indicating the connection will be allowed through
* the proxy, but the connection will be forced to use offline mode even
* when proxy running in online mode
* @return the result
*/
public static PreLoginComponentResult forceOfflineMode() {
return FORCE_OFFLINEMODE;
}
/**
* Denies the login with the specified reason.
* @param reason the reason for disallowing the connection
* @return a new result
*/
public static PreLoginComponentResult denied(@NonNull Component reason) {
public static PreLoginComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(reason);
return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
}
}
}

View File

@@ -1,10 +1,9 @@
package com.velocitypowered.api.event.player;
import com.velocitypowered.api.proxy.InboundConnection;
import org.checkerframework.checker.nullness.qual.Nullable;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in order to set up the

View File

@@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -13,12 +13,12 @@ import org.checkerframework.checker.nullness.qual.NonNull;
*/
public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
private final Component originalReason;
private final boolean duringLogin;
private ServerKickResult result;
public KickedFromServerEvent(Player player, ServerInfo server, Component originalReason, boolean duringLogin, Component fancyReason) {
public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) {
this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server");
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
@@ -40,7 +40,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@@ -91,9 +91,9 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* when this result is used.
*/
public static class RedirectPlayer implements ServerKickResult {
private final ServerInfo server;
private final RegisteredServer server;
private RedirectPlayer(ServerInfo server) {
private RedirectPlayer(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "server");
}
@@ -102,7 +102,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
return false;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}
@@ -111,7 +111,7 @@ public class KickedFromServerEvent implements ResultedEvent<KickedFromServerEven
* @param server the server to send the player to
* @return the redirect result
*/
public static RedirectPlayer create(ServerInfo server) {
public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server);
}
}

View File

@@ -1,8 +1,8 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.PlayerSettings;
public class PlayerSettingsChangedEvent {
private final Player player;

View File

@@ -2,7 +2,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
/**
* This event is fired once the player has successfully connected to the target server and the connection to the previous
@@ -10,9 +10,9 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
*/
public class ServerConnectedEvent {
private final Player player;
private final ServerInfo server;
private final RegisteredServer server;
public ServerConnectedEvent(Player player, ServerInfo server) {
public ServerConnectedEvent(Player player, RegisteredServer server) {
this.player = Preconditions.checkNotNull(player, "player");
this.server = Preconditions.checkNotNull(server, "server");
}
@@ -21,7 +21,7 @@ public class ServerConnectedEvent {
return player;
}
public ServerInfo getServer() {
public RegisteredServer getServer() {
return server;
}

View File

@@ -3,7 +3,7 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -14,11 +14,13 @@ import java.util.Optional;
*/
public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> {
private final Player player;
private final RegisteredServer originalServer;
private ServerResult result;
public ServerPreConnectEvent(Player player, ServerResult result) {
public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
this.player = Preconditions.checkNotNull(player, "player");
this.result = Preconditions.checkNotNull(result, "result");
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
this.result = ServerResult.allowed(originalServer);
}
public Player getPlayer() {
@@ -35,10 +37,15 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
this.result = Preconditions.checkNotNull(result, "result");
}
public RegisteredServer getOriginalServer() {
return originalServer;
}
@Override
public String toString() {
return "ServerPreConnectEvent{" +
"player=" + player +
", originalServer=" + originalServer +
", result=" + result +
'}';
}
@@ -50,11 +57,11 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
private static final ServerResult DENIED = new ServerResult(false, null);
private final boolean allowed;
private final ServerInfo info;
private final RegisteredServer server;
private ServerResult(boolean allowed, @Nullable ServerInfo info) {
private ServerResult(boolean allowed, @Nullable RegisteredServer server) {
this.allowed = allowed;
this.info = info;
this.server = server;
}
@Override
@@ -62,8 +69,8 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
return allowed;
}
public Optional<ServerInfo> getInfo() {
return Optional.ofNullable(info);
public Optional<RegisteredServer> getServer() {
return Optional.ofNullable(server);
}
@Override
@@ -71,14 +78,14 @@ public class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEven
if (!allowed) {
return "denied";
}
return "allowed: connect to " + info.getName();
return "allowed: connect to " + server.getServerInfo().getName();
}
public static ServerResult denied() {
return DENIED;
}
public static ServerResult allowed(ServerInfo server) {
public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server");
return new ServerResult(true, server);
}

View File

@@ -29,5 +29,5 @@ public interface PermissionFunction {
* @param permission the permission
* @return the value the permission is set to
*/
@NonNull Tristate getPermissionSetting(@NonNull String permission);
@NonNull Tristate getPermissionValue(@NonNull String permission);
}

View File

@@ -12,5 +12,15 @@ public interface PermissionSubject {
* @param permission the permission to check for
* @return whether or not the subject has the permission
*/
boolean hasPermission(@NonNull String permission);
default boolean hasPermission(@NonNull String permission) {
return getPermissionValue(permission).asBoolean();
}
/**
* Gets the subjects setting for a particular permission.
*
* @param permission the permission
* @return the value the permission is set to
*/
@NonNull Tristate getPermissionValue(@NonNull String permission);
}

View File

@@ -1,7 +1,6 @@
package com.velocitypowered.api.plugin.meta;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;

View File

@@ -1,29 +1,28 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
/**
* Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection
* request is created using {@link Player#createConnectionRequest(ServerInfo)}.
* request is created using {@link Player#createConnectionRequest(RegisteredServer)}.
*/
public interface ConnectionRequestBuilder {
/**
* Returns the server that this connection request represents.
* @return the server this request will connect to
*/
@NonNull ServerInfo getServer();
RegisteredServer getServer();
/**
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user
* has logged on. No messages will be communicated to the client: the user is responsible for all error handling.
* @return a {@link CompletableFuture} representing the status of this connection
*/
@NonNull CompletableFuture<Result> connect();
CompletableFuture<Result> connect();
/**
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error

View File

@@ -1,13 +1,15 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.title.Title;
import java.util.List;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -65,10 +67,10 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
/**
* Creates a new connection request so that the player can connect to another server.
* @param info the server to connect to
* @param server the server to connect to
* @return a new connection request
*/
ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info);
ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server);
/**
* Gets a game profile properties of player
@@ -100,4 +102,17 @@ public interface Player extends CommandSource, InboundConnection, ChannelMessage
* @param reason component with the reason
*/
void disconnect(Component reason);
/**
* Sends the specified title to the client.
* @param title the title to send
*/
void sendTitle(Title title);
/**
* Sends chat input onto the players current server as if they typed it
* into the client chat box.
* @param input the chat input to send
*/
void spoofChatInput(String input);
}

View File

@@ -1,12 +1,14 @@
package com.velocitypowered.api.proxy;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.EventManager;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler;
import net.kyori.text.Component;
import java.net.InetSocketAddress;
import java.util.Collection;
@@ -31,6 +33,12 @@ public interface ProxyServer {
*/
Optional<Player> getPlayer(UUID uuid);
/**
* Broadcasts a message to all players currently online.
* @param component the message to send
*/
void broadcast(Component component);
/**
* Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players
* online.
@@ -45,23 +53,24 @@ public interface ProxyServer {
int getPlayerCount();
/**
* Retrieves a registered {@link ServerInfo} instance by its name. The search is case-insensitive.
* Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive.
* @param name the name of the server
* @return the registered server, which may be empty
*/
Optional<ServerInfo> getServerInfo(String name);
Optional<RegisteredServer> getServer(String name);
/**
* Retrieves all {@link ServerInfo}s registered with this proxy.
* Retrieves all {@link RegisteredServer}s registered with this proxy.
* @return the servers registered with this proxy
*/
Collection<ServerInfo> getAllServers();
Collection<RegisteredServer> getAllServers();
/**
* Registers a server with this proxy. A server with this name should not already exist.
* @param server the server to register
* @return the newly registered server
*/
void registerServer(ServerInfo server);
RegisteredServer registerServer(ServerInfo server);
/**
* Unregisters this server from the proxy.

View File

@@ -2,6 +2,7 @@ package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
/**
@@ -12,6 +13,12 @@ public interface ServerConnection extends ChannelMessageSource, ChannelMessageSi
* Returns the server that this connection is connected to.
* @return the server this connection is connected to
*/
RegisteredServer getServer();
/**
* Returns the server info for this connection.
* @return the server info for this connection
*/
ServerInfo getServerInfo();
/**

View File

@@ -8,6 +8,7 @@ public interface ChannelMessageSink {
* Sends a plugin message to this target.
* @param identifier the channel identifier to send the message on
* @param data the data to send
* @return whether or not the message could be sent
*/
void sendPluginMessage(ChannelIdentifier identifier, byte[] data);
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
}

View File

@@ -1,16 +1,14 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents an interface to register and unregister {@link MessageHandler} instances for handling plugin messages from
* the client or the server.
* Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on.
*/
public interface ChannelRegistrar {
/**
* Registers the specified message handler to listen for plugin messages on the specified channels.
* @param handler the handler to register
* Registers the specified message identifiers to listen on for the
* @param identifiers the channel identifiers to register
*/
void register(MessageHandler handler, ChannelIdentifier... identifiers);
void register(ChannelIdentifier... identifiers);
/**
* Unregisters the handler for the specified channel.

View File

@@ -1,15 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents from "which side" of the proxy the plugin message came from.
*/
public enum ChannelSide {
/**
* The plugin message came from a server that a client was connected to.
*/
FROM_SERVER,
/**
* The plugin message came from the client.
*/
FROM_CLIENT
}

View File

@@ -1,28 +0,0 @@
package com.velocitypowered.api.proxy.messages;
/**
* Represents a handler for handling plugin messages.
*/
public interface MessageHandler {
/**
* Handles an incoming plugin message.
* @param source the source of the plugin message
* @param side from where the plugin message originated
* @param identifier the channel on which the message was sent
* @param data the data inside the plugin message
* @return a {@link ForwardStatus} indicating whether or not to forward this plugin message on
*/
ForwardStatus handle(ChannelMessageSource source, ChannelSide side, ChannelIdentifier identifier, byte[] data);
enum ForwardStatus {
/**
* Forwards this plugin message on to the client or server, depending on the {@link ChannelSide} it originated
* from.
*/
FORWARD,
/**
* Discard the plugin message and do not forward it on.
*/
HANDLED
}
}

View File

@@ -10,7 +10,7 @@ import java.util.regex.Pattern;
* Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use.
*/
public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+", Pattern.CASE_INSENSITIVE);
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String namespace;
private final String name;

View File

@@ -0,0 +1,30 @@
package com.velocitypowered.api.proxy.server;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
/**
* Represents a server that has been registered with the proxy.
*/
public interface RegisteredServer extends ChannelMessageSink {
/**
* Returns the {@link ServerInfo} for this server.
* @return the server info
*/
ServerInfo getServerInfo();
/**
* Returns a list of all the players currently connected to this server on this proxy.
* @return the players on this proxy
*/
Collection<Player> getPlayersConnected();
/**
* Attempts to ping the remote server and return the server list ping result.
* @return the server ping result from the server
*/
CompletableFuture<ServerPing> ping();
}

View File

@@ -16,13 +16,13 @@ public class ServerPing {
private final Players players;
private final Component description;
private final @Nullable Favicon favicon;
private final Modinfo modinfo;
private final ModInfo modinfo;
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) {
this(version, players, description, favicon, Modinfo.DEFAULT);
this(version, players, description, favicon, ModInfo.DEFAULT);
}
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable Modinfo modinfo) {
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, ServerPing.@Nullable ModInfo modinfo) {
this.version = Preconditions.checkNotNull(version, "version");
this.players = players;
this.description = Preconditions.checkNotNull(description, "description");
@@ -46,7 +46,7 @@ public class ServerPing {
return Optional.ofNullable(favicon);
}
public Optional<Modinfo> getModinfo() {
public Optional<ModInfo> getModinfo() {
return Optional.ofNullable(modinfo);
}
@@ -74,6 +74,7 @@ public class ServerPing {
builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null;
if (modinfo != null) {
builder.modType = modinfo.type;
builder.mods.addAll(modinfo.modList);
}
return builder;
@@ -91,6 +92,7 @@ public class ServerPing {
private int onlinePlayers;
private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType;
private final List<Mod> mods = new ArrayList<>();
private Component description;
private Favicon favicon;
@@ -121,6 +123,11 @@ public class ServerPing {
return this;
}
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(Mod... mods) {
this.mods.addAll(Arrays.asList(mods));
return this;
@@ -158,7 +165,7 @@ public class ServerPing {
public ServerPing build() {
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers), description, favicon,
nullOutModinfo ? null : new Modinfo(mods));
nullOutModinfo ? null : new ModInfo(modType, mods));
}
public Version getVersion() {
@@ -185,6 +192,10 @@ public class ServerPing {
return favicon;
}
public String getModType() {
return modType;
}
public List<Mod> getMods() {
return mods;
}
@@ -196,6 +207,7 @@ public class ServerPing {
", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods +
", description=" + description +
", favicon=" + favicon +
@@ -290,15 +302,24 @@ public class ServerPing {
}
}
public static class Modinfo {
public static final Modinfo DEFAULT = new Modinfo(ImmutableList.of());
public static class ModInfo {
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
private final String type = "FML";
private final String type;
private final List<Mod> modList;
public Modinfo(List<Mod> modList) {
public ModInfo(String type, List<Mod> modList) {
this.type = Preconditions.checkNotNull(type, "type");
this.modList = ImmutableList.copyOf(modList);
}
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
}
public static class Mod {
@@ -309,5 +330,13 @@ public class ServerPing {
this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version");
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
}
}

View File

@@ -24,7 +24,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder delay(int time, TimeUnit unit);
TaskBuilder delay(long time, TimeUnit unit);
/**
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled.
@@ -32,7 +32,7 @@ public interface Scheduler {
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder repeat(int time, TimeUnit unit);
TaskBuilder repeat(long time, TimeUnit unit);
/**
* Clears the delay on this task.

View File

@@ -1,66 +0,0 @@
package com.velocitypowered.api.util;
import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.regex.Pattern;
/**
* LegacyChatColorUtils contains utilities for handling legacy Minecraft color codes. Generally, you should prefer
* JSON-based components, but for convenience Velocity provides a limited set of tools to handle Minecraft color codes.
*/
public class LegacyChatColorUtils {
private LegacyChatColorUtils() {
throw new AssertionError();
}
/**
* Represents the legacy Minecraft format character, the section symbol.
*/
public static final char FORMAT_CHAR = '\u00a7';
/**
* Translates a string with Minecraft color codes prefixed with a different character than the section symbol into
* a string that uses the section symbol.
* @param originalChar the char the color codes are prefixed by
* @param text the text to translate
* @return the translated text
*/
public static String translate(char originalChar, @NonNull String text) {
Preconditions.checkNotNull(text, "text");
char[] textChars = text.toCharArray();
int foundSectionIdx = -1;
for (int i = 0; i < textChars.length; i++) {
char textChar = textChars[i];
if (textChar == originalChar) {
foundSectionIdx = i;
continue;
}
if (foundSectionIdx >= 0) {
textChar = Character.toLowerCase(textChar);
if ((textChar >= 'a' && textChar <= 'f') || (textChar >= '0' && textChar <= '9') ||
(textChar >= 'l' && textChar <= 'o' || textChar == 'r')) {
textChars[foundSectionIdx] = FORMAT_CHAR;
}
foundSectionIdx = -1;
}
}
return new String(textChars);
}
/**
* A regex that matches all Minecraft color codes and removes them.
*/
private static final Pattern CHAT_COLOR_MATCHER = Pattern.compile("(?i)" + Character.toString(FORMAT_CHAR) + "[0-9A-FL-OR]");
/**
* Removes all Minecraft color codes from the string.
* @param text the text to remove color codes from
* @return a new String without Minecraft color codes
*/
public static String removeFormatting(@NonNull String text) {
Preconditions.checkNotNull(text, "text");
return CHAT_COLOR_MATCHER.matcher(text).replaceAll("");
}
}

View File

@@ -0,0 +1,236 @@
package com.velocitypowered.api.util.title;
import com.google.common.base.Preconditions;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a "full" title, including all components. This class is immutable.
*/
public class TextTitle implements Title {
private final Component title;
private final Component subtitle;
private final int stay;
private final int fadeIn;
private final int fadeOut;
private final boolean resetBeforeSend;
private TextTitle(Builder builder) {
this.title = builder.title;
this.subtitle = builder.subtitle;
this.stay = builder.stay;
this.fadeIn = builder.fadeIn;
this.fadeOut = builder.fadeOut;
this.resetBeforeSend = builder.resetBeforeSend;
}
/**
* Returns the main title this title has, if any.
* @return the main title of this title
*/
public Optional<Component> getTitle() {
return Optional.ofNullable(title);
}
/**
* Returns the subtitle this title has, if any.
* @return the subtitle
*/
public Optional<Component> getSubtitle() {
return Optional.ofNullable(subtitle);
}
/**
* Returns the number of ticks this title will stay up.
* @return how long the title will stay, in ticks
*/
public int getStay() {
return stay;
}
/**
* Returns the number of ticks over which this title will fade in.
* @return how long the title will fade in, in ticks
*/
public int getFadeIn() {
return fadeIn;
}
/**
* Returns the number of ticks over which this title will fade out.
* @return how long the title will fade out, in ticks
*/
public int getFadeOut() {
return fadeOut;
}
/**
* Returns whether or not a reset packet will be sent before this title is sent. By default, unless explicitly
* disabled, this is enabled by default.
* @return whether or not a reset packet will be sent before this title is sent
*/
public boolean isResetBeforeSend() {
return resetBeforeSend;
}
/**
* Determines whether or not this title has times set on it. If none are set, it will update the previous title
* set on the client.
* @return whether or not this title has times set on it
*/
public boolean areTimesSet() {
return stay != 0 || fadeIn != 0 || fadeOut != 0;
}
/**
* Creates a new builder from the contents of this title so that it may be changed.
* @return a builder instance with the contents of this title
*/
public Builder toBuilder() {
return new Builder(this);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TextTitle textTitle = (TextTitle) o;
return stay == textTitle.stay &&
fadeIn == textTitle.fadeIn &&
fadeOut == textTitle.fadeOut &&
resetBeforeSend == textTitle.resetBeforeSend &&
Objects.equals(title, textTitle.title) &&
Objects.equals(subtitle, textTitle.subtitle);
}
@Override
public String toString() {
return "TextTitle{" +
"title=" + title +
", subtitle=" + subtitle +
", stay=" + stay +
", fadeIn=" + fadeIn +
", fadeOut=" + fadeOut +
", resetBeforeSend=" + resetBeforeSend +
'}';
}
@Override
public int hashCode() {
return Objects.hash(title, subtitle, stay, fadeIn, fadeOut, resetBeforeSend);
}
/**
* Creates a new builder for constructing titles.
* @return a builder for constructing titles
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private @Nullable Component title;
private @Nullable Component subtitle;
private int stay;
private int fadeIn;
private int fadeOut;
private boolean resetBeforeSend = true;
private Builder() {}
private Builder(TextTitle copy) {
this.title = copy.title;
this.subtitle = copy.subtitle;
this.stay = copy.stay;
this.fadeIn = copy.fadeIn;
this.fadeOut = copy.fadeOut;
this.resetBeforeSend = copy.resetBeforeSend;
}
public Builder title(Component title) {
this.title = Preconditions.checkNotNull(title, "title");
return this;
}
public Builder clearTitle() {
this.title = null;
return this;
}
public Builder subtitle(Component subtitle) {
this.subtitle = Preconditions.checkNotNull(subtitle, "subtitle");
return this;
}
public Builder clearSubtitle() {
this.subtitle = null;
return this;
}
public Builder stay(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.stay = ticks;
return this;
}
public Builder fadeIn(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.fadeIn = ticks;
return this;
}
public Builder fadeOut(int ticks) {
Preconditions.checkArgument(ticks >= 0, "ticks value %s is negative", ticks);
this.fadeOut = ticks;
return this;
}
public Builder resetBeforeSend(boolean b) {
this.resetBeforeSend = b;
return this;
}
public Component getTitle() {
return title;
}
public Component getSubtitle() {
return subtitle;
}
public int getStay() {
return stay;
}
public int getFadeIn() {
return fadeIn;
}
public int getFadeOut() {
return fadeOut;
}
public boolean isResetBeforeSend() {
return resetBeforeSend;
}
public TextTitle build() {
return new TextTitle(this);
}
@Override
public String toString() {
return "Builder{" +
"title=" + title +
", subtitle=" + subtitle +
", stay=" + stay +
", fadeIn=" + fadeIn +
", fadeOut=" + fadeOut +
", resetBeforeSend=" + resetBeforeSend +
'}';
}
}
}

View File

@@ -0,0 +1,7 @@
package com.velocitypowered.api.util.title;
/**
* Represents a title that can be sent to a Minecraft client.
*/
public interface Title {
}

View File

@@ -0,0 +1,50 @@
package com.velocitypowered.api.util.title;
/**
* Provides special-purpose titles.
*/
public class Titles {
private Titles() {
throw new AssertionError();
}
private static final Title RESET = new Title() {
@Override
public String toString() {
return "reset title";
}
};
private static final Title HIDE = new Title() {
@Override
public String toString() {
return "hide title";
}
};
/**
* Returns a title that, when sent to the client, will cause all title data to be reset and any existing title to be
* hidden.
* @return the reset title
*/
public static Title reset() {
return RESET;
}
/**
* Returns a title that, when sent to the client, will cause any existing title to be hidden. The title may be
* restored by a {@link TextTitle} with no title or subtitle (only a time).
* @return the hide title
*/
public static Title hide() {
return HIDE;
}
/**
* Returns a builder for {@link TextTitle}s.
* @return a builder for text titles
*/
public static TextTitle.Builder text() {
return TextTitle.builder();
}
}

View File

@@ -0,0 +1,4 @@
/**
* Provides data structures for creating and manipulating titles.
*/
package com.velocitypowered.api.util.title;

View File

@@ -1,62 +0,0 @@
package com.velocitypowered.api.util;
import com.velocitypowered.api.util.LegacyChatColorUtils;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class LegacyChatColorUtilsTest {
private static final String NON_FORMATTED = "Velocity";
private static final String FORMATTED = "\u00a7cVelocity";
private static final String FORMATTED_MULTIPLE = "\u00a7c\u00a7lVelocity";
private static final String FORMATTED_MULTIPLE_VARIED = "\u00a7c\u00a7lVelo\u00a7a\u00a7mcity";
private static final String INVALID = "\u00a7gVelocity";
private static final String RAW_SECTION = "\u00a7";
@Test
void removeFormattingNonFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(NON_FORMATTED));
}
@Test
void removeFormattingFormatted() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED));
}
@Test
void removeFormattingFormattedMultiple() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE));
}
@Test
void removeFormattingFormattedMultipleVaried() {
assertEquals(NON_FORMATTED, LegacyChatColorUtils.removeFormatting(FORMATTED_MULTIPLE_VARIED));
}
@Test
void removeFormattingInvalidFormat() {
assertEquals(INVALID, LegacyChatColorUtils.removeFormatting(INVALID));
}
@Test
void removeFormattingRawSection() {
assertEquals(RAW_SECTION, LegacyChatColorUtils.removeFormatting(RAW_SECTION));
}
@Test
void translate() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('&', "&cVelocity"));
}
@Test
void translateMultiple() {
assertEquals(FORMATTED_MULTIPLE, LegacyChatColorUtils.translate('&', "&c&lVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('&', "&c&lVelo&a&mcity"));
}
@Test
void translateDifferentChar() {
assertEquals(FORMATTED, LegacyChatColorUtils.translate('$', "$cVelocity"));
assertEquals(FORMATTED_MULTIPLE_VARIED, LegacyChatColorUtils.translate('$', "$c$lVelo$a$mcity"));
}
}