3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-24 15:20:35 +01:00

Reformat with Google code style and enforce Checkstyle.

Fixes #125
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-10-27 23:45:35 -04:00
Ursprung 53aa92db92
Commit 25b5e00125
208 geänderte Dateien mit 12851 neuen und 11620 gelöschten Zeilen

Datei anzeigen

@ -2,9 +2,11 @@ plugins {
id 'java' id 'java'
id 'com.github.johnrengelman.shadow' version '2.0.4' id 'com.github.johnrengelman.shadow' version '2.0.4'
id 'maven-publish' id 'maven-publish'
id 'checkstyle'
} }
apply from: '../gradle/checkerframework.gradle' apply from: '../gradle/checkerframework.gradle'
apply from: '../gradle/checkstyle.gradle'
sourceSets { sourceSets {
ap { ap {

Datei anzeigen

@ -3,7 +3,11 @@ package com.velocitypowered.api.plugin.ap;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginDescription;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.RoundEnvironment;
@ -16,73 +20,76 @@ import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic; import javax.tools.Diagnostic;
import javax.tools.FileObject; import javax.tools.FileObject;
import javax.tools.StandardLocation; import javax.tools.StandardLocation;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.Objects;
import java.util.Set;
@SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"}) @SupportedAnnotationTypes({"com.velocitypowered.api.plugin.Plugin"})
public class PluginAnnotationProcessor extends AbstractProcessor { public class PluginAnnotationProcessor extends AbstractProcessor {
private ProcessingEnvironment environment;
private String pluginClassFound;
private boolean warnedAboutMultiplePlugins;
@Override private ProcessingEnvironment environment;
public synchronized void init(ProcessingEnvironment processingEnv) { private String pluginClassFound;
this.environment = processingEnv; private boolean warnedAboutMultiplePlugins;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
this.environment = processingEnv;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
} }
@Override for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
public SourceVersion getSupportedSourceVersion() { if (element.getKind() != ElementKind.CLASS) {
return SourceVersion.latestSupported(); environment.getMessager()
} .printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
+ Plugin.class.getCanonicalName());
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
for (Element element : roundEnv.getElementsAnnotatedWith(Plugin.class)) {
if (element.getKind() != ElementKind.CLASS) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Only classes can be annotated with "
+ Plugin.class.getCanonicalName());
return false;
}
Name qualifiedName = ((TypeElement) element).getQualifiedName();
if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
if (!warnedAboutMultiplePlugins) {
environment.getMessager().printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " +
"multiple plugins. We are using " + pluginClassFound + " for your plugin's main class.");
warnedAboutMultiplePlugins = true;
}
return false;
}
Plugin plugin = element.getAnnotation(Plugin.class);
if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
+ qualifiedName + ". IDs must start alphabetically, have alphanumeric characters, and can " +
"contain dashes or underscores.");
return false;
}
// All good, generate the velocity-plugin.json.
SerializedPluginDescription description = SerializedPluginDescription.from(plugin, qualifiedName.toString());
try {
FileObject object = environment.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
try (Writer writer = new BufferedWriter(object.openWriter())) {
new Gson().toJson(description, writer);
}
pluginClassFound = qualifiedName.toString();
} catch (IOException e) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
}
}
return false; return false;
}
Name qualifiedName = ((TypeElement) element).getQualifiedName();
if (Objects.equals(pluginClassFound, qualifiedName.toString())) {
if (!warnedAboutMultiplePlugins) {
environment.getMessager()
.printMessage(Diagnostic.Kind.WARNING, "Velocity does not yet currently support " +
"multiple plugins. We are using " + pluginClassFound
+ " for your plugin's main class.");
warnedAboutMultiplePlugins = true;
}
return false;
}
Plugin plugin = element.getAnnotation(Plugin.class);
if (!PluginDescription.ID_PATTERN.matcher(plugin.id()).matches()) {
environment.getMessager().printMessage(Diagnostic.Kind.ERROR, "Invalid ID for plugin "
+ qualifiedName
+ ". IDs must start alphabetically, have alphanumeric characters, and can " +
"contain dashes or underscores.");
return false;
}
// All good, generate the velocity-plugin.json.
SerializedPluginDescription description = SerializedPluginDescription
.from(plugin, qualifiedName.toString());
try {
FileObject object = environment.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", "velocity-plugin.json");
try (Writer writer = new BufferedWriter(object.openWriter())) {
new Gson().toJson(description, writer);
}
pluginClassFound = qualifiedName.toString();
} catch (IOException e) {
environment.getMessager()
.printMessage(Diagnostic.Kind.ERROR, "Unable to generate plugin file");
}
} }
return false;
}
} }

Datei anzeigen

@ -4,149 +4,162 @@ import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.Plugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.checkerframework.checker.nullness.qual.Nullable;
public class SerializedPluginDescription { public class SerializedPluginDescription {
// @Nullable is used here to make GSON skip these in the serialized file
private final String id;
private final @Nullable String name;
private final @Nullable String version;
private final @Nullable String description;
private final @Nullable String url;
private final @Nullable List<String> authors;
private final @Nullable List<Dependency> dependencies;
private final String main;
public SerializedPluginDescription(String id, String name, String version, String description, String url, // @Nullable is used here to make GSON skip these in the serialized file
List<String> authors, List<Dependency> dependencies, String main) { private final String id;
this.id = Preconditions.checkNotNull(id, "id"); private final @Nullable String name;
this.name = Strings.emptyToNull(name); private final @Nullable String version;
this.version = Strings.emptyToNull(version); private final @Nullable String description;
this.description = Strings.emptyToNull(description); private final @Nullable String url;
this.url = Strings.emptyToNull(url); private final @Nullable List<String> authors;
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors; private final @Nullable List<Dependency> dependencies;
this.dependencies = dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies; private final String main;
this.main = Preconditions.checkNotNull(main, "main");
public SerializedPluginDescription(String id, String name, String version, String description,
String url,
List<String> authors, List<Dependency> dependencies, String main) {
this.id = Preconditions.checkNotNull(id, "id");
this.name = Strings.emptyToNull(name);
this.version = Strings.emptyToNull(version);
this.description = Strings.emptyToNull(description);
this.url = Strings.emptyToNull(url);
this.authors = authors == null || authors.isEmpty() ? ImmutableList.of() : authors;
this.dependencies =
dependencies == null || dependencies.isEmpty() ? ImmutableList.of() : dependencies;
this.main = Preconditions.checkNotNull(main, "main");
}
public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) {
List<Dependency> dependencies = new ArrayList<>();
for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) {
dependencies.add(new Dependency(dependency.id(), dependency.optional()));
} }
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(),
plugin.description(), plugin.url(),
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty())
.collect(Collectors.toList()), dependencies, qualifiedName);
}
public static SerializedPluginDescription from(Plugin plugin, String qualifiedName) { public String getId() {
List<Dependency> dependencies = new ArrayList<>(); return id;
for (com.velocitypowered.api.plugin.Dependency dependency : plugin.dependencies()) { }
dependencies.add(new Dependency(dependency.id(), dependency.optional()));
} public @Nullable String getName() {
return new SerializedPluginDescription(plugin.id(), plugin.name(), plugin.version(), plugin.description(), plugin.url(), return name;
Arrays.stream(plugin.authors()).filter(author -> !author.isEmpty()).collect(Collectors.toList()), dependencies, qualifiedName); }
public @Nullable String getVersion() {
return version;
}
public @Nullable String getDescription() {
return description;
}
public @Nullable String getUrl() {
return url;
}
public List<String> getAuthors() {
return authors == null ? ImmutableList.of() : authors;
}
public List<Dependency> getDependencies() {
return dependencies == null ? ImmutableList.of() : dependencies;
}
public String getMain() {
return main;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
SerializedPluginDescription that = (SerializedPluginDescription) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(version, that.version) &&
Objects.equals(description, that.description) &&
Objects.equals(url, that.url) &&
Objects.equals(authors, that.authors) &&
Objects.equals(dependencies, that.dependencies) &&
Objects.equals(main, that.main);
}
@Override
public int hashCode() {
return Objects.hash(id, name, version, description, url, authors, dependencies);
}
@Override
public String toString() {
return "SerializedPluginDescription{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", version='" + version + '\'' +
", description='" + description + '\'' +
", url='" + url + '\'' +
", authors=" + authors +
", dependencies=" + dependencies +
", main='" + main + '\'' +
'}';
}
public static class Dependency {
private final String id;
private final boolean optional;
public Dependency(String id, boolean optional) {
this.id = id;
this.optional = optional;
} }
public String getId() { public String getId() {
return id; return id;
} }
public @Nullable String getName() { public boolean isOptional() {
return name; return optional;
}
public @Nullable String getVersion() {
return version;
}
public @Nullable String getDescription() {
return description;
}
public @Nullable String getUrl() {
return url;
}
public List<String> getAuthors() {
return authors == null ? ImmutableList.of() : authors;
}
public List<Dependency> getDependencies() {
return dependencies == null ? ImmutableList.of() : dependencies;
}
public String getMain() {
return main;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) {
if (o == null || getClass() != o.getClass()) return false; return true;
SerializedPluginDescription that = (SerializedPluginDescription) o; }
return Objects.equals(id, that.id) && if (o == null || getClass() != o.getClass()) {
Objects.equals(name, that.name) && return false;
Objects.equals(version, that.version) && }
Objects.equals(description, that.description) && Dependency that = (Dependency) o;
Objects.equals(url, that.url) && return optional == that.optional &&
Objects.equals(authors, that.authors) && Objects.equals(id, that.id);
Objects.equals(dependencies, that.dependencies) &&
Objects.equals(main, that.main);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, name, version, description, url, authors, dependencies); return Objects.hash(id, optional);
} }
@Override @Override
public String toString() { public String toString() {
return "SerializedPluginDescription{" + return "Dependency{" +
"id='" + id + '\'' + "id='" + id + '\'' +
", name='" + name + '\'' + ", optional=" + optional +
", version='" + version + '\'' + '}';
", description='" + description + '\'' +
", url='" + url + '\'' +
", authors=" + authors +
", dependencies=" + dependencies +
", main='" + main + '\'' +
'}';
}
public static class Dependency {
private final String id;
private final boolean optional;
public Dependency(String id, boolean optional) {
this.id = id;
this.optional = optional;
}
public String getId() {
return id;
}
public boolean isOptional() {
return optional;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dependency that = (Dependency) o;
return optional == that.optional &&
Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id, optional);
}
@Override
public String toString() {
return "Dependency{" +
"id='" + id + '\'' +
", optional=" + optional +
'}';
}
} }
}
} }

Datei anzeigen

@ -1,44 +1,46 @@
package com.velocitypowered.api.command; package com.velocitypowered.api.command;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/** /**
* Represents a command that can be executed by a {@link CommandSource}, such as a {@link com.velocitypowered.api.proxy.Player} * Represents a command that can be executed by a {@link CommandSource}, such as a {@link
* or the console. * com.velocitypowered.api.proxy.Player} or the console.
*/ */
public interface Command { public interface Command {
/**
* Executes the command for the specified {@link CommandSource}.
* @param source the source of this command
* @param args the arguments for this command
*/
void execute(CommandSource source, String @NonNull [] args);
/** /**
* Provides tab complete suggestions for a command for a specified {@link CommandSource}. * Executes the command for the specified {@link CommandSource}.
* @param source the source to run the command for *
* @param currentArgs the current, partial arguments for this command * @param source the source of this command
* @return tab complete suggestions * @param args the arguments for this command
*/ */
default List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { void execute(CommandSource source, String @NonNull [] args);
return ImmutableList.of();
}
/** /**
* Tests to check if the {@code source} has permission to use this command * Provides tab complete suggestions for a command for a specified {@link CommandSource}.
* with the provided {@code args}. *
* * @param source the source to run the command for
* <p>If this method returns false, the handling will be forwarded onto * @param currentArgs the current, partial arguments for this command
* the players current server.</p> * @return tab complete suggestions
* */
* @param source the source of the command default List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
* @param args the arguments for this command return ImmutableList.of();
* @return whether the source has permission }
*/
default boolean hasPermission(CommandSource source, String @NonNull [] args) { /**
return true; * 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(CommandSource source, String @NonNull [] args) {
return true;
}
} }

Datei anzeigen

@ -4,24 +4,28 @@ package com.velocitypowered.api.command;
* Represents an interface to register a command executor with the proxy. * Represents an interface to register a command executor with the proxy.
*/ */
public interface CommandManager { public interface CommandManager {
/**
* Registers the specified command with the manager with the specified aliases.
* @param command the command to register
* @param aliases the alias to use
*/
void register(Command command, String... aliases);
/** /**
* Unregisters a command. * Registers the specified command with the manager with the specified aliases.
* @param alias the command alias to unregister *
*/ * @param command the command to register
void unregister(String alias); * @param aliases the alias to use
*/
void register(Command command, String... aliases);
/** /**
* Attempts to execute a command from the specified {@code cmdLine}. * Unregisters a command.
* @param source the command's source *
* @param cmdLine the command to run * @param alias the command alias to unregister
* @return true if the command was found and executed, false if it was not */
*/ void unregister(String alias);
boolean execute(CommandSource source, String cmdLine);
/**
* Attempts to execute a command from the specified {@code cmdLine}.
*
* @param source the command's source
* @param cmdLine the command to run
* @return true if the command was found and executed, false if it was not
*/
boolean execute(CommandSource source, String cmdLine);
} }

Datei anzeigen

@ -7,9 +7,11 @@ import net.kyori.text.Component;
* Represents something that can be used to run a {@link Command}. * Represents something that can be used to run a {@link Command}.
*/ */
public interface CommandSource extends PermissionSubject { public interface CommandSource extends PermissionSubject {
/**
* Sends the specified {@code component} to the invoker. /**
* @param component the text component to send * Sends the specified {@code component} to the invoker.
*/ *
void sendMessage(Component component); * @param component the text component to send
*/
void sendMessage(Component component);
} }

Datei anzeigen

@ -1,10 +1,11 @@
package com.velocitypowered.api.event; package com.velocitypowered.api.event;
/** /**
* Represents an interface to perform direct dispatch of an event. This makes integration easier to achieve with platforms * Represents an interface to perform direct dispatch of an event. This makes integration easier to
* such as RxJava. * achieve with platforms such as RxJava.
*/ */
@FunctionalInterface @FunctionalInterface
public interface EventHandler<E> { public interface EventHandler<E> {
void execute(E event);
void execute(E event);
} }

Datei anzeigen

@ -6,68 +6,82 @@ import java.util.concurrent.CompletableFuture;
* Allows plugins to register and deregister listeners for event handlers. * Allows plugins to register and deregister listeners for event handlers.
*/ */
public interface EventManager { public interface EventManager {
/**
* Requests that the specified {@code listener} listen for events and associate it with the {@code plugin}.
* @param plugin the plugin to associate with the listener
* @param listener the listener to register
*/
void register(Object plugin, Object listener);
/** /**
* Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. * Requests that the specified {@code listener} listen for events and associate it with the {@code
* @param plugin the plugin to associate with the handler * plugin}.
* @param eventClass the class for the event handler to register *
* @param handler the handler to register * @param plugin the plugin to associate with the listener
* @param <E> the event type to handle * @param listener the listener to register
*/ */
default <E> void register(Object plugin, Class<E> eventClass, EventHandler<E> handler) { void register(Object plugin, Object listener);
register(plugin, eventClass, PostOrder.NORMAL, handler);
}
/** /**
* Requests that the specified {@code handler} listen for events and associate it with the {@code plugin}. * Requests that the specified {@code handler} listen for events and associate it with the {@code
* @param plugin the plugin to associate with the handler * plugin}.
* @param eventClass the class for the event handler to register *
* @param postOrder the order in which events should be posted to the handler * @param plugin the plugin to associate with the handler
* @param handler the handler to register * @param eventClass the class for the event handler to register
* @param <E> the event type to handle * @param handler the handler to register
*/ * @param <E> the event type to handle
<E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder, EventHandler<E> handler); */
default <E> void register(Object plugin, Class<E> eventClass, EventHandler<E> handler) {
register(plugin, eventClass, PostOrder.NORMAL, handler);
}
/** /**
* Fires the specified event to the event bus asynchronously. This allows Velocity to continue servicing connections * Requests that the specified {@code handler} listen for events and associate it with the {@code
* while a plugin handles a potentially long-running operation such as a database query. * plugin}.
* @param event the event to fire *
* @return a {@link CompletableFuture} representing the posted event * @param plugin the plugin to associate with the handler
*/ * @param eventClass the class for the event handler to register
<E> CompletableFuture<E> fire(E event); * @param postOrder the order in which events should be posted to the handler
* @param handler the handler to register
* @param <E> the event type to handle
*/
<E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder,
EventHandler<E> handler);
/** /**
* Posts the specified event to the event bus and discards the result. * Fires the specified event to the event bus asynchronously. This allows Velocity to continue
* @param event the event to fire * servicing connections while a plugin handles a potentially long-running operation such as a
*/ * database query.
default void fireAndForget(Object event) { *
fire(event); * @param event the event to fire
} * @return a {@link CompletableFuture} representing the posted event
*/
<E> CompletableFuture<E> fire(E event);
/** /**
* Unregisters all listeners for the specified {@code plugin}. * Posts the specified event to the event bus and discards the result.
* @param plugin the plugin to deregister listeners for *
*/ * @param event the event to fire
void unregisterListeners(Object plugin); */
default void fireAndForget(Object event) {
fire(event);
}
/** /**
* Unregisters a specific listener for a specific plugin. * Unregisters all listeners for the specified {@code plugin}.
* @param plugin the plugin associated with the listener *
* @param listener the listener to deregister * @param plugin the plugin to deregister listeners for
*/ */
void unregisterListener(Object plugin, Object listener); void unregisterListeners(Object plugin);
/** /**
* Unregisters a specific event handler for a specific plugin. * Unregisters a specific listener for a specific plugin.
* @param plugin the plugin to associate with the handler *
* @param handler the handler to register * @param plugin the plugin associated with the listener
* @param <E> the event type to handle * @param listener the listener to deregister
*/ */
<E> void unregister(Object plugin, EventHandler<E> handler); void unregisterListener(Object plugin, Object listener);
/**
* Unregisters a specific event handler for a specific plugin.
*
* @param plugin the plugin to associate with the handler
* @param handler the handler to register
* @param <E> the event type to handle
*/
<E> void unregister(Object plugin, EventHandler<E> handler);
} }

Datei anzeigen

@ -1,11 +1,10 @@
package com.velocitypowered.api.event; package com.velocitypowered.api.event;
/** /**
* Represents the order an event will be posted to a listener method, relative * Represents the order an event will be posted to a listener method, relative to other listeners.
* to other listeners.
*/ */
public enum PostOrder { public enum PostOrder {
FIRST, EARLY, NORMAL, LATE, LAST; FIRST, EARLY, NORMAL, LATE, LAST;
} }

Datei anzeigen

@ -1,114 +1,119 @@
package com.velocitypowered.api.event; package com.velocitypowered.api.event;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.util.Optional;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.ComponentSerializers; import net.kyori.text.serializer.ComponentSerializers;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/** /**
* Indicates an event that has a result attached to it. * Indicates an event that has a result attached to it.
*/ */
public interface ResultedEvent<R extends ResultedEvent.Result> { public interface ResultedEvent<R extends ResultedEvent.Result> {
/**
* Returns the result associated with this event. /**
* @return the result of this event * Returns the result associated with this event.
*/ *
R getResult(); * @return the result of this event
*/
R getResult();
/**
* Sets the result of this event. The result must be non-null.
*
* @param result the new result
*/
void setResult(R result);
/**
* Represents a result for an event.
*/
interface Result {
/** /**
* Sets the result of this event. The result must be non-null. * Returns whether or not the event is allowed to proceed. Plugins may choose to skip denied
* @param result the new result * events, and the proxy will respect the result of this method.
*
* @return whether or not the event is allowed to proceed
*/ */
void setResult(R result); boolean isAllowed();
}
/** /**
* Represents a result for an event. * A generic "allowed/denied" result.
*/ */
interface Result { final class GenericResult implements Result {
/**
* Returns whether or not the event is allowed to proceed. Plugins may choose to skip denied events, and the private static final GenericResult ALLOWED = new GenericResult(true);
* proxy will respect the result of this method. private static final GenericResult DENIED = new GenericResult(false);
* @return whether or not the event is allowed to proceed
*/ private final boolean status;
boolean isAllowed();
private GenericResult(boolean b) {
this.status = b;
} }
/** @Override
* A generic "allowed/denied" result. public boolean isAllowed() {
*/ return status;
final class GenericResult implements Result {
private static final GenericResult ALLOWED = new GenericResult(true);
private static final GenericResult DENIED = new GenericResult(false);
private final boolean status;
private GenericResult(boolean b) {
this.status = b;
}
@Override
public boolean isAllowed() {
return status;
}
@Override
public String toString() {
return status ? "allowed" : "denied";
}
public static GenericResult allowed() {
return ALLOWED;
}
public static GenericResult denied() {
return DENIED;
}
} }
/** @Override
* Represents an "allowed/denied" result with a reason allowed for denial. public String toString() {
*/ return status ? "allowed" : "denied";
final class ComponentResult implements Result {
private static final ComponentResult ALLOWED = new ComponentResult(true, null);
private final boolean status;
private final @Nullable Component reason;
protected ComponentResult(boolean status, @Nullable Component reason) {
this.status = status;
this.reason = reason;
}
@Override
public boolean isAllowed() {
return status;
}
public Optional<Component> getReason() {
return Optional.ofNullable(reason);
}
@Override
public String toString() {
if (status) {
return "allowed";
}
if (reason != null) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason);
}
return "denied";
}
public static ComponentResult allowed() {
return ALLOWED;
}
public static ComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new ComponentResult(false, reason);
}
} }
public static GenericResult allowed() {
return ALLOWED;
}
public static GenericResult denied() {
return DENIED;
}
}
/**
* Represents an "allowed/denied" result with a reason allowed for denial.
*/
final class ComponentResult implements Result {
private static final ComponentResult ALLOWED = new ComponentResult(true, null);
private final boolean status;
private final @Nullable Component reason;
protected ComponentResult(boolean status, @Nullable Component reason) {
this.status = status;
this.reason = reason;
}
@Override
public boolean isAllowed() {
return status;
}
public Optional<Component> getReason() {
return Optional.ofNullable(reason);
}
@Override
public String toString() {
if (status) {
return "allowed";
}
if (reason != null) {
return "denied: " + ComponentSerializers.PLAIN.serialize(reason);
}
return "denied";
}
public static ComponentResult allowed() {
return ALLOWED;
}
public static ComponentResult denied(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new ComponentResult(false, reason);
}
}
} }

Datei anzeigen

@ -12,11 +12,11 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface Subscribe { public @interface Subscribe {
/** /**
* The order events will be posted to this listener. * The order events will be posted to this listener.
* *
* @return the order * @return the order
*/ */
PostOrder order() default PostOrder.NORMAL; PostOrder order() default PostOrder.NORMAL;
} }

Datei anzeigen

@ -7,20 +7,21 @@ import com.velocitypowered.api.proxy.InboundConnection;
* This event is fired when a handshake is established between a client and Velocity. * This event is fired when a handshake is established between a client and Velocity.
*/ */
public final class ConnectionHandshakeEvent { public final class ConnectionHandshakeEvent {
private final InboundConnection connection;
public ConnectionHandshakeEvent(InboundConnection connection) { private final InboundConnection connection;
this.connection = Preconditions.checkNotNull(connection, "connection");
}
public InboundConnection getConnection() { public ConnectionHandshakeEvent(InboundConnection connection) {
return connection; this.connection = Preconditions.checkNotNull(connection, "connection");
} }
@Override public InboundConnection getConnection() {
public String toString() { return connection;
return "ConnectionHandshakeEvent{" + }
"connection=" + connection +
'}'; @Override
} public String toString() {
return "ConnectionHandshakeEvent{" +
"connection=" + connection +
'}';
}
} }

Datei anzeigen

@ -4,24 +4,25 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
/** /**
* This event is fired when a player disconnects from the proxy. Operations on the provided player, aside from basic * This event is fired when a player disconnects from the proxy. Operations on the provided player,
* data retrieval operations, may behave in undefined ways. * aside from basic data retrieval operations, may behave in undefined ways.
*/ */
public final class DisconnectEvent { public final class DisconnectEvent {
private final Player player;
public DisconnectEvent(Player player) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player");
}
public Player getPlayer() { public DisconnectEvent(Player player) {
return player; this.player = Preconditions.checkNotNull(player, "player");
} }
@Override public Player getPlayer() {
public String toString() { return player;
return "DisconnectEvent{" + }
"player=" + player +
'}'; @Override
} public String toString() {
return "DisconnectEvent{" +
"player=" + player +
'}';
}
} }

Datei anzeigen

@ -5,36 +5,38 @@ import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
/** /**
* This event is fired once the player has been authenticated but before they connect to a server on the proxy. * This event is fired once the player has been authenticated but before they connect to a server on
* the proxy.
*/ */
public final class LoginEvent implements ResultedEvent<ResultedEvent.ComponentResult> { public final class LoginEvent implements ResultedEvent<ResultedEvent.ComponentResult> {
private final Player player;
private ComponentResult result;
public LoginEvent(Player player) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player"); private ComponentResult result;
this.result = ComponentResult.allowed();
}
public Player getPlayer() { public LoginEvent(Player player) {
return player; this.player = Preconditions.checkNotNull(player, "player");
} this.result = ComponentResult.allowed();
}
@Override public Player getPlayer() {
public ComponentResult getResult() { return player;
return result; }
}
@Override @Override
public void setResult(ComponentResult result) { public ComponentResult getResult() {
this.result = Preconditions.checkNotNull(result, "result"); return result;
} }
@Override @Override
public String toString() { public void setResult(ComponentResult result) {
return "LoginEvent{" + this.result = Preconditions.checkNotNull(result, "result");
"player=" + player + }
", result=" + result +
'}'; @Override
} public String toString() {
return "LoginEvent{" +
"player=" + player +
", result=" + result +
'}';
}
} }

Datei anzeigen

@ -7,99 +7,100 @@ import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Arrays; 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}) * This event is fired when a plugin message is sent to the proxy, either from a client ({@link
* or a server ({@link com.velocitypowered.api.proxy.ServerConnection}). * com.velocitypowered.api.proxy.Player}) or a server ({@link com.velocitypowered.api.proxy.ServerConnection}).
*/ */
public final class PluginMessageEvent implements ResultedEvent<PluginMessageEvent.ForwardResult> { public final 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) { private final ChannelMessageSource source;
this.source = Preconditions.checkNotNull(source, "source"); private final ChannelMessageSink target;
this.target = Preconditions.checkNotNull(target, "target"); private final ChannelIdentifier identifier;
this.identifier = Preconditions.checkNotNull(identifier, "identifier"); private final byte[] data;
this.data = Preconditions.checkNotNull(data, "data"); private ForwardResult result;
this.result = ForwardResult.forward();
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(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 final class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean status;
private ForwardResult(boolean b) {
this.status = b;
} }
@Override @Override
public ForwardResult getResult() { public boolean isAllowed() {
return result; return status;
}
@Override
public void setResult(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 @Override
public String toString() { public String toString() {
return "PluginMessageEvent{" + return status ? "forward to sink" : "handled message at proxy";
"source=" + source +
", target=" + target +
", identifier=" + identifier +
", data=" + Arrays.toString(data) +
", result=" + result +
'}';
} }
/** public static ForwardResult forward() {
* A result determining whether or not to forward this message on. return ALLOWED;
*/
public static final class ForwardResult implements ResultedEvent.Result {
private static final ForwardResult ALLOWED = new ForwardResult(true);
private static final ForwardResult DENIED = new ForwardResult(false);
private final boolean status;
private ForwardResult(boolean b) {
this.status = b;
}
@Override
public boolean isAllowed() {
return status;
}
@Override
public String toString() {
return status ? "forward to sink" : "handled message at proxy";
}
public static ForwardResult forward() {
return ALLOWED;
}
public static ForwardResult handled() {
return DENIED;
}
} }
public static ForwardResult handled() {
return DENIED;
}
}
} }

Datei anzeigen

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

Datei anzeigen

