From 2016d1482f185daf4b92b6775fb4a8d2ef1e0da3 Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Sun, 15 Sep 2024 20:22:55 -0400 Subject: [PATCH] Deprecate anonymous command registrations --- .../api/command/CommandManager.java | 4 + .../velocitypowered/proxy/VelocityServer.java | 55 ++++++++++-- .../proxy/command/VelocityCommandManager.java | 70 +++++++++++---- .../proxy/command/VelocityCommands.java | 90 ++++++++++++++++++- .../VelocityArgumentCommandNode.java | 10 +++ .../VelocityBrigadierCommandWrapper.java | 67 ++++++++++++++ .../proxy/command/builtin/GlistCommand.java | 9 +- .../proxy/command/builtin/SendCommand.java | 9 +- .../registrar/BrigadierCommandRegistrar.java | 6 +- .../registrar/InvocableCommandRegistrar.java | 5 +- .../proxy/plugin/VelocityPluginManager.java | 7 +- .../plugin/virtual/VelocityVirtualPlugin.java | 29 ++++++ .../proxy/command/BrigadierCommandTests.java | 2 +- .../proxy/command/CommandTestSuite.java | 3 +- .../proxy/testutil/FakePluginManager.java | 9 +- 15 files changed, 338 insertions(+), 37 deletions(-) create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityBrigadierCommandWrapper.java create mode 100644 proxy/src/main/java/com/velocitypowered/proxy/plugin/virtual/VelocityVirtualPlugin.java diff --git a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java index 257fc9e64..ad3893738 100644 --- a/api/src/main/java/com/velocitypowered/api/command/CommandManager.java +++ b/api/src/main/java/com/velocitypowered/api/command/CommandManager.java @@ -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); /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 2a362ccc9..2563f984f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -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; @@ -111,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) @@ -163,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); @@ -200,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; @@ -214,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(); @@ -222,11 +241,35 @@ 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(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index 24fc6a059..f8bb39f07 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -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,8 +54,7 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; @@ -73,14 +77,16 @@ public class VelocityCommandManager implements CommandManager { private final SuggestionsProvider suggestionsProvider; private final CommandGraphInjector injector; private final Map commandMetas; - private final ExecutorService asyncExecutor; + 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); @@ -92,7 +98,6 @@ public class VelocityCommandManager implements CommandManager { this.suggestionsProvider = new SuggestionsProvider<>(this.dispatcher, this.lock.readLock()); this.injector = new CommandGraphInjector<>(this.dispatcher, this.lock.readLock()); this.commandMetas = new ConcurrentHashMap<>(); - this.asyncExecutor = ForkJoinPool.commonPool(); // TODO: remove entirely } public void setAnnounceProxyCommands(boolean announceProxyCommands) { @@ -222,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 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 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) { @@ -253,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)); } } @@ -264,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())); - }, asyncExecutor); + final ParseResults parsed = this.parse( + commandResult.getCommand().orElse(cmdLine), source); + return CompletableFuture.supplyAsync( + () -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed) + ); + }, figureAsyncExecutorForParsing()); } @Override @@ -279,7 +285,13 @@ public class VelocityCommandManager implements CommandManager { Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmdLine, "cmdLine"); - return CompletableFuture.supplyAsync(() -> executeImmediately0(source, cmdLine), asyncExecutor); + return CompletableFuture.supplyAsync( + () -> this.parse(cmdLine, source), figureAsyncExecutorForParsing() + ).thenCompose( + parsed -> CompletableFuture.supplyAsync( + () -> executeImmediately0(source, parsed), this.getAsyncExecutor(parsed) + ) + ); } /** @@ -327,9 +339,10 @@ public class VelocityCommandManager implements CommandManager { * @return the parse results */ private ParseResults 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(); } @@ -373,4 +386,25 @@ public class VelocityCommandManager implements CommandManager { public CommandGraphInjector getInjector() { return injector; } + + private Executor getAsyncExecutor(ParseResults 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(); + } + } } \ No newline at end of file diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommands.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommands.java index 2f10ed25b..3c2ad1991 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommands.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommands.java @@ -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 wrap(final CommandNode 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 maybeCommand = delegate.getCommand(); + if (maybeCommand != null && !(maybeCommand instanceof VelocityBrigadierCommandWrapper)) { + maybeCommand = VelocityBrigadierCommandWrapper.wrap(delegate.getCommand(), registrant); + } + + if (delegate instanceof LiteralCommandNode lcn) { + var literalBuilder = shallowCopyAsBuilder(lcn, delegate.getName(), true); + literalBuilder.executes(maybeCommand); + // we also need to wrap any children + for (final CommandNode 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 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 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 shallowCopy( final LiteralCommandNode 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 shallowCopy( + final LiteralCommandNode original, final String newName, + final com.mojang.brigadier.Command 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 shallowCopyAsBuilder( + final LiteralCommandNode original, final String newName, + final boolean skipChildren) { // Brigadier resolves the redirect of a node if further input can be parsed. // Let be a literal node having a redirect to a 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 child : original.getChildren()) { - builder.then(child); + if (!skipChildren) { + for (final CommandNode child : original.getChildren()) { + builder.then(child); + } } - return builder.build(); + return builder; } // Arguments node diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityArgumentCommandNode.java b/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityArgumentCommandNode.java index 9faa29294..03ba6d35e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityArgumentCommandNode.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityArgumentCommandNode.java @@ -93,6 +93,16 @@ public class VelocityArgumentCommandNode extends ArgumentCommandNode withCommand(Command command) { + return new VelocityArgumentCommandNode<>(getName(), type, command, getRequirement(), + getContextRequirement(), getRedirect(), getRedirectModifier(), isFork(), getCustomSuggestions()); + } + + public VelocityArgumentCommandNode withRedirect(CommandNode target) { + return new VelocityArgumentCommandNode<>(getName(), type, getCommand(), getRequirement(), + getContextRequirement(), target, getRedirectModifier(), isFork(), getCustomSuggestions()); + } + @Override public boolean isValidInput(final String input) { return true; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityBrigadierCommandWrapper.java b/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityBrigadierCommandWrapper.java new file mode 100644 index 000000000..8502134cc --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/brigadier/VelocityBrigadierCommandWrapper.java @@ -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 . + */ + +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 { + + private final Command delegate; + private final Object registrant; + + private VelocityBrigadierCommandWrapper(Command 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 wrap(Command 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 context) throws CommandSyntaxException { + return delegate.run(context); + } + + public Object registrant() { + return registrant; + } +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java index 2cf8c92d2..2b73dcc13 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/GlistCommand.java @@ -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 context) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java index 626131daa..d0df03488 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/SendCommand.java @@ -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 context) { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/BrigadierCommandRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/BrigadierCommandRegistrar.java index bc8d02de4..74d3f30ee 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/BrigadierCommandRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/BrigadierCommandRegistrar.java @@ -40,17 +40,19 @@ public final class BrigadierCommandRegistrar extends AbstractCommandRegistrar
literal = command.getNode(); + final LiteralCommandNode wrapped = + (LiteralCommandNode) 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 diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java index a526fe3de..380f45e39 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/registrar/InvocableCommandRegistrar.java @@ -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, final I invocation = invocationFactory.create(context); return command.hasPermission(invocation); }; - final Command callback = context -> { + final Command callback = VelocityBrigadierCommandWrapper.wrap(context -> { final I invocation = invocationFactory.create(context); command.execute(invocation); return 1; // handled - }; + }, meta.getPlugin()); final LiteralCommandNode literal = LiteralArgumentBuilder .literal(alias) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java index 0af477c42..6bd0e0085 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/VelocityPluginManager.java @@ -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)); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/plugin/virtual/VelocityVirtualPlugin.java b/proxy/src/main/java/com/velocitypowered/proxy/plugin/virtual/VelocityVirtualPlugin.java new file mode 100644 index 000000000..08e4f57c3 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/plugin/virtual/VelocityVirtualPlugin.java @@ -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 . + */ + +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() { + } +} diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java index f4110ba52..abbfbc422 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/BrigadierCommandTests.java @@ -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 diff --git a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java index c8e17d0f8..66e49838a 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/command/CommandTestSuite.java @@ -29,6 +29,7 @@ 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.BeforeAll; @@ -48,7 +49,7 @@ abstract class CommandTestSuite { @BeforeEach void setUp() { - this.manager = new VelocityCommandManager(eventManager); + this.manager = new VelocityCommandManager(eventManager, new FakePluginManager()); } final void assertHandled(final String input) { diff --git a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java index b1bc1af72..7992ac52e 100644 --- a/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java +++ b/proxy/src/test/java/com/velocitypowered/proxy/testutil/FakePluginManager.java @@ -22,6 +22,7 @@ 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; @@ -39,6 +40,8 @@ public class FakePluginManager implements PluginManager { 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() @@ -50,6 +53,8 @@ public class FakePluginManager implements PluginManager { return Optional.of(containerA); } else if (instance == PLUGIN_B) { return Optional.of(containerB); + } else if (instance == VelocityVirtualPlugin.INSTANCE) { + return Optional.of(containerVelocity); } else { return Optional.empty(); } @@ -62,6 +67,8 @@ public class FakePluginManager implements PluginManager { return Optional.of(containerA); case "b": return Optional.of(containerB); + case "velocity": + return Optional.of(containerVelocity); default: return Optional.empty(); } @@ -69,7 +76,7 @@ public class FakePluginManager implements PluginManager { @Override public @NonNull Collection getPlugins() { - return ImmutableList.of(containerA, containerB); + return ImmutableList.of(containerVelocity, containerA, containerB); } @Override