1
0
Fork 0
mirror of https://github.com/PaperMC/Paper.git synced 2025-03-28 17:27:20 +01:00

Don't auto-create any brig redirects ()

This commit is contained in:
Jake Potrebic 2025-02-16 13:55:27 -08:00 committed by GitHub
parent 88cdd22076
commit 84609dc046
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 95 additions and 166 deletions
paper-api/src/main/java/io/papermc/paper/command/brigadier
paper-server
patches/sources
com/mojang/brigadier/tree
net/minecraft/commands
src/main/java/io/papermc/paper/command/brigadier

View file

@ -10,5 +10,10 @@ import org.jetbrains.annotations.ApiStatus;
*/
@ApiStatus.Internal
public enum CommandRegistrationFlag {
/**
* @deprecated This is the default behavior now.
*/
@Deprecated(since = "1.21.4")
FLATTEN_ALIASES
}

View file

@ -113,6 +113,7 @@ public interface Commands extends Registrar {
* <p>Commands have certain overriding behavior:
* <ul>
* <li>Aliases will not override already existing commands (excluding namespaced ones)</li>
* <li>Aliases are <b>not</b> Brigadier redirects, they just copy the command to a different label</li>
* <li>The main command/namespaced label will override already existing commands</li>
* </ul>
*
@ -129,6 +130,7 @@ public interface Commands extends Registrar {
* <p>Commands have certain overriding behavior:
* <ul>
* <li>Aliases will not override already existing commands (excluding namespaced ones)</li>
* <li>Aliases are <b>not</b> Brigadier redirects, they just copy the command to a different label</li>
* <li>The main command/namespaced label will override already existing commands</li>
* </ul>
*
@ -146,6 +148,7 @@ public interface Commands extends Registrar {
* <p>Commands have certain overriding behavior:
* <ul>
* <li>Aliases will not override already existing commands (excluding namespaced ones)</li>
* <li>Aliases are <b>not</b> Brigadier redirects, they just copy the command to a different label</li>
* <li>The main command/namespaced label will override already existing commands</li>
* </ul>
*
@ -163,6 +166,7 @@ public interface Commands extends Registrar {
* <p>Commands have certain overriding behavior:
* <ul>
* <li>Aliases will not override already existing commands (excluding namespaced ones)</li>
* <li>Aliases are <b>not</b> Brigadier redirects, they just copy the command to a different label</li>
* <li>The main command/namespaced label will override already existing commands</li>
* </ul>
*
@ -179,6 +183,7 @@ public interface Commands extends Registrar {
* <p>Commands have certain overriding behavior:
* <ul>
* <li>Aliases will not override already existing commands (excluding namespaced ones)</li>
* <li>Aliases are <b>not</b> Brigadier redirects, they just copy the command to a different label</li>
* <li>The main command/namespaced label will override already existing commands</li>
* </ul>
*

View file

@ -1,6 +1,6 @@
--- a/com/mojang/brigadier/tree/CommandNode.java
+++ b/com/mojang/brigadier/tree/CommandNode.java
@@ -27,11 +_,21 @@
@@ -27,11 +_,22 @@
private final Map<String, CommandNode<S>> children = new LinkedHashMap<>();
private final Map<String, LiteralCommandNode<S>> literals = new LinkedHashMap<>();
private final Map<String, ArgumentCommandNode<S, ?>> arguments = new LinkedHashMap<>();
@ -13,6 +13,7 @@
+ public CommandNode<net.minecraft.commands.CommandSourceStack> clientNode; // Paper - Brigadier API
+ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> unwrappedCached = null; // Paper - Brigadier Command API
+ public CommandNode<io.papermc.paper.command.brigadier.CommandSourceStack> wrappedCached = null; // Paper - Brigadier Command API
+ public io.papermc.paper.command.brigadier.PluginCommandMeta pluginCommandMeta; // Paper - Brigadier Command API
+ // CraftBukkit start
+ public void removeCommand(String name) {
+ this.children.remove(name);

View file

@ -1,6 +1,6 @@
--- a/net/minecraft/commands/Commands.java
+++ b/net/minecraft/commands/Commands.java
@@ -251,6 +_,30 @@
@@ -251,6 +_,24 @@
PublishCommand.register(this.dispatcher);
}
@ -14,17 +14,11 @@
+ // Paper start - Brigadier Command API
+ // Create legacy minecraft namespace commands
+ for (final CommandNode<CommandSourceStack> node : new java.util.ArrayList<>(this.dispatcher.getRoot().getChildren())) {
+ // The brigadier dispatcher is not able to resolve nested redirects.
+ // E.g. registering the alias minecraft:tp cannot redirect to tp, as tp itself redirects to teleport.
+ // Instead, target the first none redirecting node.
+ CommandNode<CommandSourceStack> flattenedAliasTarget = node;
+ while (flattenedAliasTarget.getRedirect() != null) flattenedAliasTarget = flattenedAliasTarget.getRedirect();
+
+ this.dispatcher.register(
+ com.mojang.brigadier.builder.LiteralArgumentBuilder.<CommandSourceStack>literal("minecraft:" + node.getName())
+ .executes(flattenedAliasTarget.getCommand())
+ .requires(flattenedAliasTarget.getRequirement())
+ .redirect(flattenedAliasTarget)
+ this.dispatcher.getRoot().addChild(
+ io.papermc.paper.command.brigadier.PaperBrigadier.copyLiteral(
+ "minecraft:" + node.getName(),
+ (com.mojang.brigadier.tree.LiteralCommandNode<CommandSourceStack>) node
+ )
+ );
+ }
+ // Paper end - Brigadier Command API
@ -150,11 +144,10 @@
}
return null;
@@ -360,25 +_,130 @@
@@ -360,26 +_,85 @@
}
public void sendCommands(ServerPlayer player) {
- Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
+ // Paper start - Send empty commands if tab completion is disabled
+ if (org.spigotmc.SpigotConfig.tabComplete < 0) {
+ player.connection.send(new ClientboundCommandsPacket(new RootCommandNode<>()));
@ -182,7 +175,7 @@
+
+ private void sendAsync(ServerPlayer player, java.util.Collection<CommandNode<CommandSourceStack>> dispatcherRootChildren) {
+ // Paper end - Perf: Async command map building
+ Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newIdentityHashMap(); // Use identity to prevent aliasing issues
Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> map = Maps.newHashMap();
RootCommandNode<SharedSuggestionProvider> rootCommandNode = new RootCommandNode<>();
map.put(this.dispatcher.getRoot(), rootCommandNode);
- this.fillUsableCommands(this.dispatcher.getRoot(), rootCommandNode, player.createCommandSourceStack(), map);
@ -224,7 +217,6 @@
Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> commandNodeToSuggestionNode
) {
- for (CommandNode<CommandSourceStack> commandNode : rootCommandSource.getChildren()) {
+ commandNodeToSuggestionNode.keySet().removeIf((node) -> !org.spigotmc.SpigotConfig.sendNamespaced && node.getName().contains(":")); // Paper - Remove namedspaced from result nodes to prevent redirect trimming ~ see comment below
+ for (CommandNode<CommandSourceStack> commandNode : children) { // Paper - Perf: Async command map building; pass copy of children
+ // Paper start - Brigadier API
+ if (commandNode.clientNode != null) {
@ -234,58 +226,16 @@
+ if (!org.spigotmc.SpigotConfig.sendNamespaced && commandNode.getName().contains(":")) continue; // Spigot
if (commandNode.canUse(source)) {
ArgumentBuilder<SharedSuggestionProvider, ?> argumentBuilder = (ArgumentBuilder) commandNode.createBuilder();
+ // Paper start
+ /*
+ Because of how commands can be yeeted right left and center due to bad bukkit practices
+ we need to be able to ensure that ALL commands are registered (even redirects).
+
+ What this will do is IF the redirect seems to be "dead" it will create a builder and essentially populate (flatten)
+ all the children from the dead redirect to the node.
+
+ So, if minecraft:msg redirects to msg but the original msg node has been overriden minecraft:msg will now act as msg and will explicilty inherit its children.
+
+ The only way to fix this is to either:
+ - Send EVERYTHING flattened, don't use redirects
+ - Don't allow command nodes to be deleted
+ - Do this :)
+ */
+
+ // Is there an invalid command redirect?
+ if (argumentBuilder.getRedirect() != null && commandNodeToSuggestionNode.get(argumentBuilder.getRedirect()) == null) {
+ // Create the argument builder with the same values as the specified node, but with a different literal and populated children
+
+ CommandNode<SharedSuggestionProvider> redirect = argumentBuilder.getRedirect();
+ // Diff copied from LiteralCommand#createBuilder
+ final com.mojang.brigadier.builder.LiteralArgumentBuilder<SharedSuggestionProvider> builder = com.mojang.brigadier.builder.LiteralArgumentBuilder.literal(commandNode.getName());
+ builder.requires(redirect.getRequirement());
+ // builder.forward(redirect.getRedirect(), redirect.getRedirectModifier(), redirect.isFork()); We don't want to migrate the forward, since it's invalid.
+ if (redirect.getCommand() != null) {
+ builder.executes(redirect.getCommand());
+ }
+ // Diff copied from LiteralCommand#createBuilder
+ for (CommandNode<SharedSuggestionProvider> child : redirect.getChildren()) {
+ builder.then(child);
+ }
+
+ argumentBuilder = builder;
+ }
+ // Paper end
argumentBuilder.requires(suggestions -> true);
if (argumentBuilder.getCommand() != null) {
- if (argumentBuilder.getCommand() != null) {
- argumentBuilder.executes(commandContext -> 0);
+ // Paper start - fix suggestions due to falsely equal nodes
+ // Always create a new instance
+ //noinspection Convert2Lambda
+ argumentBuilder.executes(new com.mojang.brigadier.Command<>() {
+ @Override
+ public int run(com.mojang.brigadier.context.CommandContext<SharedSuggestionProvider> commandContext) {
+ return 0;
+ }
+ });
+ // Paper end - fix suggestions due to falsely equal nodes
}
- }
+ // Paper - don't replace Command instance on suggestion node
+ // we want the exact command instance to be used for equality checks
+ // when assigning serialization ids to each command node
if (argumentBuilder instanceof RequiredArgumentBuilder) {
RequiredArgumentBuilder<SharedSuggestionProvider, ?> requiredArgumentBuilder = (RequiredArgumentBuilder<SharedSuggestionProvider, ?>)argumentBuilder;
@@ -396,7 +_,7 @@
commandNodeToSuggestionNode.put(commandNode, commandNode1);
rootSuggestion.addChild(commandNode1);

View file

@ -124,7 +124,7 @@ public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceSta
}
converted = this.unwrapArgumentWrapper(pureArgumentNode, customArgumentType, customArgumentType.getNativeType(), suggestionProvider);
} else if (pureArgumentType instanceof final VanillaArgumentProviderImpl.NativeWrapperArgumentType<?,?> nativeWrapperArgumentType) {
} 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
/*
@ -140,6 +140,8 @@ public abstract class ApiMirrorRootNode extends RootCommandNode<CommandSourceSta
// Unknown argument type was passed
throw new IllegalArgumentException("Custom unknown argument type was passed, should be wrapped inside an CustomArgumentType.");
}
} else if (pureNode == this) {
return (CommandNode) this.getDispatcher().getRoot();
} else {
throw new IllegalArgumentException("Unknown command node passed. Don't know how to unwrap this.");
}

View file

@ -1,24 +1,19 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.bukkit.BukkitBrigForwardingMap;
import io.papermc.paper.command.brigadier.bukkit.BukkitCommandNode;
import java.util.Map;
import net.minecraft.commands.CommandSource;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.craftbukkit.command.VanillaCommandWrapper;
import java.util.Map;
public final class PaperBrigadier {
@SuppressWarnings("DataFlowIssue")
@ -40,7 +35,8 @@ public final class PaperBrigadier {
throw new IllegalArgumentException("Unsure how to wrap a " + node);
}
if (!(node instanceof PluginCommandNode pluginCommandNode)) {
final PluginCommandMeta meta;
if ((meta = node.pluginCommandMeta) == null) {
return new VanillaCommandWrapper(null, node);
}
CommandNode<CommandSourceStack> argumentCommandNode = node;
@ -49,8 +45,8 @@ public final class PaperBrigadier {
}
Map<CommandNode<CommandSourceStack>, String> map = PaperCommands.INSTANCE.getDispatcherInternal().getSmartUsage(argumentCommandNode, DUMMY);
String usage = map.isEmpty() ? pluginCommandNode.getUsageText() : pluginCommandNode.getUsageText() + " " + String.join("\n" + pluginCommandNode.getUsageText() + " ", map.values());
return new PluginVanillaCommandWrapper(pluginCommandNode.getName(), pluginCommandNode.getDescription(), usage, pluginCommandNode.getAliases(), node, pluginCommandNode.getPlugin());
String usage = map.isEmpty() ? node.getUsageText() : node.getUsageText() + " " + String.join("\n" + node.getUsageText() + " ", map.values());
return new PluginVanillaCommandWrapper(node.getName(), meta.description(), usage, meta.aliases(), node, meta.plugin());
}
/*
@ -70,4 +66,19 @@ public final class PaperBrigadier {
}
}
}
public static <S> LiteralCommandNode<S> copyLiteral(final String newLiteral, final LiteralCommandNode<S> source) {
// logic copied from LiteralCommandNode#createBuilder
final LiteralArgumentBuilder<S> copyBuilder = LiteralArgumentBuilder.<S>literal(newLiteral)
.requires(source.getRequirement())
.forward(source.getRedirect(), source.getRedirectModifier(), source.isFork());
if (source.getCommand() != null) {
copyBuilder.executes(source.getCommand());
}
for (final CommandNode<S> child : source.getChildren()) {
copyBuilder.then(child);
}
return copyBuilder.build();
}
}

View file

@ -21,23 +21,20 @@ import java.util.Set;
import net.minecraft.commands.CommandBuildContext;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static java.util.Objects.requireNonNull;
@DefaultQualifier(NonNull.class)
@NullMarked
public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwner> {
public static final PaperCommands INSTANCE = new PaperCommands();
private @Nullable LifecycleEventOwner currentContext;
private @MonotonicNonNull CommandDispatcher<CommandSourceStack> dispatcher;
private @MonotonicNonNull CommandBuildContext buildContext;
private @Nullable CommandDispatcher<CommandSourceStack> dispatcher;
private @Nullable CommandBuildContext buildContext;
private boolean invalid = false;
@Override
@ -93,65 +90,47 @@ public class PaperCommands implements Commands, PaperRegistrar<LifecycleEventOwn
}
@Override
public @Unmodifiable Set<String> registerWithFlags(@NotNull final PluginMeta pluginMeta, @NotNull final LiteralCommandNode<CommandSourceStack> node, @org.jetbrains.annotations.Nullable final String description, @NotNull final Collection<String> aliases, @NotNull final Set<CommandRegistrationFlag> flags) {
final boolean hasFlattenRedirectFlag = flags.contains(CommandRegistrationFlag.FLATTEN_ALIASES);
public @Unmodifiable Set<String> registerWithFlags(final PluginMeta pluginMeta, final LiteralCommandNode<CommandSourceStack> node, final @Nullable String description, final Collection<String> aliases, final Set<CommandRegistrationFlag> flags) {
final PluginCommandMeta meta = new PluginCommandMeta(pluginMeta, description);
final String identifier = pluginMeta.getName().toLowerCase(Locale.ROOT);
final String literal = node.getLiteral();
final PluginCommandNode pluginLiteral = new PluginCommandNode(identifier + ":" + literal, pluginMeta, node, description); // Treat the keyed version of the command as the root
final LiteralCommandNode<CommandSourceStack> pluginLiteral = PaperBrigadier.copyLiteral(identifier + ":" + literal, node);
final Set<String> registeredLabels = new HashSet<>(aliases.size() * 2 + 2);
if (this.registerIntoDispatcher(pluginLiteral, true)) {
registeredLabels.add(pluginLiteral.getLiteral());
}
if (this.registerRedirect(literal, pluginMeta, pluginLiteral, description, true, hasFlattenRedirectFlag)) { // Plugin commands should override vanilla commands
if (this.registerIntoDispatcher(node, true)) { // Plugin commands should override vanilla commands
registeredLabels.add(literal);
}
// Add aliases
final List<String> registeredAliases = new ArrayList<>(aliases.size() * 2);
for (final String alias : aliases) {
if (this.registerRedirect(alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
if (this.registerCopy(alias, pluginLiteral, meta)) {
registeredAliases.add(alias);
}
if (this.registerRedirect(identifier + ":" + alias, pluginMeta, pluginLiteral, description, false, hasFlattenRedirectFlag)) {
if (this.registerCopy(identifier + ":" + alias, pluginLiteral, meta)) {
registeredAliases.add(identifier + ":" + alias);
}
}
if (!registeredAliases.isEmpty()) {
pluginLiteral.setAliases(registeredAliases);
}
pluginLiteral.pluginCommandMeta = new PluginCommandMeta(pluginMeta, description, registeredAliases);
registeredLabels.addAll(registeredAliases);
return registeredLabels.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(registeredLabels);
}
private boolean registerRedirect(final String aliasLiteral, final PluginMeta plugin, final PluginCommandNode redirectTo, final @Nullable String description, final boolean override, boolean hasFlattenRedirectFlag) {
final LiteralCommandNode<CommandSourceStack> redirect;
if (redirectTo.getChildren().isEmpty() || hasFlattenRedirectFlag) {
redirect = Commands.literal(aliasLiteral)
.executes(redirectTo.getCommand())
.requires(redirectTo.getRequirement())
.build();
for (final CommandNode<CommandSourceStack> child : redirectTo.getChildren()) {
redirect.addChild(child);
}
} else {
redirect = Commands.literal(aliasLiteral)
.executes(redirectTo.getCommand())
.redirect(redirectTo)
.requires(redirectTo.getRequirement())
.build();
}
return this.registerIntoDispatcher(new PluginCommandNode(aliasLiteral, plugin, redirect, description), override);
private boolean registerCopy(final String aliasLiteral, final LiteralCommandNode<CommandSourceStack> redirectTo, final PluginCommandMeta meta) {
final LiteralCommandNode<CommandSourceStack> node = PaperBrigadier.copyLiteral(aliasLiteral, redirectTo);
node.pluginCommandMeta = meta;
return this.registerIntoDispatcher(node, false);
}
private boolean registerIntoDispatcher(final PluginCommandNode node, boolean override) {
final @Nullable CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
if (existingChild != null && !(existingChild instanceof PluginCommandNode) && !(existingChild instanceof BukkitCommandNode)) {
private boolean registerIntoDispatcher(final LiteralCommandNode<CommandSourceStack> node, boolean override) {
final CommandNode<CommandSourceStack> existingChild = this.getDispatcher().getRoot().getChild(node.getLiteral());
if (existingChild != null && existingChild.pluginCommandMeta == null && !(existingChild instanceof BukkitCommandNode)) {
override = true; // override vanilla commands
}
if (existingChild == null || override) { // Avoid merging behavior. Maybe something to look into in the future

View file

@ -0,0 +1,26 @@
package io.papermc.paper.command.brigadier;
import io.papermc.paper.plugin.configuration.PluginMeta;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public record PluginCommandMeta(PluginMeta pluginMeta, @Nullable String description, List<String> aliases) {
public PluginCommandMeta(final PluginMeta pluginMeta, final @Nullable String description) {
this(pluginMeta, description, Collections.emptyList());
}
public PluginCommandMeta {
aliases = List.copyOf(aliases);
}
public Plugin plugin() {
return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.pluginMeta.getName()));
}
}

View file

@ -1,50 +0,0 @@
package io.papermc.paper.command.brigadier;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import io.papermc.paper.plugin.configuration.PluginMeta;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class PluginCommandNode extends LiteralCommandNode<CommandSourceStack> {
private final PluginMeta plugin;
private final String description;
private List<String> aliases = Collections.emptyList();
public PluginCommandNode(final @NotNull String literal, final @NotNull PluginMeta plugin, final @NotNull LiteralCommandNode<CommandSourceStack> rootLiteral, final @Nullable String description) {
super(
literal, rootLiteral.getCommand(), rootLiteral.getRequirement(),
rootLiteral.getRedirect(), rootLiteral.getRedirectModifier(), rootLiteral.isFork()
);
this.plugin = plugin;
this.description = description;
for (CommandNode<CommandSourceStack> argument : rootLiteral.getChildren()) {
this.addChild(argument);
}
}
@NotNull
public Plugin getPlugin() {
return Objects.requireNonNull(Bukkit.getPluginManager().getPlugin(this.plugin.getName()));
}
@NotNull
public String getDescription() {
return this.description;
}
public void setAliases(List<String> aliases) {
this.aliases = aliases;
}
public List<String> getAliases() {
return this.aliases;
}
}