@ -3,147 +3,153 @@ package com.velocitypowered.api.event.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import java.util.Optional;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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 * This event is fired when a player has initiated a connection with the proxy but before the proxy
* player with Mojang or before the player's proxy connection is fully established (for offline mode). * authenticates the player with Mojang or before the player's proxy connection is fully established
* (for offline mode).
*/ */
public final class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginComponentResult> { public final class PreLoginEvent implements ResultedEvent<PreLoginEvent.PreLoginComponentResult> {
private final InboundConnection connection;
private final String username;
private PreLoginComponentResult result;
public PreLoginEvent(InboundConnection connection, String username) { private final InboundConnection connection;
this.connection = Preconditions.checkNotNull(connection, "connection"); private final String username;
this.username = Preconditions.checkNotNull(username, "username"); private PreLoginComponentResult result;
this.result = PreLoginComponentResult.allowed();
}
public InboundConnection getConnection() { public PreLoginEvent(InboundConnection connection, String username) {
return connection; this.connection = Preconditions.checkNotNull(connection, "connection");
} this.username = Preconditions.checkNotNull(username, "username");
this.result = PreLoginComponentResult.allowed();
}
public String getUsername() { public InboundConnection getConnection() {
return username; return connection;
}
public String getUsername() {
return username;
}
@Override
public PreLoginComponentResult getResult() {
return result;
}
@Override
public void setResult(@NonNull PreLoginComponentResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
@Override
public String toString() {
return "PreLoginEvent{" +
"connection=" + connection +
", username='" + username + '\'' +
", result=" + result +
'}';
}
/**
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason
* allowed for denial.
*/
public static final class PreLoginComponentResult implements ResultedEvent.Result {
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);
private final Result result;
private final @Nullable Component reason;
private PreLoginComponentResult(Result result, @Nullable Component reason) {
this.result = result;
this.reason = reason;
} }
@Override @Override
public PreLoginComponentResult getResult() { public boolean isAllowed() {
return result; return result != Result.DISALLOWED;
} }
@Override public Optional<Component> getReason() {
public void setResult(@NonNull PreLoginComponentResult result) { return Optional.ofNullable(reason);
this.result = Preconditions.checkNotNull(result, "result"); }
public boolean isOnlineModeAllowed() {
return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
} }
@Override @Override
public String toString() { public String toString() {
return "PreLoginEvent{" + switch (result) {
"connection=" + connection + case ALLOWED:
", username='" + username + '\'' + return "allowed";
", result=" + result + case FORCE_OFFLINE:
'}'; return "allowed with force offline mode";
case FORCE_ONLINE:
return "allowed with online mode";
default:
return "denied";
}
} }
/** /**
* Represents an "allowed/allowed with forced online\offline mode/denied" result with a reason allowed for denial. * Returns a result indicating the connection will be allowed through the proxy.
*
* @return the allowed result
*/ */
public static final class PreLoginComponentResult implements ResultedEvent.Result { public static PreLoginComponentResult allowed() {
return ALLOWED;
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);
private final Result result;
private final @Nullable Component reason;
private PreLoginComponentResult(Result result, @Nullable Component reason) {
this.result = result;
this.reason = reason;
}
@Override
public boolean isAllowed() {
return result != Result.DISALLOWED;
}
public Optional<Component> getReason() {
return Optional.ofNullable(reason);
}
public boolean isOnlineModeAllowed() {
return result == Result.FORCE_ONLINE;
}
public boolean isForceOfflineMode() {
return result == Result.FORCE_OFFLINE;
}
@Override
public String toString() {
switch (result) {
case ALLOWED:
return "allowed";
case FORCE_OFFLINE:
return "allowed with force offline mode";
case FORCE_ONLINE:
return "allowed with online mode";
default:
return "denied";
}
}
/**
* Returns a result indicating the connection will be allowed through
* the proxy.
* @return the allowed result
*/
public static PreLoginComponentResult allowed() {
return ALLOWED;
}
/**
* 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(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
}
} }
/**
* 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(Component reason) {
Preconditions.checkNotNull(reason, "reason");
return new PreLoginComponentResult(Result.DISALLOWED, reason);
}
private enum Result {
ALLOWED,
FORCE_ONLINE,
FORCE_OFFLINE,
DISALLOWED
}
}
} }

Datei anzeigen

@ -12,52 +12,52 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* <p>This event is only called once per subject, on initialisation.</p> * <p>This event is only called once per subject, on initialisation.</p>
*/ */
public final class PermissionsSetupEvent { public final class PermissionsSetupEvent {
private final PermissionSubject subject;
private final PermissionProvider defaultProvider;
private PermissionProvider provider;
public PermissionsSetupEvent(PermissionSubject subject, PermissionProvider provider) { private final PermissionSubject subject;
this.subject = Preconditions.checkNotNull(subject, "subject"); private final PermissionProvider defaultProvider;
this.provider = this.defaultProvider = Preconditions.checkNotNull(provider, "provider"); private PermissionProvider provider;
}
public PermissionSubject getSubject() { public PermissionsSetupEvent(PermissionSubject subject, PermissionProvider provider) {
return this.subject; this.subject = Preconditions.checkNotNull(subject, "subject");
} this.provider = this.defaultProvider = Preconditions.checkNotNull(provider, "provider");
}
/** public PermissionSubject getSubject() {
* Uses the provider function to obtain a {@link PermissionFunction} for return this.subject;
* the subject. }
*
* @param subject the subject
* @return the obtained permission function
*/
public PermissionFunction createFunction(PermissionSubject subject) {
return this.provider.createFunction(subject);
}
public PermissionProvider getProvider() { /**
return this.provider; * Uses the provider function to obtain a {@link PermissionFunction} for the subject.
} *
* @param subject the subject
* @return the obtained permission function
*/
public PermissionFunction createFunction(PermissionSubject subject) {
return this.provider.createFunction(subject);
}
/** public PermissionProvider getProvider() {
* Sets the {@link PermissionFunction} that should be used for the subject. return this.provider;
* }
* <p>Specifying <code>null</code> will reset the provider to the default
* instance given when the event was posted.</p>
*
* @param provider the provider
*/
public void setProvider(@Nullable PermissionProvider provider) {
this.provider = provider == null ? this.defaultProvider : provider;
}
@Override /**
public String toString() { * Sets the {@link PermissionFunction} that should be used for the subject.
return "PermissionsSetupEvent{" + *
"subject=" + subject + * <p>Specifying <code>null</code> will reset the provider to the default
", defaultProvider=" + defaultProvider + * instance given when the event was posted.</p>
", provider=" + provider + *
'}'; * @param provider the provider
} */
public void setProvider(@Nullable PermissionProvider provider) {
this.provider = provider == null ? this.defaultProvider : provider;
}
@Override
public String toString() {
return "PermissionsSetupEvent{" +
"subject=" + subject +
", defaultProvider=" + defaultProvider +
", provider=" + provider +
'}';
}
} }

Datei anzeigen

@ -6,65 +6,73 @@ import com.velocitypowered.api.util.GameProfile;
import org.checkerframework.checker.nullness.qual.Nullable; 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 * This event is fired after the {@link com.velocitypowered.api.event.connection.PreLoginEvent} in
* game profile for the user. This can be used to configure a custom profile for a user, i.e. skin replacement. * order to set up the game profile for the user. This can be used to configure a custom profile for
* a user, i.e. skin replacement.
*/ */
public final class GameProfileRequestEvent { public final class GameProfileRequestEvent {
private final String username;
private final InboundConnection connection;
private final GameProfile originalProfile;
private final boolean onlineMode;
private @Nullable GameProfile gameProfile;
public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile, boolean onlineMode) { private final String username;
this.connection = Preconditions.checkNotNull(connection, "connection"); private final InboundConnection connection;
this.originalProfile = Preconditions.checkNotNull(originalProfile, "originalProfile"); private final GameProfile originalProfile;
this.username = originalProfile.getName(); private final boolean onlineMode;
this.onlineMode = onlineMode; private @Nullable GameProfile gameProfile;
}
public InboundConnection getConnection() { public GameProfileRequestEvent(InboundConnection connection, GameProfile originalProfile,
return connection; boolean onlineMode) {
} this.connection = Preconditions.checkNotNull(connection, "connection");
this.originalProfile = Preconditions.checkNotNull(originalProfile, "originalProfile");
this.username = originalProfile.getName();
this.onlineMode = onlineMode;
}
public String getUsername() { public InboundConnection getConnection() {
return username; return connection;
} }
public GameProfile getOriginalProfile() {
return originalProfile;
}
public boolean isOnlineMode() {
return onlineMode;
}
/** public String getUsername() {
* Returns the game profile that will be used to initialize the connection with. Should no profile be currently return username;
* specified, the one generated by the proxy (for offline mode) or retrieved from the Mojang session servers (for }
* online mode) will be returned instead.
* @return the user's {@link GameProfile} public GameProfile getOriginalProfile() {
*/ return originalProfile;
public GameProfile getGameProfile() { }
return gameProfile == null ? originalProfile : gameProfile;
} public boolean isOnlineMode() {
return onlineMode;
}
/**
* Returns the game profile that will be used to initialize the connection with. Should no profile
* be currently specified, the one generated by the proxy (for offline mode) or retrieved from the
* Mojang session servers (for online mode) will be returned instead.
*
* @return the user's {@link GameProfile}
*/
public GameProfile getGameProfile() {
return gameProfile == null ? originalProfile : gameProfile;
}
/**
* Sets the game profile to use for this connection. It is invalid to use this method on an
* online-mode connection.
*
* @param gameProfile the profile to use for the connection, {@code null} uses the original
* profile
*/
public void setGameProfile(@Nullable GameProfile gameProfile) {
Preconditions
.checkState(!onlineMode, "Connection is in online mode, profiles can not be faked");
this.gameProfile = gameProfile;
}
@Override
public String toString() {
return "GameProfileRequestEvent{" +
"username=" + username +
", gameProfile=" + gameProfile +
"}";
}
/**
* Sets the game profile to use for this connection. It is invalid to use this method on an online-mode connection.
* @param gameProfile the profile to use for the connection, {@code null} uses the original profile
*/
public void setGameProfile(@Nullable GameProfile gameProfile) {
Preconditions.checkState(!onlineMode, "Connection is in online mode, profiles can not be faked");
this.gameProfile = gameProfile;
}
@Override
public String toString() {
return "GameProfileRequestEvent{"+
"username=" + username +
", gameProfile=" + gameProfile +
"}";
}
} }

Datei anzeigen

@ -8,111 +8,120 @@ import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
* Fired when a player is kicked from a server. You may either allow Velocity to kick the player (with an optional reason * Fired when a player is kicked from a server. You may either allow Velocity to kick the player
* override) or redirect the player to a separate server. * (with an optional reason override) or redirect the player to a separate server.
*/ */
public final class KickedFromServerEvent implements ResultedEvent<KickedFromServerEvent.ServerKickResult> { public final class KickedFromServerEvent implements
private final Player player; ResultedEvent<KickedFromServerEvent.ServerKickResult> {
private final Player player;
private final RegisteredServer server;
private final Component originalReason;
private final boolean duringLogin;
private ServerKickResult result;
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");
this.duringLogin = duringLogin;
this.result = new DisconnectPlayer(fancyReason);
}
@Override
public ServerKickResult getResult() {
return result;
}
@Override
public void setResult(@NonNull ServerKickResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public Player getPlayer() {
return player;
}
public RegisteredServer getServer() {
return server;
}
public Component getOriginalReason() {
return originalReason;
}
public boolean kickedDuringLogin() {
return duringLogin;
}
/**
* Represents the base interface for {@link KickedFromServerEvent} results.
*/
public interface ServerKickResult extends ResultedEvent.Result {
}
/**
* Tells the proxy to disconnect the player with the specified reason.
*/
public static final class DisconnectPlayer implements ServerKickResult {
private final Component component;
private DisconnectPlayer(Component component) {
this.component = Preconditions.checkNotNull(component, "component");
}
@Override
public boolean isAllowed() {
return true;
}
public Component getReason() {
return component;
}
/**
* Creates a new {@link DisconnectPlayer} with the specified reason.
*
* @param reason the reason to use when disconnecting the player
* @return the disconnect result
*/
public static DisconnectPlayer create(Component reason) {
return new DisconnectPlayer(reason);
}
}
/**
* Tells the proxy to redirect the player to another server. No messages will be sent from the
* proxy when this result is used.
*/
public static final class RedirectPlayer implements ServerKickResult {
private final RegisteredServer server; private final RegisteredServer server;
private final Component originalReason;
private final boolean duringLogin;
private ServerKickResult result;
public KickedFromServerEvent(Player player, RegisteredServer server, Component originalReason, boolean duringLogin, Component fancyReason) { private RedirectPlayer(RegisteredServer server) {
this.player = Preconditions.checkNotNull(player, "player"); this.server = Preconditions.checkNotNull(server, "server");
this.server = Preconditions.checkNotNull(server, "server");
this.originalReason = Preconditions.checkNotNull(originalReason, "originalReason");
this.duringLogin = duringLogin;
this.result = new DisconnectPlayer(fancyReason);
} }
@Override @Override
public ServerKickResult getResult() { public boolean isAllowed() {
return result; return false;
}
@Override
public void setResult(@NonNull ServerKickResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public Player getPlayer() {
return player;
} }
public RegisteredServer getServer() { public RegisteredServer getServer() {
return server; return server;
}
public Component getOriginalReason() {
return originalReason;
}
public boolean kickedDuringLogin() {
return duringLogin;
} }
/** /**
* Represents the base interface for {@link KickedFromServerEvent} results. * Creates a new redirect result to forward the player to the specified {@code server}.
*
* @param server the server to send the player to
* @return the redirect result
*/ */
public interface ServerKickResult extends ResultedEvent.Result {} public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server);
/**
* Tells the proxy to disconnect the player with the specified reason.
*/
public static final class DisconnectPlayer implements ServerKickResult {
private final Component component;
private DisconnectPlayer(Component component) {
this.component = Preconditions.checkNotNull(component, "component");
}
@Override
public boolean isAllowed() {
return true;
}
public Component getReason() {
return component;
}
/**
* Creates a new {@link DisconnectPlayer} with the specified reason.
* @param reason the reason to use when disconnecting the player
* @return the disconnect result
*/
public static DisconnectPlayer create(Component reason) {
return new DisconnectPlayer(reason);
}
}
/**
* Tells the proxy to redirect the player to another server. No messages will be sent from the proxy
* when this result is used.
*/
public static final class RedirectPlayer implements ServerKickResult {
private final RegisteredServer server;
private RedirectPlayer(RegisteredServer server) {
this.server = Preconditions.checkNotNull(server, "server");
}
@Override
public boolean isAllowed() {
return false;
}
public RegisteredServer getServer() {
return server;
}
/**
* Creates a new redirect result to forward the player to the specified {@code server}.
* @param server the server to send the player to
* @return the redirect result
*/
public static RedirectPlayer create(RegisteredServer server) {
return new RedirectPlayer(server);
}
} }
}
} }

Datei anzeigen

@ -3,95 +3,96 @@ package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/** /**
* This event is fired when a player types in a chat message. * This event is fired when a player types in a chat message.
*/ */
public final class PlayerChatEvent implements ResultedEvent<PlayerChatEvent.ChatResult> { public final class PlayerChatEvent implements ResultedEvent<PlayerChatEvent.ChatResult> {
private final Player player;
private final String message;
private ChatResult result;
public PlayerChatEvent(Player player, String message) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player"); private final String message;
this.message = Preconditions.checkNotNull(message, "message"); private ChatResult result;
this.result = ChatResult.allowed();
}
public Player getPlayer() { public PlayerChatEvent(Player player, String message) {
return player; this.player = Preconditions.checkNotNull(player, "player");
} this.message = Preconditions.checkNotNull(message, "message");
this.result = ChatResult.allowed();
}
public String getMessage() { public Player getPlayer() {
return message; return player;
}
public String getMessage() {
return message;
}
@Override
public ChatResult getResult() {
return result;
}
@Override
public void setResult(ChatResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
@Override
public String toString() {
return "PlayerChatEvent{" +
"player=" + player +
", message=" + message +
", result=" + result +
'}';
}
/**
* Represents the result of the {@link PlayerChatEvent}.
*/
public static final class ChatResult implements ResultedEvent.Result {
private static final ChatResult ALLOWED = new ChatResult(true, null);
private static final ChatResult DENIED = new ChatResult(false, null);
// The server can not accept formatted text from clients!
private @Nullable String message;
private final boolean status;
protected ChatResult(boolean status, @Nullable String message) {
this.status = status;
this.message = message;
} }
@Override @Override
public ChatResult getResult() { public boolean isAllowed() {
return result; return status;
}
@Override
public void setResult(ChatResult result) {
this.result = Preconditions.checkNotNull(result, "result");
} }
@Override @Override
public String toString() { public String toString() {
return "PlayerChatEvent{" + return status ? "allowed" : "denied";
"player=" + player +
", message=" + message +
", result=" + result +
'}';
} }
/** public static ChatResult allowed() {
* Represents the result of the {@link PlayerChatEvent}. return ALLOWED;
*/
public static final class ChatResult implements ResultedEvent.Result {
private static final ChatResult ALLOWED = new ChatResult(true, null);
private static final ChatResult DENIED = new ChatResult(false, null);
// The server can not accept formatted text from clients!
private @Nullable String message;
private final boolean status;
protected ChatResult(boolean status, @Nullable String message) {
this.status = status;
this.message = message;
}
@Override
public boolean isAllowed() {
return status;
}
@Override
public String toString() {
return status ? "allowed" : "denied";
}
public static ChatResult allowed() {
return ALLOWED;
}
public static ChatResult denied() {
return DENIED;
}
public Optional<String> getMessage() {
return Optional.ofNullable(message);
}
public static ChatResult message(@NonNull String message) {
Preconditions.checkNotNull(message, "message");
return new ChatResult(true, message);
}
} }
public static ChatResult denied() {
return DENIED;
}
public Optional<String> getMessage() {
return Optional.ofNullable(message);
}
public static ChatResult message(@NonNull String message) {
Preconditions.checkNotNull(message, "message");
return new ChatResult(true, message);
}
}
} }

Datei anzeigen

@ -8,19 +8,20 @@ import com.velocitypowered.api.util.ModInfo;
* This event is fired when the players ModInfo is changed. * This event is fired when the players ModInfo is changed.
*/ */
public final class PlayerModInfoEvent { public final class PlayerModInfoEvent {
private final Player player;
private final ModInfo modInfo; private final Player player;
private final ModInfo modInfo;
public PlayerModInfoEvent(Player player, ModInfo modInfo) {
this.player = Preconditions.checkNotNull(player, "player"); public PlayerModInfoEvent(Player player, ModInfo modInfo) {
this.modInfo = Preconditions.checkNotNull(modInfo, "modInfo"); this.player = Preconditions.checkNotNull(player, "player");
} this.modInfo = Preconditions.checkNotNull(modInfo, "modInfo");
}
public Player getPlayer() {
return player; public Player getPlayer() {
} return player;
}
public ModInfo getModInfo() {
return modInfo; public ModInfo getModInfo() {
} return modInfo;
}
} }

Datei anzeigen

@ -5,19 +5,20 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
public final class PlayerSettingsChangedEvent { public final class PlayerSettingsChangedEvent {
private final Player player;
private final PlayerSettings playerSettings;
public PlayerSettingsChangedEvent(Player player, PlayerSettings playerSettings) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player"); private final PlayerSettings playerSettings;
this.playerSettings = Preconditions.checkNotNull(playerSettings, "playerSettings");
}
public Player getPlayer() { public PlayerSettingsChangedEvent(Player player, PlayerSettings playerSettings) {
return player; this.player = Preconditions.checkNotNull(player, "player");
} this.playerSettings = Preconditions.checkNotNull(playerSettings, "playerSettings");
}
public PlayerSettings getPlayerSettings() { public Player getPlayer() {
return playerSettings; return player;
} }
public PlayerSettings getPlayerSettings() {
return playerSettings;
}
} }

Datei anzeigen

@ -5,31 +5,32 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; 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 * This event is fired once the player has successfully connected to the target server and the
* server has been de-established. * connection to the previous server has been de-established.
*/ */
public final class ServerConnectedEvent { public final class ServerConnectedEvent {
private final Player player;
private final RegisteredServer server;
public ServerConnectedEvent(Player player, RegisteredServer server) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player"); private final RegisteredServer server;
this.server = Preconditions.checkNotNull(server, "server");
}
public Player getPlayer() { public ServerConnectedEvent(Player player, RegisteredServer server) {
return player; this.player = Preconditions.checkNotNull(player, "player");
} this.server = Preconditions.checkNotNull(server, "server");
}
public RegisteredServer getServer() { public Player getPlayer() {
return server; return player;
} }
@Override public RegisteredServer getServer() {
public String toString() { return server;
return "ServerConnectedEvent{" + }
"player=" + player +
", server=" + server + @Override
'}'; public String toString() {
} return "ServerConnectedEvent{" +
"player=" + player +
", server=" + server +
'}';
}
} }

Datei anzeigen

@ -4,87 +4,89 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.event.ResultedEvent; import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional; import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* This event is fired before the player connects to a server. * This event is fired before the player connects to a server.
*/ */
public final class ServerPreConnectEvent implements ResultedEvent<ServerPreConnectEvent.ServerResult> { public final class ServerPreConnectEvent implements
private final Player player; ResultedEvent<ServerPreConnectEvent.ServerResult> {
private final RegisteredServer originalServer;
private ServerResult result;
public ServerPreConnectEvent(Player player, RegisteredServer originalServer) { private final Player player;
this.player = Preconditions.checkNotNull(player, "player"); private final RegisteredServer originalServer;
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer"); private ServerResult result;
this.result = ServerResult.allowed(originalServer);
}
public Player getPlayer() { public ServerPreConnectEvent(Player player, RegisteredServer originalServer) {
return player; this.player = Preconditions.checkNotNull(player, "player");
this.originalServer = Preconditions.checkNotNull(originalServer, "originalServer");
this.result = ServerResult.allowed(originalServer);
}
public Player getPlayer() {
return player;
}
@Override
public ServerResult getResult() {
return result;
}
@Override
public void setResult(ServerResult result) {
this.result = Preconditions.checkNotNull(result, "result");
}
public RegisteredServer getOriginalServer() {
return originalServer;
}
@Override
public String toString() {
return "ServerPreConnectEvent{" +
"player=" + player +
", originalServer=" + originalServer +
", result=" + result +
'}';
}
/**
* Represents the result of the {@link ServerPreConnectEvent}.
*/
public static class ServerResult implements ResultedEvent.Result {
private static final ServerResult DENIED = new ServerResult(null);
private final @Nullable RegisteredServer server;
private ServerResult(@Nullable RegisteredServer server) {
this.server = server;
} }
@Override @Override
public ServerResult getResult() { public boolean isAllowed() {
return result; return server != null;
} }
@Override public Optional<RegisteredServer> getServer() {
public void setResult(ServerResult result) { return Optional.ofNullable(server);
this.result = Preconditions.checkNotNull(result, "result");
}
public RegisteredServer getOriginalServer() {
return originalServer;
} }
@Override @Override
public String toString() { public String toString() {
return "ServerPreConnectEvent{" + if (server != null) {
"player=" + player + return "allowed: connect to " + server.getServerInfo().getName();
", originalServer=" + originalServer + }
", result=" + result + return "denied";
'}';
} }
/** public static ServerResult denied() {
* Represents the result of the {@link ServerPreConnectEvent}. return DENIED;
*/
public static class ServerResult implements ResultedEvent.Result {
private static final ServerResult DENIED = new ServerResult(null);
private final @Nullable RegisteredServer server;
private ServerResult(@Nullable RegisteredServer server) {
this.server = server;
}
@Override
public boolean isAllowed() {
return server != null;
}
public Optional<RegisteredServer> getServer() {
return Optional.ofNullable(server);
}
@Override
public String toString() {
if (server != null) {
return "allowed: connect to " + server.getServerInfo().getName();
}
return "denied";
}
public static ServerResult denied() {
return DENIED;
}
public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server");
return new ServerResult(server);
}
} }
public static ServerResult allowed(RegisteredServer server) {
Preconditions.checkNotNull(server, "server");
return new ServerResult(server);
}
}
} }

Datei anzeigen

@ -1,11 +1,13 @@
package com.velocitypowered.api.event.proxy; package com.velocitypowered.api.event.proxy;
/** /**
* This event is fired by the proxy after plugins have been loaded but before the proxy starts accepting connections. * This event is fired by the proxy after plugins have been loaded but before the proxy starts
* accepting connections.
*/ */
public final class ProxyInitializeEvent { public final class ProxyInitializeEvent {
@Override
public String toString() { @Override
return "ProxyInitializeEvent"; public String toString() {
} return "ProxyInitializeEvent";
}
} }

Datei anzeigen

@ -8,31 +8,32 @@ import com.velocitypowered.api.proxy.server.ServerPing;
* This event is fired when a server list ping request is sent by a remote client. * This event is fired when a server list ping request is sent by a remote client.
*/ */
public final class ProxyPingEvent { public final class ProxyPingEvent {
private final InboundConnection connection;
private ServerPing ping;
public ProxyPingEvent(InboundConnection connection, ServerPing ping) { private final InboundConnection connection;
this.connection = Preconditions.checkNotNull(connection, "connection"); private ServerPing ping;
this.ping = Preconditions.checkNotNull(ping, "ping");
}
public InboundConnection getConnection() { public ProxyPingEvent(InboundConnection connection, ServerPing ping) {
return connection; this.connection = Preconditions.checkNotNull(connection, "connection");
} this.ping = Preconditions.checkNotNull(ping, "ping");
}
public ServerPing getPing() { public InboundConnection getConnection() {
return ping; return connection;
} }
public void setPing(ServerPing ping) { public ServerPing getPing() {
this.ping = Preconditions.checkNotNull(ping, "ping"); return ping;
} }
@Override public void setPing(ServerPing ping) {
public String toString() { this.ping = Preconditions.checkNotNull(ping, "ping");
return "ProxyPingEvent{" + }
"connection=" + connection +
", ping=" + ping + @Override
'}'; public String toString() {
} return "ProxyPingEvent{" +
"connection=" + connection +
", ping=" + ping +
'}';
}
} }

Datei anzeigen

@ -1,12 +1,13 @@
package com.velocitypowered.api.event.proxy; package com.velocitypowered.api.event.proxy;
/** /**
* This event is fired by the proxy after the proxy has stopped accepting connections but before the proxy process * This event is fired by the proxy after the proxy has stopped accepting connections but before the
* exits. * proxy process exits.
*/ */
public final class ProxyShutdownEvent { public final class ProxyShutdownEvent {
@Override
public String toString() { @Override
return "ProxyShutdownEvent"; public String toString() {
} return "ProxyShutdownEvent";
}
} }

Datei anzeigen

@ -2,82 +2,86 @@ package com.velocitypowered.api.event.query;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.server.QueryResponse; import com.velocitypowered.api.proxy.server.QueryResponse;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.net.InetAddress; import java.net.InetAddress;
import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
* This event is fired if proxy is getting queried over GS4 Query protocol * This event is fired if proxy is getting queried over GS4 Query protocol
*/ */
public final class ProxyQueryEvent { public final class ProxyQueryEvent {
private final QueryType queryType;
private final InetAddress querierAddress;
private QueryResponse response;
public ProxyQueryEvent(QueryType queryType, InetAddress querierAddress, QueryResponse response) { private final QueryType queryType;
this.queryType = Preconditions.checkNotNull(queryType, "queryType"); private final InetAddress querierAddress;
this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress"); private QueryResponse response;
this.response = Preconditions.checkNotNull(response, "response");
} public ProxyQueryEvent(QueryType queryType, InetAddress querierAddress, QueryResponse response) {
this.queryType = Preconditions.checkNotNull(queryType, "queryType");
this.querierAddress = Preconditions.checkNotNull(querierAddress, "querierAddress");
this.response = Preconditions.checkNotNull(response, "response");
}
/**
* Get query type
*
* @return query type
*/
@NonNull
public QueryType getQueryType() {
return queryType;
}
/**
* Get querier address
*
* @return querier address
*/
@NonNull
public InetAddress getQuerierAddress() {
return querierAddress;
}
/**
* Get query response
*
* @return query response
*/
@NonNull
public QueryResponse getResponse() {
return response;
}
/**
* Set query response
*
* @param response query response
*/
public void setResponse(@NonNull QueryResponse response) {
this.response = Preconditions.checkNotNull(response, "response");
}
@Override
public String toString() {
return "ProxyQueryEvent{" +
"queryType=" + queryType +
", querierAddress=" + querierAddress +
", response=" + response +
'}';
}
/**
* The type of query
*/
public enum QueryType {
/**
* Basic query asks only a subset of information, such as hostname, game type (hardcoded to
* <pre>MINECRAFT</pre>), map, current players, max players, proxy port and proxy hostname
*/
BASIC,
/** /**
* Get query type * Full query asks pretty much everything present on this event (only hardcoded values cannot be
* @return query type * modified here).
*/ */
@NonNull FULL;
public QueryType getQueryType() { }
return queryType;
}
/**
* Get querier address
* @return querier address
*/
@NonNull
public InetAddress getQuerierAddress() {
return querierAddress;
}
/**
* Get query response
* @return query response
*/
@NonNull
public QueryResponse getResponse() {
return response;
}
/**
* Set query response
* @param response query response
*/
public void setResponse(@NonNull QueryResponse response) {
this.response = Preconditions.checkNotNull(response, "response");
}
@Override
public String toString() {
return "ProxyQueryEvent{" +
"queryType=" + queryType +
", querierAddress=" + querierAddress +
", response=" + response +
'}';
}
/**
* The type of query
*/
public enum QueryType {
/**
* Basic query asks only a subset of information, such as hostname, game type (hardcoded to <pre>MINECRAFT</pre>), map,
* current players, max players, proxy port and proxy hostname
*/
BASIC,
/**
* Full query asks pretty much everything present on this event (only hardcoded values cannot be modified here).
*/
FULL
;
}
} }

