-
-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
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:
Paper/patches/server/0955-Brigadier-based-command-API.patch
Lines 355 to 405 in 817550c
| + 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