3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-06 00:00:47 +01:00

Merge branch 'refs/heads/dev/3.0.0' into fix/shutdownstyle

Dieser Commit ist enthalten in:
Isaac - The456 2024-10-31 19:51:21 +00:00
Commit 3dfba069dc
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 57A848D2F87C14EF
51 geänderte Dateien mit 971 neuen und 365 gelöschten Zeilen

Datei anzeigen

@ -44,7 +44,7 @@ body:
```
[17:44:10 INFO]: Velocity 3.3.0-SNAPSHOT (git-9d25d309-b400)
[17:44:10 INFO]: Copyright 2018-2023 Velocity Contributors. Velocity is licensed under the terms of the GNU General Public License v3.
[17:44:10 INFO]: velocitypowered.com - GitHub
[17:44:10 INFO]: PaperMC - GitHub
```
</details>
validations:

Datei anzeigen

@ -0,0 +1 @@
com.velocitypowered.api.plugin.ap.PluginAnnotationProcessor,isolating

Datei anzeigen

@ -45,7 +45,9 @@ public interface CommandManager {
* @throws IllegalArgumentException if one of the given aliases is already registered, or
* the given command does not implement a registrable {@link Command} subinterface
* @see Command for a list of registrable Command subinterfaces
* @deprecated use {@link #register(CommandMeta, Command)} instead with a plugin specified
*/
@Deprecated
default void register(String alias, Command command, String... otherAliases) {
register(metaBuilder(alias).aliases(otherAliases).build(), command);
}
@ -55,7 +57,9 @@ public interface CommandManager {
*
* @param command the command to register
* @throws IllegalArgumentException if the node alias is already registered
* @deprecated use {@link #register(CommandMeta, Command)} instead with a plugin specified
*/
@Deprecated
void register(BrigadierCommand command);
/**

Datei anzeigen

@ -45,10 +45,28 @@ public interface EventManager {
* @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
* @deprecated use {@link #register(Object, Class, short, EventHandler)} instead
*/
@Deprecated
<E> void register(Object plugin, Class<E> eventClass, PostOrder postOrder,
EventHandler<E> handler);
/**
* Requests that the specified {@code handler} listen for events and associate it with the {@code
* plugin}.
*
* <p>Note that this method will register a non-asynchronous listener by default. If you want to
* use an asynchronous event handler, return {@link EventTask#async(Runnable)} from the handler.</p>
*
* @param plugin the plugin to associate with the handler
* @param eventClass the class for the event handler to register
* @param postOrder the relative 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, short postOrder,
EventHandler<E> handler);
/**
* Fires the specified event to the event bus asynchronously. This allows Velocity to continue
* servicing connections while a plugin handles a potentially long-running operation such as a

Datei anzeigen

@ -12,6 +12,6 @@ package com.velocitypowered.api.event;
*/
public enum PostOrder {
FIRST, EARLY, NORMAL, LATE, LAST
FIRST, EARLY, NORMAL, LATE, LAST, CUSTOM
}

Datei anzeigen

@ -22,24 +22,38 @@ public @interface Subscribe {
/**
* The order events will be posted to this listener.
*
* @deprecated specify the order using {@link #priority()} instead
* @return the order
*/
@Deprecated
PostOrder order() default PostOrder.NORMAL;
/**
* Whether the handler must be called asynchronously.
* The priority of this event handler. Priorities are used to determine the order in which event
* handlers are called. The higher the priority, the earlier the event handler will be called.
*
* <p><strong>This option currently has no effect, but in the future it will. In Velocity 3.0.0,
* all event handlers run asynchronously by default. You are encouraged to determine whether or
* not to enable it now. This option is being provided as a migration aid.</strong></p>
* <p>Note that due to compatibility constraints, you must specify {@link PostOrder#CUSTOM}
* in order to use this field.</p>
*
* <p>If this method returns {@code true}, the method is guaranteed to be executed
* asynchronously. Otherwise, the handler may be executed on the current thread or
* asynchronously. <strong>This still means you must consider thread-safety in your
* event listeners</strong> as the "current thread" can and will be different each time.</p>
* @return the priority
*/
short priority() default Short.MIN_VALUE;
/**
* Whether the handler must be called asynchronously. By default, all event handlers are called
* asynchronously.
*
* <p>If any method handler targeting an event type is marked with {@code true}, then every
* handler targeting that event type will be executed asynchronously.</p>
* <p>For performance (for instance, if you use {@link EventTask#withContinuation}), you can
* optionally specify <code>false</code>. This option will become {@code false} by default
* in a future release of Velocity.</p>
*
* <p>If this is {@code true}, the method is guaranteed to be executed asynchronously. Otherwise,
* the handler may be executed on the current thread or asynchronously. <strong>This still means
* you must consider thread-safety in your event listeners</strong> as the "current thread" can
* and will be different each time.</p>
*
* <p>Note that if any method handler targeting an event type is marked with {@code true}, then
* every handler targeting that event type will be executed asynchronously.</p>
*
* @return Requires async
*/

Datei anzeigen

@ -51,6 +51,13 @@ public final class PlayerChatEvent implements ResultedEvent<PlayerChatEvent.Chat
return result;
}
/**
* Set result for the event.
*
* @param result the result of event
* @deprecated for 1.19.1 and newer, set this as denied will kick users
*/
@Deprecated
@Override
public void setResult(ChatResult result) {
this.result = Preconditions.checkNotNull(result, "result");

Datei anzeigen

@ -87,7 +87,8 @@ public enum ProtocolVersion implements Ordered<ProtocolVersion> {
MINECRAFT_1_20_2(764, "1.20.2"),
MINECRAFT_1_20_3(765, "1.20.3", "1.20.4"),
MINECRAFT_1_20_5(766, "1.20.5", "1.20.6"),
MINECRAFT_1_21(767, "1.21", "1.21.1");
MINECRAFT_1_21(767, "1.21", "1.21.1"),
MINECRAFT_1_21_2(768, "1.21.2", "1.21.3");
private static final int SNAPSHOT_BIT = 30;

Datei anzeigen

@ -26,11 +26,20 @@ public interface InboundConnection {
/**
* Returns the hostname that the user entered into the client, if applicable.
*
* <br/>
* This is partially processed, including removing a trailing dot, and discarding data after a null byte.
* @return the hostname from the client
*/
Optional<InetSocketAddress> getVirtualHost();
/**
* Returns the raw hostname that the client sent, if applicable.
*
* @return the raw hostname from the client
*/
Optional<String> getRawVirtualHost();
/**
* Determine whether or not the player remains online.
*

Datei anzeigen

@ -68,6 +68,20 @@ public interface PlayerSettings {
*/
boolean isClientListingAllowed();
/**
* Returns if the client has text filtering enabled.
*
* @return if text filtering is enabled
*/
boolean isTextFilteringEnabled();
/**
* Returns the selected "Particles" option state.
*
* @return the particle option
*/
ParticleStatus getParticleStatus();
/**
* The client's current chat display mode.
*/
@ -84,4 +98,13 @@ public interface PlayerSettings {
LEFT,
RIGHT
}
/**
* The client's current "Particles" option state.
*/
enum ParticleStatus {
ALL,
DECREASED,
MINIMAL
}
}

Datei anzeigen

@ -1,2 +1,2 @@
group=com.velocitypowered
version=3.3.0-SNAPSHOT
version=3.4.0-SNAPSHOT

Datei anzeigen

@ -2,8 +2,8 @@
configurate3 = "3.7.3"
configurate4 = "4.1.2"
flare = "2.0.1"
log4j = "2.22.1"
netty = "4.1.106.Final"
log4j = "2.24.1"
netty = "4.1.114.Final"
[plugins]
indra-publishing = "net.kyori.indra.publishing:2.0.6"
@ -12,13 +12,14 @@ spotless = "com.diffplug.spotless:6.25.0"
[libraries]
adventure-bom = "net.kyori:adventure-bom:4.17.0"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.2"
adventure-text-serializer-json-legacy-impl = "net.kyori:adventure-text-serializer-json-legacy-impl:4.17.0"
adventure-facet = "net.kyori:adventure-platform-facet:4.3.4"
asm = "org.ow2.asm:asm:9.6"
auto-service = "com.google.auto.service:auto-service:1.0.1"
auto-service-annotations = "com.google.auto.service:auto-service-annotations:1.0.1"
brigadier = "com.velocitypowered:velocity-brigadier:1.0.0-SNAPSHOT"
bstats = "org.bstats:bstats-base:3.0.2"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.5"
caffeine = "com.github.ben-manes.caffeine:caffeine:3.1.8"
checker-qual = "org.checkerframework:checker-qual:3.42.0"
checkstyle = "com.puppycrawl.tools:checkstyle:10.9.3"
completablefutures = "com.spotify:completable-futures:0.3.6"
@ -28,15 +29,15 @@ configurate3-gson = { module = "org.spongepowered:configurate-gson", version.ref
configurate4-hocon = { module = "org.spongepowered:configurate-hocon", version.ref = "configurate4" }
configurate4-yaml = { module = "org.spongepowered:configurate-yaml", version.ref = "configurate4" }
configurate4-gson = { module = "org.spongepowered:configurate-gson", version.ref = "configurate4" }
disruptor = "com.lmax:disruptor:3.4.4"
fastutil = "it.unimi.dsi:fastutil:8.5.12"
disruptor = "com.lmax:disruptor:4.0.0"
fastutil = "it.unimi.dsi:fastutil:8.5.15"
flare-core = { module = "space.vectrix.flare:flare", version.ref = "flare" }
flare-fastutil = { module = "space.vectrix.flare:flare-fastutil", version.ref = "flare" }
jline = "org.jline:jline-terminal-jansi:3.23.0"
jline = "org.jline:jline-terminal-jansi:3.27.1"
jopt = "net.sf.jopt-simple:jopt-simple:5.0.4"
junit = "org.junit.jupiter:junit-jupiter:5.10.2"
jspecify = "org.jspecify:jspecify:0.3.0"
kyori-ansi = "net.kyori:ansi:1.0.3"
kyori-ansi = "net.kyori:ansi:1.1.0"
guava = "com.google.guava:guava:25.1-jre"
gson = "com.google.code.gson:gson:2.10.1"
guice = "com.google.inject:guice:6.0.0"

Datei anzeigen

@ -92,6 +92,15 @@ tasks {
dependsOn(configurateBuildTask)
from(zipTree(configurateBuildTask.map { it.outputs.files.singleFile }))
}
runShadow {
workingDir = file("run").also(File::mkdirs)
standardInput = System.`in`
}
named<JavaExec>("run") {
workingDir = file("run").also(File::mkdirs)
standardInput = System.`in` // Doesn't work?
}
}
dependencies {
@ -118,7 +127,7 @@ dependencies {
runtimeOnly(libs.disruptor)
implementation(libs.fastutil)
implementation(platform(libs.adventure.bom))
implementation("net.kyori:adventure-nbt")
implementation(libs.adventure.text.serializer.json.legacy.impl)
implementation(libs.adventure.facet)
implementation(libs.completablefutures)
implementation(libs.nightconfig)

Datei anzeigen

@ -17,11 +17,17 @@
package com.velocitypowered.proxy;
import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.util.AddressUtil;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.List;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import joptsimple.ValueConversionException;
import joptsimple.ValueConverter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -35,6 +41,8 @@ public final class ProxyOptions {
private final boolean help;
private final @Nullable Integer port;
private final @Nullable Boolean haproxy;
private final boolean ignoreConfigServers;
private final List<ServerInfo> servers;
ProxyOptions(final String[] args) {
final OptionParser parser = new OptionParser();
@ -49,11 +57,20 @@ public final class ProxyOptions {
"Choose whether to enable haproxy protocol. "
+ "The configuration haproxy protocol will be ignored.")
.withRequiredArg().ofType(Boolean.class);
final OptionSpec<ServerInfo> servers = parser.accepts("add-server",
"Define a server mapping. "
+ "You must ensure that server name is not also registered in the config or use --ignore-config-servers.")
.withRequiredArg().withValuesConvertedBy(new ServerInfoConverter());
final OptionSpec<Void> ignoreConfigServers = parser.accepts("ignore-config-servers",
"Skip registering servers from the config file. "
+ "Useful in dynamic setups or with the --add-server flag.");
final OptionSet set = parser.parse(args);
this.help = set.has(help);
this.port = port.value(set);
this.haproxy = haproxy.value(set);
this.servers = servers.values(set);
this.ignoreConfigServers = set.has(ignoreConfigServers);
if (this.help) {
try {
@ -75,4 +92,40 @@ public final class ProxyOptions {
public @Nullable Boolean isHaproxy() {
return this.haproxy;
}
public boolean isIgnoreConfigServers() {
return this.ignoreConfigServers;
}
public List<ServerInfo> getServers() {
return this.servers;
}
private static class ServerInfoConverter implements ValueConverter<ServerInfo> {
@Override
public ServerInfo convert(String s) {
String[] split = s.split(":", 2);
if (split.length < 2) {
throw new ValueConversionException("Invalid server format. Use <name>:<address>");
}
InetSocketAddress address;
try {
address = AddressUtil.parseAddress(split[1]);
} catch (IllegalStateException e) {
throw new ValueConversionException("Invalid hostname for server flag with name: " + split[0]);
}
return new ServerInfo(split[0], address);
}
@Override
public Class<? extends ServerInfo> valueType() {
return ServerInfo.class;
}
@Override
public String valuePattern() {
return "name>:<address";
}
}
}

Datei anzeigen

@ -22,11 +22,13 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
@ -52,6 +54,9 @@ import com.velocitypowered.proxy.crypto.EncryptionUtils;
import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginContainer;
import com.velocitypowered.proxy.plugin.loader.VelocityPluginDescription;
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
import com.velocitypowered.proxy.protocol.util.GameProfileSerializer;
@ -77,6 +82,7 @@ import java.nio.file.Path;
import java.security.KeyPair;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@ -100,6 +106,7 @@ import net.kyori.adventure.translation.GlobalTranslator;
import net.kyori.adventure.translation.TranslationRegistry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bstats.MetricsBase;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
@ -110,6 +117,8 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public class VelocityServer implements ProxyServer, ForwardingAudience {
public static final String VELOCITY_URL = "https://velocitypowered.com";
private static final Logger logger = LogManager.getLogger(VelocityServer.class);
public static final Gson GENERAL_GSON = new GsonBuilder()
.registerTypeHierarchyAdapter(Favicon.class, FaviconSerializer.INSTANCE)
@ -162,7 +171,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
VelocityServer(final ProxyOptions options) {
pluginManager = new VelocityPluginManager(this);
eventManager = new VelocityEventManager(pluginManager);
commandManager = new VelocityCommandManager(eventManager);
commandManager = new VelocityCommandManager(eventManager, pluginManager);
scheduler = new VelocityScheduler(pluginManager);
console = new VelocityConsole(this);
cm = new ConnectionManager(this);
@ -199,6 +208,16 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
return new ProxyVersion(implName, implVendor, implVersion);
}
private VelocityPluginContainer createVirtualPlugin() {
ProxyVersion version = getVersion();
PluginDescription description = new VelocityPluginDescription(
"velocity", version.getName(), version.getVersion(), "The Velocity proxy",
VELOCITY_URL, ImmutableList.of(version.getVendor()), Collections.emptyList(), null);
VelocityPluginContainer container = new VelocityPluginContainer(description);
container.setInstance(VelocityVirtualPlugin.INSTANCE);
return container;
}
@Override
public VelocityCommandManager getCommandManager() {
return commandManager;
@ -213,6 +232,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
void start() {
logger.info("Booting up {} {}...", getVersion().getName(), getVersion().getVersion());
console.setupStreams();
pluginManager.registerPlugin(this.createVirtualPlugin());
registerTranslations();
@ -221,18 +241,48 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
cm.logChannelInformation();
// Initialize commands first
commandManager.register(VelocityCommand.create(this));
commandManager.register(CallbackCommand.create());
commandManager.register(ServerCommand.create(this));
commandManager.register("shutdown", ShutdownCommand.command(this),
"end", "stop");
final BrigadierCommand velocityParentCommand = VelocityCommand.create(this);
commandManager.register(
commandManager.metaBuilder(velocityParentCommand)
.plugin(VelocityVirtualPlugin.INSTANCE)
.build(),
velocityParentCommand
);
final BrigadierCommand callbackCommand = CallbackCommand.create();
commandManager.register(
commandManager.metaBuilder(callbackCommand)
.plugin(VelocityVirtualPlugin.INSTANCE)
.build(),
velocityParentCommand
);
final BrigadierCommand serverCommand = ServerCommand.create(this);
commandManager.register(
commandManager.metaBuilder(serverCommand)
.plugin(VelocityVirtualPlugin.INSTANCE)
.build(),
serverCommand
);
final BrigadierCommand shutdownCommand = ShutdownCommand.command(this);
commandManager.register(
commandManager.metaBuilder(shutdownCommand)
.plugin(VelocityVirtualPlugin.INSTANCE)
.aliases("end", "stop")
.build(),
shutdownCommand
);
new GlistCommand(this).register();
new SendCommand(this).register();
this.doStartupConfigLoad();
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
for (ServerInfo cliServer : options.getServers()) {
servers.register(cliServer);
}
if (!options.isIgnoreConfigServers()) {
for (Map.Entry<String, String> entry : configuration.getServers().entrySet()) {
servers.register(new ServerInfo(entry.getKey(), AddressUtil.parseAddress(entry.getValue())));
}
}
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
@ -263,7 +313,13 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
this.cm.queryBind(configuration.getBind().getHostString(), configuration.getQueryPort());
}
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
final String defaultPackage = new String(
new byte[] { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's' });
if (!MetricsBase.class.getPackage().getName().startsWith(defaultPackage)) {
Metrics.VelocityMetrics.startMetrics(this, configuration.getMetrics());
} else {
logger.warn("debug environment, metrics is disabled!");
}
}
private void registerTranslations() {
@ -533,7 +589,6 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
eventManager.fire(new ProxyShutdownEvent()).join();
timedOut = !eventManager.shutdown() || timedOut;
timedOut = !scheduler.shutdown() || timedOut;
if (timedOut) {

Datei anzeigen

@ -20,6 +20,7 @@ package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.ParseResults;
@ -37,11 +38,15 @@ import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.VelocityBrigadierMessage;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.PostCommandInvocationEvent;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
import com.velocitypowered.proxy.command.registrar.BrigadierCommandRegistrar;
import com.velocitypowered.proxy.command.registrar.CommandRegistrar;
import com.velocitypowered.proxy.command.registrar.RawCommandRegistrar;
import com.velocitypowered.proxy.command.registrar.SimpleCommandRegistrar;
import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
import io.netty.util.concurrent.FastThreadLocalThread;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -49,6 +54,7 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
@ -71,13 +77,16 @@ public class VelocityCommandManager implements CommandManager {
private final SuggestionsProvider<CommandSource> suggestionsProvider;
private final CommandGraphInjector<CommandSource> injector;
private final Map<String, CommandMeta> commandMetas;
private final PluginManager pluginManager;
/**
* Constructs a command manager.
*
* @param eventManager the event manager
*/
public VelocityCommandManager(final VelocityEventManager eventManager) {
public VelocityCommandManager(final VelocityEventManager eventManager,
PluginManager pluginManager) {
this.pluginManager = pluginManager;
this.lock = new ReentrantReadWriteLock();
this.dispatcher = new CommandDispatcher<>();
this.eventManager = Preconditions.checkNotNull(eventManager);
@ -218,16 +227,13 @@ public class VelocityCommandManager implements CommandManager {
return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
}
private boolean executeImmediately0(final CommandSource source, final String cmdLine) {
private boolean executeImmediately0(final CommandSource source, final ParseResults<CommandSource> parsed) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
final String normalizedInput = VelocityCommands.normalizeInput(cmdLine, true);
CommandResult result = CommandResult.EXCEPTION;
try {
// The parse can fail if the requirement predicates throw
final ParseResults<CommandSource> parse = this.parse(normalizedInput, source);
boolean executed = dispatcher.execute(parse) != BrigadierCommand.FORWARD;
boolean executed = dispatcher.execute(parsed) != BrigadierCommand.FORWARD;
result = executed ? CommandResult.EXECUTED : CommandResult.FORWARDED;
return executed;
} catch (final CommandSyntaxException e) {
@ -249,9 +255,9 @@ public class VelocityCommandManager implements CommandManager {
}
} catch (final Throwable e) {
// Ugly, ugly swallowing of everything Throwable, because plugins are naughty.
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
throw new RuntimeException("Unable to invoke command " + parsed.getReader().getString() + "for " + source, e);
} finally {
eventManager.fireAndForget(new PostCommandInvocationEvent(source, cmdLine, result));
eventManager.fireAndForget(new PostCommandInvocationEvent(source, parsed.getReader().getString(), result));
}
}
@ -260,13 +266,17 @@ public class VelocityCommandManager implements CommandManager {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
return callCommandEvent(source, cmdLine).thenApplyAsync(event -> {
return callCommandEvent(source, cmdLine).thenComposeAsync(event -> {
CommandExecuteEvent.CommandResult commandResult = event.getResult();
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
return false;
return CompletableFuture.completedFuture(false);
}
return executeImmediately0(source, commandResult.getCommand().orElse(event.getCommand()));
}, eventManager.getAsyncExecutor());
final ParseResults<CommandSource> parsed = this.parse(
commandResult.getCommand().orElse(cmdLine), source);
return CompletableFuture.supplyAsync(
() -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed)
);
}, figureAsyncExecutorForParsing());
}
@Override
@ -276,7 +286,12 @@ public class VelocityCommandManager implements CommandManager {
Preconditions.checkNotNull(cmdLine, "cmdLine");
return CompletableFuture.supplyAsync(
() -> executeImmediately0(source, cmdLine), eventManager.getAsyncExecutor());
() -> this.parse(cmdLine, source), figureAsyncExecutorForParsing()
).thenCompose(
parsed -> CompletableFuture.supplyAsync(
() -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed)
)
);
}
/**
@ -324,9 +339,10 @@ public class VelocityCommandManager implements CommandManager {
* @return the parse results
*/
private ParseResults<CommandSource> parse(final String input, final CommandSource source) {
final String normalizedInput = VelocityCommands.normalizeInput(input, true);
lock.readLock().lock();
try {
return dispatcher.parse(input, source);
return dispatcher.parse(normalizedInput, source);
} finally {
lock.readLock().unlock();
}
@ -370,4 +386,25 @@ public class VelocityCommandManager implements CommandManager {
public CommandGraphInjector<CommandSource> getInjector() {
return injector;
}
private Executor getAsyncExecutor(ParseResults<CommandSource> parse) {
Object registrant;
if (parse.getContext().getCommand() instanceof VelocityBrigadierCommandWrapper vbcw) {
registrant = vbcw.registrant() == null ? VelocityVirtualPlugin.INSTANCE : vbcw.registrant();
} else {
registrant = VelocityVirtualPlugin.INSTANCE;
}
return pluginManager.ensurePluginContainer(registrant).getExecutorService();
}
private Executor figureAsyncExecutorForParsing() {
final Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
// we *never* want to block the Netty event loop, so use the async executor
return pluginManager.ensurePluginContainer(VelocityVirtualPlugin.INSTANCE).getExecutorService();
} else {
// it's some other thread that isn't a Netty event loop thread. direct execution it is!
return MoreExecutors.directExecutor();
}
}
}

Datei anzeigen

@ -24,6 +24,7 @@ import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
@ -32,6 +33,7 @@ import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.InvocableCommand;
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentCommandNode;
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -44,6 +46,59 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*/
public final class VelocityCommands {
// Wrapping
/**
* Walks the command node tree and wraps all {@link Command} instances in a {@link VelocityBrigadierCommandWrapper},
* to indicate the plugin that registered the command. This also has the side effect of cloning
* the command node tree.
*
* @param delegate the command node to wrap
* @param registrant the plugin that registered the command
* @return the wrapped command node
*/
public static CommandNode<CommandSource> wrap(final CommandNode<CommandSource> delegate,
final @Nullable Object registrant) {
Preconditions.checkNotNull(delegate, "delegate");
if (registrant == null) {
// the registrant is null if the `plugin` was absent when we try to register the command
return delegate;
}
com.mojang.brigadier.Command<CommandSource> maybeCommand = delegate.getCommand();
if (maybeCommand != null && !(maybeCommand instanceof VelocityBrigadierCommandWrapper)) {
maybeCommand = VelocityBrigadierCommandWrapper.wrap(delegate.getCommand(), registrant);
}
if (delegate instanceof LiteralCommandNode<CommandSource> lcn) {
var literalBuilder = shallowCopyAsBuilder(lcn, delegate.getName(), true);
literalBuilder.executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
literalBuilder.then(wrap(child, registrant));
}
if (delegate.getRedirect() != null) {
literalBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
return literalBuilder.build();
} else if (delegate instanceof VelocityArgumentCommandNode<CommandSource, ?> vacn) {
return vacn.withCommand(maybeCommand)
.withRedirect(delegate.getRedirect() != null ? wrap(delegate.getRedirect(), registrant) : null);
} else if (delegate instanceof ArgumentCommandNode) {
var argBuilder = delegate.createBuilder().executes(maybeCommand);
// we also need to wrap any children
for (final CommandNode<CommandSource> child : delegate.getChildren()) {
argBuilder.then(wrap(child, registrant));
}
if (delegate.getRedirect() != null) {
argBuilder.redirect(wrap(delegate.getRedirect(), registrant));
}
return argBuilder.build();
} else {
throw new IllegalArgumentException("Unsupported node type: " + delegate.getClass());
}
}
// Normalization
/**
@ -135,6 +190,33 @@ public final class VelocityCommands {
*/
public static LiteralCommandNode<CommandSource> shallowCopy(
final LiteralCommandNode<CommandSource> original, final String newName) {
return shallowCopy(original, newName, original.getCommand());
}
/**
* Creates a copy of the given literal with the specified name.
*
* @param original the literal node to copy
* @param newName the name of the returned literal node
* @param newCommand the new command to set on the copied node
* @return a copy of the literal with the given name
*/
private static LiteralCommandNode<CommandSource> shallowCopy(
final LiteralCommandNode<CommandSource> original, final String newName,
final com.mojang.brigadier.Command<CommandSource> newCommand) {
return shallowCopyAsBuilder(original, newName, false).executes(newCommand).build();
}
/**
* Creates a copy of the given literal with the specified name.
*
* @param original the literal node to copy
* @param newName the name of the returned literal node
* @return a copy of the literal with the given name
*/
private static LiteralArgumentBuilder<CommandSource> shallowCopyAsBuilder(
final LiteralCommandNode<CommandSource> original, final String newName,
final boolean skipChildren) {
// Brigadier resolves the redirect of a node if further input can be parsed.
// Let <bar> be a literal node having a redirect to a <foo> literal. Then,
// the context returned by CommandDispatcher#parseNodes when given the input
@ -150,10 +232,12 @@ public final class VelocityCommands {
.requiresWithContext(original.getContextRequirement())
.forward(original.getRedirect(), original.getRedirectModifier(), original.isFork())
.executes(original.getCommand());
for (final CommandNode<CommandSource> child : original.getChildren()) {
builder.then(child);
if (!skipChildren) {
for (final CommandNode<CommandSource> child : original.getChildren()) {
builder.then(child);
}
}
return builder.build();
return builder;
}
// Arguments node

Datei anzeigen

@ -93,6 +93,16 @@ public class VelocityArgumentCommandNode<S, T> extends ArgumentCommandNode<S, St
throw new UnsupportedOperationException();
}
public VelocityArgumentCommandNode<S, T> withCommand(Command<S> command) {
return new VelocityArgumentCommandNode<>(getName(), type, command, getRequirement(),
getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(), getCustomSuggestions());
}
public VelocityArgumentCommandNode<S, T> withRedirect(CommandNode<S> target) {
return new VelocityArgumentCommandNode<>(getName(), type, getCommand(), getRequirement(),
getContextRequirement(), target, getRedirectModifier(), isFork(), getCustomSuggestions());
}
@Override
public boolean isValidInput(final String input) {
return true;

Datei anzeigen

@ -0,0 +1,67 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.command.brigadier;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.velocitypowered.api.command.CommandSource;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Wraps a Brigadier command to allow us to track the registrant.
*/
public class VelocityBrigadierCommandWrapper implements Command<CommandSource> {
private final Command<CommandSource> delegate;
private final Object registrant;
private VelocityBrigadierCommandWrapper(Command<CommandSource> delegate, Object registrant) {
this.delegate = delegate;
this.registrant = registrant;
}
/**
* Transforms the given command into a {@code VelocityBrigadierCommandWrapper} if the registrant
* is not null and if the command is not already wrapped.
*
* @param delegate the command to wrap
* @param registrant the registrant of the command
* @return the wrapped command, if necessary
*/
public static Command<CommandSource> wrap(Command<CommandSource> delegate, @Nullable Object registrant) {
if (registrant == null) {
// nothing to wrap
return delegate;
}
if (delegate instanceof VelocityBrigadierCommandWrapper) {
// already wrapped
return delegate;
}
return new VelocityBrigadierCommandWrapper(delegate, registrant);
}
@Override
public int run(CommandContext<CommandSource> context) throws CommandSyntaxException {
return delegate.run(context);
}
public Object registrant() {
return registrant;
}
}

Datei anzeigen

@ -31,6 +31,7 @@ import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.text.Component;
@ -80,7 +81,13 @@ public class GlistCommand {
.executes(this::serverCount)
.build();
rootNode.then(serverNode);
server.getCommandManager().register(new BrigadierCommand(rootNode));
final BrigadierCommand command = new BrigadierCommand(rootNode);
server.getCommandManager().register(
server.getCommandManager().metaBuilder(command)
.plugin(VelocityVirtualPlugin.INSTANCE)
.build(),
command
);
}
private int totalCount(final CommandContext<CommandSource> context) {

Datei anzeigen

@ -30,6 +30,7 @@ import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
import java.util.Objects;
import java.util.Optional;
import net.kyori.adventure.text.Component;
@ -96,7 +97,13 @@ public class SendCommand {
.build();
playerNode.then(serverNode);
rootNode.then(playerNode.build());
server.getCommandManager().register(new BrigadierCommand(rootNode.build()));
final BrigadierCommand command = new BrigadierCommand(rootNode);
server.getCommandManager().register(
server.getCommandManager().metaBuilder(command)
.plugin(VelocityVirtualPlugin.INSTANCE)
.build(),
command
);
}
private int usage(final CommandContext<CommandSource> context) {

Datei anzeigen

@ -24,6 +24,7 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.proxy.ConsoleCommandSource;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
@ -45,7 +46,7 @@ public final class ShutdownCommand {
*/
public static BrigadierCommand command(final VelocityServer server) {
return new BrigadierCommand(LiteralArgumentBuilder.<CommandSource>literal("shutdown")
.requires(source -> source == server.getConsoleCommandSource())
.requires(source -> source instanceof ConsoleCommandSource)
.executes(context -> {
server.shutdown(true);
return Command.SINGLE_SUCCESS;

Datei anzeigen

@ -174,10 +174,10 @@ public final class VelocityCommand {
if (version.getName().equals("Velocity")) {
final TextComponent embellishment = Component.text()
.append(Component.text()
.content("velocitypowered.com")
.content("PaperMC")
.color(NamedTextColor.GREEN)
.clickEvent(
ClickEvent.openUrl("https://velocitypowered.com"))
ClickEvent.openUrl("https://papermc.io/software/velocity"))
.build())
.append(Component.text(" - "))
.append(Component.text()

Datei anzeigen

@ -40,17 +40,19 @@ public final class BrigadierCommandRegistrar extends AbstractCommandRegistrar<Br
// Register it (if valid), since it's probably what the user expects.
// If invalid, the metadata contains the same alias, but in lowercase.
final LiteralCommandNode<CommandSource> literal = command.getNode();
final LiteralCommandNode<CommandSource> wrapped =
(LiteralCommandNode<CommandSource>) VelocityCommands.wrap(literal, meta.getPlugin());
final String primaryAlias = literal.getName();
if (VelocityCommands.isValidAlias(primaryAlias)) {
// Register directly without copying
this.register(literal);
this.register(wrapped);
}
for (final String alias : meta.getAliases()) {
if (primaryAlias.equals(alias)) {
continue;
}
this.register(literal, alias);
this.register(wrapped, alias);
}
// Brigadier commands don't support hinting, ignore

Datei anzeigen

@ -32,6 +32,7 @@ import com.velocitypowered.api.command.InvocableCommand;
import com.velocitypowered.proxy.command.VelocityCommandMeta;
import com.velocitypowered.proxy.command.VelocityCommands;
import com.velocitypowered.proxy.command.brigadier.VelocityArgumentBuilder;
import com.velocitypowered.proxy.command.brigadier.VelocityBrigadierCommandWrapper;
import com.velocitypowered.proxy.command.invocation.CommandInvocationFactory;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
@ -76,11 +77,11 @@ abstract class InvocableCommandRegistrar<T extends InvocableCommand<I>,
final I invocation = invocationFactory.create(context);
return command.hasPermission(invocation);
};
final Command<CommandSource> callback = context -> {
final Command<CommandSource> callback = VelocityBrigadierCommandWrapper.wrap(context -> {
final I invocation = invocationFactory.create(context);
command.execute(invocation);
return 1; // handled
};
}, meta.getPlugin());
final LiteralCommandNode<CommandSource> literal = LiteralArgumentBuilder
.<CommandSource>literal(alias)

Datei anzeigen

@ -53,8 +53,6 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
@ -72,7 +70,6 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
private boolean gracefulDisconnect = false;
private BackendConnectionPhase connectionPhase = BackendConnectionPhases.UNKNOWN;
private final Map<Long, Long> pendingPings = new HashMap<>();
private @MonotonicNonNull CompoundBinaryTag activeDimensionRegistry;
/**
* Initializes a new server connection.
@ -366,12 +363,4 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
public boolean hasCompletedJoin() {
return hasCompletedJoin;
}
public CompoundBinaryTag getActiveDimensionRegistry() {
return activeDimensionRegistry;
}
public void setActiveDimensionRegistry(CompoundBinaryTag activeDimensionRegistry) {
this.activeDimensionRegistry = activeDimensionRegistry;
}
}

Datei anzeigen

@ -96,7 +96,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
// Initiate a regular connection and move over to it.
ConnectedPlayer player = new ConnectedPlayer(server, profileEvent.getGameProfile(),
mcConnection, inbound.getVirtualHost().orElse(null), onlineMode,
mcConnection, inbound.getVirtualHost().orElse(null), inbound.getRawVirtualHost().orElse(null), onlineMode,
inbound.getIdentifiedKey());
this.connectedPlayer = player;
if (!server.canRegisterConnection(player)) {

Datei anzeigen

@ -565,8 +565,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
}
destination.setActiveDimensionRegistry(joinGame.getRegistry()); // 1.16
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
// track them.
for (UUID serverBossBar : serverBossBars) {

Datei anzeigen

@ -30,7 +30,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class ClientSettingsWrapper implements PlayerSettings {
static final PlayerSettings DEFAULT = new ClientSettingsWrapper(
new ClientSettingsPacket("en_US", (byte) 10, 0, true, (short) 127, 1, true, false));
new ClientSettingsPacket("en_us", (byte) 2, 0, true, (short) 0, 1, false, false, 0));
private final ClientSettingsPacket settings;
private final SkinParts parts;
@ -56,11 +56,11 @@ public class ClientSettingsWrapper implements PlayerSettings {
@Override
public ChatMode getChatMode() {
int chat = settings.getChatVisibility();
if (chat < 0 || chat > 2) {
return ChatMode.SHOWN;
}
return ChatMode.values()[chat];
return switch (settings.getChatVisibility()) {
case 1 -> ChatMode.COMMANDS_ONLY;
case 2 -> ChatMode.HIDDEN;
default -> ChatMode.SHOWN;
};
}
@Override
@ -83,6 +83,20 @@ public class ClientSettingsWrapper implements PlayerSettings {
return settings.isClientListingAllowed();
}
@Override
public boolean isTextFilteringEnabled() {
return settings.isTextFilteringEnabled();
}
@Override
public ParticleStatus getParticleStatus() {
return switch (settings.getParticleStatus()) {
case 1 -> ParticleStatus.DECREASED;
case 2 -> ParticleStatus.MINIMAL;
default -> ParticleStatus.ALL;
};
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {

Datei anzeigen

@ -155,6 +155,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
*/
private final MinecraftConnection connection;
private final @Nullable InetSocketAddress virtualHost;
private final @Nullable String rawVirtualHost;
private GameProfile profile;
private PermissionFunction permissionFunction;
private int tryIndex = 0;
@ -191,12 +192,13 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private final ChatBuilderFactory chatBuilderFactory;
ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection,
@Nullable InetSocketAddress virtualHost, boolean onlineMode,
@Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode,
@Nullable IdentifiedKey playerKey) {
this.server = server;
this.profile = profile;
this.connection = connection;
this.virtualHost = virtualHost;
this.rawVirtualHost = rawVirtualHost;
this.permissionFunction = PermissionFunction.ALWAYS_UNDEFINED;
this.connectionPhase = connection.getType().getInitialClientPhase();
this.onlineMode = onlineMode;
@ -356,6 +358,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return Optional.ofNullable(virtualHost);
}
@Override
public Optional<String> getRawVirtualHost() {
return Optional.ofNullable(rawVirtualHost);
}
void setPermissionFunction(PermissionFunction permissionFunction) {
this.permissionFunction = permissionFunction;
}

Datei anzeigen

@ -243,6 +243,11 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
return Optional.ofNullable(ping.getVhost());
}
@Override
public Optional<String> getRawVirtualHost() {
return getVirtualHost().map(InetSocketAddress::getHostName);
}
@Override
public boolean isActive() {
return !connection.isClosed();

Datei anzeigen

@ -63,6 +63,11 @@ public final class InitialInboundConnection implements VelocityInboundConnection
return Optional.of(InetSocketAddress.createUnresolved(cleanedAddress, handshake.getPort()));
}
@Override
public Optional<String> getRawVirtualHost() {
return Optional.of(handshake.getServerAddress());
}
@Override
public boolean isActive() {
return connection.getChannel().isActive();

Datei anzeigen

@ -71,6 +71,11 @@ public class LoginInboundConnection implements LoginPhaseConnection, KeyIdentifi
return delegate.getVirtualHost();
}
@Override
public Optional<String> getRawVirtualHost() {
return delegate.getRawVirtualHost();
}
@Override
public boolean isActive() {
return delegate.isActive();

Datei anzeigen

@ -33,8 +33,9 @@ class EventTypeTracker {
}
public Collection<Class<?>> getFriendsOf(final Class<?> eventType) {
if (friends.containsKey(eventType)) {
return friends.get(eventType);
ImmutableSet<Class<?>> existingFriends = friends.get(eventType);
if (existingFriends != null) {
return existingFriends;
}
final Collection<Class<?>> types = getEventTypes(eventType);

Datei anzeigen

@ -25,7 +25,6 @@ import com.google.common.base.VerifyException;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.event.Continuation;
import com.velocitypowered.api.event.EventHandler;
import com.velocitypowered.api.event.EventManager;
@ -38,6 +37,7 @@ import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.proxy.event.UntargetedEventHandler.EventTaskHandler;
import com.velocitypowered.proxy.event.UntargetedEventHandler.VoidHandler;
import com.velocitypowered.proxy.event.UntargetedEventHandler.WithContinuationHandler;
import com.velocitypowered.proxy.util.collect.Enum2IntMap;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
@ -55,9 +55,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiConsumer;
@ -76,6 +73,14 @@ import org.lanternpowered.lmbda.LambdaType;
*/
public class VelocityEventManager implements EventManager {
private static final Enum2IntMap<PostOrder> POST_ORDER_MAP = new Enum2IntMap.Builder<>(PostOrder.class)
.put(PostOrder.FIRST, Short.MAX_VALUE - 1)
.put(PostOrder.EARLY, Short.MAX_VALUE / 2)
.put(PostOrder.NORMAL, 0)
.put(PostOrder.LATE, Short.MIN_VALUE / 2)
.put(PostOrder.LAST, Short.MIN_VALUE + 1)
.put(PostOrder.CUSTOM, 0)
.build();
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
private static final MethodHandles.Lookup methodHandlesLookup = MethodHandles.lookup();
@ -87,9 +92,8 @@ public class VelocityEventManager implements EventManager {
LambdaType.of(WithContinuationHandler.class);
private static final Comparator<HandlerRegistration> handlerComparator =
Comparator.comparingInt(o -> o.order);
Collections.reverseOrder(Comparator.comparingInt(o -> o.order));
private final ExecutorService asyncExecutor;
private final PluginManager pluginManager;
private final ListMultimap<Class<?>, HandlerRegistration> handlersByType =
@ -112,9 +116,6 @@ public class VelocityEventManager implements EventManager {
*/
public VelocityEventManager(final PluginManager pluginManager) {
this.pluginManager = pluginManager;
this.asyncExecutor = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactoryBuilder()
.setNameFormat("Velocity Async Event Executor - #%d").setDaemon(true).build());
}
/**
@ -140,6 +141,7 @@ public class VelocityEventManager implements EventManager {
final short order;
final Class<?> eventType;
final EventHandler<Object> handler;
final AsyncType asyncType;
/**
* The instance of the {@link EventHandler} or the listener instance that was registered.
@ -147,31 +149,40 @@ public class VelocityEventManager implements EventManager {
final Object instance;
public HandlerRegistration(final PluginContainer plugin, final short order,
final Class<?> eventType, final Object instance, final EventHandler<Object> handler) {
final Class<?> eventType, final Object instance, final EventHandler<Object> handler,
final AsyncType asyncType) {
this.plugin = plugin;
this.order = order;
this.eventType = eventType;
this.instance = instance;
this.handler = handler;
this.asyncType = asyncType;
}
}
enum AsyncType {
/**
* The complete event will be handled on an async thread.
*/
ALWAYS,
/**
* The event will never run async, everything is handled on the netty thread.
*/
NEVER
NEVER,
/**
* The event will initially start on the thread calling the {@code fire} method, and possibly
* switch over to an async thread.
*/
SOMETIMES,
/**
* The complete event will be handled on an async thread.
*/
ALWAYS
}
static final class HandlersCache {
final AsyncType asyncType;
final HandlerRegistration[] handlers;
HandlersCache(final HandlerRegistration[] handlers) {
HandlersCache(AsyncType asyncType, final HandlerRegistration[] handlers) {
this.asyncType = asyncType;
this.handlers = handlers;
}
}
@ -194,7 +205,15 @@ public class VelocityEventManager implements EventManager {
}
baked.sort(handlerComparator);
return new HandlersCache(baked.toArray(new HandlerRegistration[0]));
AsyncType asyncType = AsyncType.NEVER;
for (HandlerRegistration registration : baked) {
if (registration.asyncType.compareTo(asyncType) > 0) {
asyncType = registration.asyncType;
}
}
return new HandlersCache(asyncType, baked.toArray(new HandlerRegistration[0]));
}
/**
@ -230,15 +249,17 @@ public class VelocityEventManager implements EventManager {
static final class MethodHandlerInfo {
final Method method;
final AsyncType asyncType;
final @Nullable Class<?> eventType;
final short order;
final @Nullable String errors;
final @Nullable Class<?> continuationType;
private MethodHandlerInfo(final Method method, final @Nullable Class<?> eventType,
final short order, final @Nullable String errors,
private MethodHandlerInfo(final Method method, final AsyncType asyncType,
final @Nullable Class<?> eventType, final short order, final @Nullable String errors,
final @Nullable Class<?> continuationType) {
this.method = method;
this.asyncType = asyncType;
this.eventType = eventType;
this.order = order;
this.errors = errors;
@ -302,17 +323,41 @@ public class VelocityEventManager implements EventManager {
}
}
}
AsyncType asyncType = AsyncType.NEVER;
final Class<?> returnType = method.getReturnType();
if (handlerAdapter == null) {
final Class<?> returnType = method.getReturnType();
if (returnType != void.class && continuationType == Continuation.class) {
errors.add("method return type must be void if a continuation parameter is provided");
} else if (returnType != void.class && returnType != EventTask.class) {
errors.add("method return type must be void or EventTask");
errors.add("method return type must be void, AsyncTask, "
+ "EventTask.Basic or EventTask.WithContinuation");
} else if (returnType == EventTask.class) {
// technically, for compatibility, we *should* assume that the method must be invoked
// async, however, from examining some publicly-available plugins, developers did
// generally follow the contract and returned an EventTask only if they wanted this
// behavior. enable it for them.
asyncType = AsyncType.SOMETIMES;
}
} else {
// for custom handlers, we always expect a return type of EventTask. this feature appears
// to have not been used in the wild AFAIK, so it gets the new behavior by default
asyncType = AsyncType.SOMETIMES;
}
if (paramCount == 1 && returnType == void.class && subscribe.async()) {
// these are almost always a dead giveaway of a plugin that will need its handlers
// run async, so unless we're told otherwise, we'll assume that's the case
asyncType = AsyncType.ALWAYS;
}
final short order;
if (subscribe.order() == PostOrder.CUSTOM) {
order = subscribe.priority();
} else {
order = (short) POST_ORDER_MAP.get(subscribe.order());
}
final short order = (short) subscribe.order().ordinal();
final String errorsJoined = errors.isEmpty() ? null : String.join(",", errors);
collected.put(key, new MethodHandlerInfo(method, eventType, order, errorsJoined,
collected.put(key, new MethodHandlerInfo(method, asyncType, eventType, order, errorsJoined,
continuationType));
}
final Class<?> superclass = targetClass.getSuperclass();
@ -351,12 +396,29 @@ public class VelocityEventManager implements EventManager {
@SuppressWarnings("unchecked")
public <E> void register(final Object plugin, final Class<E> eventClass,
final PostOrder order, final EventHandler<E> handler) {
if (order == PostOrder.CUSTOM) {
throw new IllegalArgumentException(
"This method does not support custom post orders. Use the overload with short instead."
);
}
register(plugin, eventClass, (short) POST_ORDER_MAP.get(order), handler, AsyncType.ALWAYS);
}
@Override
public <E> void register(Object plugin, Class<E> eventClass, short postOrder,
EventHandler<E> handler) {
register(plugin, eventClass, postOrder, handler, AsyncType.SOMETIMES);
}
private <E> void register(Object plugin, Class<E> eventClass, short postOrder,
EventHandler<E> handler, AsyncType asyncType) {
final PluginContainer pluginContainer = pluginManager.ensurePluginContainer(plugin);
requireNonNull(eventClass, "eventClass");
requireNonNull(handler, "handler");
final HandlerRegistration registration = new HandlerRegistration(pluginContainer,
(short) order.ordinal(), eventClass, handler, (EventHandler<Object>) handler);
postOrder, eventClass, handler, (EventHandler<Object>) handler,
AsyncType.ALWAYS);
register(Collections.singletonList(registration));
}
@ -386,7 +448,7 @@ public class VelocityEventManager implements EventManager {
final EventHandler<Object> handler = untargetedHandler.buildHandler(listener);
registrations.add(new HandlerRegistration(pluginContainer, info.order,
info.eventType, listener, handler));
info.eventType, listener, handler, info.asyncType));
}
register(registrations);
@ -473,10 +535,13 @@ public class VelocityEventManager implements EventManager {
private <E> void fire(final @Nullable CompletableFuture<E> future,
final E event, final HandlersCache handlersCache) {
// In Velocity 1.1.0, all events were fired asynchronously. As Velocity 3.0.0 is intended to be
// largely (albeit not 100%) compatible with 1.1.x, we also fire events async. This behavior
// will go away in Velocity Polymer.
asyncExecutor.execute(() -> fire(future, event, 0, true, handlersCache.handlers));
final HandlerRegistration registration = handlersCache.handlers[0];
if (registration.asyncType == AsyncType.ALWAYS) {
registration.plugin.getExecutorService().execute(
() -> fire(future, event, 0, true, handlersCache.handlers));
} else {
fire(future, event, 0, false, handlersCache.handlers);
}
}
private static final int TASK_STATE_DEFAULT = 0;
@ -505,6 +570,7 @@ public class VelocityEventManager implements EventManager {
private final @Nullable CompletableFuture<E> future;
private final boolean currentlyAsync;
private final E event;
private final Thread firedOnThread;
// This field is modified via a VarHandle, so this field is used and cannot be final.
@SuppressWarnings({"UnusedVariable", "FieldMayBeFinal", "FieldCanBeLocal"})
@ -527,6 +593,7 @@ public class VelocityEventManager implements EventManager {
this.event = event;
this.index = index;
this.currentlyAsync = currentlyAsync;
this.firedOnThread = Thread.currentThread();
}
@Override
@ -537,8 +604,8 @@ public class VelocityEventManager implements EventManager {
}
/**
* Executes the task and returns whether the next one should be executed immediately after this
* one without scheduling.
* Executes the task and returns whether the next handler should be executed immediately
* after this one, without additional scheduling.
*/
boolean execute() {
state = TASK_STATE_EXECUTING;
@ -580,7 +647,18 @@ public class VelocityEventManager implements EventManager {
}
if (!CONTINUATION_TASK_STATE.compareAndSet(
this, TASK_STATE_EXECUTING, TASK_STATE_CONTINUE_IMMEDIATELY)) {
asyncExecutor.execute(() -> fire(future, event, index + 1, true, registrations));
// We established earlier that registrations[index + 1] is a valid index.
// If we are remaining in the same thread for the next handler, fire
// the next event immediately, else fire it within the executor service
// of the plugin with the next handler.
final HandlerRegistration next = registrations[index + 1];
final Thread currentThread = Thread.currentThread();
if (currentThread == firedOnThread && next.asyncType != AsyncType.ALWAYS) {
fire(future, event, index + 1, currentlyAsync, registrations);
} else {
next.plugin.getExecutorService().execute(() ->
fire(future, event, index + 1, true, registrations));
}
}
}
@ -606,7 +684,7 @@ public class VelocityEventManager implements EventManager {
continue;
}
} else {
asyncExecutor.execute(continuationTask);
registration.plugin.getExecutorService().execute(continuationTask);
}
// fire will continue in another thread once the async task is
// executed and the continuation is resumed
@ -626,13 +704,4 @@ public class VelocityEventManager implements EventManager {
logger.error("Couldn't pass {} to {} {}", registration.eventType.getSimpleName(),
pluginDescription.getId(), pluginDescription.getVersion().orElse(""), t);
}
public boolean shutdown() throws InterruptedException {
asyncExecutor.shutdown();
return asyncExecutor.awaitTermination(10, TimeUnit.SECONDS);
}
public ExecutorService getAsyncExecutor() {
return asyncExecutor;
}
}

Datei anzeigen

@ -109,6 +109,12 @@ public final class ConnectionManager {
final Channel channel = future.channel();
if (future.isSuccess()) {
this.endpoints.put(address, new Endpoint(channel, ListenerType.MINECRAFT));
// Warn people with console access that HAProxy is in use, see PR: #1436
if (this.server.getConfiguration().isProxyProtocol()) {
LOGGER.warn("Using HAProxy and listening on {}, please ensure this listener is adequately firewalled.", channel.localAddress());
}
LOGGER.info("Listening on {}", channel.localAddress());
// Fire the proxy bound event after the socket is bound

Datei anzeigen

@ -68,7 +68,12 @@ public class VelocityPluginManager implements PluginManager {
this.server = checkNotNull(server, "server");
}
private void registerPlugin(PluginContainer plugin) {
/**
* Registers a plugin with the plugin manager.
*
* @param plugin the plugin to register
*/
public void registerPlugin(PluginContainer plugin) {
pluginsById.put(plugin.getDescription().getId(), plugin);
Optional<?> instance = plugin.getInstance();
instance.ifPresent(o -> pluginInstances.put(o, plugin));

Datei anzeigen

@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.plugin.virtual;
/**
* A singleton plugin object that represents the Velocity proxy itself.
*/
public class VelocityVirtualPlugin {
@SuppressWarnings("InstantiationOfUtilityClass")
public static final VelocityVirtualPlugin INSTANCE = new VelocityVirtualPlugin();
private VelocityVirtualPlugin() {
}
}

Datei anzeigen

@ -25,7 +25,6 @@ import com.velocitypowered.api.proxy.crypto.IdentifiedKey;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.crypto.IdentifiedKeyImpl;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.util.VelocityLegacyHoverEventSerializer;
import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream;
@ -47,6 +46,7 @@ import net.kyori.adventure.nbt.BinaryTagTypes;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.json.JSONOptions;
import net.kyori.adventure.text.serializer.json.legacyimpl.NBTLegacyHoverEventSerializer;
import net.kyori.option.OptionState;
/**
@ -58,8 +58,7 @@ public enum ProtocolUtils {
private static final GsonComponentSerializer PRE_1_16_SERIALIZER =
GsonComponentSerializer.builder()
.downsampleColors()
.emitLegacyHoverEvent()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionState.optionState()
// before 1.16
@ -74,7 +73,7 @@ public enum ProtocolUtils {
.build();
private static final GsonComponentSerializer PRE_1_20_3_SERIALIZER =
GsonComponentSerializer.builder()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionState.optionState()
// after 1.16
@ -89,7 +88,7 @@ public enum ProtocolUtils {
.build();
private static final GsonComponentSerializer MODERN_SERIALIZER =
GsonComponentSerializer.builder()
.legacyHoverEventSerializer(VelocityLegacyHoverEventSerializer.INSTANCE)
.legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.get())
.options(
OptionState.optionState()
// after 1.16

Datei anzeigen

@ -37,6 +37,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_3;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_20_5;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_21_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -252,7 +253,8 @@ public enum StateRegistry {
map(0x08, MINECRAFT_1_19_3, false),
map(0x09, MINECRAFT_1_19_4, false),
map(0x0A, MINECRAFT_1_20_2, false),
map(0x0B, MINECRAFT_1_20_5, false));
map(0x0B, MINECRAFT_1_20_5, false),
map(0x0D, MINECRAFT_1_21_2, false));
serverbound.register(
LegacyChatPacket.class,
LegacyChatPacket::new,
@ -264,7 +266,8 @@ public enum StateRegistry {
serverbound.register(
ChatAcknowledgementPacket.class,
ChatAcknowledgementPacket::new,
map(0x03, MINECRAFT_1_19_3, false));
map(0x03, MINECRAFT_1_19_3, false),
map(0x04, MINECRAFT_1_21_2, false));
serverbound.register(KeyedPlayerCommandPacket.class, KeyedPlayerCommandPacket::new,
map(0x03, MINECRAFT_1_19, false),
map(0x04, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
@ -273,14 +276,17 @@ public enum StateRegistry {
map(0x05, MINECRAFT_1_19_1, MINECRAFT_1_19_1, false));
serverbound.register(SessionPlayerCommandPacket.class, SessionPlayerCommandPacket::new,
map(0x04, MINECRAFT_1_19_3, false),
map(0x05, MINECRAFT_1_20_5, false));
map(0x05, MINECRAFT_1_20_5, false),
map(0x06, MINECRAFT_1_21_2, false));
serverbound.register(UnsignedPlayerCommandPacket.class, UnsignedPlayerCommandPacket::new,
map(0x04, MINECRAFT_1_20_5, false));
map(0x04, MINECRAFT_1_20_5, false),
map(0x05, MINECRAFT_1_21_2, false));
serverbound.register(
SessionPlayerChatPacket.class,
SessionPlayerChatPacket::new,
map(0x05, MINECRAFT_1_19_3, false),
map(0x06, MINECRAFT_1_20_5, false));
map(0x06, MINECRAFT_1_20_5, false),
map(0x07, MINECRAFT_1_21_2, false));
serverbound.register(
ClientSettingsPacket.class,
ClientSettingsPacket::new,
@ -294,10 +300,12 @@ public enum StateRegistry {
map(0x07, MINECRAFT_1_19_3, false),
map(0x08, MINECRAFT_1_19_4, false),
map(0x09, MINECRAFT_1_20_2, false),
map(0x0A, MINECRAFT_1_20_5, false));
map(0x0A, MINECRAFT_1_20_5, false),
map(0x0C, MINECRAFT_1_21_2, false));
serverbound.register(
ServerboundCookieResponsePacket.class, ServerboundCookieResponsePacket::new,
map(0x11, MINECRAFT_1_20_5, false));
map(0x11, MINECRAFT_1_20_5, false),
map(0x13, MINECRAFT_1_21_2, false));
serverbound.register(
PluginMessagePacket.class,
PluginMessagePacket::new,
@ -314,7 +322,8 @@ public enum StateRegistry {
map(0x0D, MINECRAFT_1_19_4, false),
map(0x0F, MINECRAFT_1_20_2, false),
map(0x10, MINECRAFT_1_20_3, false),
map(0x12, MINECRAFT_1_20_5, false));
map(0x12, MINECRAFT_1_20_5, false),
map(0x14, MINECRAFT_1_21_2, false));
serverbound.register(
KeepAlivePacket.class,
KeepAlivePacket::new,
@ -332,7 +341,8 @@ public enum StateRegistry {
map(0x12, MINECRAFT_1_19_4, false),
map(0x14, MINECRAFT_1_20_2, false),
map(0x15, MINECRAFT_1_20_3, false),
map(0x18, MINECRAFT_1_20_5, false));
map(0x18, MINECRAFT_1_20_5, false),
map(0x1A, MINECRAFT_1_21_2, false));
serverbound.register(
ResourcePackResponsePacket.class,
ResourcePackResponsePacket::new,
@ -347,11 +357,13 @@ public enum StateRegistry {
map(0x24, MINECRAFT_1_19_1, false),
map(0x27, MINECRAFT_1_20_2, false),
map(0x28, MINECRAFT_1_20_3, false),
map(0x2B, MINECRAFT_1_20_5, false));
map(0x2B, MINECRAFT_1_20_5, false),
map(0x2D, MINECRAFT_1_21_2, false));
serverbound.register(
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false),
map(0x0C, MINECRAFT_1_20_5, false));
map(0x0C, MINECRAFT_1_20_5, false),
map(0x0E, MINECRAFT_1_21_2, false));
clientbound.register(
BossBarPacket.class,
@ -449,7 +461,8 @@ public enum StateRegistry {
map(0x1F, MINECRAFT_1_19_3, false),
map(0x23, MINECRAFT_1_19_4, false),
map(0x24, MINECRAFT_1_20_2, false),
map(0x26, MINECRAFT_1_20_5, false));
map(0x26, MINECRAFT_1_20_5, false),
map(0x27, MINECRAFT_1_21_2, false));
clientbound.register(
JoinGamePacket.class,
JoinGamePacket::new,
@ -466,7 +479,8 @@ public enum StateRegistry {
map(0x24, MINECRAFT_1_19_3, false),
map(0x28, MINECRAFT_1_19_4, false),
map(0x29, MINECRAFT_1_20_2, false),
map(0x2B, MINECRAFT_1_20_5, false));
map(0x2B, MINECRAFT_1_20_5, false),
map(0x2C, MINECRAFT_1_21_2, false));
clientbound.register(
RespawnPacket.class,
RespawnPacket::new,
@ -486,12 +500,14 @@ public enum StateRegistry {
map(0x41, MINECRAFT_1_19_4, true),
map(0x43, MINECRAFT_1_20_2, true),
map(0x45, MINECRAFT_1_20_3, true),
map(0x47, MINECRAFT_1_20_5, true));
map(0x47, MINECRAFT_1_20_5, true),
map(0x4C, MINECRAFT_1_21_2, true));
clientbound.register(
RemoveResourcePackPacket.class,
RemoveResourcePackPacket::new,
map(0x43, MINECRAFT_1_20_3, false),
map(0x45, MINECRAFT_1_20_5, false));
map(0x45, MINECRAFT_1_20_5, false),
map(0x4A, MINECRAFT_1_21_2, false));
clientbound.register(
ResourcePackRequestPacket.class,
ResourcePackRequestPacket::new,
@ -511,7 +527,8 @@ public enum StateRegistry {
map(0x40, MINECRAFT_1_19_4, false),
map(0x42, MINECRAFT_1_20_2, false),
map(0x44, MINECRAFT_1_20_3, false),
map(0x46, MINECRAFT_1_20_5, false));
map(0x46, MINECRAFT_1_20_5, false),
map(0x4B, MINECRAFT_1_21_2, false));
clientbound.register(
HeaderAndFooterPacket.class,
HeaderAndFooterPacket::new,
@ -532,7 +549,8 @@ public enum StateRegistry {
map(0x65, MINECRAFT_1_19_4, true),
map(0x68, MINECRAFT_1_20_2, true),
map(0x6A, MINECRAFT_1_20_3, true),
map(0x6D, MINECRAFT_1_20_5, true));
map(0x6D, MINECRAFT_1_20_5, true),
map(0x74, MINECRAFT_1_21_2, true));
clientbound.register(
LegacyTitlePacket.class,
LegacyTitlePacket::new,
@ -552,7 +570,8 @@ public enum StateRegistry {
map(0x5D, MINECRAFT_1_19_4, true),
map(0x5F, MINECRAFT_1_20_2, true),
map(0x61, MINECRAFT_1_20_3, true),
map(0x63, MINECRAFT_1_20_5, true));
map(0x63, MINECRAFT_1_20_5, true),
map(0x6A, MINECRAFT_1_21_2, true));
clientbound.register(
TitleTextPacket.class,
TitleTextPacket::new,
@ -563,7 +582,8 @@ public enum StateRegistry {
map(0x5F, MINECRAFT_1_19_4, true),
map(0x61, MINECRAFT_1_20_2, true),
map(0x63, MINECRAFT_1_20_3, true),
map(0x65, MINECRAFT_1_20_5, true));
map(0x65, MINECRAFT_1_20_5, true),
map(0x6C, MINECRAFT_1_21_2, true));
clientbound.register(
TitleActionbarPacket.class,
TitleActionbarPacket::new,
@ -574,7 +594,8 @@ public enum StateRegistry {
map(0x46, MINECRAFT_1_19_4, true),
map(0x48, MINECRAFT_1_20_2, true),
map(0x4A, MINECRAFT_1_20_3, true),
map(0x4C, MINECRAFT_1_20_5, true));
map(0x4C, MINECRAFT_1_20_5, true),
map(0x51, MINECRAFT_1_21_2, true));
clientbound.register(
TitleTimesPacket.class,
TitleTimesPacket::new,
@ -585,7 +606,8 @@ public enum StateRegistry {
map(0x60, MINECRAFT_1_19_4, true),
map(0x62, MINECRAFT_1_20_2, true),
map(0x64, MINECRAFT_1_20_3, true),
map(0x66, MINECRAFT_1_20_5, true));
map(0x66, MINECRAFT_1_20_5, true),
map(0x6D, MINECRAFT_1_21_2, true));
clientbound.register(
TitleClearPacket.class,
TitleClearPacket::new,
@ -612,17 +634,20 @@ public enum StateRegistry {
map(0x35, MINECRAFT_1_19_3, false),
map(0x39, MINECRAFT_1_19_4, false),
map(0x3B, MINECRAFT_1_20_2, false),
map(0x3D, MINECRAFT_1_20_5, false));
map(0x3D, MINECRAFT_1_20_5, false),
map(0x3F, MINECRAFT_1_21_2, false));
clientbound.register(
UpsertPlayerInfoPacket.class,
UpsertPlayerInfoPacket::new,
map(0x36, MINECRAFT_1_19_3, false),
map(0x3A, MINECRAFT_1_19_4, false),
map(0x3C, MINECRAFT_1_20_2, false),
map(0x3E, MINECRAFT_1_20_5, false));
map(0x3E, MINECRAFT_1_20_5, false),
map(0x40, MINECRAFT_1_21_2, false));
clientbound.register(
ClientboundStoreCookiePacket.class, ClientboundStoreCookiePacket::new,
map(0x6B, MINECRAFT_1_20_5, false));
map(0x6B, MINECRAFT_1_20_5, false),
map(0x72, MINECRAFT_1_21_2, false));
clientbound.register(
SystemChatPacket.class,
SystemChatPacket::new,
@ -632,7 +657,8 @@ public enum StateRegistry {
map(0x64, MINECRAFT_1_19_4, true),
map(0x67, MINECRAFT_1_20_2, true),
map(0x69, MINECRAFT_1_20_3, true),
map(0x6C, MINECRAFT_1_20_5, true));
map(0x6C, MINECRAFT_1_20_5, true),
map(0x73, MINECRAFT_1_21_2, true));
clientbound.register(
PlayerChatCompletionPacket.class,
PlayerChatCompletionPacket::new,
@ -650,13 +676,15 @@ public enum StateRegistry {
map(0x45, MINECRAFT_1_19_4, false),
map(0x47, MINECRAFT_1_20_2, false),
map(0x49, MINECRAFT_1_20_3, false),
map(0x4B, MINECRAFT_1_20_5, false));
map(0x4B, MINECRAFT_1_20_5, false),
map(0x50, MINECRAFT_1_21_2, false));
clientbound.register(
StartUpdatePacket.class,
() -> StartUpdatePacket.INSTANCE,
map(0x65, MINECRAFT_1_20_2, false),
map(0x67, MINECRAFT_1_20_3, false),
map(0x69, MINECRAFT_1_20_5, false));
map(0x69, MINECRAFT_1_20_5, false),
map(0x70, MINECRAFT_1_21_2, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
@ -664,12 +692,18 @@ public enum StateRegistry {
clientbound.register(
TransferPacket.class,
TransferPacket::new,
map(0x73, MINECRAFT_1_20_5, false)
);
clientbound.register(ClientboundCustomReportDetailsPacket.class, ClientboundCustomReportDetailsPacket::new,
map(0x7A, MINECRAFT_1_21, false));
clientbound.register(ClientboundServerLinksPacket.class, ClientboundServerLinksPacket::new,
map(0x7B, MINECRAFT_1_21, false));
map(0x73, MINECRAFT_1_20_5, false),
map(0x7A, MINECRAFT_1_21_2, false));
clientbound.register(
ClientboundCustomReportDetailsPacket.class,
ClientboundCustomReportDetailsPacket::new,
map(0x7A, MINECRAFT_1_21, false),
map(0x81, MINECRAFT_1_21_2, false));
clientbound.register(
ClientboundServerLinksPacket.class,
ClientboundServerLinksPacket::new,
map(0x7B, MINECRAFT_1_21, false),
map(0x82, MINECRAFT_1_21_2, false));
}
},
LOGIN {

Datei anzeigen

@ -46,6 +46,7 @@ public class MinecraftVarintFrameDecoder extends ByteToMessageDecoder {
// skip any runs of 0x00 we might find
int packetStart = in.forEachByte(FIND_NON_NUL);
if (packetStart == -1) {
in.clear();
return;
}
in.readerIndex(packetStart);

Datei anzeigen

@ -34,21 +34,25 @@ public class ClientSettingsPacket implements MinecraftPacket {
private byte difficulty; // 1.7 Protocol
private short skinParts;
private int mainHand;
private boolean chatFilteringEnabled; // Added in 1.17
private boolean textFilteringEnabled; // Added in 1.17
private boolean clientListingAllowed; // Added in 1.18, overwrites server-list "anonymous" mode
private int particleStatus; // Added in 1.21.2
public ClientSettingsPacket() {
}
public ClientSettingsPacket(String locale, byte viewDistance, int chatVisibility, boolean chatColors,
short skinParts, int mainHand, boolean chatFilteringEnabled, boolean clientListingAllowed) {
short skinParts, int mainHand, boolean textFilteringEnabled, boolean clientListingAllowed,
int particleStatus) {
this.locale = locale;
this.viewDistance = viewDistance;
this.chatVisibility = chatVisibility;
this.chatColors = chatColors;
this.skinParts = skinParts;
this.mainHand = mainHand;
this.textFilteringEnabled = textFilteringEnabled;
this.clientListingAllowed = clientListingAllowed;
this.particleStatus = particleStatus;
}
public String getLocale() {
@ -102,12 +106,12 @@ public class ClientSettingsPacket implements MinecraftPacket {
this.mainHand = mainHand;
}
public boolean isChatFilteringEnabled() {
return chatFilteringEnabled;
public boolean isTextFilteringEnabled() {
return textFilteringEnabled;
}
public void setChatFilteringEnabled(boolean chatFilteringEnabled) {
this.chatFilteringEnabled = chatFilteringEnabled;
public void setTextFilteringEnabled(boolean textFilteringEnabled) {
this.textFilteringEnabled = textFilteringEnabled;
}
public boolean isClientListingAllowed() {
@ -118,12 +122,20 @@ public class ClientSettingsPacket implements MinecraftPacket {
this.clientListingAllowed = clientListingAllowed;
}
public int getParticleStatus() {
return particleStatus;
}
public void setParticleStatus(int particleStatus) {
this.particleStatus = particleStatus;
}
@Override
public String toString() {
return "ClientSettings{" + "locale='" + locale + '\'' + ", viewDistance=" + viewDistance +
", chatVisibility=" + chatVisibility + ", chatColors=" + chatColors + ", skinParts=" +
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + chatFilteringEnabled +
", clientListingAllowed=" + clientListingAllowed + '}';
skinParts + ", mainHand=" + mainHand + ", chatFilteringEnabled=" + textFilteringEnabled +
", clientListingAllowed=" + clientListingAllowed + ", particleStatus=" + particleStatus + '}';
}
@Override
@ -143,10 +155,14 @@ public class ClientSettingsPacket implements MinecraftPacket {
this.mainHand = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
this.chatFilteringEnabled = buf.readBoolean();
this.textFilteringEnabled = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
this.clientListingAllowed = buf.readBoolean();
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.particleStatus = ProtocolUtils.readVarInt(buf);
}
}
}
}
@ -172,11 +188,15 @@ public class ClientSettingsPacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, mainHand);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
buf.writeBoolean(chatFilteringEnabled);
buf.writeBoolean(textFilteringEnabled);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_18)) {
buf.writeBoolean(clientListingAllowed);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, particleStatus);
}
}
}
}
@ -201,8 +221,9 @@ public class ClientSettingsPacket implements MinecraftPacket {
&& difficulty == that.difficulty
&& skinParts == that.skinParts
&& mainHand == that.mainHand
&& chatFilteringEnabled == that.chatFilteringEnabled
&& textFilteringEnabled == that.textFilteringEnabled
&& clientListingAllowed == that.clientListingAllowed
&& particleStatus == that.particleStatus
&& Objects.equals(locale, that.locale);
}
@ -216,7 +237,8 @@ public class ClientSettingsPacket implements MinecraftPacket {
difficulty,
skinParts,
mainHand,
chatFilteringEnabled,
clientListingAllowed);
textFilteringEnabled,
clientListingAllowed,
particleStatus);
}
}

Datei anzeigen

@ -51,6 +51,7 @@ public class JoinGamePacket implements MinecraftPacket {
private int simulationDistance; // 1.18+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
private boolean enforcesSecureChat; // 1.20.5+
public int getEntityId() {
@ -181,6 +182,14 @@ public class JoinGamePacket implements MinecraftPacket {
this.portalCooldown = portalCooldown;
}
public int getSeaLevel() {
return seaLevel;
}
public void setSeaLevel(int seaLevel) {
this.seaLevel = seaLevel;
}
public boolean getEnforcesSecureChat() {
return this.enforcesSecureChat;
}
@ -204,6 +213,7 @@ public class JoinGamePacket implements MinecraftPacket {
dimensionInfo + '\'' + ", currentDimensionData='" + currentDimensionData + '\'' +
", previousGamemode=" + previousGamemode + ", simulationDistance=" + simulationDistance +
", lastDeathPosition='" + lastDeathPosition + '\'' + ", portalCooldown=" + portalCooldown +
", seaLevel=" + seaLevel +
'}';
}
@ -343,6 +353,11 @@ public class JoinGamePacket implements MinecraftPacket {
}
this.portalCooldown = ProtocolUtils.readVarInt(buf);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
this.enforcesSecureChat = buf.readBoolean();
}
@ -491,6 +506,10 @@ public class JoinGamePacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, portalCooldown);
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_5)) {
buf.writeBoolean(this.enforcesSecureChat);
}

Datei anzeigen

@ -41,6 +41,7 @@ public class RespawnPacket implements MinecraftPacket {
private CompoundBinaryTag currentDimensionData; // 1.16.2+
private @Nullable Pair<String, Long> lastDeathPosition; // 1.19+
private int portalCooldown; // 1.20+
private int seaLevel; // 1.21.2+
public RespawnPacket() {
}
@ -48,7 +49,8 @@ public class RespawnPacket implements MinecraftPacket {
public RespawnPacket(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType, byte dataToKeep, DimensionInfo dimensionInfo,
short previousGamemode, CompoundBinaryTag currentDimensionData,
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown) {
@Nullable Pair<String, Long> lastDeathPosition, int portalCooldown,
int seaLevel) {
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
@ -60,13 +62,15 @@ public class RespawnPacket implements MinecraftPacket {
this.currentDimensionData = currentDimensionData;
this.lastDeathPosition = lastDeathPosition;
this.portalCooldown = portalCooldown;
this.seaLevel = seaLevel;
}
public static RespawnPacket fromJoinGame(JoinGamePacket joinGame) {
return new RespawnPacket(joinGame.getDimension(), joinGame.getPartialHashedSeed(),
joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType(),
(byte) 0, joinGame.getDimensionInfo(), joinGame.getPreviousGamemode(),
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(), joinGame.getPortalCooldown());
joinGame.getCurrentDimensionData(), joinGame.getLastDeathPosition(),
joinGame.getPortalCooldown(), joinGame.getSeaLevel());
}
public int getDimension() {
@ -141,6 +145,14 @@ public class RespawnPacket implements MinecraftPacket {
this.portalCooldown = portalCooldown;
}
public int getSeaLevel() {
return seaLevel;
}
public void setSeaLevel(int seaLevel) {
this.seaLevel = seaLevel;
}
@Override
public String toString() {
return "Respawn{"
@ -155,6 +167,7 @@ public class RespawnPacket implements MinecraftPacket {
+ ", previousGamemode=" + previousGamemode
+ ", dimensionData=" + currentDimensionData
+ ", portalCooldown=" + portalCooldown
+ ", seaLevel=" + seaLevel
+ '}';
}
@ -204,6 +217,9 @@ public class RespawnPacket implements MinecraftPacket {
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20)) {
this.portalCooldown = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
this.seaLevel = ProtocolUtils.readVarInt(buf);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
this.dataToKeep = buf.readByte();
}
@ -262,6 +278,10 @@ public class RespawnPacket implements MinecraftPacket {
ProtocolUtils.writeVarInt(buf, portalCooldown);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_21_2)) {
ProtocolUtils.writeVarInt(buf, seaLevel);
}
if (version.noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) {
buf.writeByte(dataToKeep);
}

Datei anzeigen

@ -188,6 +188,11 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
if (info.displayName != null) {
info.displayName.write(buf);
}
}),
UPDATE_LIST_ORDER((version, buf, info) -> { // read
info.listOrder = ProtocolUtils.readVarInt(buf);
}, (version, buf, info) -> { // write
ProtocolUtils.writeVarInt(buf, info.listOrder);
});
private final Read read;
@ -218,6 +223,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
private int gameMode;
@Nullable
private ComponentHolder displayName;
private int listOrder;
@Nullable
private RemoteChatSession chatSession;
@ -250,6 +256,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
return displayName;
}
public int getListOrder() {
return listOrder;
}
@Nullable
public RemoteChatSession getChatSession() {
return chatSession;
@ -275,6 +285,10 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
this.displayName = displayName;
}
public void setListOrder(int listOrder) {
this.listOrder = listOrder;
}
public void setChatSession(@Nullable RemoteChatSession chatSession) {
this.chatSession = chatSession;
}
@ -288,6 +302,7 @@ public class UpsertPlayerInfoPacket implements MinecraftPacket {
", latency=" + latency +
", gameMode=" + gameMode +
", displayName=" + displayName +
", listOrder=" + listOrder +
", chatSession=" + chatSession +
'}';
}

Datei anzeigen

@ -71,7 +71,8 @@ public class SessionChatHandler implements ChatHandler<SessionPlayerChatPacket>
invalidChange(logger, player);
return null;
}
return this.player.getChatBuilderFactory().builder().message(packet.message)
return this.player.getChatBuilderFactory().builder()
.message(chatResult.getMessage().orElse(packet.getMessage()))
.setTimestamp(packet.timestamp)
.setLastSeenMessages(newLastSeenMessages)
.toServer();

Datei anzeigen

@ -1,123 +0,0 @@
/*
* Copyright (C) 2020-2023 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.util;
import java.io.IOException;
import java.util.UUID;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.TagStringIO;
import net.kyori.adventure.nbt.api.BinaryTagHolder;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.event.HoverEvent.ShowEntity;
import net.kyori.adventure.text.event.HoverEvent.ShowItem;
import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.util.Codec.Decoder;
import net.kyori.adventure.util.Codec.Encoder;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* An implementation of {@link LegacyHoverEventSerializer} that implements the interface in the most
* literal, albeit "incompatible" way possible.
*/
public class VelocityLegacyHoverEventSerializer implements LegacyHoverEventSerializer {
public static final LegacyHoverEventSerializer INSTANCE =
new VelocityLegacyHoverEventSerializer();
private VelocityLegacyHoverEventSerializer() {
}
private static Key legacyIdToFakeKey(byte id) {
return Key.key("velocity", "legacy_hover/id_" + id);
}
@Override
public HoverEvent.@NonNull ShowItem deserializeShowItem(@NonNull Component input)
throws IOException {
String snbt = PlainTextComponentSerializer.plainText().serialize(input);
CompoundBinaryTag item = TagStringIO.get().asCompound(snbt);
Key key;
String idIfString = item.getString("id", "");
if (idIfString.isEmpty()) {
key = legacyIdToFakeKey(item.getByte("id"));
} else {
key = Key.key(idIfString);
}
byte count = item.getByte("Count", (byte) 1);
return ShowItem.of(key, count, BinaryTagHolder.binaryTagHolder(snbt));
}
@Override
public HoverEvent.@NonNull ShowEntity deserializeShowEntity(@NonNull Component input,
Decoder<Component, String, ? extends RuntimeException> componentDecoder) throws IOException {
String snbt = PlainTextComponentSerializer.plainText().serialize(input);
CompoundBinaryTag item = TagStringIO.get().asCompound(snbt);
Component name;
try {
name = componentDecoder.decode(item.getString("name"));
} catch (Exception e) {
name = Component.text(item.getString("name"));
}
return ShowEntity.of(Key.key(item.getString("type")),
UUID.fromString(item.getString("id")),
name);
}
@Override
public @NonNull Component serializeShowItem(HoverEvent.@NonNull ShowItem input)
throws IOException {
final CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder()
.putByte("Count", (byte) input.count());
String keyAsString = input.item().asString();
if (keyAsString.startsWith("velocity:legacy_hover/id_")) {
builder.putByte("id", Byte.parseByte(keyAsString
.substring("velocity:legacy_hover/id_".length())));
} else {
builder.putString("id", keyAsString);
}
BinaryTagHolder nbt = input.nbt();
if (nbt != null) {
builder.put("tag", TagStringIO.get().asCompound(nbt.string()));
}
return Component.text(TagStringIO.get().asString(builder.build()));
}
@Override
public @NonNull Component serializeShowEntity(HoverEvent.@NonNull ShowEntity input,
Encoder<Component, String, ? extends RuntimeException> componentEncoder) throws IOException {
CompoundBinaryTag.Builder tag = CompoundBinaryTag.builder()
.putString("id", input.id().toString())
.putString("type", input.type().asString());
Component name = input.name();
if (name != null) {
tag.putString("name", componentEncoder.encode(name));
}
return Component.text(TagStringIO.get().asString(tag.build()));
}
}

Datei anzeigen

@ -230,7 +230,7 @@ public class BrigadierCommandTests extends CommandTestSuite {
final Exception wrapper = assertThrows(CompletionException.class, () ->
manager.executeAsync(source, "hello").join());
assertSame(expected, wrapper.getCause().getCause());
assertSame(expected, wrapper.getCause());
}
// Suggestions

Datei anzeigen

@ -29,9 +29,9 @@ import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.event.MockEventManager;
import com.velocitypowered.proxy.event.VelocityEventManager;
import com.velocitypowered.proxy.testutil.FakePluginManager;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
@ -47,19 +47,9 @@ abstract class CommandTestSuite {
eventManager = new MockEventManager();
}
@AfterAll
static void afterAll() {
try {
eventManager.shutdown();
eventManager = null;
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@BeforeEach
void setUp() {
this.manager = new VelocityCommandManager(eventManager);
this.manager = new VelocityCommandManager(eventManager, new FakePluginManager());
}
final void assertHandled(final String input) {

Datei anzeigen

@ -41,20 +41,24 @@ import org.junit.jupiter.api.TestInstance;
public class EventTest {
public static final String CONTINUATION_TEST_THREAD_NAME = "Continuation test thread";
private final VelocityEventManager eventManager =
new VelocityEventManager(new FakePluginManager());
private final FakePluginManager pluginManager = new FakePluginManager();
private final VelocityEventManager eventManager = new VelocityEventManager(pluginManager);
@AfterAll
void shutdown() throws Exception {
eventManager.shutdown();
pluginManager.shutdown();
}
static final class TestEvent {
}
static void assertSyncThread(final Thread thread) {
assertEquals(Thread.currentThread(), thread);
}
static void assertAsyncThread(final Thread thread) {
assertTrue(thread.getName().contains("Velocity Async Event Executor"));
assertTrue(thread.getName().contains("Test Async Thread"));
}
static void assertContinuationThread(final Thread thread) {
@ -90,6 +94,7 @@ public class EventTest {
eventManager.fire(new TestEvent()).get();
} finally {
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
}
// Check that the order is A < B < C.
@ -119,6 +124,7 @@ public class EventTest {
eventManager.fire(new TestEvent()).get();
} finally {
eventManager.unregisterListeners(FakePluginManager.PLUGIN_A);
eventManager.unregisterListeners(FakePluginManager.PLUGIN_B);
}
// Check that the order is A < B < C.
@ -126,6 +132,26 @@ public class EventTest {
assertTrue(listener2Invoked.get() < listener3Invoked.get(), "Listener C invoked before B!");
}
@Test
void testAlwaysSync() throws Exception {
final AlwaysSyncListener listener = new AlwaysSyncListener();
handleMethodListener(listener);
assertSyncThread(listener.thread);
assertEquals(1, listener.result);
}
static final class AlwaysSyncListener {
@MonotonicNonNull Thread thread;
int result;
@Subscribe(async = false)
void sync(TestEvent event) {
result++;
thread = Thread.currentThread();
}
}
@Test
void testAlwaysAsync() throws Exception {
final AlwaysAsyncListener listener = new AlwaysAsyncListener();
@ -143,7 +169,7 @@ public class EventTest {
@MonotonicNonNull Thread threadC;
int result;
@Subscribe
@Subscribe(async = true, order = PostOrder.EARLY)
void firstAsync(TestEvent event) {
result++;
threadA = Thread.currentThread();
@ -155,50 +181,93 @@ public class EventTest {
return EventTask.async(() -> result++);
}
@Subscribe
@Subscribe(order = PostOrder.LATE)
void thirdAsync(TestEvent event) {
result++;
threadC = Thread.currentThread();
}
}
@Test
void testSometimesAsync() throws Exception {
final SometimesAsyncListener listener = new SometimesAsyncListener();
handleMethodListener(listener);
assertSyncThread(listener.threadA);
assertSyncThread(listener.threadB);
assertAsyncThread(listener.threadC);
assertAsyncThread(listener.threadD);
assertEquals(3, listener.result);
}
static final class SometimesAsyncListener {
@MonotonicNonNull Thread threadA;
@MonotonicNonNull Thread threadB;
@MonotonicNonNull Thread threadC;
@MonotonicNonNull Thread threadD;
int result;
@Subscribe(order = PostOrder.EARLY, async = false)
void notAsync(TestEvent event) {
result++;
threadA = Thread.currentThread();
}
@Subscribe
EventTask notAsyncUntilTask(TestEvent event) {
threadB = Thread.currentThread();
return EventTask.async(() -> {
threadC = Thread.currentThread();
result++;
});
}
@Subscribe(order = PostOrder.LATE, async = false)
void stillAsyncAfterTask(TestEvent event) {
threadD = Thread.currentThread();
result++;
}
}
@Test
void testContinuation() throws Exception {
final ContinuationListener listener = new ContinuationListener();
handleMethodListener(listener);
assertAsyncThread(listener.thread1);
assertAsyncThread(listener.thread2);
assertContinuationThread(listener.thread2Custom);
assertAsyncThread(listener.thread3);
assertSyncThread(listener.threadA);
assertSyncThread(listener.threadB);
assertAsyncThread(listener.threadC);
assertEquals(2, listener.value.get());
}
static final class ContinuationListener {
@MonotonicNonNull Thread thread1;
@MonotonicNonNull Thread thread2;
@MonotonicNonNull Thread thread2Custom;
@MonotonicNonNull Thread thread3;
@MonotonicNonNull Thread threadA;
@MonotonicNonNull Thread threadB;
@MonotonicNonNull Thread threadC;
final AtomicInteger value = new AtomicInteger();
@Subscribe(order = PostOrder.EARLY)
EventTask continuation(TestEvent event) {
thread1 = Thread.currentThread();
threadA = Thread.currentThread();
return EventTask.withContinuation(continuation -> {
value.incrementAndGet();
thread2 = Thread.currentThread();
threadB = Thread.currentThread();
new Thread(() -> {
thread2Custom = Thread.currentThread();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
value.incrementAndGet();
continuation.resume();
}, CONTINUATION_TEST_THREAD_NAME).start();
}).start();
});
}
@Subscribe(order = PostOrder.LATE)
void afterContinuation(TestEvent event) {
thread3 = Thread.currentThread();
threadC = Thread.currentThread();
}
}
@ -207,9 +276,9 @@ public class EventTest {
final ResumeContinuationImmediatelyListener listener =
new ResumeContinuationImmediatelyListener();
handleMethodListener(listener);
assertAsyncThread(listener.threadA);
assertAsyncThread(listener.threadB);
assertAsyncThread(listener.threadC);
assertSyncThread(listener.threadA);
assertSyncThread(listener.threadB);
assertSyncThread(listener.threadC);
assertEquals(2, listener.result);
}
@ -241,42 +310,44 @@ public class EventTest {
void testContinuationParameter() throws Exception {
final ContinuationParameterListener listener = new ContinuationParameterListener();
handleMethodListener(listener);
assertAsyncThread(listener.thread1);
assertAsyncThread(listener.thread2);
assertContinuationThread(listener.thread2Custom);
assertAsyncThread(listener.thread3);
assertSyncThread(listener.threadA);
assertSyncThread(listener.threadB);
assertAsyncThread(listener.threadC);
assertEquals(3, listener.result.get());
}
static final class ContinuationParameterListener {
@MonotonicNonNull Thread thread1;
@MonotonicNonNull Thread thread2;
@MonotonicNonNull Thread thread2Custom;
@MonotonicNonNull Thread thread3;
@MonotonicNonNull Thread threadA;
@MonotonicNonNull Thread threadB;
@MonotonicNonNull Thread threadC;
final AtomicInteger result = new AtomicInteger();
@Subscribe
void resume(TestEvent event, Continuation continuation) {
thread1 = Thread.currentThread();
threadA = Thread.currentThread();
result.incrementAndGet();
continuation.resume();
}
@Subscribe(order = PostOrder.LATE)
void resumeFromCustomThread(TestEvent event, Continuation continuation) {
thread2 = Thread.currentThread();
threadB = Thread.currentThread();
new Thread(() -> {
thread2Custom = Thread.currentThread();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
result.incrementAndGet();
continuation.resume();
}, CONTINUATION_TEST_THREAD_NAME).start();
}).start();
}
@Subscribe(order = PostOrder.LAST)
void afterCustomThread(TestEvent event, Continuation continuation) {
thread3 = Thread.currentThread();
threadC = Thread.currentThread();
result.incrementAndGet();
continuation.resume();
}
@ -328,8 +399,7 @@ public class EventTest {
+ "the second is the fancy continuation");
}
},
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {
},
new TypeToken<TriConsumer<Object, Object, FancyContinuation>>() {},
invokeFunction -> (instance, event) ->
EventTask.withContinuation(continuation ->
invokeFunction.accept(instance, event, new FancyContinuationImpl(continuation))
@ -349,4 +419,4 @@ public class EventTest {
continuation.resume();
}
}
}
}

Datei anzeigen

@ -18,14 +18,16 @@
package com.velocitypowered.proxy.testutil;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
import com.velocitypowered.api.plugin.PluginManager;
import com.velocitypowered.proxy.plugin.virtual.VelocityVirtualPlugin;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Executors;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
@ -36,15 +38,23 @@ public class FakePluginManager implements PluginManager {
public static final Object PLUGIN_A = new Object();
public static final Object PLUGIN_B = new Object();
private static final PluginContainer PC_A = new FakePluginContainer("a", PLUGIN_A);
private static final PluginContainer PC_B = new FakePluginContainer("b", PLUGIN_B);
private final PluginContainer containerA = new FakePluginContainer("a", PLUGIN_A);
private final PluginContainer containerB = new FakePluginContainer("b", PLUGIN_B);
private final PluginContainer containerVelocity = new FakePluginContainer("velocity",
VelocityVirtualPlugin.INSTANCE);
private ExecutorService service = Executors.newCachedThreadPool(
new ThreadFactoryBuilder().setNameFormat("Test Async Thread").setDaemon(true).build()
);
@Override
public @NonNull Optional<PluginContainer> fromInstance(@NonNull Object instance) {
if (instance == PLUGIN_A) {
return Optional.of(PC_A);
return Optional.of(containerA);
} else if (instance == PLUGIN_B) {
return Optional.of(PC_B);
return Optional.of(containerB);
} else if (instance == VelocityVirtualPlugin.INSTANCE) {
return Optional.of(containerVelocity);
} else {
return Optional.empty();
}
@ -54,9 +64,11 @@ public class FakePluginManager implements PluginManager {
public @NonNull Optional<PluginContainer> getPlugin(@NonNull String id) {
switch (id) {
case "a":
return Optional.of(PC_A);
return Optional.of(containerA);
case "b":
return Optional.of(PC_B);
return Optional.of(containerB);
case "velocity":
return Optional.of(containerVelocity);
default:
return Optional.empty();
}
@ -64,7 +76,7 @@ public class FakePluginManager implements PluginManager {
@Override
public @NonNull Collection<PluginContainer> getPlugins() {
return ImmutableList.of(PC_A, PC_B);
return ImmutableList.of(containerVelocity, containerA, containerB);
}
@Override
@ -77,16 +89,18 @@ public class FakePluginManager implements PluginManager {
throw new UnsupportedOperationException();
}
private static class FakePluginContainer implements PluginContainer {
public void shutdown() {
this.service.shutdownNow();
}
private class FakePluginContainer implements PluginContainer {
private final String id;
private final Object instance;
private final ExecutorService service;
private FakePluginContainer(String id, Object instance) {
this.id = id;
this.instance = instance;
this.service = ForkJoinPool.commonPool();
}
@Override