Datei anzeigen

@ -1,31 +1,31 @@
package com.velocitypowered.api.permission; package com.velocitypowered.api.permission;
/** /**
* Function that calculates the permission settings for a given * Function that calculates the permission settings for a given {@link PermissionSubject}.
* {@link PermissionSubject}.
*/ */
@FunctionalInterface @FunctionalInterface
public interface PermissionFunction { public interface PermissionFunction {
/**
* A permission function that always returns {@link Tristate#TRUE}.
*/
PermissionFunction ALWAYS_TRUE = p -> Tristate.TRUE;
/** /**
* A permission function that always returns {@link Tristate#FALSE}. * A permission function that always returns {@link Tristate#TRUE}.
*/ */
PermissionFunction ALWAYS_FALSE = p -> Tristate.FALSE; PermissionFunction ALWAYS_TRUE = p -> Tristate.TRUE;
/** /**
* A permission function that always returns {@link Tristate#UNDEFINED}. * A permission function that always returns {@link Tristate#FALSE}.
*/ */
PermissionFunction ALWAYS_UNDEFINED = p -> Tristate.UNDEFINED; PermissionFunction ALWAYS_FALSE = p -> Tristate.FALSE;
/** /**
* Gets the subjects setting for a particular permission. * A permission function that always returns {@link Tristate#UNDEFINED}.
* */
* @param permission the permission PermissionFunction ALWAYS_UNDEFINED = p -> Tristate.UNDEFINED;
* @return the value the permission is set to
*/ /**
Tristate getPermissionValue(String permission); * Gets the subjects setting for a particular permission.
*
* @param permission the permission
* @return the value the permission is set to
*/
Tristate getPermissionValue(String permission);
} }

Datei anzeigen

@ -5,11 +5,12 @@ package com.velocitypowered.api.permission;
*/ */
@FunctionalInterface @FunctionalInterface
public interface PermissionProvider { public interface PermissionProvider {
/**
* Creates a {@link PermissionFunction} for the subject. /**
* * Creates a {@link PermissionFunction} for the subject.
* @param subject the subject *
* @return the function * @param subject the subject
*/ * @return the function
PermissionFunction createFunction(PermissionSubject subject); */
PermissionFunction createFunction(PermissionSubject subject);
} }

Datei anzeigen

@ -4,21 +4,22 @@ package com.velocitypowered.api.permission;
* Represents a object that has a set of queryable permissions. * Represents a object that has a set of queryable permissions.
*/ */
public interface PermissionSubject { public interface PermissionSubject {
/**
* Determines whether or not the subject has a particular permission.
*
* @param permission the permission to check for
* @return whether or not the subject has the permission
*/
default boolean hasPermission(String permission) {
return getPermissionValue(permission).asBoolean();
}
/** /**
* Gets the subjects setting for a particular permission. * Determines whether or not the subject has a particular permission.
* *
* @param permission the permission * @param permission the permission to check for
* @return the value the permission is set to * @return whether or not the subject has the permission
*/ */
Tristate getPermissionValue(String permission); default boolean hasPermission(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
*/
Tristate getPermissionValue(String permission);
} }

Datei anzeigen

@ -8,69 +8,70 @@ import org.checkerframework.checker.nullness.qual.Nullable;
* <p>Possible values:</p> * <p>Possible values:</p>
* <p></p> * <p></p>
* <ul> * <ul>
* <li>{@link #TRUE} - a positive setting</li> * <li>{@link #TRUE} - a positive setting</li>
* <li>{@link #FALSE} - a negative (negated) setting</li> * <li>{@link #FALSE} - a negative (negated) setting</li>
* <li>{@link #UNDEFINED} - a non-existent setting</li> * <li>{@link #UNDEFINED} - a non-existent setting</li>
* </ul> * </ul>
*/ */
public enum Tristate { public enum Tristate {
/** /**
* A value indicating a positive setting * A value indicating a positive setting
*/ */
TRUE(true), TRUE(true),
/** /**
* A value indicating a negative (negated) setting * A value indicating a negative (negated) setting
*/ */
FALSE(false), FALSE(false),
/** /**
* A value indicating a non-existent setting * A value indicating a non-existent setting
*/ */
UNDEFINED(false); UNDEFINED(false);
/** /**
* Returns a {@link Tristate} from a boolean * Returns a {@link Tristate} from a boolean
* *
* @param val the boolean value * @param val the boolean value
* @return {@link #TRUE} or {@link #FALSE}, if the value is <code>true</code> or <code>false</code>, respectively. * @return {@link #TRUE} or {@link #FALSE}, if the value is <code>true</code> or
*/ * <code>false</code>, respectively.
public static Tristate fromBoolean(boolean val) { */
return val ? TRUE : FALSE; public static Tristate fromBoolean(boolean val) {
return val ? TRUE : FALSE;
}
/**
* Returns a {@link Tristate} from a nullable boolean.
*
* <p>Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED}
* if the value is null.</p>
*
* @param val the boolean value
* @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value is <code>null</code>,
* <code>true</code> or <code>false</code>, respectively.
*/
public static Tristate fromNullableBoolean(@Nullable Boolean val) {
if (val == null) {
return UNDEFINED;
} }
return val ? TRUE : FALSE;
}
/** private final boolean booleanValue;
* Returns a {@link Tristate} from a nullable boolean.
*
* <p>Unlike {@link #fromBoolean(boolean)}, this method returns {@link #UNDEFINED}
* if the value is null.</p>
*
* @param val the boolean value
* @return {@link #UNDEFINED}, {@link #TRUE} or {@link #FALSE}, if the value
* is <code>null</code>, <code>true</code> or <code>false</code>, respectively.
*/
public static Tristate fromNullableBoolean(@Nullable Boolean val) {
if (val == null) {
return UNDEFINED;
}
return val ? TRUE : FALSE;
}
private final boolean booleanValue; Tristate(boolean booleanValue) {
this.booleanValue = booleanValue;
}
Tristate(boolean booleanValue) { /**
this.booleanValue = booleanValue; * Returns the value of the Tristate as a boolean.
} *
* <p>A value of {@link #UNDEFINED} converts to false.</p>
/** *
* Returns the value of the Tristate as a boolean. * @return a boolean representation of the Tristate.
* */
* <p>A value of {@link #UNDEFINED} converts to false.</p> public boolean asBoolean() {
* return this.booleanValue;
* @return a boolean representation of the Tristate. }
*/
public boolean asBoolean() {
return this.booleanValue;
}
} }

Datei anzeigen

@ -10,19 +10,19 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target({}) @Target({})
public @interface Dependency { public @interface Dependency {
/**
* The plugin ID of the dependency.
*
* @return The dependency plugin ID
* @see Plugin#id()
*/
String id();
/** /**
* If this dependency is optional for the plugin to work. By default * The plugin ID of the dependency.
* this is {@code false}. *
* * @return The dependency plugin ID
* @return true if the dependency is optional for the plugin to work * @see Plugin#id()
*/ */
boolean optional() default false; String id();
/**
* If this dependency is optional for the plugin to work. By default this is {@code false}.
*
* @return true if the dependency is optional for the plugin to work
*/
boolean optional() default false;
} }

Datei anzeigen

@ -1,19 +1,20 @@
package com.velocitypowered.api.plugin; package com.velocitypowered.api.plugin;
public class InvalidPluginException extends Exception { public class InvalidPluginException extends Exception {
public InvalidPluginException() {
super();
}
public InvalidPluginException(String message) { public InvalidPluginException() {
super(message); super();
} }
public InvalidPluginException(String message, Throwable cause) { public InvalidPluginException(String message) {
super(message, cause); super(message);
} }
public InvalidPluginException(Throwable cause) { public InvalidPluginException(String message, Throwable cause) {
super(cause); super(message, cause);
} }
public InvalidPluginException(Throwable cause) {
super(cause);
}
} }

Datei anzeigen

@ -11,56 +11,55 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface Plugin { public @interface Plugin {
/**
* The ID of the plugin. This ID should be unique as to
* not conflict with other plugins.
*
* The plugin ID must match the {@link PluginDescription#ID_PATTERN}.
*
* @return the ID for this plugin
*/
String id();
/** /**
* The human readable name of the plugin as to be used in descriptions and * The ID of the plugin. This ID should be unique as to not conflict with other plugins.
* similar things. *
* * The plugin ID must match the {@link PluginDescription#ID_PATTERN}.
* @return The plugin name, or an empty string if unknown *
*/ * @return the ID for this plugin
String name() default ""; */
String id();
/** /**
* The version of the plugin. * The human readable name of the plugin as to be used in descriptions and similar things.
* *
* @return the version of the plugin, or an empty string if unknown * @return The plugin name, or an empty string if unknown
*/ */
String version() default ""; String name() default "";
/** /**
* The description of the plugin, explaining what it can be used for. * The version of the plugin.
* *
* @return The plugin description, or an empty string if unknown * @return the version of the plugin, or an empty string if unknown
*/ */
String description() default ""; String version() default "";
/** /**
* The URL or website of the plugin. * The description of the plugin, explaining what it can be used for.
* *
* @return The plugin url, or an empty string if unknown * @return The plugin description, or an empty string if unknown
*/ */
String url() default ""; String description() default "";
/** /**
* The author of the plugin. * The URL or website of the plugin.
* *
* @return the plugin's author, or empty if unknown * @return The plugin url, or an empty string if unknown
*/ */
String[] authors() default ""; String url() default "";
/** /**
* The dependencies required to load before this plugin. * The author of the plugin.
* *
* @return the plugin dependencies * @return the plugin's author, or empty if unknown
*/ */
Dependency[] dependencies() default {}; String[] authors() default "";
/**
* The dependencies required to load before this plugin.
*
* @return the plugin dependencies
*/
Dependency[] dependencies() default {};
} }

Datei anzeigen

@ -6,19 +6,20 @@ import java.util.Optional;
* A wrapper around a plugin loaded by the proxy. * A wrapper around a plugin loaded by the proxy.
*/ */
public interface PluginContainer { public interface PluginContainer {
/**
* Returns the plugin's description.
*
* @return the plugin's description
*/
PluginDescription getDescription();
/** /**
* Returns the created plugin if it is available. * Returns the plugin's description.
* *
* @return the instance if available * @return the plugin's description
*/ */
default Optional<?> getInstance() { PluginDescription getDescription();
return Optional.empty();
} /**
* Returns the created plugin if it is available.
*
* @return the instance if available
*/
default Optional<?> getInstance() {
return Optional.empty();
}
} }

Datei anzeigen

@ -3,7 +3,6 @@ package com.velocitypowered.api.plugin;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.velocitypowered.api.plugin.meta.PluginDependency; import com.velocitypowered.api.plugin.meta.PluginDependency;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -14,93 +13,92 @@ import java.util.regex.Pattern;
* Represents metadata for a specific version of a plugin. * Represents metadata for a specific version of a plugin.
*/ */
public interface PluginDescription { public interface PluginDescription {
/**
* The pattern plugin IDs must match. Plugin IDs may only contain
* alphanumeric characters, dashes or underscores, must start with
* an alphabetic character and cannot be longer than 64 characters.
*/
Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
/** /**
* Gets the qualified ID of the {@link Plugin} within this container. * The pattern plugin IDs must match. Plugin IDs may only contain alphanumeric characters, dashes
* * or underscores, must start with an alphabetic character and cannot be longer than 64
* @return the plugin ID * characters.
* @see Plugin#id() */
*/ Pattern ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{0,63}");
String getId();
/** /**
* Gets the name of the {@link Plugin} within this container. * Gets the qualified ID of the {@link Plugin} within this container.
* *
* @return an {@link Optional} with the plugin name, may be empty * @return the plugin ID
* @see Plugin#name() * @see Plugin#id()
*/ */
default Optional<String> getName() { String getId();
return Optional.empty();
}
/** /**
* Gets the version of the {@link Plugin} within this container. * Gets the name of the {@link Plugin} within this container.
* *
* @return an {@link Optional} with the plugin version, may be empty * @return an {@link Optional} with the plugin name, may be empty
* @see Plugin#version() * @see Plugin#name()
*/ */
default Optional<String> getVersion() { default Optional<String> getName() {
return Optional.empty(); return Optional.empty();
} }
/** /**
* Gets the description of the {@link Plugin} within this container. * Gets the version of the {@link Plugin} within this container.
* *
* @return an {@link Optional} with the plugin description, may be empty * @return an {@link Optional} with the plugin version, may be empty
* @see Plugin#description() * @see Plugin#version()
*/ */
default Optional<String> getDescription() { default Optional<String> getVersion() {
return Optional.empty(); return Optional.empty();
} }
/** /**
* Gets the url or website of the {@link Plugin} within this container. * Gets the description of the {@link Plugin} within this container.
* *
* @return an {@link Optional} with the plugin url, may be empty * @return an {@link Optional} with the plugin description, may be empty
* @see Plugin#url() * @see Plugin#description()
*/ */
default Optional<String> getUrl() { default Optional<String> getDescription() {
return Optional.empty(); return Optional.empty();
} }
/** /**
* Gets the authors of the {@link Plugin} within this container. * Gets the url or website of the {@link Plugin} within this container.
* *
* @return the plugin authors, may be empty * @return an {@link Optional} with the plugin url, may be empty
* @see Plugin#authors() * @see Plugin#url()
*/ */
default List<String> getAuthors() { default Optional<String> getUrl() {
return ImmutableList.of(); return Optional.empty();
} }
/** /**
* Gets a {@link Collection} of all dependencies of the {@link Plugin} within * Gets the authors of the {@link Plugin} within this container.
* this container. *
* * @return the plugin authors, may be empty
* @return the plugin dependencies, can be empty * @see Plugin#authors()
* @see Plugin#dependencies() */
*/ default List<String> getAuthors() {
default Collection<PluginDependency> getDependencies() { return ImmutableList.of();
return ImmutableSet.of(); }
}
default Optional<PluginDependency> getDependency(String id) { /**
return Optional.empty(); * Gets a {@link Collection} of all dependencies of the {@link Plugin} within this container.
} *
* @return the plugin dependencies, can be empty
* @see Plugin#dependencies()
*/
default Collection<PluginDependency> getDependencies() {
return ImmutableSet.of();
}
/** default Optional<PluginDependency> getDependency(String id) {
* Returns the source the plugin was loaded from. return Optional.empty();
* }
* @return the source the plugin was loaded from or {@link Optional#empty()}
* if unknown /**
*/ * Returns the source the plugin was loaded from.
default Optional<Path> getSource() { *
return Optional.empty(); * @return the source the plugin was loaded from or {@link Optional#empty()} if unknown
} */
default Optional<Path> getSource() {
return Optional.empty();
}
} }

Datei anzeigen

@ -5,47 +5,49 @@ import java.util.Collection;
import java.util.Optional; import java.util.Optional;
/** /**
* Manages plugins loaded on the proxy. This manager can retrieve {@link PluginContainer}s from plugin instances * Manages plugins loaded on the proxy. This manager can retrieve {@link PluginContainer}s from
* and inject arbitrary JAR files into the plugin classpath with {@link #addToClasspath(Object, Path)}. * plugin instances and inject arbitrary JAR files into the plugin classpath with {@link
* #addToClasspath(Object, Path)}.
*/ */
public interface PluginManager { public interface PluginManager {
/**
* Gets the plugin container from an instance.
*
* @param instance the instance
* @return the container
*/
Optional<PluginContainer> fromInstance(Object instance);
/** /**
* Retrieves a {@link PluginContainer} based on its ID. * Gets the plugin container from an instance.
* *
* @param id the plugin ID * @param instance the instance
* @return the plugin, if available * @return the container
*/ */
Optional<PluginContainer> getPlugin(String id); Optional<PluginContainer> fromInstance(Object instance);
/** /**
* Gets a {@link Collection} of all {@link PluginContainer}s. * Retrieves a {@link PluginContainer} based on its ID.
* *
* @return the plugins * @param id the plugin ID
*/ * @return the plugin, if available
Collection<PluginContainer> getPlugins(); */
Optional<PluginContainer> getPlugin(String id);
/** /**
* Checks if a plugin is loaded based on its ID. * Gets a {@link Collection} of all {@link PluginContainer}s.
* *
* @param id the id of the plugin * @return the plugins
* @return {@code true} if loaded */
*/ Collection<PluginContainer> getPlugins();
boolean isLoaded(String id);
/** /**
* Adds the specified {@code path} to the plugin classpath. * Checks if a plugin is loaded based on its ID.
* *
* @param plugin the plugin * @param id the id of the plugin
* @param path the path to the JAR you want to inject into the classpath * @return {@code true} if loaded
* @throws UnsupportedOperationException if the operation is not applicable to this plugin */
*/ boolean isLoaded(String id);
void addToClasspath(Object plugin, Path path);
/**
* Adds the specified {@code path} to the plugin classpath.
*
* @param plugin the plugin
* @param path the path to the JAR you want to inject into the classpath
* @throws UnsupportedOperationException if the operation is not applicable to this plugin
*/
void addToClasspath(Object plugin, Path path);
} }

Datei anzeigen

@ -1,18 +1,18 @@
package com.velocitypowered.api.plugin.annotation; package com.velocitypowered.api.plugin.annotation;
import com.google.inject.BindingAnnotation; import com.google.inject.BindingAnnotation;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* This annotation requests that Velocity inject a {@link java.nio.file.Path} instance with a plugin-specific data * This annotation requests that Velocity inject a {@link java.nio.file.Path} instance with a
* directory. * plugin-specific data directory.
*/ */
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@BindingAnnotation @BindingAnnotation
public @interface DataDirectory { public @interface DataDirectory {
} }

Datei anzeigen

@ -1,80 +1,83 @@
package com.velocitypowered.api.plugin.meta; package com.velocitypowered.api.plugin.meta;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Optional;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.emptyToNull; import static com.google.common.base.Strings.emptyToNull;
import java.util.Objects;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents a dependency on another plugin. * Represents a dependency on another plugin.
*/ */
public final class PluginDependency { public final class PluginDependency {
private final String id;
@Nullable
private final String version;
private final boolean optional; private final String id;
@Nullable
private final String version;
public PluginDependency(String id, @Nullable String version, boolean optional) { private final boolean optional;
this.id = checkNotNull(id, "id");
checkArgument(!id.isEmpty(), "id cannot be empty"); public PluginDependency(String id, @Nullable String version, boolean optional) {
this.version = emptyToNull(version); this.id = checkNotNull(id, "id");
this.optional = optional; checkArgument(!id.isEmpty(), "id cannot be empty");
this.version = emptyToNull(version);
this.optional = optional;
}
/**
* Returns the plugin ID of this {@link PluginDependency}
*
* @return the plugin ID
*/
public String getId() {
return id;
}
/**
* Returns the version this {@link PluginDependency} should match.
*
* @return an {@link Optional} with the plugin version, may be empty
*/
public Optional<String> getVersion() {
return Optional.ofNullable(version);
}
/**
* Returns whether the dependency is optional for the plugin to work correctly.
*
* @return true if dependency is optional
*/
public boolean isOptional() {
return optional;
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
/** return false;
* Returns the plugin ID of this {@link PluginDependency}
*
* @return the plugin ID
*/
public String getId() {
return id;
} }
PluginDependency that = (PluginDependency) o;
return optional == that.optional &&
Objects.equals(id, that.id) &&
Objects.equals(version, that.version);
}
/** @Override
* Returns the version this {@link PluginDependency} should match. public int hashCode() {
* return Objects.hash(id, version, optional);
* @return an {@link Optional} with the plugin version, may be empty }
*/
public Optional<String> getVersion() {
return Optional.ofNullable(version);
}
/** @Override
* Returns whether the dependency is optional for the plugin to work public String toString() {
* correctly. return "PluginDependency{" +
* "id='" + id + '\'' +
* @return true if dependency is optional ", version='" + version + '\'' +
*/ ", optional=" + optional +
public boolean isOptional() { '}';
return optional; }
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PluginDependency that = (PluginDependency) o;
return optional == that.optional &&
Objects.equals(id, that.id) &&
Objects.equals(version, that.version);
}
@Override
public int hashCode() {
return Objects.hash(id, version, optional);
}
@Override
public String toString() {
return "PluginDependency{" +
"id='" + id + '\'' +
", version='" + version + '\'' +
", optional=" + optional +
'}';
}
} }

Datei anzeigen

@ -1,91 +1,100 @@
package com.velocitypowered.api.proxy; package com.velocitypowered.api.proxy;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import net.kyori.text.Component;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import net.kyori.text.Component;
/** /**
* Provides a fluent interface to compose and send a connection request to another server behind the proxy. A connection * Provides a fluent interface to compose and send a connection request to another server behind the
* request is created using {@link Player#createConnectionRequest(RegisteredServer)}. * proxy. A connection request is created using {@link Player#createConnectionRequest(RegisteredServer)}.
*/ */
public interface ConnectionRequestBuilder { public interface ConnectionRequestBuilder {
/**
* Returns the server that this connection request represents. /**
* @return the server this request will connect to * Returns the server that this connection request represents.
*/ *
RegisteredServer getServer(); * @return the server this request will connect to
*/
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
*/
CompletableFuture<Result> connect();
/**
* Initiates the connection to the remote server and emits a result on the {@link
* CompletableFuture} after the user has logged on. Velocity's own built-in handling will be used
* to provide errors to the client.
*
* @return a {@link CompletableFuture} representing the status of this connection
*/
CompletableFuture<Boolean> connectWithIndication();
/**
* Initiates the connection to the remote server without waiting for a result. Velocity will use
* generic error handling code to notify the user.
*/
void fireAndForget();
/**
* Represents the result of a connection request.
*/
interface Result {
/** /**
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user * Determines whether or not the connection request was successful.
* 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 * @return whether or not the request succeeded
*/ */
CompletableFuture<Result> connect(); default boolean isSuccessful() {
return getStatus() == Status.SUCCESS;
/**
* Initiates the connection to the remote server and emits a result on the {@link CompletableFuture} after the user
* has logged on. Velocity's own built-in handling will be used to provide errors to the client.
* @return a {@link CompletableFuture} representing the status of this connection
*/
CompletableFuture<Boolean> connectWithIndication();
/**
* Initiates the connection to the remote server without waiting for a result. Velocity will use generic error
* handling code to notify the user.
*/
void fireAndForget();
/**
* Represents the result of a connection request.
*/
interface Result {
/**
* Determines whether or not the connection request was successful.
* @return whether or not the request succeeded
*/
default boolean isSuccessful() {
return getStatus() == Status.SUCCESS;
}
/**
* Returns the status associated with this result.
* @return the status for this result
*/
Status getStatus();
/**
* Returns an (optional) textual reason for the failure to connect to the server.
* @return the reason why the user could not connect to the server
*/
Optional<Component> getReason();
} }
/** /**
* Represents the status of a connection request initiated by a {@link ConnectionRequestBuilder}. * Returns the status associated with this result.
*
* @return the status for this result
*/ */
enum Status { Status getStatus();
/**
* The player was successfully connected to the server. /**
*/ * Returns an (optional) textual reason for the failure to connect to the server.
SUCCESS, *
/** * @return the reason why the user could not connect to the server
* The player is already connected to this server. */
*/ Optional<Component> getReason();
ALREADY_CONNECTED, }
/**
* The connection is already in progress. /**
*/ * Represents the status of a connection request initiated by a {@link ConnectionRequestBuilder}.
CONNECTION_IN_PROGRESS, */
/** enum Status {
* A plugin has cancelled this connection. /**
*/ * The player was successfully connected to the server.
CONNECTION_CANCELLED, */
/** SUCCESS,
* The server disconnected the user. A reason may be provided in the {@link Result} object. /**
*/ * The player is already connected to this server.
SERVER_DISCONNECTED */
} ALREADY_CONNECTED,
/**
* The connection is already in progress.
*/
CONNECTION_IN_PROGRESS,
/**
* A plugin has cancelled this connection.
*/
CONNECTION_CANCELLED,
/**
* The server disconnected the user. A reason may be provided in the {@link Result} object.
*/
SERVER_DISCONNECTED
}
} }

Datei anzeigen

@ -7,27 +7,32 @@ import java.util.Optional;
* Represents an incoming connection to the proxy. * Represents an incoming connection to the proxy.
*/ */
public interface InboundConnection { public interface InboundConnection {
/**
* Returns the player's IP address.
* @return the player's IP
*/
InetSocketAddress getRemoteAddress();
/** /**
* Returns the hostname that the user entered into the client, if applicable. * Returns the player's IP address.
* @return the hostname from the client *
*/ * @return the player's IP
Optional<InetSocketAddress> getVirtualHost(); */
InetSocketAddress getRemoteAddress();
/** /**
* Determine whether or not the player remains online. * Returns the hostname that the user entered into the client, if applicable.
* @return whether or not the player active *
*/ * @return the hostname from the client
boolean isActive(); */
Optional<InetSocketAddress> getVirtualHost();
/** /**
* Returns the current protocol version this connection uses. * Determine whether or not the player remains online.
* @return the protocol version the connection uses *
*/ * @return whether or not the player active
int getProtocolVersion(); */
boolean isActive();
/**
* Returns the current protocol version this connection uses.
*
* @return the protocol version the connection uses
*/
int getProtocolVersion();
} }

Datei anzeigen

@ -4,136 +4,150 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink; import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelMessageSource; import com.velocitypowered.api.proxy.messages.ChannelMessageSource;
import com.velocitypowered.api.proxy.player.PlayerSettings; import com.velocitypowered.api.proxy.player.PlayerSettings;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.proxy.player.TabList;
import com.velocitypowered.api.util.MessagePosition; import com.velocitypowered.api.util.MessagePosition;
import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.ModInfo;
import com.velocitypowered.api.util.title.Title; import com.velocitypowered.api.util.title.Title;
import java.util.List; import java.util.List;
import net.kyori.text.Component;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import net.kyori.text.Component;
/** /**
* Represents a player who is connected to the proxy. * Represents a player who is connected to the proxy.
*/ */
public interface Player extends CommandSource, InboundConnection, ChannelMessageSource, ChannelMessageSink { public interface Player extends CommandSource, InboundConnection, ChannelMessageSource,
/** ChannelMessageSink {
* Returns the player's current username.
* @return the username
*/
String getUsername();
/** /**
* Returns the player's UUID. * Returns the player's current username.
* @return the UUID *
*/ * @return the username
UUID getUniqueId(); */
String getUsername();
/** /**
* Returns the server that the player is currently connected to. * Returns the player's UUID.
* @return an {@link Optional} the server that the player is connected to, which may be empty *
*/ * @return the UUID
Optional<ServerConnection> getCurrentServer(); */
UUID getUniqueId();
/** /**
* Returns the player settings * Returns the server that the player is currently connected to.
* @return the settings *
*/ * @return an {@link Optional} the server that the player is connected to, which may be empty
PlayerSettings getPlayerSettings(); */
Optional<ServerConnection> getCurrentServer();
/**
* Returns the player's mod info if they have a modded client.
* @return an {@link Optional} the mod info. which may be empty
*/
Optional<ModInfo> getModInfo();
/** /**
* Returns the current player's ping * Returns the player settings
* @return the player's ping or -1 if ping information is currently unknown *
*/ * @return the settings
long getPing(); */
PlayerSettings getPlayerSettings();
/**
* Sends a chat message to the player's client.
* @param component the chat message to send
*/
default void sendMessage(Component component) {
sendMessage(component, MessagePosition.CHAT);
}
/** /**
* Sends a chat message to the player's client in the specified position. * Returns the player's mod info if they have a modded client.
* @param component the chat message to send *
* @param position the position for the message * @return an {@link Optional} the mod info. which may be empty
*/ */
void sendMessage(Component component, MessagePosition position); Optional<ModInfo> getModInfo();
/** /**
* Creates a new connection request so that the player can connect to another server. * Returns the current player's ping
* @param server the server to connect to *
* @return a new connection request * @return the player's ping or -1 if ping information is currently unknown
*/ */
ConnectionRequestBuilder createConnectionRequest(RegisteredServer server); long getPing();
/** /**
* Gets the player's profile properties. * Sends a chat message to the player's client.
* *
* <p>The returned list may be unmodifiable.</p> * @param component the chat message to send
* */
* @return the player's profile properties default void sendMessage(Component component) {
*/ sendMessage(component, MessagePosition.CHAT);
List<GameProfile.Property> getGameProfileProperties(); }
/**
* Sets the player's profile properties.
*
* @param properties the properties
*/
void setGameProfileProperties(List<GameProfile.Property> properties);
/**
* Sets the tab list header and footer for the player.
* @param header the header component
* @param footer the footer component
* @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}.
*/
@Deprecated
void setHeaderAndFooter(Component header, Component footer);
/** /**
* Clears the tab list header and footer for the player. * Sends a chat message to the player's client in the specified position.
* @deprecated Use {@link TabList#clearHeaderAndFooter()}. *
*/ * @param component the chat message to send
@Deprecated * @param position the position for the message
void clearHeaderAndFooter(); */
void sendMessage(Component component, MessagePosition position);
/**
* Returns the player's tab list.
* @return this player's tab list
*/
TabList getTabList();
/** /**
* Disconnects the player with the specified reason. Once this method is called, further calls to other {@link Player} * Creates a new connection request so that the player can connect to another server.
* methods will become undefined. *
* @param reason component with the reason * @param server the server to connect to
*/ * @return a new connection request
void disconnect(Component reason); */
ConnectionRequestBuilder createConnectionRequest(RegisteredServer server);
/** /**
* Sends the specified title to the client. * Gets the player's profile properties.
* @param title the title to send *
*/ * <p>The returned list may be unmodifiable.</p>
void sendTitle(Title title); *
* @return the player's profile properties
*/
List<GameProfile.Property> getGameProfileProperties();
/** /**
* Sends chat input onto the players current server as if they typed it * Sets the player's profile properties.
* into the client chat box. *
* @param input the chat input to send * @param properties the properties
*/ */
void spoofChatInput(String input); void setGameProfileProperties(List<GameProfile.Property> properties);
/**
* Sets the tab list header and footer for the player.
*
* @param header the header component
* @param footer the footer component
* @deprecated Use {@link TabList#setHeaderAndFooter(Component, Component)}.
*/
@Deprecated
void setHeaderAndFooter(Component header, Component footer);
/**
* Clears the tab list header and footer for the player.
*
* @deprecated Use {@link TabList#clearHeaderAndFooter()}.
*/
@Deprecated
void clearHeaderAndFooter();
/**
* Returns the player's tab list.
*
* @return this player's tab list
*/
TabList getTabList();
/**
* Disconnects the player with the specified reason. Once this method is called, further calls to
* other {@link Player} methods will become undefined.
*
* @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);
} }

Datei anzeigen

@ -10,132 +10,151 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.api.scheduler.Scheduler; import com.velocitypowered.api.scheduler.Scheduler;
import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.api.util.ProxyVersion;
import net.kyori.text.Component;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import net.kyori.text.Component;
/** /**
* Provides an interface to a Minecraft server proxy. * Provides an interface to a Minecraft server proxy.
*/ */
public interface ProxyServer { public interface ProxyServer {
/**
* Retrieves the player currently connected to this proxy by their Minecraft username. The search is case-insensitive.
* @param username the username to search for
* @return an {@link Optional} with the player, which may be empty
*/
Optional<Player> getPlayer(String username);
/** /**
* Retrieves the player currently connected to this proxy by their Minecraft UUID. * Retrieves the player currently connected to this proxy by their Minecraft username. The search
* @param uuid the UUID * is case-insensitive.
* @return an {@link Optional} with the player, which may be empty *
*/ * @param username the username to search for
Optional<Player> getPlayer(UUID uuid); * @return an {@link Optional} with the player, which may be empty
*/
Optional<Player> getPlayer(String username);
/** /**
* Broadcasts a message to all players currently online. * Retrieves the player currently connected to this proxy by their Minecraft UUID.
* @param component the message to send *
*/ * @param uuid the UUID
void broadcast(Component component); * @return an {@link Optional} with the player, which may be empty
*/
Optional<Player> getPlayer(UUID uuid);
/** /**
* Retrieves all players currently connected to this proxy. This call may or may not be a snapshot of all players * Broadcasts a message to all players currently online.
* online. *
* @return the players online on this proxy * @param component the message to send
*/ */
Collection<Player> getAllPlayers(); void broadcast(Component component);
/** /**
* Returns the number of players currently connected to this proxy. * Retrieves all players currently connected to this proxy. This call may or may not be a snapshot
* @return the players on this proxy * of all players online.
*/ *
int getPlayerCount(); * @return the players online on this proxy
*/
Collection<Player> getAllPlayers();
/** /**
* Retrieves a registered {@link RegisteredServer} instance by its name. The search is case-insensitive. * Returns the number of players currently connected to this proxy.
* @param name the name of the server *
* @return the registered server, which may be empty * @return the players on this proxy
*/ */
Optional<RegisteredServer> getServer(String name); int getPlayerCount();
/** /**
* Retrieves all {@link RegisteredServer}s registered with this proxy. * Retrieves a registered {@link RegisteredServer} instance by its name. The search is
* @return the servers registered with this proxy * case-insensitive.
*/ *
Collection<RegisteredServer> getAllServers(); * @param name the name of the server
* @return the registered server, which may be empty
*/
Optional<RegisteredServer> getServer(String name);
/** /**
* Registers a server with this proxy. A server with this name should not already exist. * Retrieves all {@link RegisteredServer}s registered with this proxy.
* @param server the server to register *
* @return the newly registered server * @return the servers registered with this proxy
*/ */
RegisteredServer registerServer(ServerInfo server); Collection<RegisteredServer> getAllServers();
/** /**
* Unregisters this server from the proxy. * Registers a server with this proxy. A server with this name should not already exist.
* @param server the server to unregister *
*/ * @param server the server to register
void unregisterServer(ServerInfo server); * @return the newly registered server
*/
RegisteredServer registerServer(ServerInfo server);
/** /**
* Returns an instance of {@link CommandSource} that can be used to determine if the command is being invoked by * Unregisters this server from the proxy.
* the console or a console-like executor. Plugins that execute commands are strongly urged to implement their own *
* {@link CommandSource} instead of using the console invoker. * @param server the server to unregister
* @return the console command invoker */
*/ void unregisterServer(ServerInfo server);
CommandSource getConsoleCommandSource();
/** /**
* Gets the {@link PluginManager} instance. * Returns an instance of {@link CommandSource} that can be used to determine if the command is
* * being invoked by the console or a console-like executor. Plugins that execute commands are
* @return the plugin manager instance * strongly urged to implement their own {@link CommandSource} instead of using the console
*/ * invoker.
PluginManager getPluginManager(); *
* @return the console command invoker
*/
CommandSource getConsoleCommandSource();
/** /**
* Gets the {@link EventManager} instance. * Gets the {@link PluginManager} instance.
* *
* @return the event manager instance * @return the plugin manager instance
*/ */
EventManager getEventManager(); PluginManager getPluginManager();
/** /**
* Gets the {@link CommandManager} instance. * Gets the {@link EventManager} instance.
* @return the command manager *
*/ * @return the event manager instance
CommandManager getCommandManager(); */
EventManager getEventManager();
/** /**
* Gets the {@link Scheduler} instance. * Gets the {@link CommandManager} instance.
* @return the scheduler instance *
*/ * @return the command manager
Scheduler getScheduler(); */
CommandManager getCommandManager();
/** /**
* Gets the {@link ChannelRegistrar} instance. * Gets the {@link Scheduler} instance.
* @return the channel registrar *
*/ * @return the scheduler instance
ChannelRegistrar getChannelRegistrar(); */
Scheduler getScheduler();
/** /**
* Gets the address that this proxy is bound to. This does not necessarily indicate the external IP address of the * Gets the {@link ChannelRegistrar} instance.
* proxy. *
* @return the address the proxy is bound to * @return the channel registrar
*/ */
InetSocketAddress getBoundAddress(); ChannelRegistrar getChannelRegistrar();
/** /**
* Gets the {@link ProxyConfig} instance. * Gets the address that this proxy is bound to. This does not necessarily indicate the external
* @return the proxy config * IP address of the proxy.
* */ *
ProxyConfig getConfiguration(); * @return the address the proxy is bound to
*/
InetSocketAddress getBoundAddress();
/** /**
* Returns the version of the proxy. * Gets the {@link ProxyConfig} instance.
* @return the proxy version *
*/ * @return the proxy config
ProxyVersion getVersion(); */
ProxyConfig getConfiguration();
/**
* Returns the version of the proxy.
*
* @return the proxy version
*/
ProxyVersion getVersion();
} }

Datei anzeigen

@ -9,21 +9,25 @@ import com.velocitypowered.api.proxy.server.ServerInfo;
* Represents a connection to a backend server from the proxy for a client. * Represents a connection to a backend server from the proxy for a client.
*/ */
public interface ServerConnection extends ChannelMessageSource, ChannelMessageSink { public interface ServerConnection extends ChannelMessageSource, ChannelMessageSink {
/**
* 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. * Returns the server that this connection is connected to.
* @return the server info for this connection *
*/ * @return the server this connection is connected to
ServerInfo getServerInfo(); */
RegisteredServer getServer();
/** /**
* Returns the player that this connection is associated with. * Returns the server info for this connection.
* @return the player for this connection *
*/ * @return the server info for this connection
Player getPlayer(); */
ServerInfo getServerInfo();
/**
* Returns the player that this connection is associated with.
*
* @return the player for this connection
*/
Player getPlayer();
} }

Datei anzeigen

@ -1,116 +1,133 @@
package com.velocitypowered.api.proxy.config; package com.velocitypowered.api.proxy.config;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.Favicon;
import net.kyori.text.Component;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import net.kyori.text.Component;
/** /**
* Provides an interface to a proxy configuration * Provides an interface to a proxy configuration
*/ */
public interface ProxyConfig { public interface ProxyConfig {
/**
* Whether GameSpy 4 queries are accepted by the proxy
* @return queries enabled
*/
boolean isQueryEnabled();
/** /**
* Get the port GameSpy 4 queries are accepted on * Whether GameSpy 4 queries are accepted by the proxy
* @return the query port *
*/ * @return queries enabled
int getQueryPort(); */
boolean isQueryEnabled();
/** /**
* Get the map name reported to GameSpy 4 query services * Get the port GameSpy 4 queries are accepted on
* @return the map name *
*/ * @return the query port
String getQueryMap(); */
int getQueryPort();
/** /**
* Whether GameSpy 4 queries should show plugins installed on * Get the map name reported to GameSpy 4 query services
* Velocity by default *
* @return show plugins in query * @return the map name
*/ */
boolean shouldQueryShowPlugins(); String getQueryMap();
/** /**
* Get the MOTD component shown in the tab list * Whether GameSpy 4 queries should show plugins installed on Velocity by default
* @return the motd component *
*/ * @return show plugins in query
Component getMotdComponent(); */
boolean shouldQueryShowPlugins();
/** /**
* Get the maximum players shown in the tab list * Get the MOTD component shown in the tab list
* @return max players *
*/ * @return the motd component
int getShowMaxPlayers(); */
Component getMotdComponent();
/** /**
* Get whether the proxy is online mode. This determines if players are authenticated with Mojang servers. * Get the maximum players shown in the tab list
* @return online mode enabled *
*/ * @return max players
boolean isOnlineMode(); */
int getShowMaxPlayers();
/** /**
* Get a Map of all servers registered on this proxy * Get whether the proxy is online mode. This determines if players are authenticated with Mojang
* @return registered servers map * servers.
*/ *
Map<String, String> getServers(); * @return online mode enabled
*/
boolean isOnlineMode();
/** /**
* Get the order of servers that players will be connected to * Get a Map of all servers registered on this proxy
* @return connection order list *
*/ * @return registered servers map
List<String> getAttemptConnectionOrder(); */
Map<String, String> getServers();
/** /**
* Get forced servers mapped to given virtual host * Get the order of servers that players will be connected to
* @return list of server names *
*/ * @return connection order list
Map<String, List<String>> getForcedHosts(); */
List<String> getAttemptConnectionOrder();
/** /**
* Get the minimum compression threshold for packets * Get forced servers mapped to given virtual host
* @return the compression threshold *
*/ * @return list of server names
int getCompressionThreshold(); */
Map<String, List<String>> getForcedHosts();
/** /**
* Get the level of compression that packets will be compressed to * Get the minimum compression threshold for packets
* @return the compression level *
*/ * @return the compression threshold
int getCompressionLevel(); */
int getCompressionThreshold();
/** /**
* Get the limit for how long a player must wait to log back in * Get the level of compression that packets will be compressed to
* @return the login rate limit (in milliseconds) *
*/ * @return the compression level
int getLoginRatelimit(); */
int getCompressionLevel();
/** /**
* Get the proxy favicon shown in the tablist * Get the limit for how long a player must wait to log back in
* @return optional favicon *
*/ * @return the login rate limit (in milliseconds)
Optional<Favicon> getFavicon(); */
int getLoginRatelimit();
/** /**
* Get whether this proxy displays that it supports Forge/FML * Get the proxy favicon shown in the tablist
* @return forge announce enabled *
*/ * @return optional favicon
boolean isAnnounceForge(); */
Optional<Favicon> getFavicon();
/** /**
* Get how long this proxy will wait until performing a read timeout * Get whether this proxy displays that it supports Forge/FML
* @return connection timeout (in milliseconds) *
*/ * @return forge announce enabled
int getConnectTimeout(); */
boolean isAnnounceForge();
/** /**
* Get how long this proxy will wait until performing a read timeout * Get how long this proxy will wait until performing a read timeout
* @return read timeout (in milliseconds) *
*/ * @return connection timeout (in milliseconds)
int getReadTimeout(); */
int getConnectTimeout();
/**
* Get how long this proxy will wait until performing a read timeout
*
* @return read timeout (in milliseconds)
*/
int getReadTimeout();
} }

Datei anzeigen

@ -4,9 +4,11 @@ package com.velocitypowered.api.proxy.messages;
* Represents a kind of channel identifier. * Represents a kind of channel identifier.
*/ */
public interface ChannelIdentifier { public interface ChannelIdentifier {
/**
* Returns the textual representation of this identifier. /**
* @return the textual representation of the identifier * Returns the textual representation of this identifier.
*/ *
String getId(); * @return the textual representation of the identifier
*/
String getId();
} }

Datei anzeigen

@ -4,11 +4,13 @@ package com.velocitypowered.api.proxy.messages;
* Represents something that can send plugin messages. * Represents something that can send plugin messages.
*/ */
public interface ChannelMessageSink { public interface ChannelMessageSink {
/**
* Sends a plugin message to this target. /**
* @param identifier the channel identifier to send the message on * Sends a plugin message to this target.
* @param data the data to send *
* @return whether or not the message could be sent * @param identifier the channel identifier to send the message on
*/ * @param data the data to send
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data); * @return whether or not the message could be sent
*/
boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data);
} }

Datei anzeigen

@ -4,4 +4,5 @@ package com.velocitypowered.api.proxy.messages;
* A marker interface that indicates a source of plugin messages. * A marker interface that indicates a source of plugin messages.
*/ */
public interface ChannelMessageSource { public interface ChannelMessageSource {
} }

Datei anzeigen

@ -1,18 +1,22 @@
package com.velocitypowered.api.proxy.messages; package com.velocitypowered.api.proxy.messages;
/** /**
* Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to listen on. * Represents an interface to register and unregister {@link ChannelIdentifier}s for the proxy to
* listen on.
*/ */
public interface ChannelRegistrar { public interface ChannelRegistrar {
/**
* Registers the specified message identifiers to listen on for the
* @param identifiers the channel identifiers to register
*/
void register(ChannelIdentifier... identifiers);
/** /**
* Unregisters the handler for the specified channel. * Registers the specified message identifiers to listen on for the
* @param identifiers the identifiers to unregister *
*/ * @param identifiers the channel identifiers to register
void unregister(ChannelIdentifier... identifiers); */
void register(ChannelIdentifier... identifiers);
/**
* Unregisters the handler for the specified channel.
*
* @param identifiers the identifiers to unregister
*/
void unregister(ChannelIdentifier... identifiers);
} }

Datei anzeigen

@ -2,46 +2,51 @@ package com.velocitypowered.api.proxy.messages;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
/** /**
* Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin messages, please see * Reperesents a legacy channel identifier (for Minecraft 1.12 and below). For modern 1.13 plugin
* {@link MinecraftChannelIdentifier}. This class is immutable and safe for multi-threaded use. * messages, please see {@link MinecraftChannelIdentifier}. This class is immutable and safe for
* multi-threaded use.
*/ */
public final class LegacyChannelIdentifier implements ChannelIdentifier { public final class LegacyChannelIdentifier implements ChannelIdentifier {
private final String name;
public LegacyChannelIdentifier(String name) { private final String name;
Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "provided name is empty");
this.name = name;
}
public String getName() { public LegacyChannelIdentifier(String name) {
return name; Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "provided name is empty");
} this.name = name;
}
@Override public String getName() {
public String toString() { return name;
return name + " (legacy)"; }
}
@Override @Override
public boolean equals(@Nullable Object o) { public String toString() {
if (this == o) return true; return name + " (legacy)";
if (o == null || getClass() != o.getClass()) return false; }
LegacyChannelIdentifier that = (LegacyChannelIdentifier) o;
return Objects.equals(name, that.name);
}
@Override @Override
public int hashCode() { public boolean equals(@Nullable Object o) {
return Objects.hash(name); if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
return false;
}
LegacyChannelIdentifier that = (LegacyChannelIdentifier) o;
return Objects.equals(name, that.name);
}
@Override @Override
public String getId() { public int hashCode() {
return this.getName(); return Objects.hash(name);
} }
@Override
public String getId() {
return this.getName();
}
} }

Datei anzeigen

@ -2,78 +2,87 @@ package com.velocitypowered.api.proxy.messages;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for multi-threaded use. * Represents a Minecraft 1.13+ channel identifier. This class is immutable and safe for
* multi-threaded use.
*/ */
public final class MinecraftChannelIdentifier implements ChannelIdentifier { public final class MinecraftChannelIdentifier implements ChannelIdentifier {
private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String namespace; private static final Pattern VALID_IDENTIFIER_REGEX = Pattern.compile("[a-z0-9\\-_]+");
private final String name;
private MinecraftChannelIdentifier(String namespace, String name) { private final String namespace;
this.namespace = namespace; private final String name;
this.name = name;
private MinecraftChannelIdentifier(String namespace, String name) {
this.namespace = namespace;
this.name = name;
}
/**
* Creates an identifier in the default namespace ({@code minecraft}). Plugins are strongly
* encouraged to provide their own namespace.
*
* @param name the name in the default namespace to use
* @return a new channel identifier
*/
public static MinecraftChannelIdentifier forDefaultNamespace(String name) {
return new MinecraftChannelIdentifier("minecraft", name);
}
/**
* Creates an identifier in the specified namespace.
*
* @param namespace the namespace to use
* @param name the channel name inside the specified namespace
* @return a new channel identifier
*/
public static MinecraftChannelIdentifier create(String namespace, String name) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty");
Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(),
"namespace is not valid");
Preconditions
.checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(), "name is not valid");
return new MinecraftChannelIdentifier(namespace, name);
}
public String getNamespace() {
return namespace;
}
public String getName() {
return name;
}
@Override
public String toString() {
return namespace + ":" + name + " (modern)";
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
/** return false;
* Creates an identifier in the default namespace ({@code minecraft}). Plugins are strongly encouraged to provide
* their own namespace.
* @param name the name in the default namespace to use
* @return a new channel identifier
*/
public static MinecraftChannelIdentifier forDefaultNamespace(String name) {
return new MinecraftChannelIdentifier("minecraft", name);
} }
MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o;
return Objects.equals(namespace, that.namespace) &&
Objects.equals(name, that.name);
}
/** @Override
* Creates an identifier in the specified namespace. public int hashCode() {
* @param namespace the namespace to use return Objects.hash(namespace, name);
* @param name the channel name inside the specified namespace }
* @return a new channel identifier
*/
public static MinecraftChannelIdentifier create(String namespace, String name) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(namespace), "namespace is null or empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "namespace is null or empty");
Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(namespace).matches(), "namespace is not valid");
Preconditions.checkArgument(VALID_IDENTIFIER_REGEX.matcher(name).matches(), "name is not valid");
return new MinecraftChannelIdentifier(namespace, name);
}
public String getNamespace() { @Override
return namespace; public String getId() {
} return namespace + ":" + name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return namespace + ":" + name + " (modern)";
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MinecraftChannelIdentifier that = (MinecraftChannelIdentifier) o;
return Objects.equals(namespace, that.namespace) &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(namespace, name);
}
@Override
public String getId() {
return namespace + ":" + name;
}
} }

