Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Brigadier API : supports to redirect to the RootCommandNode #11647

Open
marcbal opened this issue Nov 22, 2024 · 0 comments
Open

Brigadier API : supports to redirect to the RootCommandNode #11647

marcbal opened this issue Nov 22, 2024 · 0 comments

Comments

@marcbal
Copy link
Contributor

marcbal commented Nov 22, 2024

Is your feature request related to a problem?

I have a Brigadier command that has similar functionality to /execute, i.e. to conditionally run commands based on plugin data. Since I moved from using lucko's commodore to using the Lifecycle API to register Brigadier commands, my /execute clone is not able to register anymore when it contains a redirection to the root command node:

[LifecycleEventRunner] Could not run 'commands' lifecycle event handler from PandacubePaper vdev
java.lang.IllegalArgumentException: Unknown command node passed. Don't know how to unwrap this.
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.convertFromPureBrigNode(ApiMirrorRootNode.java:144)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.unwrapNode(ApiMirrorRootNode.java:93)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.simpleUnwrap(ApiMirrorRootNode.java:249)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.convertFromPureBrigNode(ApiMirrorRootNode.java:106)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.unwrapNode(ApiMirrorRootNode.java:93)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.convertFromPureBrigNode(ApiMirrorRootNode.java:155)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.unwrapNode(ApiMirrorRootNode.java:93)~
at io.papermc.paper.command.brigadier.ApiMirrorRootNode.addChild(ApiMirrorRootNode.java:205)~
at io.papermc.paper.command.brigadier.PaperCommands.registerIntoDispatcher(PaperCommands.java:156)~
at io.papermc.paper.command.brigadier.PaperCommands.registerWithFlags(PaperCommands.java:102)~
at io.papermc.paper.command.brigadier.PaperCommands.register(PaperCommands.java:90)~
at io.papermc.paper.command.brigadier.PaperCommands.register(PaperCommands.java:85)~
at PandacubePaper-1668.jar/fr.pandacube.lib.paper.commands.PaperBrigadierCommand.lambda$register$6(PaperBrigadierCommand.java:169)~
at io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.lambda$callEvent$2(LifecycleEventRunner.java:68)~
at io.papermc.paper.plugin.lifecycle.event.types.PrioritizableLifecycleEventType.forEachHandler(PrioritizableLifecycleEventType.java:53)~
at io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.callEvent(LifecycleEventRunner.java:63)~
at io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.callReloadableRegistrarEvent(LifecycleEventRunner.java:107)~
at NMS.MinecraftServer.loadWorld0(MinecraftServer.java:678)~
at NMS.MinecraftServer.loadLevel(MinecraftServer.java:437)~
at NMS.dedicated.DedicatedServer.initServer(DedicatedServer.java:323)~
at NMS.MinecraftServer.runServer(MinecraftServer.java:1136)~
at NMS.MinecraftServer.lambda$spin$0(MinecraftServer.java:323)~
at java.base/java.lang.Thread.run(Thread.java:1583)~

It seems like the node unwrapper does not support encountering the RootCommandNode instance in the tree, since it’s not a subclass for LiteralCommandNode or ArgumentCommandNode:

+ private @NotNull CommandNode<CommandSourceStack> convertFromPureBrigNode(final CommandNode<CommandSourceStack> pureNode) {
+ /*
+ Logic for converting a node.
+ */
+ final CommandNode<CommandSourceStack> converted;
+ if (pureNode instanceof final LiteralCommandNode<CommandSourceStack> node) {
+ /*
+ Remap the literal node, we only have to account
+ for the redirect in this case.
+ */
+ converted = this.simpleUnwrap(node);
+ } else if (pureNode instanceof final ArgumentCommandNode<CommandSourceStack, ?> pureArgumentNode) {
+ final ArgumentType<?> pureArgumentType = pureArgumentNode.getType();
+ /*
+ Check to see if this argument type is a wrapped type, if so we know that
+ we can unwrap the node to get an NMS type.
+ */
+ if (pureArgumentType instanceof final CustomArgumentType<?, ?> customArgumentType) {
+ final SuggestionProvider<?> suggestionProvider;
+ try {
+ final Method listSuggestions = customArgumentType.getClass().getMethod("listSuggestions", CommandContext.class, SuggestionsBuilder.class);
+ if (listSuggestions.getDeclaringClass() != CustomArgumentType.class) {
+ suggestionProvider = customArgumentType::listSuggestions;
+ } else {
+ suggestionProvider = null;
+ }
+ } catch (final NoSuchMethodException ex) {
+ throw new IllegalStateException("Could not determine if the custom argument type " + customArgumentType + " overrides listSuggestions", ex);
+ }
+
+ converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider);
+ } else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) {
+ converted = this.unwrapArgumentWrapper(pureArgumentNode, nativeWrapperArgumentType, nativeWrapperArgumentType, null); // "null" for suggestion provider so it uses the argument type's suggestion provider
+
+ /*
+ If it's not a wrapped type, it either has to be a primitive or an already
+ defined NMS type.
+ This method allows us to check if this is recognized by vanilla.
+ */
+ } else if (ArgumentTypeInfos.isClassRecognized(pureArgumentType.getClass())) {
+ // Allow any type of argument, as long as it's recognized by the client (but in most cases, this should be API only types)
+ // Previously we only allowed whitelisted types.
+ converted = this.simpleUnwrap(pureArgumentNode);
+ } else {
+ // Unknown argument type was passed
+ throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType.");
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this.");
+ }
+

Describe the solution you'd like.

Being able to do this :

getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
    event.registrar().register(literal("myexecute")
            .then(literal("run")
                    .redirect(event.registrar().getDispatcher().getRoot())
            )
            .build()
    );
});

Then run the following command in chat or command block: /myexecute run tp @p 100 70 100

Describe alternatives you've considered.

  • Take the conditionally run command into a StringArgument and run it through Bukkit.dispatchCommand() if conditions are met
  • Use reflection to add the feature myself:
getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {
    CommandDispatcher<CommandSourceStack> dispatcher = event.registrar().getDispatcher();
    RootCommandNode<CommandSourceStack> wrappedRoot = dispatcher.getRoot();
    ReflectClass<?> apiMirrorRootNodeClass = Reflect.ofClassOfInstance(wrappedRoot);
    try {
        RootCommandNode<?> unwrappedRoot = ((CommandDispatcher<?>) apiMirrorRootNodeClass.method("getDispatcher").invoke(wrappedRoot)).getRoot();

        Reflect.ofClass(CommandNode.class).field("unwrappedCached").setValue(wrappedRoot, unwrappedRoot);
        Reflect.ofClass(CommandNode.class).field("wrappedCached").setValue(unwrappedRoot, wrappedRoot);

    } catch (InvocationTargetException|IllegalAccessException|NoSuchMethodException|NoSuchFieldException e) {
        Log.severe("Unable to trick the Paper/Brigadier unwrapper to properly handle commands redirecting to root command node.", e);
    }
});

Other

No response

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant