diff --git a/patches/minecraft/net/minecraft/commands/Commands.java.patch b/patches/minecraft/net/minecraft/commands/Commands.java.patch index 0db5752866..34e491ee72 100644 --- a/patches/minecraft/net/minecraft/commands/Commands.java.patch +++ b/patches/minecraft/net/minecraft/commands/Commands.java.patch @@ -36,3 +36,13 @@ } catch (CommandRuntimeException commandruntimeexception) { p_82118_.m_81352_(commandruntimeexception.m_79226_()); return 0; +@@ -276,7 +_,8 @@ + Map, CommandNode> map = Maps.newHashMap(); + RootCommandNode rootcommandnode = new RootCommandNode<>(); + map.put(this.f_82090_.getRoot(), rootcommandnode); +- this.m_82112_(this.f_82090_.getRoot(), rootcommandnode, p_82096_.m_20203_(), map); ++ // FORGE: Use our own command node merging method to handle redirect nodes properly, see issue #7551 ++ net.minecraftforge.server.command.CommandHelper.mergeCommandNode(this.f_82090_.getRoot(), rootcommandnode, map, p_82096_.m_20203_(), ctx -> 0, suggest -> SuggestionProviders.m_121664_((com.mojang.brigadier.suggestion.SuggestionProvider) (com.mojang.brigadier.suggestion.SuggestionProvider) suggest)); + p_82096_.f_8906_.m_141995_(new ClientboundCommandsPacket(rootcommandnode)); + } + diff --git a/src/main/java/net/minecraftforge/client/ClientCommandHandler.java b/src/main/java/net/minecraftforge/client/ClientCommandHandler.java index a1d0b70d36..f09476fa9c 100644 --- a/src/main/java/net/minecraftforge/client/ClientCommandHandler.java +++ b/src/main/java/net/minecraftforge/client/ClientCommandHandler.java @@ -5,18 +5,13 @@ package net.minecraftforge.client; -import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.ParseResults; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.tree.ArgumentCommandNode; import com.mojang.brigadier.tree.CommandNode; -import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.RootCommandNode; import net.minecraft.ChatFormatting; import net.minecraft.Util; @@ -36,12 +31,12 @@ import net.minecraft.network.chat.TranslatableComponent; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.RegisterClientCommandsEvent; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.server.command.CommandHelper; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.IdentityHashMap; import java.util.Map; -import java.util.function.Function; public class ClientCommandHandler { @@ -66,7 +61,7 @@ public class ClientCommandHandler /* * For internal use - * + * * Merges command dispatcher use for suggestions to the command dispatcher used for client commands so they can be sent to the server, and vice versa so client commands appear * with server commands in suggestions */ @@ -86,11 +81,11 @@ public class ClientCommandHandler // Copies the server side commands into a temporary server side commands root node to be used later without the client commands RootCommandNode serverCommandsCopy = new RootCommandNode<>(); - mergeCommandNode(newServerCommands.getRoot(), serverCommandsCopy, new IdentityHashMap<>(), + CommandHelper.mergeCommandNode(newServerCommands.getRoot(), serverCommandsCopy, new IdentityHashMap<>(), Minecraft.getInstance().getConnection().getSuggestionsProvider(), (context) -> 0, (suggestions) -> null); // Copies the client side commands into the server side commands to be used for suggestions - mergeCommandNode(commands.getRoot(), newServerCommands.getRoot(), new IdentityHashMap<>(), getSource(), (context) -> 0, (suggestions) -> { + CommandHelper.mergeCommandNode(commands.getRoot(), newServerCommands.getRoot(), new IdentityHashMap<>(), getSource(), (context) -> 0, (suggestions) -> { SuggestionProvider suggestionProvider = SuggestionProviders .safelySwap((SuggestionProvider) (SuggestionProvider) suggestions); if (suggestionProvider == SuggestionProviders.ASK_SERVER) @@ -111,7 +106,7 @@ public class ClientCommandHandler }); // Copies the server side commands into the client side commands so that they can be sent to the server as a chat message - mergeCommandNode(serverCommandsCopy, commands.getRoot(), new IdentityHashMap<>(), Minecraft.getInstance().getConnection().getSuggestionsProvider(), + CommandHelper.mergeCommandNode(serverCommandsCopy, commands.getRoot(), new IdentityHashMap<>(), Minecraft.getInstance().getConnection().getSuggestionsProvider(), (context) -> { Minecraft.getInstance().player.chat((context.getInput().startsWith("/") ? "" : "/") + context.getInput()); return 0; @@ -138,9 +133,9 @@ public class ClientCommandHandler } /** - * + * * Creates a deep copy of the sourceNode while keeping the redirects referring to the old command tree - * + * * @param sourceNode * the original * @param resultNode @@ -163,106 +158,12 @@ public class ClientCommandHandler } } - /** - * - * Deep copies the children of a command node and stores a link between the source and the copy - * - * @param sourceNode - * the original command node - * @param resultNode - * the result command node - * @param sourceToResult - * a map storing the original command node as the key and the result command node as the value - * @param canUse - * used to check if the player can use the command - * @param execute - * the command to execute in place of the old command - * @param sourceToResultSuggestion - * a function to convert from the {@link SuggestionProvider} with the original source stack to the {@link SuggestionProvider} with the result source stack - */ - private static void mergeCommandNode(CommandNode sourceNode, CommandNode resultNode, Map, CommandNode> sourceToResult, - S canUse, Command execute, Function, SuggestionProvider> sourceToResultSuggestion) - { - sourceToResult.put(sourceNode, resultNode); - for (CommandNode sourceChild : sourceNode.getChildren()) - { - if (sourceChild.canUse(canUse)) - { - resultNode.addChild(toResult(sourceChild, sourceToResult, canUse, execute, sourceToResultSuggestion)); - } - } - } - - /** - * - * Creates a deep copy of a command node with a different source stack - * - * @param sourceNode - * the original command node - * @param sourceToResult - * a map storing the original command node as the key and the result command node as the value - * @param canUse - * used to check if the player can use the command - * @param execute - * the command to execute in place of the old command - * @param sourceToResultSuggestion - * a function to convert from the {@link SuggestionProvider} with the original source stack to the {@link SuggestionProvider} with the result source stack - * @return the deep copied command node with the new source stack - */ - private static CommandNode toResult(CommandNode sourceNode, Map, CommandNode> sourceToResult, S canUse, Command execute, - Function, SuggestionProvider> sourceToResultSuggestion) - { - if (sourceToResult.containsKey(sourceNode)) - return sourceToResult.get(sourceNode); - - ArgumentBuilder resultBuilder; - if (sourceNode instanceof ArgumentCommandNode) - { - ArgumentCommandNode sourceArgument = (ArgumentCommandNode) sourceNode; - RequiredArgumentBuilder resultArgumentBuilder = RequiredArgumentBuilder.argument(sourceArgument.getName(), sourceArgument.getType()); - if (sourceArgument.getCustomSuggestions() != null) - { - resultArgumentBuilder.suggests(sourceToResultSuggestion.apply(sourceArgument.getCustomSuggestions())); - } - resultBuilder = resultArgumentBuilder; - } - else if (sourceNode instanceof LiteralCommandNode) - { - LiteralCommandNode sourceLiteral = (LiteralCommandNode) sourceNode; - resultBuilder = LiteralArgumentBuilder.literal(sourceLiteral.getLiteral()); - } - else if (sourceNode instanceof RootCommandNode) - { - CommandNode resultNode = new RootCommandNode<>(); - mergeCommandNode(sourceNode, resultNode, sourceToResult, canUse, execute, sourceToResultSuggestion); - return resultNode; - } - else - { - throw new IllegalStateException("Node type " + sourceNode + " is not a standard node type"); - } - - if (sourceNode.getCommand() != null) - { - resultBuilder.executes(execute); - } - - if (sourceNode.getRedirect() != null) - { - resultBuilder.redirect(toResult(sourceNode.getRedirect(), sourceToResult, canUse, execute, sourceToResultSuggestion)); - } - - CommandNode resultNode = resultBuilder.build(); - mergeCommandNode(sourceNode, resultNode, sourceToResult, canUse, execute, sourceToResultSuggestion); - return resultNode; - } - /** * Always try to execute the cached parsing of client message as a command. Requires that the execute field of the commands to be set to send to server so that they aren't * treated as client command's that do nothing. - * + * * {@link net.minecraft.commands.Commands#performCommand(CommandSourceStack, String)} for reference - * + * * @param sendMessage * the chat message * @return false leaves the message to be sent to the server, true means it should be caught before {@link LocalPlayer#chat(String)} diff --git a/src/main/java/net/minecraftforge/server/command/CommandHelper.java b/src/main/java/net/minecraftforge/server/command/CommandHelper.java new file mode 100644 index 0000000000..0ddf163a7a --- /dev/null +++ b/src/main/java/net/minecraftforge/server/command/CommandHelper.java @@ -0,0 +1,111 @@ +/* + * Minecraft Forge - Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.server.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; + +import java.util.Map; +import java.util.function.Function; + +/** + * Internal utility class for various command-related operations. + * + * For internal Forge use only. + * + * @hidden + */ +public final class CommandHelper +{ + private CommandHelper() + { + } + + /** + * Deep copies the children of a command node and stores a link between the source and the copy + * + * @param sourceNode the original command node + * @param resultNode the result command node + * @param sourceToResult a map storing the original command node as the key and the result command node as the value + * @param canUse used to check if the player can use the command + * @param execute the command to execute in place of the old command + * @param sourceToResultSuggestion a function to convert from the {@link SuggestionProvider} with the original source stack to the {@link SuggestionProvider} with the result source stack + */ + public static void mergeCommandNode(CommandNode sourceNode, CommandNode resultNode, Map, CommandNode> sourceToResult, + S canUse, Command execute, Function, SuggestionProvider> sourceToResultSuggestion) + { + sourceToResult.put(sourceNode, resultNode); + for (CommandNode sourceChild : sourceNode.getChildren()) + { + if (sourceChild.canUse(canUse)) + { + resultNode.addChild(toResult(sourceChild, sourceToResult, canUse, execute, sourceToResultSuggestion)); + } + } + } + + /** + * Creates a deep copy of a command node with a different source stack + * + * @param sourceNode the original command node + * @param sourceToResult a map storing the original command node as the key and the result command node as the value + * @param canUse used to check if the player can use the command + * @param execute the command to execute in place of the old command + * @param sourceToResultSuggestion a function to convert from the {@link SuggestionProvider} with the original source stack to the {@link SuggestionProvider} with the result source stack + * @return the deep copied command node with the new source stack + */ + private static CommandNode toResult(CommandNode sourceNode, Map, CommandNode> sourceToResult, S canUse, Command execute, + Function, SuggestionProvider> sourceToResultSuggestion) + { + if (sourceToResult.containsKey(sourceNode)) + return sourceToResult.get(sourceNode); + + ArgumentBuilder resultBuilder; + if (sourceNode instanceof ArgumentCommandNode) + { + ArgumentCommandNode sourceArgument = (ArgumentCommandNode) sourceNode; + RequiredArgumentBuilder resultArgumentBuilder = RequiredArgumentBuilder.argument(sourceArgument.getName(), sourceArgument.getType()); + if (sourceArgument.getCustomSuggestions() != null) + { + resultArgumentBuilder.suggests(sourceToResultSuggestion.apply(sourceArgument.getCustomSuggestions())); + } + resultBuilder = resultArgumentBuilder; + } else if (sourceNode instanceof LiteralCommandNode) + { + LiteralCommandNode sourceLiteral = (LiteralCommandNode) sourceNode; + resultBuilder = LiteralArgumentBuilder.literal(sourceLiteral.getLiteral()); + } else if (sourceNode instanceof RootCommandNode) + { + CommandNode resultNode = new RootCommandNode<>(); + mergeCommandNode(sourceNode, resultNode, sourceToResult, canUse, execute, sourceToResultSuggestion); + return resultNode; + } else + { + throw new IllegalStateException("Node type " + sourceNode + " is not a standard node type"); + } + + if (sourceNode.getCommand() != null) + { + resultBuilder.executes(execute); + } + + if (sourceNode.getRedirect() != null) + { + resultBuilder.redirect(toResult(sourceNode.getRedirect(), sourceToResult, canUse, execute, sourceToResultSuggestion)); + } + + CommandNode resultNode = resultBuilder.build(); + mergeCommandNode(sourceNode, resultNode, sourceToResult, canUse, execute, sourceToResultSuggestion); + return resultNode; + } +}