Datei anzeigen

@ -1,4 +1,5 @@
/** /**
* Provides an interface to receive, handle, and send plugin messages on the proxy from clients and servers. * Provides an interface to receive, handle, and send plugin messages on the proxy from clients and
* servers.
*/ */
package com.velocitypowered.api.proxy.messages; package com.velocitypowered.api.proxy.messages;

Datei anzeigen

@ -6,51 +6,58 @@ import java.util.Locale;
* Represents the client settings for the player. * Represents the client settings for the player.
*/ */
public interface PlayerSettings { public interface PlayerSettings {
/**
* Returns the locale of the Minecraft client.
* @return the client locale
*/
Locale getLocale();
/** /**
* Returns the client's view distance. This does not guarantee the client will see this many chunks, since your * Returns the locale of the Minecraft client.
* servers are responsible for sending the chunks. *
* @return the client view distance * @return the client locale
*/ */
byte getViewDistance(); Locale getLocale();
/** /**
* Returns the chat setting for the client. * Returns the client's view distance. This does not guarantee the client will see this many
* @return the chat setting * chunks, since your servers are responsible for sending the chunks.
*/ *
ChatMode getChatMode(); * @return the client view distance
*/
byte getViewDistance();
/** /**
* Returns whether or not the client has chat colors disabled. * Returns the chat setting for the client.
* @return whether or not the client has chat colors disabled *
*/ * @return the chat setting
boolean hasChatColors(); */
ChatMode getChatMode();
/** /**
* Returns the parts of player skins the client will show. * Returns whether or not the client has chat colors disabled.
* @return the skin parts for the client *
*/ * @return whether or not the client has chat colors disabled
SkinParts getSkinParts(); */
boolean hasChatColors();
/** /**
* Returns the primary hand of the client. * Returns the parts of player skins the client will show.
* @return the primary hand of the client *
*/ * @return the skin parts for the client
MainHand getMainHand(); */
SkinParts getSkinParts();
enum ChatMode { /**
SHOWN, * Returns the primary hand of the client.
COMMANDS_ONLY, *
HIDDEN * @return the primary hand of the client
} */
MainHand getMainHand();
enum MainHand { enum ChatMode {
LEFT, SHOWN,
RIGHT COMMANDS_ONLY,
} HIDDEN
}
enum MainHand {
LEFT,
RIGHT
}
} }

Datei anzeigen

@ -1,37 +1,38 @@
package com.velocitypowered.api.proxy.player; package com.velocitypowered.api.proxy.player;
public final class SkinParts { public final class SkinParts {
private final byte bitmask;
public SkinParts(byte skinBitmask) { private final byte bitmask;
this.bitmask = skinBitmask;
}
public boolean hasCape() { public SkinParts(byte skinBitmask) {
return (bitmask & 1) == 1; this.bitmask = skinBitmask;
} }
public boolean hasJacket() { public boolean hasCape() {
return ((bitmask >> 1) & 1) == 1; return (bitmask & 1) == 1;
} }
public boolean hasLeftSleeve() { public boolean hasJacket() {
return ((bitmask >> 2) & 1) == 1; return ((bitmask >> 1) & 1) == 1;
} }
public boolean hasRightSleeve() { public boolean hasLeftSleeve() {
return ((bitmask >> 3) & 1) == 1; return ((bitmask >> 2) & 1) == 1;
} }
public boolean hasLeftPants() { public boolean hasRightSleeve() {
return ((bitmask >> 4) & 1) == 1; return ((bitmask >> 3) & 1) == 1;
} }
public boolean hasRightPants() { public boolean hasLeftPants() {
return ((bitmask >> 5) & 1) == 1; return ((bitmask >> 4) & 1) == 1;
} }
public boolean hasHat() { public boolean hasRightPants() {
return ((bitmask >> 6) & 1) == 1; return ((bitmask >> 5) & 1) == 1;
} }
public boolean hasHat() {
return ((bitmask >> 6) & 1) == 1;
}
} }

Datei anzeigen

@ -2,51 +2,56 @@ package com.velocitypowered.api.proxy.player;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection; import java.util.Collection;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents the tab list of a {@link Player}. * Represents the tab list of a {@link Player}.
*/ */
public interface TabList { public interface TabList {
/**
* Sets the tab list header and footer for the player.
* @param header the header component
* @param footer the footer component
*/
void setHeaderAndFooter(Component header, Component footer);
/** /**
* Clears the tab list header and footer for the player. * Sets the tab list header and footer for the player.
*/ *
void clearHeaderAndFooter(); * @param header the header component
* @param footer the footer component
/** */
* Adds a {@link TabListEntry} to the {@link Player}'s tab list. void setHeaderAndFooter(Component header, Component footer);
* @param entry to add to the tab list
*/
void addEntry(TabListEntry entry);
/**
* Removes the {@link TabListEntry} from the tab list with the {@link GameProfile}
* identified with the specified {@link UUID}.
* @param uuid of the
* @return {@link Optional} containing the removed {@link TabListEntry} if present,
* otherwise {@link Optional#empty()}
*/
Optional<TabListEntry> removeEntry(UUID uuid);
/**
* Returns an immutable {@link Collection} of the {@link TabListEntry}s in the tab list.
* @return immutable {@link Collection} of tab list entries
*/
Collection<TabListEntry> getEntries();
// Necessary because the TabListEntry implementation isn't in the api /**
@Deprecated * Clears the tab list header and footer for the player.
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency, int gameMode); */
void clearHeaderAndFooter();
/**
* Adds a {@link TabListEntry} to the {@link Player}'s tab list.
*
* @param entry to add to the tab list
*/
void addEntry(TabListEntry entry);
/**
* Removes the {@link TabListEntry} from the tab list with the {@link GameProfile} identified with
* the specified {@link UUID}.
*
* @param uuid of the
* @return {@link Optional} containing the removed {@link TabListEntry} if present, otherwise
* {@link Optional#empty()}
*/
Optional<TabListEntry> removeEntry(UUID uuid);
/**
* Returns an immutable {@link Collection} of the {@link TabListEntry}s in the tab list.
*
* @return immutable {@link Collection} of tab list entries
*/
Collection<TabListEntry> getEntries();
// Necessary because the TabListEntry implementation isn't in the api
@Deprecated
TabListEntry buildEntry(GameProfile profile, @Nullable Component displayName, int latency,
int gameMode);
} }

Datei anzeigen

@ -1,177 +1,195 @@
package com.velocitypowered.api.proxy.player; package com.velocitypowered.api.proxy.player;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import java.util.Optional;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Optional;
/** /**
* Represents a single entry in a {@link TabList}. * Represents a single entry in a {@link TabList}.
*/ */
public interface TabListEntry { public interface TabListEntry {
/**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
* @return parent {@link TabList}
*/
TabList getTabList();
/**
* Returns the {@link GameProfile} of the entry, which uniquely identifies the entry
* with the containing {@link java.util.UUID}, as well as deciding what is shown
* as the player head in the tab list.
* @return {@link GameProfile} of the entry
*/
GameProfile getProfile();
/**
* Returns {@link Optional} text {@link Component}, which if present is the text displayed for
* {@code this} entry in the {@link TabList}, otherwise {@link GameProfile#getName()} is shown.
* @return {@link Optional} text {@link Component} of name displayed in the tab list
*/
Optional<Component> getDisplayName();
/**
* Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}.
* If {@code null}, {@link GameProfile#getName()} will be shown.
* @param displayName to show in the {@link TabList} for {@code this} entry
* @return {@code this}, for chaining
*/
TabListEntry setDisplayName(@Nullable Component displayName);
/**
* Returns the latency for {@code this} entry.
* <p>The icon shown in the tab list is calculated by the latency in the following way:<p>
* <ul>
* <li>A negative latency will display the no connection icon</li>
* <li>0-150 will display 5 bars</li>
* <li>150-300 will display 4 bars</li>
* <li>300-600 will display 3 bars</li>
* <li>600-1000 will display 2 bars</li>
* <li>A latency move than 1 second will display 1 bar</li>
* <li></li>
* </ul>
* @return latency set for {@code this} entry
*/
int getLatency();
/**
* Sets the latency for {@code this} entry to the specified value
* @see #getLatency()
* @param latency to changed to
* @return {@code this}, for chaining
*/
TabListEntry setLatency(int latency);
/**
* Gets the game mode {@code this} entry has been set to.
* <p>The number corresponds to the game mode in the following way:</p>
* <ol start="0">
* <li>Survival</li>
* <li>Creative</li>
* <li>Adventure</li>
* <li>Spectator</li>
* </ol>
* @return the game mode
*/
int getGameMode();
/**
* Sets the game mode for {@code this} entry to the specified value
* @see #getGameMode()
* @param gameMode to change to
* @return {@code this}, for chaining
*/
TabListEntry setGameMode(int gameMode);
/**
* Returns a {@link Builder} to create a {@link TabListEntry}.
* @return {@link TabListEntry} builder
*/
static Builder builder() {
return new Builder();
}
/**
* Represents a builder which creates {@link TabListEntry}s.
* @see TabListEntry
*/
class Builder {
private @Nullable TabList tabList;
private @Nullable GameProfile profile;
private @Nullable Component displayName;
private int latency = 0;
private int gameMode = 0;
private Builder() {} /**
* Returns the parent {@link TabList} of this {@code this} {@link TabListEntry}.
/** *
* Sets the parent {@link TabList} for this entry, * @return parent {@link TabList}
* the entry will only be able to be added to that specific {@link TabList}. */
* @param tabList to set TabList getTabList();
* @return {@code this}, for chaining
*/ /**
public Builder tabList(TabList tabList) { * Returns the {@link GameProfile} of the entry, which uniquely identifies the entry with the
this.tabList = tabList; * containing {@link java.util.UUID}, as well as deciding what is shown as the player head in the
return this; * tab list.
} *
* @return {@link GameProfile} of the entry
/** */
* Sets the {@link GameProfile} of the {@link TabListEntry}. GameProfile getProfile();
* @see TabListEntry#getProfile()
* @param profile to set /**
* @return {@code this}, for chaining * Returns {@link Optional} text {@link Component}, which if present is the text displayed for
*/ * {@code this} entry in the {@link TabList}, otherwise {@link GameProfile#getName()} is shown.
public Builder profile(GameProfile profile) { *
this.profile = profile; * @return {@link Optional} text {@link Component} of name displayed in the tab list
return this; */
} Optional<Component> getDisplayName();
/** /**
* Sets the displayed name of the {@link TabListEntry} * Sets the text {@link Component} to be displayed for {@code this} {@link TabListEntry}. If
* @see TabListEntry#getDisplayName() * {@code null}, {@link GameProfile#getName()} will be shown.
* @param displayName to set *
* @return {@code this}, for chaining * @param displayName to show in the {@link TabList} for {@code this} entry
*/ * @return {@code this}, for chaining
public Builder displayName(@Nullable Component displayName) { */
this.displayName = displayName; TabListEntry setDisplayName(@Nullable Component displayName);
return this;
} /**
* Returns the latency for {@code this} entry.
/** * <p>The icon shown in the tab list is calculated by the latency in the following way:<p>
* Sets the latency of the {@link TabListEntry} * <ul>
* @see TabListEntry#getLatency() * <li>A negative latency will display the no connection icon</li>
* @param latency to set * <li>0-150 will display 5 bars</li>
* @return {@code this}, for chaining * <li>150-300 will display 4 bars</li>
*/ * <li>300-600 will display 3 bars</li>
public Builder latency(int latency) { * <li>600-1000 will display 2 bars</li>
this.latency = latency; * <li>A latency move than 1 second will display 1 bar</li>
return this; * <li></li>
} * </ul>
*
/** * @return latency set for {@code this} entry
* Sets the game mode of the {@link TabListEntry} */
* @see TabListEntry#getGameMode() int getLatency();
* @param gameMode to set
* @return {@code this}, for chaining /**
*/ * Sets the latency for {@code this} entry to the specified value
public Builder gameMode(int gameMode) { *
this.gameMode = gameMode; * @param latency to changed to
return this; * @return {@code this}, for chaining
} * @see #getLatency()
*/
/** TabListEntry setLatency(int latency);
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
* @return the constructed {@link TabListEntry} /**
*/ * Gets the game mode {@code this} entry has been set to.
public TabListEntry build() { * <p>The number corresponds to the game mode in the following way:</p>
if (tabList == null) { * <ol start="0">
throw new IllegalStateException("The Tablist must be set when building a TabListEntry"); * <li>Survival</li>
} * <li>Creative</li>
if (profile == null) { * <li>Adventure</li>
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry"); * <li>Spectator</li>
} * </ol>
return tabList.buildEntry(profile, displayName, latency, gameMode); *
} * @return the game mode
*/
int getGameMode();
/**
* Sets the game mode for {@code this} entry to the specified value
*
* @param gameMode to change to
* @return {@code this}, for chaining
* @see #getGameMode()
*/
TabListEntry setGameMode(int gameMode);
/**
* Returns a {@link Builder} to create a {@link TabListEntry}.
*
* @return {@link TabListEntry} builder
*/
static Builder builder() {
return new Builder();
}
/**
* Represents a builder which creates {@link TabListEntry}s.
*
* @see TabListEntry
*/
class Builder {
private @Nullable TabList tabList;
private @Nullable GameProfile profile;
private @Nullable Component displayName;
private int latency = 0;
private int gameMode = 0;
private Builder() {
} }
/**
* Sets the parent {@link TabList} for this entry, the entry will only be able to be added to
* that specific {@link TabList}.
*
* @param tabList to set
* @return {@code this}, for chaining
*/
public Builder tabList(TabList tabList) {
this.tabList = tabList;
return this;
}
/**
* Sets the {@link GameProfile} of the {@link TabListEntry}.
*
* @param profile to set
* @return {@code this}, for chaining
* @see TabListEntry#getProfile()
*/
public Builder profile(GameProfile profile) {
this.profile = profile;
return this;
}
/**
* Sets the displayed name of the {@link TabListEntry}
*
* @param displayName to set
* @return {@code this}, for chaining
* @see TabListEntry#getDisplayName()
*/
public Builder displayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
/**
* Sets the latency of the {@link TabListEntry}
*
* @param latency to set
* @return {@code this}, for chaining
* @see TabListEntry#getLatency()
*/
public Builder latency(int latency) {
this.latency = latency;
return this;
}
/**
* Sets the game mode of the {@link TabListEntry}
*
* @param gameMode to set
* @return {@code this}, for chaining
* @see TabListEntry#getGameMode()
*/
public Builder gameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
/**
* Constructs the {@link TabListEntry} specified by {@code this} {@link Builder}.
*
* @return the constructed {@link TabListEntry}
*/
public TabListEntry build() {
if (tabList == null) {
throw new IllegalStateException("The Tablist must be set when building a TabListEntry");
}
if (profile == null) {
throw new IllegalStateException("The GameProfile must be set when building a TabListEntry");
}
return tabList.buildEntry(profile, displayName, latency, gameMode);
}
}
} }

Datei anzeigen

@ -3,301 +3,323 @@ package com.velocitypowered.api.proxy.server;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.config.ProxyConfig;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* GS4 query response. This class is immutable. * GS4 query response. This class is immutable.
*/ */
public final class QueryResponse { public final class QueryResponse {
private final String hostname;
private final String gameVersion;
private final String map;
private final int currentPlayers;
private final int maxPlayers;
private final String proxyHost;
private final int proxyPort;
private final Collection<String> players;
private final String proxyVersion;
private final Collection<PluginInformation> plugins;
private QueryResponse(String hostname, String gameVersion, String map, int currentPlayers, int maxPlayers, String proxyHost, int proxyPort, Collection<String> players, String proxyVersion, Collection<PluginInformation> plugins) { private final String hostname;
this.hostname = hostname; private final String gameVersion;
this.gameVersion = gameVersion; private final String map;
this.map = map; private final int currentPlayers;
this.currentPlayers = currentPlayers; private final int maxPlayers;
this.maxPlayers = maxPlayers; private final String proxyHost;
this.proxyHost = proxyHost; private final int proxyPort;
this.proxyPort = proxyPort; private final Collection<String> players;
this.players = players; private final String proxyVersion;
this.proxyVersion = proxyVersion; private final Collection<PluginInformation> plugins;
this.plugins = plugins;
private QueryResponse(String hostname, String gameVersion, String map, int currentPlayers,
int maxPlayers, String proxyHost, int proxyPort, Collection<String> players,
String proxyVersion, Collection<PluginInformation> plugins) {
this.hostname = hostname;
this.gameVersion = gameVersion;
this.map = map;
this.currentPlayers = currentPlayers;
this.maxPlayers = maxPlayers;
this.proxyHost = proxyHost;
this.proxyPort = proxyPort;
this.players = players;
this.proxyVersion = proxyVersion;
this.plugins = plugins;
}
/**
* Get hostname which will be used to reply to the query. By default it is {@link
* ProxyConfig#getMotdComponent()} in plain text without colour codes.
*
* @return hostname
*/
public String getHostname() {
return hostname;
}
/**
* 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 gameVersion;
}
/**
* Get map name which will be used to reply to the query. By default {@link
* ProxyConfig#getQueryMap()} is sent.
*
* @return map name
*/
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 proxy (public facing) hostname
*
* @return proxy hostname
*/
public String getProxyHost() {
return proxyHost;
}
/**
* Get proxy (public facing) port
*
* @return proxy port
*/
public int getProxyPort() {
return proxyPort;
}
/**
* Get collection of players which will be used to reply to the query.
*
* @return collection of players
*/
public Collection<String> getPlayers() {
return players;
}
/**
* Get server software (name and version) which will be used to reply to the query.
*
* @return server software
*/
public String getProxyVersion() {
return proxyVersion;
}
/**
* Get list of plugins which will be used to reply to the query.
*
* @return collection of plugins
*/
public Collection<PluginInformation> getPlugins() {
return plugins;
}
/**
* Creates a new {@link Builder} instance from data represented by this response
*
* @return {@link QueryResponse} builder
*/
public Builder toBuilder() {
return QueryResponse.builder()
.hostname(getHostname())
.gameVersion(getGameVersion())
.map(getMap())
.currentPlayers(getCurrentPlayers())
.maxPlayers(getMaxPlayers())
.proxyHost(getProxyHost())
.proxyPort(getProxyPort())
.players(getPlayers())
.proxyVersion(getProxyVersion())
.plugins(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 {
@MonotonicNonNull
private String hostname;
@MonotonicNonNull
private String gameVersion;
@MonotonicNonNull
private String map;
@MonotonicNonNull
private String proxyHost;
@MonotonicNonNull
private String proxyVersion;
private int currentPlayers;
private int maxPlayers;
private int proxyPort;
private List<String> players = new ArrayList<>();
private List<PluginInformation> plugins = new ArrayList<>();
private Builder() {
}
public Builder hostname(String hostname) {
this.hostname = Preconditions.checkNotNull(hostname, "hostname");
return this;
}
public Builder gameVersion(String gameVersion) {
this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
return this;
}
public Builder map(String map) {
this.map = Preconditions.checkNotNull(map, "map");
return this;
}
public Builder currentPlayers(int currentPlayers) {
Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
this.currentPlayers = currentPlayers;
return this;
}
public Builder maxPlayers(int maxPlayers) {
Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
this.maxPlayers = maxPlayers;
return this;
}
public Builder proxyHost(String proxyHost) {
this.proxyHost = Preconditions.checkNotNull(proxyHost, "proxyHost");
return this;
}
public Builder proxyPort(int proxyPort) {
Preconditions
.checkArgument(proxyPort >= 1 && proxyPort <= 65535, "proxyPort must be between 1-65535");
this.proxyPort = proxyPort;
return this;
}
public Builder players(Collection<String> players) {
this.players.addAll(Preconditions.checkNotNull(players, "players"));
return this;
}
public Builder players(String... players) {
this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
return this;
}
public Builder clearPlayers() {
this.players.clear();
return this;
}
public Builder proxyVersion(String proxyVersion) {
this.proxyVersion = Preconditions.checkNotNull(proxyVersion, "proxyVersion");
return this;
}
public Builder plugins(Collection<PluginInformation> plugins) {
this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
return this;
}
public Builder plugins(PluginInformation... plugins) {
this.plugins.addAll(Arrays.asList(Preconditions.checkNotNull(plugins, "plugins")));
return this;
}
public Builder clearPlugins() {
this.plugins.clear();
return this;
} }
/** /**
* Get hostname which will be used to reply to the query. By default it is {@link ProxyConfig#getMotdComponent()} in plain text without colour codes. * Builds new {@link QueryResponse} with supplied data
* @return hostname *
* @return response
*/ */
public String getHostname() { public QueryResponse build() {
return hostname; return new QueryResponse(
Preconditions.checkNotNull(hostname, "hostname"),
Preconditions.checkNotNull(gameVersion, "gameVersion"),
Preconditions.checkNotNull(map, "map"),
currentPlayers,
maxPlayers,
Preconditions.checkNotNull(proxyHost, "proxyHost"),
proxyPort,
ImmutableList.copyOf(players),
Preconditions.checkNotNull(proxyVersion, "proxyVersion"),
ImmutableList.copyOf(plugins)
);
}
}
/**
* Plugin information
*/
public static class PluginInformation {
private String name;
private String version;
public PluginInformation(String name, String version) {
this.name = Preconditions.checkNotNull(name, "name");
this.version = Preconditions.checkNotNull(version, "version");
} }
/** public String getName() {
* Get game version which will be used to reply to the query. By default supported Minecraft versions range is sent. return name;
* @return game version
*/
public String getGameVersion() {
return gameVersion;
} }
/** public void setName(String name) {
* Get map name which will be used to reply to the query. By default {@link ProxyConfig#getQueryMap()} is sent. this.name = name;
* @return map name
*/
public String getMap() {
return map;
} }
/** public void setVersion(@Nullable String version) {
* Get current online player count which will be used to reply to the query. this.version = version;
* @return online player count
*/
public int getCurrentPlayers() {
return currentPlayers;
} }
/** @Nullable
* Get max player count which will be used to reply to the query. public String getVersion() {
* @return max player count return version;
*/
public int getMaxPlayers() {
return maxPlayers;
} }
/** public static PluginInformation of(String name, @Nullable String version) {
* Get proxy (public facing) hostname return new PluginInformation(name, version);
* @return proxy hostname
*/
public String getProxyHost() {
return proxyHost;
}
/**
* Get proxy (public facing) port
* @return proxy port
*/
public int getProxyPort() {
return proxyPort;
}
/**
* Get collection of players which will be used to reply to the query.
* @return collection of players
*/
public Collection<String> getPlayers() {
return players;
}
/**
* Get server software (name and version) which will be used to reply to the query.
* @return server software
*/
public String getProxyVersion() {
return proxyVersion;
}
/**
* Get list of plugins which will be used to reply to the query.
* @return collection of plugins
*/
public Collection<PluginInformation> getPlugins() {
return plugins;
}
/**
* Creates a new {@link Builder} instance from data represented by this response
* @return {@link QueryResponse} builder
*/
public Builder toBuilder() {
return QueryResponse.builder()
.hostname(getHostname())
.gameVersion(getGameVersion())
.map(getMap())
.currentPlayers(getCurrentPlayers())
.maxPlayers(getMaxPlayers())
.proxyHost(getProxyHost())
.proxyPort(getProxyPort())
.players(getPlayers())
.proxyVersion(getProxyVersion())
.plugins(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 {
@MonotonicNonNull
private String hostname;
@MonotonicNonNull
private String gameVersion;
@MonotonicNonNull
private String map;
@MonotonicNonNull
private String proxyHost;
@MonotonicNonNull
private String proxyVersion;
private int currentPlayers;
private int maxPlayers;
private int proxyPort;
private List<String> players = new ArrayList<>();
private List<PluginInformation> plugins = new ArrayList<>();
private Builder() {}
public Builder hostname(String hostname) {
this.hostname = Preconditions.checkNotNull(hostname, "hostname");
return this;
}
public Builder gameVersion(String gameVersion) {
this.gameVersion = Preconditions.checkNotNull(gameVersion, "gameVersion");
return this;
}
public Builder map(String map) {
this.map = Preconditions.checkNotNull(map, "map");
return this;
}
public Builder currentPlayers(int currentPlayers) {
Preconditions.checkArgument(currentPlayers >= 0, "currentPlayers cannot be negative");
this.currentPlayers = currentPlayers;
return this;
}
public Builder maxPlayers(int maxPlayers) {
Preconditions.checkArgument(maxPlayers >= 0, "maxPlayers cannot be negative");
this.maxPlayers = maxPlayers;
return this;
}
public Builder proxyHost(String proxyHost) {
this.proxyHost = Preconditions.checkNotNull(proxyHost, "proxyHost");
return this;
}
public Builder proxyPort(int proxyPort) {
Preconditions.checkArgument(proxyPort >= 1 && proxyPort <= 65535, "proxyPort must be between 1-65535");
this.proxyPort = proxyPort;
return this;
}
public Builder players(Collection<String> players) {
this.players.addAll(Preconditions.checkNotNull(players, "players"));
return this;
}
public Builder players(String... players) {
this.players.addAll(Arrays.asList(Preconditions.checkNotNull(players, "players")));
return this;
}
public Builder clearPlayers() {
this.players.clear();
return this;
}
public Builder proxyVersion(String proxyVersion) {
this.proxyVersion = Preconditions.checkNotNull(proxyVersion, "proxyVersion");
return this;
}
public Builder plugins(Collection<PluginInformation> plugins) {
this.plugins.addAll(Preconditions.checkNotNull(plugins, "plugins"));
return this;
}
public Builder plugins(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(hostname, "hostname"),
Preconditions.checkNotNull(gameVersion, "gameVersion"),
Preconditions.checkNotNull(map, "map"),
currentPlayers,
maxPlayers,
Preconditions.checkNotNull(proxyHost, "proxyHost"),
proxyPort,
ImmutableList.copyOf(players),
Preconditions.checkNotNull(proxyVersion, "proxyVersion"),
ImmutableList.copyOf(plugins)
);
}
}
/**
* Plugin information
*/
public static class PluginInformation {
private String name;
private String version;
public PluginInformation(String name, String version) {
this.name = Preconditions.checkNotNull(name, "name");
this.version = Preconditions.checkNotNull(version, "version");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setVersion(@Nullable String version) {
this.version = version;
}
@Nullable
public String getVersion() {
return version;
}
public static PluginInformation of(String name, @Nullable String version) {
return new PluginInformation(name, version);
}
} }
}
} }

Datei anzeigen

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

Datei anzeigen

@ -1,55 +1,61 @@
package com.velocitypowered.api.proxy.server; package com.velocitypowered.api.proxy.server;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.Objects; import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* ServerInfo represents a server that a player can connect to. This object is immutable and safe for concurrent access. * ServerInfo represents a server that a player can connect to. This object is immutable and safe
* for concurrent access.
*/ */
public final class ServerInfo { public final class ServerInfo {
private final String name;
private final InetSocketAddress address;
/** private final String name;
* Creates a new ServerInfo object. private final InetSocketAddress address;
* @param name the name for the server
* @param address the address of the server to connect to
*/
public ServerInfo(String name, InetSocketAddress address) {
this.name = Preconditions.checkNotNull(name, "name");
this.address = Preconditions.checkNotNull(address, "address");
}
public final String getName() { /**
return name; * Creates a new ServerInfo object.
} *
* @param name the name for the server
* @param address the address of the server to connect to
*/
public ServerInfo(String name, InetSocketAddress address) {
this.name = Preconditions.checkNotNull(name, "name");
this.address = Preconditions.checkNotNull(address, "address");
}
public final InetSocketAddress getAddress() { public final String getName() {
return address; return name;
} }
@Override public final InetSocketAddress getAddress() {
public String toString() { return address;
return "ServerInfo{" + }
"name='" + name + '\'' +
", address=" + address +
'}';
}
@Override @Override
public final boolean equals(@Nullable Object o) { public String toString() {
if (this == o) return true; return "ServerInfo{" +
if (o == null || getClass() != o.getClass()) return false; "name='" + name + '\'' +
ServerInfo that = (ServerInfo) o; ", address=" + address +
return Objects.equals(name, that.name) && '}';
Objects.equals(address, that.address); }
}
@Override @Override
public final int hashCode() { public final boolean equals(@Nullable Object o) {
return Objects.hash(name, address); if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
return false;
}
ServerInfo that = (ServerInfo) o;
return Objects.equals(name, that.name) &&
Objects.equals(address, that.address);
}
@Override
public final int hashCode() {
return Objects.hash(name, address);
}
} }

Datei anzeigen

@ -4,308 +4,319 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.util.ModInfo; import com.velocitypowered.api.util.ModInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import net.kyori.text.Component; import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.*;
/** /**
* Represents a 1.7 and above server list ping response. This class is immutable. * Represents a 1.7 and above server list ping response. This class is immutable.
*/ */
public final class ServerPing { public final class ServerPing {
private final Version version;
private final @Nullable Players players;
private final Component description;
private final @Nullable Favicon favicon;
private final @Nullable ModInfo modinfo;
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon) { private final Version version;
this(version, players, description, favicon, ModInfo.DEFAULT); private final @Nullable Players players;
private final Component description;
private final @Nullable Favicon favicon;
private final @Nullable ModInfo modinfo;
public ServerPing(Version version, @Nullable Players players, Component description,
@Nullable Favicon favicon) {
this(version, players, description, favicon, ModInfo.DEFAULT);
}
public ServerPing(Version version, @Nullable Players players, Component description,
@Nullable Favicon favicon, @Nullable ModInfo modinfo) {
this.version = Preconditions.checkNotNull(version, "version");
this.players = players;
this.description = Preconditions.checkNotNull(description, "description");
this.favicon = favicon;
this.modinfo = modinfo;
}
public Version getVersion() {
return version;
}
public Optional<Players> getPlayers() {
return Optional.ofNullable(players);
}
public Component getDescription() {
return description;
}
public Optional<Favicon> getFavicon() {
return Optional.ofNullable(favicon);
}
public Optional<ModInfo> getModinfo() {
return Optional.ofNullable(modinfo);
}
@Override
public String toString() {
return "ServerPing{" +
"version=" + version +
", players=" + players +
", description=" + description +
", favicon='" + favicon + '\'' +
'}';
}
public Builder asBuilder() {
Builder builder = new Builder();
builder.version = version;
if (players != null) {
builder.onlinePlayers = players.online;
builder.maximumPlayers = players.max;
builder.samplePlayers.addAll(players.sample);
} else {
builder.nullOutPlayers = true;
}
builder.description = description;
builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null;
if (modinfo != null) {
builder.modType = modinfo.getType();
builder.mods.addAll(modinfo.getMods());
}
return builder;
}
public static Builder builder() {
return new Builder();
}
/**
* A builder for {@link ServerPing} objects.
*/
public static final class Builder {
private Version version = new Version(0, "Unknown");
private int onlinePlayers;
private int maximumPlayers;
private final List<SamplePlayer> samplePlayers = new ArrayList<>();
private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>();
private @Nullable Component description;
private @Nullable Favicon favicon;
private boolean nullOutPlayers;
private boolean nullOutModinfo;
private Builder() {
} }
public ServerPing(Version version, @Nullable Players players, Component description, @Nullable Favicon favicon, @Nullable ModInfo modinfo) { public Builder version(Version version) {
this.version = Preconditions.checkNotNull(version, "version"); this.version = Preconditions.checkNotNull(version, "version");
this.players = players; return this;
this.description = Preconditions.checkNotNull(description, "description"); }
this.favicon = favicon;
this.modinfo = modinfo; public Builder onlinePlayers(int onlinePlayers) {
this.onlinePlayers = onlinePlayers;
return this;
}
public Builder maximumPlayers(int maximumPlayers) {
this.maximumPlayers = maximumPlayers;
return this;
}
public Builder samplePlayers(SamplePlayer... players) {
this.samplePlayers.addAll(Arrays.asList(players));
return this;
}
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(ModInfo.Mod... mods) {
this.mods.addAll(Arrays.asList(mods));
return this;
}
public Builder clearMods() {
this.mods.clear();
return this;
}
public Builder clearSamplePlayers() {
this.samplePlayers.clear();
return this;
}
public Builder notModCompatible() {
this.nullOutModinfo = true;
return this;
}
public Builder nullPlayers() {
this.nullOutPlayers = true;
return this;
}
public Builder description(Component description) {
this.description = Preconditions.checkNotNull(description, "description");
return this;
}
public Builder favicon(Favicon favicon) {
this.favicon = Preconditions.checkNotNull(favicon, "favicon");
return this;
}
public ServerPing build() {
if (this.version == null) {
throw new IllegalStateException("version not specified");
}
if (this.description == null) {
throw new IllegalStateException("no server description supplied");
}
return new ServerPing(version,
nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers),
description, favicon, nullOutModinfo ? null : new ModInfo(modType, mods));
} }
public Version getVersion() { public Version getVersion() {
return version; return version;
} }
public Optional<Players> getPlayers() { public int getOnlinePlayers() {
return Optional.ofNullable(players); return onlinePlayers;
} }
public Component getDescription() { public int getMaximumPlayers() {
return description; return maximumPlayers;
}
public List<SamplePlayer> getSamplePlayers() {
return samplePlayers;
}
public Optional<Component> getDescription() {
return Optional.ofNullable(description);
} }
public Optional<Favicon> getFavicon() { public Optional<Favicon> getFavicon() {
return Optional.ofNullable(favicon); return Optional.ofNullable(favicon);
} }
public Optional<ModInfo> getModinfo() { public String getModType() {
return Optional.ofNullable(modinfo); return modType;
}
public List<ModInfo.Mod> getMods() {
return mods;
} }
@Override @Override
public String toString() { public String toString() {
return "ServerPing{" + return "Builder{" +
"version=" + version + "version=" + version +
", players=" + players + ", onlinePlayers=" + onlinePlayers +
", description=" + description + ", maximumPlayers=" + maximumPlayers +
", favicon='" + favicon + '\'' + ", samplePlayers=" + samplePlayers +
'}'; ", modType=" + modType +
", mods=" + mods +
", description=" + description +
", favicon=" + favicon +
", nullOutPlayers=" + nullOutPlayers +
", nullOutModinfo=" + nullOutModinfo +
'}';
}
}
public static final class Version {
private final int protocol;
private final String name;
public Version(int protocol, String name) {
this.protocol = protocol;
this.name = name;
} }
public Builder asBuilder() { public int getProtocol() {
Builder builder = new Builder(); return protocol;
builder.version = version;
if (players != null) {
builder.onlinePlayers = players.online;
builder.maximumPlayers = players.max;
builder.samplePlayers.addAll(players.sample);
} else {
builder.nullOutPlayers = true;
}
builder.description = description;
builder.favicon = favicon;
builder.nullOutModinfo = modinfo == null;
if (modinfo != null) {
builder.modType = modinfo.getType();
builder.mods.addAll(modinfo.getMods());
}
return builder;
} }
public static Builder builder() { public String getName() {
return new Builder(); return name;
} }
/** @Override
* A builder for {@link ServerPing} objects. public String toString() {
*/ return "Version{" +
public static final class Builder { "protocol=" + protocol +
private Version version = new Version(0, "Unknown"); ", name='" + name + '\'' +
private int onlinePlayers; '}';
private int maximumPlayers; }
private final List<SamplePlayer> samplePlayers = new ArrayList<>(); }
private String modType = "FML";
private final List<ModInfo.Mod> mods = new ArrayList<>();
private @Nullable Component description;
private @Nullable Favicon favicon;
private boolean nullOutPlayers;
private boolean nullOutModinfo;
private Builder() { public static final class Players {
} private final int online;
private final int max;
private final List<SamplePlayer> sample;
public Builder version(Version version) { public Players(int online, int max, List<SamplePlayer> sample) {
this.version = Preconditions.checkNotNull(version, "version"); this.online = online;
return this; this.max = max;
} this.sample = ImmutableList.copyOf(sample);
public Builder onlinePlayers(int onlinePlayers) {
this.onlinePlayers = onlinePlayers;
return this;
}
public Builder maximumPlayers(int maximumPlayers) {
this.maximumPlayers = maximumPlayers;
return this;
}
public Builder samplePlayers(SamplePlayer... players) {
this.samplePlayers.addAll(Arrays.asList(players));
return this;
}
public Builder modType(String modType) {
this.modType = Preconditions.checkNotNull(modType, "modType");
return this;
}
public Builder mods(ModInfo.Mod... mods) {
this.mods.addAll(Arrays.asList(mods));
return this;
}
public Builder clearMods() {
this.mods.clear();
return this;
}
public Builder clearSamplePlayers() {
this.samplePlayers.clear();
return this;
}
public Builder notModCompatible() {
this.nullOutModinfo = true;
return this;
}
public Builder nullPlayers() {
this.nullOutPlayers = true;
return this;
}
public Builder description(Component description) {
this.description = Preconditions.checkNotNull(description, "description");
return this;
}
public Builder favicon(Favicon favicon) {
this.favicon = Preconditions.checkNotNull(favicon, "favicon");
return this;
}
public ServerPing build() {
if (this.version == null) {
throw new IllegalStateException("version not specified");
}
if (this.description == null) {
throw new IllegalStateException("no server description supplied");
}
return new ServerPing(version, nullOutPlayers ? null : new Players(onlinePlayers, maximumPlayers, samplePlayers),
description, favicon, nullOutModinfo ? null : new ModInfo(modType, mods));
}
public Version getVersion() {
return version;
}
public int getOnlinePlayers() {
return onlinePlayers;
}
public int getMaximumPlayers() {
return maximumPlayers;
}
public List<SamplePlayer> getSamplePlayers() {
return samplePlayers;
}
public Optional<Component> getDescription() {
return Optional.ofNullable(description);
}
public Optional<Favicon> getFavicon() {
return Optional.ofNullable(favicon);
}
public String getModType() {
return modType;
}
public List<ModInfo.Mod> getMods() {
return mods;
}
@Override
public String toString() {
return "Builder{" +
"version=" + version +
", onlinePlayers=" + onlinePlayers +
", maximumPlayers=" + maximumPlayers +
", samplePlayers=" + samplePlayers +
", modType=" + modType +
", mods=" + mods +
", description=" + description +
", favicon=" + favicon +
", nullOutPlayers=" + nullOutPlayers +
", nullOutModinfo=" + nullOutModinfo +
'}';
}
} }
public static final class Version { public int getOnline() {
private final int protocol; return online;
private final String name;
public Version(int protocol, String name) {
this.protocol = protocol;
this.name = name;
}
public int getProtocol() {
return protocol;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Version{" +
"protocol=" + protocol +
", name='" + name + '\'' +
'}';
}
} }
public static final class Players { public int getMax() {
private final int online; return max;
private final int max;
private final List<SamplePlayer> sample;
public Players(int online, int max, List<SamplePlayer> sample) {
this.online = online;
this.max = max;
this.sample = ImmutableList.copyOf(sample);
}
public int getOnline() {
return online;
}
public int getMax() {
return max;
}
public List<SamplePlayer> getSample() {
return sample;
}
@Override
public String toString() {
return "Players{" +
"online=" + online +
", max=" + max +
", sample=" + sample +
'}';
}
} }
public static final class SamplePlayer { public List<SamplePlayer> getSample() {
private final String name; return sample;
private final UUID id;
public SamplePlayer(String name, UUID id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public UUID getId() {
return id;
}
@Override
public String toString() {
return "SamplePlayer{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
} }
@Override
public String toString() {
return "Players{" +
"online=" + online +
", max=" + max +
", sample=" + sample +
'}';
}
}
public static final class SamplePlayer {
private final String name;
private final UUID id;
public SamplePlayer(String name, UUID id) {
this.name = name;
this.id = id;
}
public String getName() {
return name;
}
public UUID getId() {
return id;
}
@Override
public String toString() {
return "SamplePlayer{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
} }

Datei anzeigen

@ -4,21 +4,24 @@ package com.velocitypowered.api.scheduler;
* Represents a task that is scheduled to run on the proxy. * Represents a task that is scheduled to run on the proxy.
*/ */
public interface ScheduledTask { public interface ScheduledTask {
/**
* Returns the plugin that scheduled this task.
* @return the plugin that scheduled this task
*/
Object plugin();
/** /**
* Returns the current status of this task. * Returns the plugin that scheduled this task.
* @return the current status of this task *
*/ * @return the plugin that scheduled this task
TaskStatus status(); */
Object plugin();
/** /**
* Cancels this task. If the task is already running, the thread in which it is running will be interrupted. * Returns the current status of this task.
* If the task is not currently running, Velocity will terminate it safely. *
*/ * @return the current status of this task
void cancel(); */
TaskStatus status();
/**
* Cancels this task. If the task is already running, the thread in which it is running will be
* interrupted. If the task is not currently running, Velocity will terminate it safely.
*/
void cancel();
} }

Datei anzeigen

@ -1,57 +1,65 @@
package com.velocitypowered.api.scheduler; package com.velocitypowered.api.scheduler;
import org.checkerframework.common.value.qual.IntRange;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.checkerframework.common.value.qual.IntRange;
/** /**
* Represents a scheduler to execute tasks on the proxy. * Represents a scheduler to execute tasks on the proxy.
*/ */
public interface Scheduler { public interface Scheduler {
/**
* Initializes a new {@link TaskBuilder} for creating a task on the proxy. /**
* @param plugin the plugin to request the task for * Initializes a new {@link TaskBuilder} for creating a task on the proxy.
* @param runnable the task to run when scheduled *
* @return the task builder * @param plugin the plugin to request the task for
*/ * @param runnable the task to run when scheduled
TaskBuilder buildTask(Object plugin, Runnable runnable); * @return the task builder
*/
TaskBuilder buildTask(Object plugin, Runnable runnable);
/**
* Represents a fluent interface to schedule tasks on the proxy.
*/
interface TaskBuilder {
/** /**
* Represents a fluent interface to schedule tasks on the proxy. * Specifies that the task should delay its execution by the specified amount of time.
*
* @param time the time to delay by
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/ */
interface TaskBuilder { TaskBuilder delay(@IntRange(from = 0) long time, TimeUnit unit);
/**
* Specifies that the task should delay its execution by the specified amount of time.
* @param time the time to delay by
* @param unit the unit of time for {@code time}
* @return this builder, for chaining
*/
TaskBuilder delay(@IntRange(from = 0) long time, TimeUnit unit);
/** /**
* Specifies that the task should continue running after waiting for the specified amount, until it is cancelled. * Specifies that the task should continue running after waiting for the specified amount, until
* @param time the time to delay by * it is cancelled.
* @param unit the unit of time for {@code time} *
* @return this builder, for chaining * @param time the time to delay by
*/ * @param unit the unit of time for {@code time}
TaskBuilder repeat(@IntRange(from = 0) long time, TimeUnit unit); * @return this builder, for chaining
*/
TaskBuilder repeat(@IntRange(from = 0) long time, TimeUnit unit);
/** /**
* Clears the delay on this task. * Clears the delay on this task.
* @return this builder, for chaining *
*/ * @return this builder, for chaining
TaskBuilder clearDelay(); */
TaskBuilder clearDelay();
/** /**
* Clears the repeat interval on this task. * Clears the repeat interval on this task.
* @return this builder, for chaining *
*/ * @return this builder, for chaining
TaskBuilder clearRepeat(); */
TaskBuilder clearRepeat();
/** /**
* Schedules this task for execution. * Schedules this task for execution.
* @return the scheduled task *
*/ * @return the scheduled task
ScheduledTask schedule(); */
} ScheduledTask schedule();
}
} }

Datei anzeigen

@ -1,16 +1,16 @@
package com.velocitypowered.api.scheduler; package com.velocitypowered.api.scheduler;
public enum TaskStatus { public enum TaskStatus {
/** /**
* The task is scheduled and is currently running. * The task is scheduled and is currently running.
*/ */
SCHEDULED, SCHEDULED,
/** /**
* The task was cancelled with {@link ScheduledTask#cancel()}. * The task was cancelled with {@link ScheduledTask#cancel()}.
*/ */
CANCELLED, CANCELLED,
/** /**
* The task has run to completion. This is applicable only for tasks without a repeat. * The task has run to completion. This is applicable only for tasks without a repeat.
*/ */
FINISHED FINISHED
} }

Datei anzeigen

@ -1,9 +1,6 @@
package com.velocitypowered.api.util; package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.Nullable;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@ -12,78 +9,92 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Base64; import java.util.Base64;
import java.util.Objects; import java.util.Objects;
import javax.imageio.ImageIO;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be displayed to a remote * Represents a Minecraft server favicon. A Minecraft server favicon is a 64x64 image that can be
* client that sends a Server List Ping packet, and is automatically displayed in the Minecraft client. * displayed to a remote client that sends a Server List Ping packet, and is automatically displayed
* in the Minecraft client.
*/ */
public final class Favicon { public final class Favicon {
private final String base64Url;
/** private final String base64Url;
* Directly create a favicon using its Base64 URL directly. You are generally better served by the create() series
* of functions.
* @param base64Url the url for use with this favicon
*/
public Favicon(String base64Url) {
this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url");
}
/** /**
* Returns the Base64-encoded URI for this image. * Directly create a favicon using its Base64 URL directly. You are generally better served by the
* @return a URL representing this favicon * create() series of functions.
*/ *
public String getBase64Url() { * @param base64Url the url for use with this favicon
return base64Url; */
} public Favicon(String base64Url) {
this.base64Url = Preconditions.checkNotNull(base64Url, "base64Url");
}
@Override /**
public boolean equals(@Nullable Object o) { * Returns the Base64-encoded URI for this image.
if (this == o) return true; *
if (o == null || getClass() != o.getClass()) return false; * @return a URL representing this favicon
Favicon favicon = (Favicon) o; */
return Objects.equals(base64Url, favicon.base64Url); public String getBase64Url() {
} return base64Url;
}
@Override @Override
public int hashCode() { public boolean equals(@Nullable Object o) {
return Objects.hash(base64Url); if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
return false;
}
Favicon favicon = (Favicon) o;
return Objects.equals(base64Url, favicon.base64Url);
}
@Override @Override
public String toString() { public int hashCode() {
return "Favicon{" + return Objects.hash(base64Url);
"base64Url='" + base64Url + '\'' + }
'}';
}
/** @Override
* Creates a new {@code Favicon} from the specified {@code image}. public String toString() {
* @param image the image to use for the favicon return "Favicon{" +
* @return the created {@link Favicon} instance "base64Url='" + base64Url + '\'' +
*/ '}';
public static Favicon create(BufferedImage image) { }
Preconditions.checkNotNull(image, "image");
Preconditions.checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" +
" 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight());
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(image, "PNG", os);
} catch (IOException e) {
throw new AssertionError(e);
}
return new Favicon("data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()));
}
/** /**
* Creates a new {@code Favicon} by reading the image from the specified {@code path}. * Creates a new {@code Favicon} from the specified {@code image}.
* @param path the path to the image to create a favicon for *
* @return the created {@link Favicon} instance * @param image the image to use for the favicon
* @throws IOException if the file could not be read from the path * @return the created {@link Favicon} instance
*/ */
public static Favicon create(Path path) throws IOException { public static Favicon create(BufferedImage image) {
try (InputStream stream = Files.newInputStream(path)) { Preconditions.checkNotNull(image, "image");
return create(ImageIO.read(stream)); Preconditions
} .checkArgument(image.getWidth() == 64 && image.getHeight() == 64, "Image does not have" +
" 64x64 dimensions (found %sx%s)", image.getWidth(), image.getHeight());
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
ImageIO.write(image, "PNG", os);
} catch (IOException e) {
throw new AssertionError(e);
} }
return new Favicon(
"data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()));
}
/**
* Creates a new {@code Favicon} by reading the image from the specified {@code path}.
*
* @param path the path to the image to create a favicon for
* @return the created {@link Favicon} instance
* @throws IOException if the file could not be read from the path
*/
public static Favicon create(Path path) throws IOException {
try (InputStream stream = Files.newInputStream(path)) {
return create(ImageIO.read(stream));
}
}
} }

Datei anzeigen

@ -2,7 +2,6 @@ package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@ -10,82 +9,85 @@ import java.util.UUID;
* Represents a Mojang game profile. This class is immutable. * Represents a Mojang game profile. This class is immutable.
*/ */
public final class GameProfile { public final class GameProfile {
private final String id;
private final String id;
private final String name;
private final List<Property> properties;
public GameProfile(String id, String name, List<Property> properties) {
this.id = Preconditions.checkNotNull(id, "id");
this.name = Preconditions.checkNotNull(name, "name");
this.properties = ImmutableList.copyOf(properties);
}
public String getId() {
return id;
}
public UUID idAsUuid() {
return UuidUtils.fromUndashed(id);
}
public String getName() {
return name;
}
public List<Property> getProperties() {
return properties;
}
/**
* Creates a game profile suitable for use in offline-mode.
*
* @param username the username to use
* @return the new offline-mode game profile
*/
public static GameProfile forOfflinePlayer(String username) {
Preconditions.checkNotNull(username, "username");
String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username));
return new GameProfile(id, username, ImmutableList.of());
}
@Override
public String toString() {
return "GameProfile{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", properties=" + properties +
'}';
}
public static final class Property {
private final String name; private final String name;
private final List<Property> properties; private final String value;
private final String signature;
public GameProfile(String id, String name, List<Property> properties) { public Property(String name, String value, String signature) {
this.id = Preconditions.checkNotNull(id, "id"); this.name = Preconditions.checkNotNull(name, "name");
this.name = Preconditions.checkNotNull(name, "name"); this.value = Preconditions.checkNotNull(value, "value");
this.properties = ImmutableList.copyOf(properties); this.signature = Preconditions.checkNotNull(signature, "signature");
}
public String getId() {
return id;
}
public UUID idAsUuid() {
return UuidUtils.fromUndashed(id);
} }
public String getName() { public String getName() {
return name; return name;
} }
public List<Property> getProperties() { public String getValue() {
return properties; return value;
} }
/** public String getSignature() {
* Creates a game profile suitable for use in offline-mode. return signature;
* @param username the username to use
* @return the new offline-mode game profile
*/
public static GameProfile forOfflinePlayer(String username) {
Preconditions.checkNotNull(username, "username");
String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username));
return new GameProfile(id, username, ImmutableList.of());
} }
@Override @Override
public String toString() { public String toString() {
return "GameProfile{" + return "Property{" +
"id='" + id + '\'' + "name='" + name + '\'' +
", name='" + name + '\'' + ", value='" + value + '\'' +
", properties=" + properties + ", signature='" + signature + '\'' +
'}'; '}';
}
public static final class Property {
private final String name;
private final String value;
private final String signature;
public Property(String name, String value, String signature) {
this.name = Preconditions.checkNotNull(name, "name");
this.value = Preconditions.checkNotNull(value, "value");
this.signature = Preconditions.checkNotNull(signature, "signature");
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
public String getSignature() {
return signature;
}
@Override
public String toString() {
return "Property{" +
"name='" + name + '\'' +
", value='" + value + '\'' +
", signature='" + signature + '\'' +
'}';
}
} }
}
} }

Datei anzeigen

@ -4,17 +4,18 @@ package com.velocitypowered.api.util;
* Represents where a chat message is going to be sent. * Represents where a chat message is going to be sent.
*/ */
public enum MessagePosition { public enum MessagePosition {
/** /**
* The chat message will appear in the client's HUD. These messages can be filtered out by the client. * The chat message will appear in the client's HUD. These messages can be filtered out by the
*/ * client.
CHAT, */
/** CHAT,
* The chat message will appear in the client's HUD and can't be dismissed. /**
*/ * The chat message will appear in the client's HUD and can't be dismissed.
SYSTEM, */
/** SYSTEM,
* The chat message will appear above the player's main HUD. This text format doesn't support many component features, /**
* such as hover events. * The chat message will appear above the player's main HUD. This text format doesn't support many
*/ * component features, such as hover events.
ACTION_BAR */
ACTION_BAR
} }

Datei anzeigen

@ -2,59 +2,60 @@ package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
public final class ModInfo { public final class ModInfo {
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
public static final ModInfo DEFAULT = new ModInfo("FML", ImmutableList.of());
private final String type;
private final List<Mod> modList; private final String type;
private final List<Mod> modList;
public ModInfo(String type, List<Mod> modList) {
this.type = Preconditions.checkNotNull(type, "type"); public ModInfo(String type, List<Mod> modList) {
this.modList = ImmutableList.copyOf(modList); this.type = Preconditions.checkNotNull(type, "type");
this.modList = ImmutableList.copyOf(modList);
}
public String getType() {
return type;
}
public List<Mod> getMods() {
return modList;
}
@Override
public String toString() {
return "ModInfo{" +
"type='" + type + '\'' +
", modList=" + modList +
'}';
}
public static final class Mod {
private final String id;
private final String version;
public Mod(String id, String version) {
this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version");
} }
public String getType() { public String getId() {
return type; return id;
} }
public List<Mod> getMods() { public String getVersion() {
return modList; return version;
} }
@Override @Override
public String toString() { public String toString() {
return "ModInfo{" + return "Mod{" +
"type='" + type + '\'' + "id='" + id + '\'' +
", modList=" + modList + ", version='" + version + '\'' +
'}'; '}';
}
public static final class Mod {
private final String id;
private final String version;
public Mod(String id, String version) {
this.id = Preconditions.checkNotNull(id, "id");
this.version = Preconditions.checkNotNull(version, "version");
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
@Override
public String toString() {
return "Mod{" +
"id='" + id + '\'' +
", version='" + version + '\'' +
'}';
}
} }
}
} }

Datei anzeigen

@ -1,57 +1,61 @@
package com.velocitypowered.api.util; package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects; import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Provides a version object for the proxy. * Provides a version object for the proxy.
*/ */
public final class ProxyVersion { public final class ProxyVersion {
private final String name;
private final String vendor;
private final String version;
public ProxyVersion(String name, String vendor, String version) { private final String name;
this.name = Preconditions.checkNotNull(name, "name"); private final String vendor;
this.vendor = Preconditions.checkNotNull(vendor, "vendor"); private final String version;
this.version = Preconditions.checkNotNull(version, "version");
}
public String getName() { public ProxyVersion(String name, String vendor, String version) {
return name; this.name = Preconditions.checkNotNull(name, "name");
} this.vendor = Preconditions.checkNotNull(vendor, "vendor");
this.version = Preconditions.checkNotNull(version, "version");
}
public String getVendor() { public String getName() {
return vendor; return name;
} }
public String getVersion() { public String getVendor() {
return version; return vendor;
} }
@Override public String getVersion() {
public boolean equals(@Nullable Object o) { return version;
if (this == o) return true; }
if (o == null || getClass() != o.getClass()) return false;
ProxyVersion that = (ProxyVersion) o;
return Objects.equals(name, that.name) &&
Objects.equals(vendor, that.vendor) &&
Objects.equals(version, that.version);
}
@Override @Override
public int hashCode() { public boolean equals(@Nullable Object o) {
return Objects.hash(name, vendor, version); if (this == o) {
return true;
} }
if (o == null || getClass() != o.getClass()) {
return false;
}
ProxyVersion that = (ProxyVersion) o;
return Objects.equals(name, that.name) &&
Objects.equals(vendor, that.vendor) &&
Objects.equals(version, that.version);
}
@Override @Override
public String toString() { public int hashCode() {
return "ProxyVersion{" + return Objects.hash(name, vendor, version);
"name='" + name + '\'' + }
", vendor='" + vendor + '\'' +
", version='" + version + '\'' + @Override
'}'; public String toString() {
} return "ProxyVersion{" +
"name='" + name + '\'' +
", vendor='" + vendor + '\'' +
", version='" + version + '\'' +
'}';
}
} }

Datei anzeigen

@ -2,7 +2,6 @@ package com.velocitypowered.api.util;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
@ -11,41 +10,45 @@ import java.util.UUID;
* Provides a small, useful selection of utilities for working with Minecraft UUIDs. * Provides a small, useful selection of utilities for working with Minecraft UUIDs.
*/ */
public final class UuidUtils { public final class UuidUtils {
private UuidUtils() {
throw new AssertionError();
}
/** private UuidUtils() {
* Converts from an undashed Mojang-style UUID into a Java {@link UUID} object. throw new AssertionError();
* @param string the string to convert }
* @return the UUID object
*/
public static UUID fromUndashed(final String string) {
Objects.requireNonNull(string, "string");
Preconditions.checkArgument(string.length() == 32, "Length is incorrect");
return new UUID(
Long.parseUnsignedLong(string.substring(0, 16), 16),
Long.parseUnsignedLong(string.substring(16), 16)
);
}
/** /**
* Converts from a Java {@link UUID} object into an undashed Mojang-style UUID. * Converts from an undashed Mojang-style UUID into a Java {@link UUID} object.
* @param uuid the UUID to convert *
* @return the undashed UUID * @param string the string to convert
*/ * @return the UUID object
public static String toUndashed(final UUID uuid) { */
Preconditions.checkNotNull(uuid, "uuid"); public static UUID fromUndashed(final String string) {
return Strings.padStart(Long.toHexString(uuid.getMostSignificantBits()), 16, '0') + Objects.requireNonNull(string, "string");
Strings.padStart(Long.toHexString(uuid.getLeastSignificantBits()), 16, '0'); Preconditions.checkArgument(string.length() == 32, "Length is incorrect");
} return new UUID(
Long.parseUnsignedLong(string.substring(0, 16), 16),
Long.parseUnsignedLong(string.substring(16), 16)
);
}
/** /**
* Generates a UUID for use for offline mode. * Converts from a Java {@link UUID} object into an undashed Mojang-style UUID.
* @param username the username to use *
* @return the offline mode UUID * @param uuid the UUID to convert
*/ * @return the undashed UUID
public static UUID generateOfflinePlayerUuid(String username) { */
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); public static String toUndashed(final UUID uuid) {
} Preconditions.checkNotNull(uuid, "uuid");
return Strings.padStart(Long.toHexString(uuid.getMostSignificantBits()), 16, '0') +
Strings.padStart(Long.toHexString(uuid.getLeastSignificantBits()), 16, '0');
}
/**
* Generates a UUID for use for offline mode.
*
* @param username the username to use
* @return the offline mode UUID
*/
public static UUID generateOfflinePlayerUuid(String username) {
return UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
}
} }

Datei anzeigen

@ -1,236 +1,251 @@
package com.velocitypowered.api.util.title; package com.velocitypowered.api.util.title;
import com.google.common.base.Preconditions; 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.Objects;
import java.util.Optional; import java.util.Optional;
import net.kyori.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents a "full" title, including all components. This class is immutable. * Represents a "full" title, including all components. This class is immutable.
*/ */
public final class TextTitle implements Title { public final class TextTitle implements Title {
private final @Nullable Component title;
private final @Nullable Component subtitle;
private final int stay;
private final int fadeIn;
private final int fadeOut;
private final boolean resetBeforeSend;
private TextTitle(Builder builder) { private final @Nullable Component title;
this.title = builder.title; private final @Nullable Component subtitle;
this.subtitle = builder.subtitle; private final int stay;
this.stay = builder.stay; private final int fadeIn;
this.fadeIn = builder.fadeIn; private final int fadeOut;
this.fadeOut = builder.fadeOut; private final boolean resetBeforeSend;
this.resetBeforeSend = builder.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(@Nullable 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;
} }
/**
* Returns the main title this title has, if any.
* @return the main title of this title
*/
public Optional<Component> getTitle() { public Optional<Component> getTitle() {
return Optional.ofNullable(title); return Optional.ofNullable(title);
} }
/**
* Returns the subtitle this title has, if any.
* @return the subtitle
*/
public Optional<Component> getSubtitle() { public Optional<Component> getSubtitle() {
return Optional.ofNullable(subtitle); 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() { public int getStay() {
return stay; 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() { public int getFadeIn() {
return fadeIn; 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() { public int getFadeOut() {
return fadeOut; 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() { public boolean isResetBeforeSend() {
return resetBeforeSend; return resetBeforeSend;
} }
/** public TextTitle build() {
* Determines whether or not this title has times set on it. If none are set, it will update the previous title return new TextTitle(this);
* 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(@Nullable 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 @Override
public String toString() { public String toString() {
return "TextTitle{" + return "Builder{" +
"title=" + title + "title=" + title +
", subtitle=" + subtitle + ", subtitle=" + subtitle +
", stay=" + stay + ", stay=" + stay +
", fadeIn=" + fadeIn + ", fadeIn=" + fadeIn +
", fadeOut=" + fadeOut + ", fadeOut=" + fadeOut +
", resetBeforeSend=" + resetBeforeSend + ", 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 Optional<Component> getTitle() {
return Optional.ofNullable(title);
}
public Optional<Component> getSubtitle() {
return Optional.ofNullable(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 +
'}';
}
} }
}
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -1,67 +1,81 @@
package com.velocitypowered.api.util; package com.velocitypowered.api.util;
import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.UUID; import java.util.UUID;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class UuidUtilsTest { class UuidUtilsTest {
private static final UUID EXPECTED_DASHED_UUID = UUID.fromString("6b501978-d3be-4f33-bcf6-6e7808f37a0d");
private static final String ACTUAL_UNDASHED_UUID = EXPECTED_DASHED_UUID.toString().replace("-", "");
private static final UUID ISSUE_109_ZERO_UUID = new UUID(0, 0); private static final UUID EXPECTED_DASHED_UUID = UUID
private static final String ISSUE_109_ZERO_UUID_UNDASHED = "00000000000000000000000000000000"; .fromString("6b501978-d3be-4f33-bcf6-6e7808f37a0d");
private static final String ACTUAL_UNDASHED_UUID = EXPECTED_DASHED_UUID.toString()
.replace("-", "");
private static final UUID ISSUE_109_ONE_LSB_UUID = new UUID(0, 1); private static final UUID ISSUE_109_ZERO_UUID = new UUID(0, 0);
private static final String ISSUE_109_ONE_LSB_UUID_UNDASHED = "00000000000000000000000000000001"; private static final String ISSUE_109_ZERO_UUID_UNDASHED = "00000000000000000000000000000000";
private static final UUID ISSUE_109_ONE_MLSB_UUID = new UUID(1, 1); private static final UUID ISSUE_109_ONE_LSB_UUID = new UUID(0, 1);
private static final String ISSUE_109_ONE_MLSB_UUID_UNDASHED = "00000000000000010000000000000001"; private static final String ISSUE_109_ONE_LSB_UUID_UNDASHED = "00000000000000000000000000000001";
private static final UUID ISSUE_109_LEADING_ZERO_UUID = UUID.fromString("0d470a25-0416-48a1-b7a6-2a27aa5eb251"); private static final UUID ISSUE_109_ONE_MLSB_UUID = new UUID(1, 1);
private static final String ISSUE_109_LEADING_ZERO_UNDASHED = "0d470a25041648a1b7a62a27aa5eb251"; private static final String ISSUE_109_ONE_MLSB_UUID_UNDASHED = "00000000000000010000000000000001";
private static final UUID TEST_OFFLINE_PLAYER_UUID = UUID.fromString("708f6260-183d-3912-bbde-5e279a5e739a"); private static final UUID ISSUE_109_LEADING_ZERO_UUID = UUID
private static final String TEST_OFFLINE_PLAYER = "tuxed"; .fromString("0d470a25-0416-48a1-b7a6-2a27aa5eb251");
private static final String ISSUE_109_LEADING_ZERO_UNDASHED = "0d470a25041648a1b7a62a27aa5eb251";
@Test private static final UUID TEST_OFFLINE_PLAYER_UUID = UUID
void generateOfflinePlayerUuid() { .fromString("708f6260-183d-3912-bbde-5e279a5e739a");
assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER), "UUIDs do not match"); private static final String TEST_OFFLINE_PLAYER = "tuxed";
}
@Test @Test
void fromUndashed() { void generateOfflinePlayerUuid() {
assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID), "UUIDs do not match"); assertEquals(TEST_OFFLINE_PLAYER_UUID, UuidUtils.generateOfflinePlayerUuid(TEST_OFFLINE_PLAYER),
} "UUIDs do not match");
}
@Test @Test
void toUndashed() { void fromUndashed() {
assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID), "UUIDs do not match"); assertEquals(EXPECTED_DASHED_UUID, UuidUtils.fromUndashed(ACTUAL_UNDASHED_UUID),
} "UUIDs do not match");
}
@Test @Test
void zeroUuidIssue109() { void toUndashed() {
assertEquals(ISSUE_109_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_ZERO_UUID_UNDASHED), "UUIDs do not match"); assertEquals(ACTUAL_UNDASHED_UUID, UuidUtils.toUndashed(EXPECTED_DASHED_UUID),
assertEquals(ISSUE_109_ZERO_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ZERO_UUID), "UUIDs do not match"); "UUIDs do not match");
} }
@Test @Test
void leadingZeroUuidIssue109() { void zeroUuidIssue109() {
assertEquals(ISSUE_109_LEADING_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_LEADING_ZERO_UNDASHED), "UUIDs do not match"); assertEquals(ISSUE_109_ZERO_UUID, UuidUtils.fromUndashed(ISSUE_109_ZERO_UUID_UNDASHED),
assertEquals(ISSUE_109_LEADING_ZERO_UNDASHED, UuidUtils.toUndashed(ISSUE_109_LEADING_ZERO_UUID), "UUIDs do not match"); "UUIDs do not match");
} assertEquals(ISSUE_109_ZERO_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ZERO_UUID),
"UUIDs do not match");
}
@Test @Test
void oneUuidLsbIssue109() { void leadingZeroUuidIssue109() {
assertEquals(ISSUE_109_ONE_LSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_LSB_UUID_UNDASHED), "UUIDs do not match"); assertEquals(ISSUE_109_LEADING_ZERO_UUID,
assertEquals(ISSUE_109_ONE_LSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_LSB_UUID), "UUIDs do not match"); UuidUtils.fromUndashed(ISSUE_109_LEADING_ZERO_UNDASHED), "UUIDs do not match");
} assertEquals(ISSUE_109_LEADING_ZERO_UNDASHED, UuidUtils.toUndashed(ISSUE_109_LEADING_ZERO_UUID),
"UUIDs do not match");
}
@Test @Test
void oneUuidMsbAndLsbIssue109() { void oneUuidLsbIssue109() {
assertEquals(ISSUE_109_ONE_MLSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_MLSB_UUID_UNDASHED), "UUIDs do not match"); assertEquals(ISSUE_109_ONE_LSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_LSB_UUID_UNDASHED),
assertEquals(ISSUE_109_ONE_MLSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_MLSB_UUID), "UUIDs do not match"); "UUIDs do not match");
} assertEquals(ISSUE_109_ONE_LSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_LSB_UUID),
"UUIDs do not match");
}
@Test
void oneUuidMsbAndLsbIssue109() {
assertEquals(ISSUE_109_ONE_MLSB_UUID, UuidUtils.fromUndashed(ISSUE_109_ONE_MLSB_UUID_UNDASHED),
"UUIDs do not match");
assertEquals(ISSUE_109_ONE_MLSB_UUID_UNDASHED, UuidUtils.toUndashed(ISSUE_109_ONE_MLSB_UUID),
"UUIDs do not match");
}
} }

Datei anzeigen

@ -0,0 +1,256 @@
<?xml version="1.0"?>
<!DOCTYPE module PUBLIC
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
"https://checkstyle.org/dtds/configuration_1_3.dtd">
<!--
Checkstyle configuration that checks the Google coding conventions from Google Java Style
that can be found at https://google.github.io/styleguide/javaguide.html.
Checkstyle is very configurable. Be sure to read the documentation at
http://checkstyle.sf.net (or in your downloaded distribution).
To completely disable a check, just comment it out or delete it from the file.
Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.
-->
<module name="Checker">
<property name="charset" value="UTF-8"/>
<property name="severity" value="warning"/>
<property name="fileExtensions" value="java, properties, xml"/>
<!-- Checks for whitespace -->
<!-- See http://checkstyle.sf.net/config_whitespace.html -->
<module name="FileTabCharacter">
<property name="eachLine" value="true"/>
</module>
<module name="TreeWalker">
<module name="OuterTypeFilename"/>
<module name="IllegalTokenText">
<property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/>
<property name="format"
value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/>
<property name="message"
value="Consider using special escape sequence instead of octal value or Unicode escaped value."/>
</module>
<module name="AvoidEscapedUnicodeCharacters">
<property name="allowEscapesForControlCharacters" value="true"/>
<property name="allowByTailComment" value="true"/>
<property name="allowNonPrintableEscapes" value="true"/>
</module>
<module name="LineLength">
<property name="max" value="100"/>
<property name="ignorePattern"
value="^package.*|^import.*|a href|href|http://|https://|ftp://"/>
</module>
<module name="AvoidStarImport"/>
<module name="OneTopLevelClass"/>
<module name="NoLineWrap"/>
<module name="EmptyBlock">
<property name="option" value="TEXT"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="id" value="RightCurlySame"/>
<property name="tokens"
value="LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE,
LITERAL_DO"/>
</module>
<module name="RightCurly">
<property name="id" value="RightCurlyAlone"/>
<property name="option" value="alone"/>
<property name="tokens"
value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT,
INSTANCE_INIT"/>
</module>
<module name="WhitespaceAround">
<property name="allowEmptyConstructors" value="true"/>
<property name="allowEmptyMethods" value="true"/>
<property name="allowEmptyTypes" value="true"/>
<property name="allowEmptyLoops" value="true"/>
<message key="ws.notFollowed"
value="WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)"/>
<message key="ws.notPreceded"
value="WhitespaceAround: ''{0}'' is not preceded with whitespace."/>
</module>
<module name="OneStatementPerLine"/>
<module name="MultipleVariableDeclarations"/>
<module name="ArrayTypeStyle"/>
<module name="MissingSwitchDefault"/>
<module name="FallThrough"/>
<module name="UpperEll"/>
<module name="ModifierOrder"/>
<module name="EmptyLineSeparator">
<property name="allowNoEmptyLineBetweenFields" value="true"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapDot"/>
<property name="tokens" value="DOT"/>
<property name="option" value="nl"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapComma"/>
<property name="tokens" value="COMMA"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->
<property name="id" value="SeparatorWrapEllipsis"/>
<property name="tokens" value="ELLIPSIS"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->
<property name="id" value="SeparatorWrapArrayDeclarator"/>
<property name="tokens" value="ARRAY_DECLARATOR"/>
<property name="option" value="EOL"/>
</module>
<module name="SeparatorWrap">
<property name="id" value="SeparatorWrapMethodRef"/>
<property name="tokens" value="METHOD_REF"/>
<property name="option" value="nl"/>
</module>
<module name="PackageName">
<property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
<message key="name.invalidPattern"
value="Package name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="TypeName">
<message key="name.invalidPattern"
value="Type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MemberName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9]*$"/>
<message key="name.invalidPattern"
value="Member name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LambdaParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Lambda parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="CatchParameterName">
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Catch parameter name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="LocalVariableName">
<property name="tokens" value="VARIABLE_DEF"/>
<property name="format" value="^[a-z]([a-z0-9][a-zA-Z0-9]*)?$"/>
<message key="name.invalidPattern"
value="Local variable name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="ClassTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Class type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="MethodTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Method type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="InterfaceTypeParameterName">
<property name="format" value="(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)"/>
<message key="name.invalidPattern"
value="Interface type name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="NoFinalizer"/>
<module name="GenericWhitespace">
<message key="ws.followed"
value="GenericWhitespace ''{0}'' is followed by whitespace."/>
<message key="ws.preceded"
value="GenericWhitespace ''{0}'' is preceded with whitespace."/>
<message key="ws.illegalFollow"
value="GenericWhitespace ''{0}'' should followed by whitespace."/>
<message key="ws.notPreceded"
value="GenericWhitespace ''{0}'' is not preceded with whitespace."/>
</module>
<module name="Indentation">
<property name="basicOffset" value="2"/>
<property name="braceAdjustment" value="0"/>
<property name="caseIndent" value="2"/>
<property name="throwsIndent" value="4"/>
<property name="lineWrappingIndentation" value="4"/>
<property name="arrayInitIndent" value="2"/>
</module>
<module name="AbbreviationAsWordInName">
<property name="ignoreFinal" value="false"/>
<property name="allowedAbbreviationLength" value="1"/>
</module>
<module name="OverloadMethodsDeclarationOrder"/>
<module name="VariableDeclarationUsageDistance"/>
<module name="CustomImportOrder">
<property name="sortImportsInGroupAlphabetically" value="true"/>
<property name="separateLineBetweenGroups" value="true"/>
<property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/>
</module>
<module name="MethodParamPad"/>
<module name="NoWhitespaceBefore">
<property name="tokens"
value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/>
<property name="allowLineBreaks" value="true"/>
</module>
<module name="ParenPad"/>
<module name="OperatorWrap">
<property name="option" value="NL"/>
<property name="tokens"
value="BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR,
LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF "/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationMostCases"/>
<property name="tokens"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF"/>
</module>
<module name="AnnotationLocation">
<property name="id" value="AnnotationLocationVariables"/>
<property name="tokens" value="VARIABLE_DEF"/>
<property name="allowSamelineMultipleAnnotations" value="true"/>
</module>
<module name="NonEmptyAtclauseDescription"/>
<module name="JavadocTagContinuationIndentation"/>
<module name="SummaryJavadoc">
<property name="forbiddenSummaryFragments"
value="^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )"/>
</module>
<module name="JavadocParagraph"/>
<module name="AtclauseOrder">
<property name="tagOrder" value="@param, @return, @throws, @deprecated"/>
<property name="target"
value="CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF"/>
</module>
<module name="JavadocMethod">
<property name="scope" value="public"/>
<property name="allowMissingParamTags" value="true"/>
<property name="allowMissingThrowsTags" value="true"/>
<property name="allowMissingReturnTag" value="true"/>
<property name="minLineCount" value="2"/>
<property name="allowedAnnotations" value="Override, Test"/>
<property name="allowThrowsTagsForSubclasses" value="true"/>
</module>
<module name="MethodName">
<property name="format" value="^[a-z][a-z0-9][a-zA-Z0-9_]*$"/>
<message key="name.invalidPattern"
value="Method name ''{0}'' must match pattern ''{1}''."/>
</module>
<module name="SingleLineJavadoc">
<property name="ignoreInlineTags" value="false"/>
</module>
<module name="EmptyCatchBlock">
<property name="exceptionVariableName" value="expected"/>
</module>
<module name="CommentsIndentation"/>
</module>
</module>

Datei anzeigen

@ -25,7 +25,7 @@ dependencies {
implementation "org.checkerframework:checker-qual:${checkerFrameworkVersion}" implementation "org.checkerframework:checker-qual:${checkerFrameworkVersion}"
} else if (System.getenv("CHECKERFRAMEWORK") == null) { } else if (System.getenv("CHECKERFRAMEWORK") == null) {
throw new GradleException("Environment variable CHECKERFRAMEWORK is not set") throw new GradleException("Environment variable CHECKERFRAMEWORK is not set")
} else if (! file(System.getenv("CHECKERFRAMEWORK")).exists()) { } else if (!file(System.getenv("CHECKERFRAMEWORK")).exists()) {
throw new GradleException("Environment variable CHECKERFRAMEWORK is set to non-existent directory " + System.getenv("CHECKERFRAMEWORK")); throw new GradleException("Environment variable CHECKERFRAMEWORK is set to non-existent directory " + System.getenv("CHECKERFRAMEWORK"));
} else { } else {
ext.checkerframeworkdist = "$System.env.CHECKERFRAMEWORK/checker/dist" ext.checkerframeworkdist = "$System.env.CHECKERFRAMEWORK/checker/dist"

4
gradle/checkstyle.gradle Normale Datei
Datei anzeigen

@ -0,0 +1,4 @@
checkstyle {
toolVersion '8.14'
configFile new File(project.rootDir, ['config', 'checkstyle', 'checkstyle.xml'].join(File.separator))
}

Datei anzeigen

@ -1,8 +1,10 @@
plugins { plugins {
id 'java' id 'java'
id 'checkstyle'
} }
apply from: '../gradle/checkerframework.gradle' apply from: '../gradle/checkerframework.gradle'
apply from: '../gradle/checkstyle.gradle'
dependencies { dependencies {
compile "com.google.guava:guava:${guavaVersion}" compile "com.google.guava:guava:${guavaVersion}"

Datei anzeigen

@ -1,13 +1,16 @@
package com.velocitypowered.natives; package com.velocitypowered.natives;
/** /**
* This marker interface indicates that this object should be explicitly disposed before the object can no longer be used. * This marker interface indicates that this object should be explicitly disposed before the object
* Not disposing these objects will likely leak native resources and eventually lead to resource exhaustion. * can no longer be used. Not disposing these objects will likely leak native resources and
* eventually lead to resource exhaustion.
*/ */
public interface Disposable { public interface Disposable {
/**
* Disposes this object. After this call returns, any use of this object becomes invalid. Multiple calls to /**
* this function should be safe: there should be no side-effects once an object is disposed. * Disposes this object. After this call returns, any use of this object becomes invalid. Multiple
*/ * calls to this function should be safe: there should be no side-effects once an object is
void dispose(); * disposed.
*/
void dispose();
} }

Datei anzeigen

@ -2,62 +2,62 @@ package com.velocitypowered.natives.compression;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.Inflater; import java.util.zip.Inflater;
public class JavaVelocityCompressor implements VelocityCompressor { public class JavaVelocityCompressor implements VelocityCompressor {
public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new;
private final Deflater deflater; public static final VelocityCompressorFactory FACTORY = JavaVelocityCompressor::new;
private final Inflater inflater;
private final byte[] buf;
private boolean disposed = false;
private JavaVelocityCompressor(int level) { private final Deflater deflater;
this.deflater = new Deflater(level); private final Inflater inflater;
this.inflater = new Inflater(); private final byte[] buf;
this.buf = new byte[ZLIB_BUFFER_SIZE]; private boolean disposed = false;
private JavaVelocityCompressor(int level) {
this.deflater = new Deflater(level);
this.inflater = new Inflater();
this.buf = new byte[ZLIB_BUFFER_SIZE];
}
@Override
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData);
inflater.setInput(inData);
while (!inflater.finished()) {
int read = inflater.inflate(buf);
destination.writeBytes(buf, 0, read);
} }
inflater.reset();
}
@Override @Override
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()]; byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData); source.readBytes(inData);
inflater.setInput(inData); deflater.setInput(inData);
while (!inflater.finished()) { deflater.finish();
int read = inflater.inflate(buf); while (!deflater.finished()) {
destination.writeBytes(buf, 0, read); int bytes = deflater.deflate(buf);
} destination.writeBytes(buf, 0, bytes);
inflater.reset();
} }
deflater.reset();
}
@Override @Override
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { public void dispose() {
ensureNotDisposed(); disposed = true;
deflater.end();
inflater.end();
}
byte[] inData = new byte[source.readableBytes()]; private void ensureNotDisposed() {
source.readBytes(inData); Preconditions.checkState(!disposed, "Object already disposed");
deflater.setInput(inData); }
deflater.finish();
while (!deflater.finished()) {
int bytes = deflater.deflate(buf);
destination.writeBytes(buf, 0, bytes);
}
deflater.reset();
}
@Override
public void dispose() {
disposed = true;
deflater.end();
inflater.end();
}
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
} }

Datei anzeigen

@ -2,75 +2,78 @@ package com.velocitypowered.natives.compression;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
public class NativeVelocityCompressor implements VelocityCompressor { public class NativeVelocityCompressor implements VelocityCompressor {
public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
private final NativeZlibInflate inflate = new NativeZlibInflate(); public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
private final long inflateCtx;
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
private final long deflateCtx;
private boolean disposed = false;
private NativeVelocityCompressor(int level) { private final NativeZlibInflate inflate = new NativeZlibInflate();
this.inflateCtx = inflate.init(); private final long inflateCtx;
this.deflateCtx = deflate.init(level); private final NativeZlibDeflate deflate = new NativeZlibDeflate();
private final long deflateCtx;
private boolean disposed = false;
private NativeVelocityCompressor(int level) {
this.inflateCtx = inflate.init();
this.deflateCtx = deflate.init(level);
}
@Override
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed();
source.memoryAddress();
destination.memoryAddress();
while (!inflate.finished && source.isReadable()) {
if (!destination.isWritable()) {
destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(),
source.readableBytes(),
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes());
source.readerIndex(source.readerIndex() + inflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
} }
@Override inflate.reset(inflateCtx);
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { inflate.consumed = 0;
ensureNotDisposed(); inflate.finished = false;
source.memoryAddress(); }
destination.memoryAddress();
while (!inflate.finished && source.isReadable()) { @Override
if (!destination.isWritable()) { public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
destination.ensureWritable(ZLIB_BUFFER_SIZE); ensureNotDisposed();
} source.memoryAddress();
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(), destination.memoryAddress();
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes());
source.readerIndex(source.readerIndex() + inflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
}
inflate.reset(inflateCtx); while (!deflate.finished) {
inflate.consumed = 0; if (!destination.isWritable()) {
inflate.finished = false; destination.ensureWritable(ZLIB_BUFFER_SIZE);
}
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(),
source.readableBytes(),
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(),
!source.isReadable());
source.readerIndex(source.readerIndex() + deflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
} }
@Override deflate.reset(deflateCtx);
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { deflate.consumed = 0;
ensureNotDisposed(); deflate.finished = false;
source.memoryAddress(); }
destination.memoryAddress();
while (!deflate.finished) { private void ensureNotDisposed() {
if (!destination.isWritable()) { Preconditions.checkState(!disposed, "Object already disposed");
destination.ensureWritable(ZLIB_BUFFER_SIZE); }
}
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(), source.readableBytes(),
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(), !source.isReadable());
source.readerIndex(source.readerIndex() + deflate.consumed);
destination.writerIndex(destination.writerIndex() + produced);
}
deflate.reset(deflateCtx); @Override
deflate.consumed = 0; public void dispose() {
deflate.finished = false; if (!disposed) {
} inflate.free(inflateCtx);
deflate.free(deflateCtx);
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
@Override
public void dispose() {
if (!disposed) {
inflate.free(inflateCtx);
deflate.free(deflateCtx);
}
disposed = true;
} }
disposed = true;
}
} }

Datei anzeigen

@ -4,21 +4,23 @@ package com.velocitypowered.natives.compression;
* Represents a native interface for zlib's deflate functions. * Represents a native interface for zlib's deflate functions.
*/ */
class NativeZlibDeflate { class NativeZlibDeflate {
boolean finished;
int consumed;
native long init(int level); boolean finished;
int consumed;
native long free(long ctx); native long init(int level);
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength, native long free(long ctx);
boolean flush);
native void reset(long ctx); native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
int destinationLength,
boolean flush);
static { native void reset(long ctx);
initIDs();
}
private static native void initIDs(); static {
initIDs();
}
private static native void initIDs();
} }

Datei anzeigen

@ -4,20 +4,22 @@ package com.velocitypowered.natives.compression;
* Represents a native interface for zlib's inflate functions. * Represents a native interface for zlib's inflate functions.
*/ */
class NativeZlibInflate { class NativeZlibInflate {
boolean finished;
int consumed;
native long init(); boolean finished;
int consumed;
native long free(long ctx); native long init();
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, int destinationLength); native long free(long ctx);
native void reset(long ctx); native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
int destinationLength);
static { native void reset(long ctx);
initIDs();
}
private static native void initIDs(); static {
initIDs();
}
private static native void initIDs();
} }

Datei anzeigen

@ -2,19 +2,19 @@ package com.velocitypowered.natives.compression;
import com.velocitypowered.natives.Disposable; import com.velocitypowered.natives.Disposable;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
/** /**
* Provides an interface to inflate and deflate {@link ByteBuf}s using zlib. * Provides an interface to inflate and deflate {@link ByteBuf}s using zlib.
*/ */
public interface VelocityCompressor extends Disposable { public interface VelocityCompressor extends Disposable {
/**
* The default preferred output buffer size for zlib.
*/
int ZLIB_BUFFER_SIZE = 8192;
void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException; /**
* The default preferred output buffer size for zlib.
*/
int ZLIB_BUFFER_SIZE = 8192;
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException; void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
} }

Datei anzeigen

@ -1,5 +1,6 @@
package com.velocitypowered.natives.compression; package com.velocitypowered.natives.compression;
public interface VelocityCompressorFactory { public interface VelocityCompressorFactory {
VelocityCompressor create(int level);
VelocityCompressor create(int level);
} }

Datei anzeigen

@ -2,53 +2,54 @@ package com.velocitypowered.natives.encryption;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import java.security.GeneralSecurityException;
public class JavaVelocityCipher implements VelocityCipher { public class JavaVelocityCipher implements VelocityCipher {
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
@Override
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
return new JavaVelocityCipher(true, key);
}
@Override public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { @Override
return new JavaVelocityCipher(false, key); public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
} return new JavaVelocityCipher(true, key);
};
private final Cipher cipher;
private boolean disposed = false;
private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
} }
@Override @Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
ensureNotDisposed(); return new JavaVelocityCipher(false, key);
byte[] sourceAsBytes = new byte[source.readableBytes()];
source.readBytes(sourceAsBytes);
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
byte[] destinationBytes = new byte[outputSize];
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
destination.writeBytes(destinationBytes);
} }
};
@Override private final Cipher cipher;
public void dispose() { private boolean disposed = false;
disposed = true;
}
private void ensureNotDisposed() { private JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
Preconditions.checkState(!disposed, "Object already disposed"); this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
} this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key,
new IvParameterSpec(key.getEncoded()));
}
@Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
ensureNotDisposed();
byte[] sourceAsBytes = new byte[source.readableBytes()];
source.readBytes(sourceAsBytes);
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
byte[] destinationBytes = new byte[outputSize];
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
destination.writeBytes(destinationBytes);
}
@Override
public void dispose() {
disposed = true;
}
private void ensureNotDisposed() {
Preconditions.checkState(!disposed, "Object already disposed");
}
} }

Datei anzeigen

@ -1,9 +1,11 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
public class MbedtlsAesImpl { public class MbedtlsAesImpl {
native long init(byte[] key);
native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, boolean encrypt); native long init(byte[] key);
native void free(long ptr); native void process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
boolean encrypt);
native void free(long ptr);
} }

Datei anzeigen

@ -1,55 +1,55 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.security.GeneralSecurityException;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
import java.security.GeneralSecurityException;
public class NativeVelocityCipher implements VelocityCipher { public class NativeVelocityCipher implements VelocityCipher {
public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
@Override
public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
return new NativeVelocityCipher(true, key);
}
@Override public static final VelocityCipherFactory FACTORY = new VelocityCipherFactory() {
public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException { @Override
return new NativeVelocityCipher(false, key); public VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException {
} return new NativeVelocityCipher(true, key);
};
private static final MbedtlsAesImpl impl = new MbedtlsAesImpl();
private final long ctx;
private final boolean encrypt;
private boolean disposed = false;
private NativeVelocityCipher(boolean encrypt, SecretKey key) {
this.encrypt = encrypt;
this.ctx = impl.init(key.getEncoded());
} }
@Override @Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException { public VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException {
source.memoryAddress(); return new NativeVelocityCipher(false, key);
destination.memoryAddress();
// The exact amount we read in is also the amount we write out.
int len = source.readableBytes();
destination.ensureWritable(len);
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
destination.memoryAddress() + destination.writerIndex(), encrypt);
source.skipBytes(len);
destination.writerIndex(destination.writerIndex() + len);
} }
};
private static final MbedtlsAesImpl impl = new MbedtlsAesImpl();
@Override private final long ctx;
public void dispose() { private final boolean encrypt;
if (!disposed) { private boolean disposed = false;
impl.free(ctx);
} private NativeVelocityCipher(boolean encrypt, SecretKey key) {
disposed = true; this.encrypt = encrypt;
this.ctx = impl.init(key.getEncoded());
}
@Override
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
source.memoryAddress();
destination.memoryAddress();
// The exact amount we read in is also the amount we write out.
int len = source.readableBytes();
destination.ensureWritable(len);
impl.process(ctx, source.memoryAddress() + source.readerIndex(), len,
destination.memoryAddress() + destination.writerIndex(), encrypt);
source.skipBytes(len);
destination.writerIndex(destination.writerIndex() + len);
}
@Override
public void dispose() {
if (!disposed) {
impl.free(ctx);
} }
disposed = true;
}
} }

Datei anzeigen

@ -2,9 +2,9 @@ package com.velocitypowered.natives.encryption;
import com.velocitypowered.natives.Disposable; import com.velocitypowered.natives.Disposable;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import javax.crypto.ShortBufferException; import javax.crypto.ShortBufferException;
public interface VelocityCipher extends Disposable { public interface VelocityCipher extends Disposable {
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
} }

Datei anzeigen

@ -1,10 +1,11 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
import javax.crypto.SecretKey;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import javax.crypto.SecretKey;
public interface VelocityCipherFactory { public interface VelocityCipherFactory {
VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException;
VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException; VelocityCipher forEncryption(SecretKey key) throws GeneralSecurityException;
VelocityCipher forDecryption(SecretKey key) throws GeneralSecurityException;
} }

Datei anzeigen

@ -1,81 +1,85 @@
package com.velocitypowered.natives.util; package com.velocitypowered.natives.util;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.List; import java.util.List;
import java.util.function.BooleanSupplier; import java.util.function.BooleanSupplier;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;
public final class NativeCodeLoader<T> implements Supplier<T> { public final class NativeCodeLoader<T> implements Supplier<T> {
private final Variant<T> selected;
NativeCodeLoader(List<Variant<T>> variants) { private final Variant<T> selected;
this.selected = getVariant(variants);
NativeCodeLoader(List<Variant<T>> variants) {
this.selected = getVariant(variants);
}
@Override
public T get() {
return selected.object;
}
private static <T> Variant<T> getVariant(List<Variant<T>> variants) {
for (Variant<T> variant : variants) {
T got = variant.get();
if (got == null) {
continue;
}
return variant;
}
throw new IllegalArgumentException("Can't find any suitable variants");
}
public String getLoadedVariant() {
return selected.name;
}
static class Variant<T> {
private Status status;
private final Runnable setup;
private final String name;
private final T object;
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) {
this.status =
possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
this.setup = setup;
this.name = name;
this.object = object;
} }
@Override public @Nullable T get() {
public T get() { if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) {
return selected.object; return null;
} }
private static <T> Variant<T> getVariant(List<Variant<T>> variants) { // Make sure setup happens only once
for (Variant<T> variant : variants) { if (status == Status.POSSIBLY_AVAILABLE) {
T got = variant.get(); try {
if (got == null) { setup.run();
continue; status = Status.SETUP;
} } catch (Exception e) {
return variant; status = Status.SETUP_FAILURE;
return null;
} }
throw new IllegalArgumentException("Can't find any suitable variants"); }
return object;
} }
}
public String getLoadedVariant() { private enum Status {
return selected.name; NOT_AVAILABLE,
} POSSIBLY_AVAILABLE,
SETUP,
SETUP_FAILURE
}
static class Variant<T> { static final BooleanSupplier MACOS = () ->
private Status status; System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") &&
private final Runnable setup; System.getProperty("os.arch").equals("x86_64");
private final String name; static final BooleanSupplier LINUX = () ->
private final T object; System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") &&
System.getProperty("os.arch").equals("amd64");
Variant(BooleanSupplier possiblyAvailable, Runnable setup, String name, T object) { static final BooleanSupplier ALWAYS = () -> true;
this.status = possiblyAvailable.getAsBoolean() ? Status.POSSIBLY_AVAILABLE : Status.NOT_AVAILABLE;
this.setup = setup;
this.name = name;
this.object = object;
}
public @Nullable T get() {
if (status == Status.NOT_AVAILABLE || status == Status.SETUP_FAILURE) {
return null;
}
// Make sure setup happens only once
if (status == Status.POSSIBLY_AVAILABLE) {
try {
setup.run();
status = Status.SETUP;
} catch (Exception e) {
status = Status.SETUP_FAILURE;
return null;
}
}
return object;
}
}
private enum Status {
NOT_AVAILABLE,
POSSIBLY_AVAILABLE,
SETUP,
SETUP_FAILURE
}
static final BooleanSupplier MACOS = () -> System.getProperty("os.name", "").equalsIgnoreCase("Mac OS X") &&
System.getProperty("os.arch").equals("x86_64");
static final BooleanSupplier LINUX = () -> System.getProperties().getProperty("os.name", "").equalsIgnoreCase("Linux") &&
System.getProperty("os.arch").equals("amd64");
static final BooleanSupplier ALWAYS = () -> true;
} }

Datei anzeigen

@ -6,7 +6,6 @@ import com.velocitypowered.natives.compression.NativeVelocityCompressor;
import com.velocitypowered.natives.compression.VelocityCompressorFactory; import com.velocitypowered.natives.compression.VelocityCompressorFactory;
import com.velocitypowered.natives.encryption.JavaVelocityCipher; import com.velocitypowered.natives.encryption.JavaVelocityCipher;
import com.velocitypowered.natives.encryption.VelocityCipherFactory; import com.velocitypowered.natives.encryption.VelocityCipherFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
@ -14,55 +13,58 @@ import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
public class Natives { public class Natives {
private Natives() {
throw new AssertionError();
}
private static Runnable copyAndLoadNative(String path) { private Natives() {
return () -> { throw new AssertionError();
try { }
Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.')));
InputStream nativeLib = Natives.class.getResourceAsStream(path);
if (nativeLib == null) {
throw new IllegalStateException("Native library " + path + " not found.");
}
Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING); private static Runnable copyAndLoadNative(String path) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> { return () -> {
try { try {
Files.deleteIfExists(tempFile); Path tempFile = Files.createTempFile("native-", path.substring(path.lastIndexOf('.')));
} catch (IOException ignored) { InputStream nativeLib = Natives.class.getResourceAsStream(path);
// Well, it doesn't matter... if (nativeLib == null) {
} throw new IllegalStateException("Native library " + path + " not found.");
})); }
System.load(tempFile.toAbsolutePath().toString());
} catch (IOException e) {
throw new RuntimeException(e);
}
};
}
public static final NativeCodeLoader<VelocityCompressorFactory> compressor = new NativeCodeLoader<>( Files.copy(nativeLib, tempFile, StandardCopyOption.REPLACE_EXISTING);
ImmutableList.of( Runtime.getRuntime().addShutdownHook(new Thread(() -> {
new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, try {
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)", Files.deleteIfExists(tempFile);
NativeVelocityCompressor.FACTORY), } catch (IOException ignored) {
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, // Well, it doesn't matter...
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)", }
NativeVelocityCompressor.FACTORY), }));
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCompressor.FACTORY) System.load(tempFile.toAbsolutePath().toString());
) } catch (IOException e) {
); throw new RuntimeException(e);
}
};
}
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>( public static final NativeCodeLoader<VelocityCompressorFactory> compressor = new NativeCodeLoader<>(
ImmutableList.of( ImmutableList.of(
new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)",
NativeVelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
NativeVelocityCompressor.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
}, "Java", JavaVelocityCompressor.FACTORY)
)
);
public static final NativeCodeLoader<VelocityCipherFactory> cipher = new NativeCodeLoader<>(
ImmutableList.of(
/*new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS, /*new NativeCodeLoader.Variant<>(NativeCodeLoader.MACOS,
copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)", copyAndLoadNative("/macosx/velocity-cipher.dylib"), "mbed TLS (macOS)",
NativeVelocityCipher.FACTORY), NativeVelocityCipher.FACTORY),
new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX, new NativeCodeLoader.Variant<>(NativeCodeLoader.LINUX,
copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)", copyAndLoadNative("/linux_x64/velocity-cipher.so"), "mbed TLS (Linux amd64)",
NativeVelocityCipher.FACTORY),*/ NativeVelocityCipher.FACTORY),*/
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {}, "Java", JavaVelocityCipher.FACTORY) new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
) }, "Java", JavaVelocityCipher.FACTORY)
); )
);
} }

Datei anzeigen

@ -1,65 +1,66 @@
package com.velocitypowered.natives.compression; package com.velocitypowered.natives.compression;
import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
import java.util.Random;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail; import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.condition.OS.LINUX; import static org.junit.jupiter.api.condition.OS.LINUX;
import static org.junit.jupiter.api.condition.OS.MAC; import static org.junit.jupiter.api.condition.OS.MAC;
import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.util.Random;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnOs;
class VelocityCompressorTest { class VelocityCompressorTest {
@BeforeAll
static void checkNatives() { @BeforeAll
Natives.compressor.getLoadedVariant(); static void checkNatives() {
Natives.compressor.getLoadedVariant();
}
@Test
@EnabledOnOs({MAC, LINUX})
void nativeIntegrityCheck() throws DataFormatException {
VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION);
if (compressor instanceof JavaVelocityCompressor) {
compressor.dispose();
fail("Loaded regular compressor");
} }
check(compressor);
}
@Test @Test
@EnabledOnOs({ MAC, LINUX }) void javaIntegrityCheck() throws DataFormatException {
void nativeIntegrityCheck() throws DataFormatException { VelocityCompressor compressor = JavaVelocityCompressor.FACTORY
VelocityCompressor compressor = Natives.compressor.get().create(Deflater.DEFAULT_COMPRESSION); .create(Deflater.DEFAULT_COMPRESSION);
if (compressor instanceof JavaVelocityCompressor) { check(compressor);
compressor.dispose(); }
fail("Loaded regular compressor");
} private void check(VelocityCompressor compressor) throws DataFormatException {
check(compressor); ByteBuf source = Unpooled.directBuffer();
} ByteBuf dest = Unpooled.directBuffer();
ByteBuf decompressed = Unpooled.directBuffer();
@Test
void javaIntegrityCheck() throws DataFormatException { Random random = new Random(1);
VelocityCompressor compressor = JavaVelocityCompressor.FACTORY.create(Deflater.DEFAULT_COMPRESSION); byte[] randomBytes = new byte[1 << 16];
check(compressor); random.nextBytes(randomBytes);
} source.writeBytes(randomBytes);
private void check(VelocityCompressor compressor) throws DataFormatException { try {
ByteBuf source = Unpooled.directBuffer(); compressor.deflate(source, dest);
ByteBuf dest = Unpooled.directBuffer(); compressor.inflate(dest, decompressed);
ByteBuf decompressed = Unpooled.directBuffer(); source.readerIndex(0);
assertTrue(ByteBufUtil.equals(source, decompressed));
Random random = new Random(1); } finally {
byte[] randomBytes = new byte[1 << 16]; source.release();
random.nextBytes(randomBytes); dest.release();
source.writeBytes(randomBytes); decompressed.release();
compressor.dispose();
try {
compressor.deflate(source, dest);
compressor.inflate(dest, decompressed);
source.readerIndex(0);
assertTrue(ByteBufUtil.equals(source, decompressed));
} finally {
source.release();
dest.release();
decompressed.release();
compressor.dispose();
}
} }
}
} }

Datei anzeigen

@ -1,71 +1,71 @@
package com.velocitypowered.natives.encryption; package com.velocitypowered.natives.encryption;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import com.velocitypowered.natives.util.Natives; import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.security.GeneralSecurityException;
import java.util.Random;
import javax.crypto.spec.SecretKeySpec;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javax.crypto.spec.SecretKeySpec;
import java.security.GeneralSecurityException;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
class VelocityCipherTest { class VelocityCipherTest {
private static final int ENCRYPT_DATA_SIZE = 1 << 16;
@BeforeAll private static final int ENCRYPT_DATA_SIZE = 1 << 16;
static void checkNatives() {
Natives.cipher.getLoadedVariant(); @BeforeAll
static void checkNatives() {
Natives.cipher.getLoadedVariant();
}
@Test
@Disabled
void nativeIntegrityCheck() throws GeneralSecurityException {
VelocityCipherFactory factory = Natives.cipher.get();
if (factory == JavaVelocityCipher.FACTORY) {
fail("Loaded regular compressor");
} }
check(factory);
}
@Test @Test
@Disabled void javaIntegrityCheck() throws GeneralSecurityException {
void nativeIntegrityCheck() throws GeneralSecurityException { check(JavaVelocityCipher.FACTORY);
VelocityCipherFactory factory = Natives.cipher.get(); }
if (factory == JavaVelocityCipher.FACTORY) {
fail("Loaded regular compressor"); private void check(VelocityCipherFactory factory) throws GeneralSecurityException {
} // Generate a random 16-byte key.
check(factory); Random random = new Random(1);
} byte[] key = new byte[16];
random.nextBytes(key);
@Test
void javaIntegrityCheck() throws GeneralSecurityException { VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES"));
check(JavaVelocityCipher.FACTORY); VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES"));
}
ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
private void check(VelocityCipherFactory factory) throws GeneralSecurityException { ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
// Generate a random 16-byte key. ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
Random random = new Random(1);
byte[] key = new byte[16]; byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE];
random.nextBytes(key); random.nextBytes(randomBytes);
source.writeBytes(randomBytes);
VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES"));
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES")); try {
encrypt.process(source, dest);
ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); decrypt.process(dest, decryptionBuf);
ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); source.readerIndex(0);
ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); assertTrue(ByteBufUtil.equals(source, decryptionBuf));
} finally {
byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE]; source.release();
random.nextBytes(randomBytes); dest.release();
source.writeBytes(randomBytes); decryptionBuf.release();
decrypt.dispose();
try { encrypt.dispose();
encrypt.process(source, dest);
decrypt.process(dest, decryptionBuf);
source.readerIndex(0);
assertTrue(ByteBufUtil.equals(source, decryptionBuf));
} finally {
source.release();
dest.release();
decryptionBuf.release();
decrypt.dispose();
encrypt.dispose();
}
} }
}
} }

Datei anzeigen

@ -2,17 +2,11 @@ plugins {
id 'java' id 'java'
id 'com.github.johnrengelman.shadow' version '2.0.4' id 'com.github.johnrengelman.shadow' version '2.0.4'
id 'de.sebastianboegl.shadow.transformer.log4j' version '2.1.1' id 'de.sebastianboegl.shadow.transformer.log4j' version '2.1.1'
id 'checkstyle'
} }
apply from: '../gradle/checkerframework.gradle' apply from: '../gradle/checkerframework.gradle'
apply from: '../gradle/checkstyle.gradle'
compileJava {
options.compilerArgs += ['-proc:none']
}
compileTestJava {
options.compilerArgs += ['-proc:none']
}
jar { jar {
manifest { manifest {

Datei anzeigen

@ -1,29 +1,29 @@
package com.velocitypowered.proxy; package com.velocitypowered.proxy;
import java.text.DecimalFormat;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.text.DecimalFormat;
public class Velocity { public class Velocity {
private static final Logger logger = LogManager.getLogger(Velocity.class);
static { private static final Logger logger = LogManager.getLogger(Velocity.class);
// We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
// Force AWT to work with its head chopped off.
System.setProperty("java.awt.headless", "true");
}
public static void main(String... args) { static {
long startTime = System.currentTimeMillis(); // We use BufferedImage for favicons, and on macOS this puts the Java application in the dock. How inconvenient.
// Force AWT to work with its head chopped off.
System.setProperty("java.awt.headless", "true");
}
VelocityServer server = new VelocityServer(); public static void main(String... args) {
server.start(); long startTime = System.currentTimeMillis();
Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread")); VelocityServer server = new VelocityServer();
server.start();
double bootTime = (System.currentTimeMillis() - startTime) / 1000d; Runtime.getRuntime().addShutdownHook(new Thread(server::shutdown, "Shutdown thread"));
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
server.getConsoleCommandSource().start(); double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
} logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
server.getConsoleCommandSource().start();
}
} }

Datei anzeigen

@ -24,7 +24,6 @@ import com.velocitypowered.proxy.config.AnnotatedConfig;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.network.http.NettyHttpClient; import com.velocitypowered.proxy.network.http.NettyHttpClient;
import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityEventManager;
@ -36,356 +35,367 @@ import com.velocitypowered.proxy.server.ServerMap;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import com.velocitypowered.proxy.util.EncryptionUtils; import com.velocitypowered.proxy.util.EncryptionUtils;
import com.velocitypowered.proxy.util.Ratelimiter; import com.velocitypowered.proxy.util.Ratelimiter;
import com.velocitypowered.proxy.util.VelocityChannelRegistrar;
import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.Bootstrap;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.*;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.security.KeyPair; import java.security.KeyPair;
import java.util.*; import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import net.kyori.text.Component;
import net.kyori.text.TextComponent;
import net.kyori.text.serializer.GsonComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer { public class VelocityServer implements ProxyServer {
private static final Logger logger = LogManager.getLogger(VelocityServer.class); private static final Logger logger = LogManager.getLogger(VelocityServer.class);
public static final Gson GSON = new GsonBuilder() public static final Gson GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer()) .registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
.registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer()) .registerTypeHierarchyAdapter(Favicon.class, new FaviconSerializer())
.create(); .create();
private @MonotonicNonNull ConnectionManager cm; private @MonotonicNonNull ConnectionManager cm;
private @MonotonicNonNull VelocityConfiguration configuration; private @MonotonicNonNull VelocityConfiguration configuration;
private @MonotonicNonNull NettyHttpClient httpClient; private @MonotonicNonNull NettyHttpClient httpClient;
private @MonotonicNonNull KeyPair serverKeyPair; private @MonotonicNonNull KeyPair serverKeyPair;
private @MonotonicNonNull ServerMap servers; private @MonotonicNonNull ServerMap servers;
private final VelocityCommandManager commandManager = new VelocityCommandManager(); private final VelocityCommandManager commandManager = new VelocityCommandManager();
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false); private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
private boolean shutdown = false; private boolean shutdown = false;
private @MonotonicNonNull VelocityPluginManager pluginManager; private @MonotonicNonNull VelocityPluginManager pluginManager;
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>(); private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>(); private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
private @MonotonicNonNull VelocityConsole console; private @MonotonicNonNull VelocityConsole console;
private @MonotonicNonNull Ratelimiter ipAttemptLimiter; private @MonotonicNonNull Ratelimiter ipAttemptLimiter;
private @MonotonicNonNull VelocityEventManager eventManager; private @MonotonicNonNull VelocityEventManager eventManager;
private @MonotonicNonNull VelocityScheduler scheduler; private @MonotonicNonNull VelocityScheduler scheduler;
private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar(); private final VelocityChannelRegistrar channelRegistrar = new VelocityChannelRegistrar();
public KeyPair getServerKeyPair() { public KeyPair getServerKeyPair() {
if (serverKeyPair == null) { if (serverKeyPair == null) {
throw new AssertionError(); throw new AssertionError();
} }
return serverKeyPair; return serverKeyPair;
}
public VelocityConfiguration getConfiguration() {
VelocityConfiguration cfg = this.configuration;
if (cfg == null) {
throw new IllegalStateException("Configuration not initialized!");
}
return cfg;
}
@Override
public ProxyVersion getVersion() {
Package pkg = VelocityServer.class.getPackage();
String implName;
String implVersion;
String implVendor;
if (pkg != null) {
implName = MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity");
implVersion = MoreObjects.firstNonNull(pkg.getImplementationVersion(), "<unknown>");
implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors");
} else {
implName = "Velocity";
implVersion = "<unknown>";
implVendor = "Velocity Contributors";
} }
public VelocityConfiguration getConfiguration() { return new ProxyVersion(implName, implVendor, implVersion);
VelocityConfiguration cfg = this.configuration; }
if (cfg == null) {
throw new IllegalStateException("Configuration not initialized!"); @Override
} public VelocityCommandManager getCommandManager() {
return cfg; return commandManager;
}
@EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler",
"console", "cm", "configuration"})
public void start() {
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024);
pluginManager = new VelocityPluginManager(this);
eventManager = new VelocityEventManager(pluginManager);
scheduler = new VelocityScheduler(pluginManager);
console = new VelocityConsole(this);
cm = new ConnectionManager(this);
servers = new ServerMap(this);
cm.logChannelInformation();
// Initialize commands first
commandManager.register(new VelocityCommand(this), "velocity");
commandManager.register(new ServerCommand(this), "server");
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
try {
Path configPath = Paths.get("velocity.toml");
configuration = VelocityConfiguration.read(configPath);
if (!configuration.validate()) {
logger.error(
"Your configuration is invalid. Velocity will refuse to start up until the errors are resolved.");
LogManager.shutdown();
System.exit(1);
}
AnnotatedConfig
.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values
} catch (Exception e) {
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
LogManager.shutdown();
System.exit(1);
} }
@Override for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
public ProxyVersion getVersion() { servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
Package pkg = VelocityServer.class.getPackage(); }
String implName;
String implVersion; ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit());
String implVendor; httpClient = new NettyHttpClient(this);
if (pkg != null) { loadPlugins();
implName = MoreObjects.firstNonNull(pkg.getImplementationTitle(), "Velocity");
implVersion = MoreObjects.firstNonNull(pkg.getImplementationVersion(), "<unknown>"); // Go ahead and fire the proxy initialization event. We block since plugins should have a chance
implVendor = MoreObjects.firstNonNull(pkg.getImplementationVendor(), "Velocity Contributors"); // to fully initialize before we accept any connections to the server.
} else { eventManager.fire(new ProxyInitializeEvent()).join();
implName = "Velocity";
implVersion = "<unknown>"; // init console permissions after plugins are loaded
implVendor = "Velocity Contributors"; console.setupPermissions();
this.cm.bind(configuration.getBind());
if (configuration.isQueryEnabled()) {
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
}
}
@RequiresNonNull({"pluginManager", "eventManager"})
private void loadPlugins() {
logger.info("Loading plugins...");
try {
Path pluginPath = Paths.get("plugins");
if (!pluginPath.toFile().exists()) {
Files.createDirectory(pluginPath);
} else {
if (!pluginPath.toFile().isDirectory()) {
logger.warn("Plugin location {} is not a directory, continuing without loading plugins",
pluginPath);
return;
} }
return new ProxyVersion(implName, implVendor, implVersion); pluginManager.loadPlugins(pluginPath);
}
} catch (Exception e) {
logger.error("Couldn't load plugins", e);
} }
@Override // Register the plugin main classes so that we may proceed with firing the proxy initialize event
public VelocityCommandManager getCommandManager() { for (PluginContainer plugin : pluginManager.getPlugins()) {
return commandManager; Optional<?> instance = plugin.getInstance();
if (instance.isPresent()) {
eventManager.register(instance.get(), instance.get());
}
} }
@EnsuresNonNull({"serverKeyPair", "servers", "pluginManager", "eventManager", "scheduler", "console", "cm", "configuration"}) logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
public void start() { }
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
serverKeyPair = EncryptionUtils.createRsaKeyPair(1024); public Bootstrap initializeGenericBootstrap() {
pluginManager = new VelocityPluginManager(this); if (cm == null) {
eventManager = new VelocityEventManager(pluginManager); throw new IllegalStateException("Server did not initialize properly.");
scheduler = new VelocityScheduler(pluginManager); }
console = new VelocityConsole(this); return this.cm.createWorker();
cm = new ConnectionManager(this); }
servers = new ServerMap(this);
cm.logChannelInformation(); public boolean isShutdown() {
return shutdown;
}
// Initialize commands first public void shutdown() {
commandManager.register(new VelocityCommand(this), "velocity"); if (eventManager == null || pluginManager == null || cm == null || scheduler == null) {
commandManager.register(new ServerCommand(this), "server"); throw new AssertionError();
commandManager.register(new ShutdownCommand(this), "shutdown", "end");
try {
Path configPath = Paths.get("velocity.toml");
configuration = VelocityConfiguration.read(configPath);
if (!configuration.validate()) {
logger.error("Your configuration is invalid. Velocity will refuse to start up until the errors are resolved.");
LogManager.shutdown();
System.exit(1);
}
AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); //Resave config to add new values
} catch (Exception e) {
logger.error("Unable to read/load/save your velocity.toml. The server will shut down.", e);
LogManager.shutdown();
System.exit(1);
}
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
}
ipAttemptLimiter = new Ratelimiter(configuration.getLoginRatelimit());
httpClient = new NettyHttpClient(this);
loadPlugins();
// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
// to fully initialize before we accept any connections to the server.
eventManager.fire(new ProxyInitializeEvent()).join();
// init console permissions after plugins are loaded
console.setupPermissions();
this.cm.bind(configuration.getBind());
if (configuration.isQueryEnabled()) {
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
}
} }
@RequiresNonNull({"pluginManager", "eventManager"}) if (!shutdownInProgress.compareAndSet(false, true)) {
private void loadPlugins() { return;
logger.info("Loading plugins..."); }
logger.info("Shutting down the proxy...");
try { for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) {
Path pluginPath = Paths.get("plugins"); player.close(TextComponent.of("Proxy shutting down."));
if (!pluginPath.toFile().exists()) {
Files.createDirectory(pluginPath);
} else {
if (!pluginPath.toFile().isDirectory()) {
logger.warn("Plugin location {} is not a directory, continuing without loading plugins", pluginPath);
return;
}
pluginManager.loadPlugins(pluginPath);
}
} catch (Exception e) {
logger.error("Couldn't load plugins", e);
}
// Register the plugin main classes so that we may proceed with firing the proxy initialize event
for (PluginContainer plugin : pluginManager.getPlugins()) {
Optional<?> instance = plugin.getInstance();
if (instance.isPresent()) {
eventManager.register(instance.get(), instance.get());
}
}
logger.info("Loaded {} plugins", pluginManager.getPlugins().size());
} }
public Bootstrap initializeGenericBootstrap() { this.cm.shutdown();
if (cm == null) {
throw new IllegalStateException("Server did not initialize properly."); eventManager.fire(new ProxyShutdownEvent());
} try {
return this.cm.createWorker(); if (!eventManager.shutdown() || !scheduler.shutdown()) {
logger.error("Your plugins took over 10 seconds to shut down.");
}
} catch (InterruptedException e) {
// Not much we can do about this...
Thread.currentThread().interrupt();
} }
public boolean isShutdown() { shutdown = true;
return shutdown; }
public NettyHttpClient getHttpClient() {
if (httpClient == null) {
throw new IllegalStateException("HTTP client not initialized");
} }
return httpClient;
}
public void shutdown() { public Ratelimiter getIpAttemptLimiter() {
if (eventManager == null || pluginManager == null || cm == null || scheduler == null) { if (ipAttemptLimiter == null) {
throw new AssertionError(); throw new IllegalStateException("Ratelimiter not initialized");
}
if (!shutdownInProgress.compareAndSet(false, true)) {
return;
}
logger.info("Shutting down the proxy...");
for (ConnectedPlayer player : ImmutableList.copyOf(connectionsByUuid.values())) {
player.close(TextComponent.of("Proxy shutting down."));
}
this.cm.shutdown();
eventManager.fire(new ProxyShutdownEvent());
try {
if (!eventManager.shutdown() || !scheduler.shutdown()) {
logger.error("Your plugins took over 10 seconds to shut down.");
}
} catch (InterruptedException e) {
// Not much we can do about this...
Thread.currentThread().interrupt();
}
shutdown = true;
} }
return ipAttemptLimiter;
}
public NettyHttpClient getHttpClient() { public boolean registerConnection(ConnectedPlayer connection) {
if (httpClient == null) { String lowerName = connection.getUsername().toLowerCase(Locale.US);
throw new IllegalStateException("HTTP client not initialized"); if (connectionsByName.putIfAbsent(lowerName, connection) != null) {
} return false;
return httpClient;
} }
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
connectionsByName.remove(lowerName, connection);
return false;
}
return true;
}
public Ratelimiter getIpAttemptLimiter() { public void unregisterConnection(ConnectedPlayer connection) {
if (ipAttemptLimiter == null) { connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection);
throw new IllegalStateException("Ratelimiter not initialized"); connectionsByUuid.remove(connection.getUniqueId(), connection);
} }
return ipAttemptLimiter;
}
public boolean registerConnection(ConnectedPlayer connection) { @Override
String lowerName = connection.getUsername().toLowerCase(Locale.US); public Optional<Player> getPlayer(String username) {
if (connectionsByName.putIfAbsent(lowerName, connection) != null) { Preconditions.checkNotNull(username, "username");
return false; return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US)));
} }
if (connectionsByUuid.putIfAbsent(connection.getUniqueId(), connection) != null) {
connectionsByName.remove(lowerName, connection);
return false;
}
return true;
}
public void unregisterConnection(ConnectedPlayer connection) { @Override
connectionsByName.remove(connection.getUsername().toLowerCase(Locale.US), connection); public Optional<Player> getPlayer(UUID uuid) {
connectionsByUuid.remove(connection.getUniqueId(), connection); Preconditions.checkNotNull(uuid, "uuid");
} return Optional.ofNullable(connectionsByUuid.get(uuid));
}
@Override @Override
public Optional<Player> getPlayer(String username) { public void broadcast(Component component) {
Preconditions.checkNotNull(username, "username"); Preconditions.checkNotNull(component, "component");
return Optional.ofNullable(connectionsByName.get(username.toLowerCase(Locale.US))); Chat chat = Chat.createClientbound(component);
for (ConnectedPlayer player : connectionsByUuid.values()) {
player.getConnection().write(chat);
} }
}
@Override @Override
public Optional<Player> getPlayer(UUID uuid) { public Collection<Player> getAllPlayers() {
Preconditions.checkNotNull(uuid, "uuid"); return ImmutableList.copyOf(connectionsByUuid.values());
return Optional.ofNullable(connectionsByUuid.get(uuid)); }
}
@Override @Override
public void broadcast(Component component) { public int getPlayerCount() {
Preconditions.checkNotNull(component, "component"); return connectionsByUuid.size();
Chat chat = Chat.createClientbound(component); }
for (ConnectedPlayer player : connectionsByUuid.values()) {
player.getConnection().write(chat);
}
}
@Override @Override
public Collection<Player> getAllPlayers() { public Optional<RegisteredServer> getServer(String name) {
return ImmutableList.copyOf(connectionsByUuid.values()); Preconditions.checkNotNull(name, "name");
if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
} }
return servers.getServer(name);
}
@Override @Override
public int getPlayerCount() { public Collection<RegisteredServer> getAllServers() {
return connectionsByUuid.size(); if (servers == null) {
throw new IllegalStateException("Server did not initialize properly.");
} }
return servers.getAllServers();
}
@Override @Override
public Optional<RegisteredServer> getServer(String name) { public RegisteredServer registerServer(ServerInfo server) {
Preconditions.checkNotNull(name, "name"); if (servers == null) {
if (servers == null) { throw new IllegalStateException("Server did not initialize properly.");
throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getServer(name);
} }
return servers.register(server);
}
@Override @Override
public Collection<RegisteredServer> getAllServers() { public void unregisterServer(ServerInfo server) {
if (servers == null) { if (servers == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException("Server did not initialize properly.");
}
return servers.getAllServers();
} }
servers.unregister(server);
}
@Override @Override
public RegisteredServer registerServer(ServerInfo server) { public VelocityConsole getConsoleCommandSource() {
if (servers == null) { if (console == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException("Server did not initialize properly.");
}
return servers.register(server);
} }
return console;
}
@Override @Override
public void unregisterServer(ServerInfo server) { public PluginManager getPluginManager() {
if (servers == null) { if (pluginManager == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException("Server did not initialize properly.");
}
servers.unregister(server);
} }
return pluginManager;
}
@Override @Override
public VelocityConsole getConsoleCommandSource() { public EventManager getEventManager() {
if (console == null) { if (eventManager == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException("Server did not initialize properly.");
}
return console;
} }
return eventManager;
}
@Override @Override
public PluginManager getPluginManager() { public VelocityScheduler getScheduler() {
if (pluginManager == null) { if (scheduler == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException("Server did not initialize properly.");
}
return pluginManager;
} }
return scheduler;
}
@Override @Override
public EventManager getEventManager() { public VelocityChannelRegistrar getChannelRegistrar() {
if (eventManager == null) { return channelRegistrar;
throw new IllegalStateException("Server did not initialize properly."); }
}
return eventManager;
}
@Override @Override
public VelocityScheduler getScheduler() { public InetSocketAddress getBoundAddress() {
if (scheduler == null) { if (configuration == null) {
throw new IllegalStateException("Server did not initialize properly."); throw new IllegalStateException(
} "No configuration"); // even though you'll never get the chance... heh, heh
return scheduler;
}
@Override
public VelocityChannelRegistrar getChannelRegistrar() {
return channelRegistrar;
}
@Override
public InetSocketAddress getBoundAddress() {
if (configuration == null) {
throw new IllegalStateException("No configuration"); // even though you'll never get the chance... heh, heh
}
return configuration.getBind();
} }
return configuration.getBind();
}
} }

Datei anzeigen

@ -9,90 +9,96 @@ import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection; import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo; import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import net.kyori.text.TextComponent; import net.kyori.text.TextComponent;
import net.kyori.text.event.ClickEvent; import net.kyori.text.event.ClickEvent;
import net.kyori.text.event.HoverEvent; import net.kyori.text.event.HoverEvent;
import net.kyori.text.format.TextColor; import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class ServerCommand implements Command { public class ServerCommand implements Command {
private final ProxyServer server;
public ServerCommand(ProxyServer server) { private final ProxyServer server;
this.server = server;
public ServerCommand(ProxyServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (!(source instanceof Player)) {
source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED));
return;
} }
@Override Player player = (Player) source;
public void execute(CommandSource source, String @NonNull [] args) { if (args.length == 1) {
if (!(source instanceof Player)) { // Trying to connect to a server.
source.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED)); String serverName = args[0];
return; Optional<RegisteredServer> toConnect = server.getServer(serverName);
} if (!toConnect.isPresent()) {
player.sendMessage(
TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
return;
}
Player player = (Player) source; player.createConnectionRequest(toConnect.get()).fireAndForget();
if (args.length == 1) { } else {
// Trying to connect to a server. String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo)
String serverName = args[0]; .map(ServerInfo::getName)
Optional<RegisteredServer> toConnect = server.getServer(serverName); .orElse("<unknown>");
if (!toConnect.isPresent()) { player.sendMessage(TextComponent
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED)); .of("You are currently connected to " + currentServer + ".", TextColor.YELLOW));
return;
}
player.createConnectionRequest(toConnect.get()).fireAndForget(); // Assemble the list of servers as components
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ")
.color(TextColor.YELLOW);
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
for (int i = 0; i < infos.size(); i++) {
RegisteredServer rs = infos.get(i);
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
String playersText = rs.getPlayersConnected().size() + " player(s) online";
if (rs.getServerInfo().getName().equals(currentServer)) {
infoComponent = infoComponent.color(TextColor.GREEN)
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
TextComponent.of("Currently connected to this server\n" + playersText)));
} else { } else {
String currentServer = player.getCurrentServer().map(ServerConnection::getServerInfo).map(ServerInfo::getName) infoComponent = infoComponent.color(TextColor.GRAY)
.orElse("<unknown>"); .clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND,
player.sendMessage(TextComponent.of("You are currently connected to " + currentServer + ".", TextColor.YELLOW)); "/server " + rs.getServerInfo().getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
// Assemble the list of servers as components TextComponent.of("Click to connect to this server\n" + playersText)));
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
for (int i = 0; i < infos.size(); i++) {
RegisteredServer rs = infos.get(i);
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
String playersText = rs.getPlayersConnected().size() + " player(s) online";
if (rs.getServerInfo().getName().equals(currentServer)) {
infoComponent = infoComponent.color(TextColor.GREEN)
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
TextComponent.of("Currently connected to this server\n" + playersText)));
} else {
infoComponent = infoComponent.color(TextColor.GRAY)
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
}
serverListBuilder.append(infoComponent);
if (i != infos.size() - 1) {
serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY));
}
}
player.sendMessage(serverListBuilder.build());
} }
} serverListBuilder.append(infoComponent);
if (i != infos.size() - 1) {
@Override serverListBuilder.append(TextComponent.of(", ", TextColor.GRAY));
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) {
return server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList());
} else if (currentArgs.length == 1) {
return server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList());
} else {
return ImmutableList.of();
} }
} }
@Override player.sendMessage(serverListBuilder.build());
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
} }
}
@Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) {
return server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName())
.collect(Collectors.toList());
} else if (currentArgs.length == 1) {
return server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName())
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList());
} else {
return ImmutableList.of();
}
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
}
} }

Datei anzeigen

@ -8,23 +8,25 @@ import net.kyori.text.format.TextColor;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public class ShutdownCommand implements Command { public class ShutdownCommand implements Command {
private final VelocityServer server;
public ShutdownCommand(VelocityServer server) { private final VelocityServer server;
this.server = server;
}
@Override public ShutdownCommand(VelocityServer server) {
public void execute(CommandSource source, String @NonNull [] args) { this.server = server;
if (source != server.getConsoleCommandSource()) { }
source.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED));
return;
}
server.shutdown();
}
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public void execute(CommandSource source, String @NonNull [] args) {
return source == server.getConsoleCommandSource(); if (source != server.getConsoleCommandSource()) {
source
.sendMessage(TextComponent.of("You are not allowed to use this command.", TextColor.RED));
return;
} }
server.shutdown();
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source == server.getConsoleCommandSource();
}
} }

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen