diff --git a/api/src/main/java/net/md_5/bungee/api/event/CommandsDeclareEvent.java b/api/src/main/java/net/md_5/bungee/api/event/CommandsDeclareEvent.java
new file mode 100644
index 00000000..ab96dd9b
--- /dev/null
+++ b/api/src/main/java/net/md_5/bungee/api/event/CommandsDeclareEvent.java
@@ -0,0 +1,157 @@
+package net.md_5.bungee.api.event;
+
+import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.arguments.IntegerArgumentType;
+import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.ArgumentBuilder;
+import com.mojang.brigadier.builder.RequiredArgumentBuilder;
+import com.mojang.brigadier.suggestion.SuggestionProvider;
+import com.mojang.brigadier.tree.CommandNode;
+import com.mojang.brigadier.tree.RootCommandNode;
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Setter;
+import lombok.ToString;
+import net.md_5.bungee.api.CommandSender;
+import net.md_5.bungee.api.connection.Connection;
+import net.md_5.bungee.api.plugin.Command;
+import net.md_5.bungee.api.plugin.Plugin;
+import net.md_5.bungee.api.plugin.PluginManager;
+import net.md_5.bungee.api.plugin.TabExecutor;
+
+/**
+ * Event called when a downstream server (on 1.13+) sends the command structure
+ * to a player, but before BungeeCord adds the dummy command nodes of
+ * registered commands.
+ *
+ * BungeeCord will not overwrite the modifications made by the listeners.
+ *
+ *
Usage example
+ * Here is a usage example of this event, to declare a command structure.
+ * This illustrates the commands /server and /send of Bungee.
+ *
+ * event.getRoot().addChild( LiteralArgumentBuilder.<CommandSender>literal( "server" )
+ * .requires( sender -> sender.hasPermission( "bungeecord.command.server" ) )
+ * .executes( a -> 0 )
+ * .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
+ * .suggests( SuggestionRegistry.ASK_SERVER )
+ * )
+ * .build()
+ * );
+ * event.getRoot().addChild( LiteralArgumentBuilder.<CommandSender>literal( "send" )
+ * .requires( sender -> sender.hasPermission( "bungeecord.command.send" ) )
+ * .then( RequiredArgumentBuilder.argument( "playerName", StringArgumentType.word() )
+ * .suggests( SuggestionRegistry.ASK_SERVER )
+ * .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
+ * .suggests( SuggestionRegistry.ASK_SERVER )
+ * )
+ * )
+ * .build()
+ * );
+ *
+ *
+ * Flag a {@link CommandNode} as executable or not
+ * The implementation of a {@link com.mojang.brigadier.Command Command} used in
+ * {@link ArgumentBuilder#executes(com.mojang.brigadier.Command)} will never be
+ * executed. This will only tell to the client if the current node is
+ * executable or not.
+ *
+ * -
+ * {@code builder.executes(null)} (default) to mark the node as not
+ * executable.
+ *
+ * -
+ * {@code builder.executes(a -> 0)}, or any non null argument, to mark
+ * the node as executable (the child arguments are displayed as
+ * optional).
+ *
+ *
+ *
+ * {@link CommandNode}’s suggestions management
+ * The implementation of a SuggestionProvider used in
+ * {@link RequiredArgumentBuilder#suggests(SuggestionProvider)} will never be
+ * executed. This will only tell to the client how to deal with the
+ * auto-completion of the argument.
+ *
+ * -
+ * {@code builder.suggests(null)} (default) to disable auto-completion
+ * for this argument.
+ *
+ * -
+ * {@code builder.suggests(SuggestionRegistry.ALL_RECIPES)} to suggest
+ * Minecraft’s recipes.
+ *
+ * -
+ * {@code builder.suggests(SuggestionRegistry.AVAILABLE_SOUNDS)} to
+ * suggest Minecraft’s default sound identifiers.
+ *
+ * -
+ * {@code builder.suggests(SuggestionRegistry.SUMMONABLE_ENTITIES)} to
+ * suggest Minecraft’s default summonable entities identifiers.
+ *
+ * -
+ * {@code builder.suggests(SuggestionRegistry.ASK_SERVER)}, or any
+ * other non null argument, to make the Minecraft client ask
+ * auto-completion to the server. Any specified implementation of
+ * {@link SuggestionProvider} will never be executed.
+ *
+ *
+ *
+ * Argument types
+ * When building a new argument command node using
+ * {@link RequiredArgumentBuilder#argument(String, ArgumentType)}, you have to
+ * specify an {@link ArgumentType}. You can use all subclasses of
+ * {@link ArgumentType} provided with brigadier (for instance,
+ * {@link StringArgumentType} or {@link IntegerArgumentType}), or call any
+ * {@code ArgumentRegistry.minecraft*()} methods to use a {@code minecraft:*}
+ * argument type.
+ *
+ * Limitations with brigadier API
+ * This event is only used for the client to show command syntax, suggest
+ * sub-commands and color the arguments in the chat box. The command execution
+ * needs to be implemented using {@link PluginManager#registerCommand(Plugin,
+ * Command)} and the server-side tab-completion using {@link TabCompleteEvent}
+ * or {@link TabExecutor}.
+ */
+@Data
+@ToString(callSuper = true)
+@EqualsAndHashCode(callSuper = true)
+public class CommandsDeclareEvent extends TargetedEvent
+{
+ /**
+ * Wether or not the command tree is modified by this event.
+ *
+ * If this value is set to true, BungeeCord will ensure that the
+ * modifications made in the command tree, will be sent to the player.
+ * If this is false, the modifications may not be taken into account.
+ *
+ * When calling {@link #getRoot()}, this value is automatically set
+ * to true.
+ */
+ @Setter(value = AccessLevel.NONE)
+ private boolean modified = false;
+
+ /**
+ * The root command node of the command structure that will be send to the
+ * player.
+ */
+ private final RootCommandNode root;
+
+ public CommandsDeclareEvent(Connection sender, Connection receiver, RootCommandNode root)
+ {
+ super( sender, receiver );
+ this.root = root;
+ }
+
+ /**
+ * The root command node of the command structure that will be send to the
+ * player.
+ * @return The root command node
+ */
+ public RootCommandNode getRoot()
+ {
+ modified = true;
+ return root;
+ }
+}
diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java
index 05761bf5..b8010373 100644
--- a/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java
+++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java
@@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
+import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
@@ -303,7 +304,7 @@ public class Commands extends DefinedPacket
}
@Data
- private static class ArgumentRegistry
+ public static class ArgumentRegistry
{
private static final Map PROVIDERS = new HashMap<>();
@@ -325,18 +326,29 @@ public class Commands extends DefinedPacket
{
}
};
- private static final ArgumentSerializer BOOLEAN = new ArgumentSerializer()
+ private static final ProperArgumentSerializer BOOLEAN = new ProperArgumentSerializer()
{
@Override
- protected Boolean read(ByteBuf buf)
+ protected BoolArgumentType read(ByteBuf buf)
{
- return buf.readBoolean();
+ return BoolArgumentType.bool();
}
@Override
- protected void write(ByteBuf buf, Boolean t)
+ protected void write(ByteBuf buf, BoolArgumentType t)
{
- buf.writeBoolean( t );
+ }
+
+ @Override
+ protected int getIntKey()
+ {
+ return 0;
+ }
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:bool";
}
};
private static final ArgumentSerializer BYTE = new ArgumentSerializer()
@@ -353,7 +365,7 @@ public class Commands extends DefinedPacket
buf.writeByte( t );
}
};
- private static final ArgumentSerializer FLOAT_RANGE = new ArgumentSerializer()
+ private static final ProperArgumentSerializer FLOAT_RANGE = new ProperArgumentSerializer()
{
@Override
protected FloatArgumentType read(ByteBuf buf)
@@ -381,8 +393,20 @@ public class Commands extends DefinedPacket
buf.writeFloat( t.getMaximum() );
}
}
+
+ @Override
+ protected int getIntKey()
+ {
+ return 1;
+ }
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:float";
+ }
};
- private static final ArgumentSerializer DOUBLE_RANGE = new ArgumentSerializer()
+ private static final ProperArgumentSerializer DOUBLE_RANGE = new ProperArgumentSerializer()
{
@Override
protected DoubleArgumentType read(ByteBuf buf)
@@ -410,8 +434,20 @@ public class Commands extends DefinedPacket
buf.writeDouble( t.getMaximum() );
}
}
+
+ @Override
+ protected int getIntKey()
+ {
+ return 2;
+ }
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:double";
+ }
};
- private static final ArgumentSerializer INTEGER_RANGE = new ArgumentSerializer()
+ private static final ProperArgumentSerializer INTEGER_RANGE = new ProperArgumentSerializer()
{
@Override
protected IntegerArgumentType read(ByteBuf buf)
@@ -439,6 +475,18 @@ public class Commands extends DefinedPacket
buf.writeInt( t.getMaximum() );
}
}
+
+ @Override
+ protected int getIntKey()
+ {
+ return 3;
+ }
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:integer";
+ }
};
private static final ArgumentSerializer INTEGER = new ArgumentSerializer()
{
@@ -454,7 +502,7 @@ public class Commands extends DefinedPacket
buf.writeInt( t );
}
};
- private static final ArgumentSerializer LONG_RANGE = new ArgumentSerializer()
+ private static final ProperArgumentSerializer LONG_RANGE = new ProperArgumentSerializer()
{
@Override
protected LongArgumentType read(ByteBuf buf)
@@ -482,6 +530,18 @@ public class Commands extends DefinedPacket
buf.writeLong( t.getMaximum() );
}
}
+
+ @Override
+ protected int getIntKey()
+ {
+ return 4;
+ }
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:long";
+ }
};
private static final ProperArgumentSerializer STRING = new ProperArgumentSerializer()
{
@@ -537,11 +597,20 @@ public class Commands extends DefinedPacket
static
{
- register( "brigadier:bool", VOID );
+ register( "brigadier:bool", BOOLEAN );
+ PROPER_PROVIDERS.put( BoolArgumentType.class, BOOLEAN );
+
register( "brigadier:float", FLOAT_RANGE );
+ PROPER_PROVIDERS.put( FloatArgumentType.class, FLOAT_RANGE );
+
register( "brigadier:double", DOUBLE_RANGE );
+ PROPER_PROVIDERS.put( DoubleArgumentType.class, DOUBLE_RANGE );
+
register( "brigadier:integer", INTEGER_RANGE );
- register( "brigadier:long", LONG_RANGE );
+ PROPER_PROVIDERS.put( IntegerArgumentType.class, INTEGER_RANGE );
+
+ register( "brigadier:long", LONG_RANGE ); // 1.14+
+ PROPER_PROVIDERS.put( LongArgumentType.class, LONG_RANGE );
register( "brigadier:string", STRING );
PROPER_PROVIDERS.put( StringArgumentType.class, STRING );
@@ -756,6 +825,404 @@ public class Commands extends DefinedPacket
return serializer;
}
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:entity}.
+ * @param singleEntity if the argument restrict to only one entity
+ * @param onlyPlayers if the argument restrict to players only
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftEntity(boolean singleEntity, boolean onlyPlayers)
+ {
+ byte flags = 0;
+ if ( singleEntity )
+ {
+ flags |= 1;
+ }
+ if ( onlyPlayers )
+ {
+ flags |= 2;
+ }
+
+ return minecraftArgumentType( "minecraft:entity", flags );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:game_profile}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftGameProfile()
+ {
+ return minecraftArgumentType( "minecraft:game_profile", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:block_pos}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftBlockPos()
+ {
+ return minecraftArgumentType( "minecraft:block_pos", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:column_pos}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftColumnPos()
+ {
+ return minecraftArgumentType( "minecraft:column_pos", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:vec3}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftVec3()
+ {
+ return minecraftArgumentType( "minecraft:vec3", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:vec2}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftVec2()
+ {
+ return minecraftArgumentType( "minecraft:vec2", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:block_state}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftBlockState()
+ {
+ return minecraftArgumentType( "minecraft:block_state", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:block_predicate}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftBlockPredicate()
+ {
+ return minecraftArgumentType( "minecraft:block_predicate", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:item_stack}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftItemStack()
+ {
+ return minecraftArgumentType( "minecraft:item_stack", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:item_predicate}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftItemPredicate()
+ {
+ return minecraftArgumentType( "minecraft:item_predicate", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:color}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftColor()
+ {
+ return minecraftArgumentType( "minecraft:color", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:component}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftComponent()
+ {
+ return minecraftArgumentType( "minecraft:component", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:message}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftMessage()
+ {
+ return minecraftArgumentType( "minecraft:message", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:nbt_compound_tag}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftNBTCompoundTag()
+ {
+ return minecraftArgumentType( "minecraft:nbt_compound_tag", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:nbt_tag}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftNBTTag()
+ {
+ return minecraftArgumentType( "minecraft:nbt_tag", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:nbt}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftNBT()
+ {
+ return minecraftArgumentType( "minecraft:nbt", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:nbt_path}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftNBTPath()
+ {
+ return minecraftArgumentType( "minecraft:nbt_path", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:objective}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftObjective()
+ {
+ return minecraftArgumentType( "minecraft:objective", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:objective_criteria}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftObjectiveCriteria()
+ {
+ return minecraftArgumentType( "minecraft:objective_criteria", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:operation}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftOperation()
+ {
+ return minecraftArgumentType( "minecraft:operation", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:particle}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftParticle()
+ {
+ return minecraftArgumentType( "minecraft:particle", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:rotation}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftRotation()
+ {
+ return minecraftArgumentType( "minecraft:rotation", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:scoreboard_slot}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftScoreboardSlot()
+ {
+ return minecraftArgumentType( "minecraft:scoreboard_slot", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:score_holder}.
+ * @param allowMultiple if the argument allows multiple entities
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftScoreHolder(boolean allowMultiple)
+ {
+ byte flags = 0;
+ if ( allowMultiple )
+ {
+ flags |= 1;
+ }
+
+ return minecraftArgumentType( "minecraft:score_holder", flags );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:swizzle}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftSwizzle()
+ {
+ return minecraftArgumentType( "minecraft:swizzle", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:team}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftTeam()
+ {
+ return minecraftArgumentType( "minecraft:team", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:item_slot}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftItemSlot()
+ {
+ return minecraftArgumentType( "minecraft:item_slot", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:resource_location}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftResourceLocation()
+ {
+ return minecraftArgumentType( "minecraft:resource_location", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:mob_effect}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftMobEffect()
+ {
+ return minecraftArgumentType( "minecraft:mob_effect", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:function}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftFunction()
+ {
+ return minecraftArgumentType( "minecraft:function", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:entity_anchor}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftEntityAnchor()
+ {
+ return minecraftArgumentType( "minecraft:entity_anchor", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:int_range}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftIntRange()
+ {
+ return minecraftArgumentType( "minecraft:int_range", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:float_range}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftFloatRange()
+ {
+ return minecraftArgumentType( "minecraft:float_range", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:item_enchantment}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftItemEnchantment()
+ {
+ return minecraftArgumentType( "minecraft:item_enchantment", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:entity_summon}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftEntitySummon()
+ {
+ return minecraftArgumentType( "minecraft:entity_summon", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:dimension}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftDimension()
+ {
+ return minecraftArgumentType( "minecraft:dimension", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:time}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftTime()
+ {
+ return minecraftArgumentType( "minecraft:time", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:uuid}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftUUID()
+ {
+ return minecraftArgumentType( "minecraft:uuid", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:angle}.
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftAngle()
+ {
+ return minecraftArgumentType( "minecraft:angle", null );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:resource}.
+ * @param rawString the raw string for the argument
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftResource(String rawString)
+ {
+ return minecraftArgumentType( "minecraft:resource", rawString );
+ }
+
+ /**
+ * Returns the Minecraft ArgumentType {@code minecraft:resource_or_tag}.
+ * @param rawString the raw string for the argument
+ * @return an ArgumentType instance
+ */
+ public static ArgumentType> minecraftResourceOrTag(String rawString)
+ {
+ return minecraftArgumentType( "minecraft:resource_or_tag", rawString );
+ }
+
+ private static ArgumentType> minecraftArgumentType(String key, Object rawValue)
+ {
+ ArgumentSerializer reader = PROVIDERS.get( key );
+ Preconditions.checkArgument( reader != null, "No provider for argument " + key );
+
+ return new DummyType( key, reader, rawValue );
+ }
+
private static ArgumentType> read(ByteBuf buf, int protocolVersion)
{
Object key;
@@ -879,9 +1346,13 @@ public class Commands extends DefinedPacket
private static String getKey(SuggestionProvider provider)
{
- Preconditions.checkArgument( provider instanceof DummyProvider, "Non dummy provider " + provider );
+ Preconditions.checkNotNull( provider );
+ if ( provider instanceof DummyProvider )
+ {
+ return ( (DummyProvider) provider ).key;
+ }
- return ( (DummyProvider) provider ).key;
+ return ( (DummyProvider) ASK_SERVER ).key;
}
@Data
diff --git a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java
index 1715a663..cf55d25e 100644
--- a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java
+++ b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java
@@ -6,6 +6,7 @@ import com.google.common.collect.Lists;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import com.mojang.brigadier.arguments.StringArgumentType;
+import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.StringRange;
@@ -19,8 +20,11 @@ import io.netty.channel.unix.DomainSocketAddress;
import java.io.DataInput;
import java.net.InetSocketAddress;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
+import java.util.logging.Level;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.ServerConnection.KeepAliveData;
@@ -31,6 +35,7 @@ import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
+import net.md_5.bungee.api.event.CommandsDeclareEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerDisconnectEvent;
@@ -674,6 +679,11 @@ public class DownstreamBridge extends PacketHandler
{
boolean modified = false;
+ CommandsDeclareEvent commandsDeclareEvent = new CommandsDeclareEvent( server, con, commands.getRoot() );
+ bungee.getPluginManager().callEvent( commandsDeclareEvent );
+
+ modified = commandsDeclareEvent.isModified();
+
for ( Map.Entry command : bungee.getPluginManager().getCommands() )
{
if ( !bungee.getDisabledCommands().contains( command.getKey() ) && commands.getRoot().getChild( command.getKey() ) == null && command.getValue().hasPermission( con ) )
@@ -690,11 +700,65 @@ public class DownstreamBridge extends PacketHandler
if ( modified )
{
+ commands.setRoot( (com.mojang.brigadier.tree.RootCommandNode) filterCommandNode( commands.getRoot(), new IdentityHashMap<>() ) );
con.unsafe().sendPacket( commands );
throw CancelSendSignal.INSTANCE;
}
}
+ /*
+ * Create a deep copy of the provided command node but removes any node that are not accessible by the player
+ * (using {@link CommandNode#getRequirement()})
+ */
+ private CommandNode filterCommandNode(CommandNode source, IdentityHashMap commandNodeMapping)
+ {
+ CommandNode dest;
+ if ( source instanceof com.mojang.brigadier.tree.RootCommandNode )
+ {
+ dest = new com.mojang.brigadier.tree.RootCommandNode();
+ } else
+ {
+ if ( source.getRequirement() != null )
+ {
+ try
+ {
+ if ( !source.getRequirement().test( con ) )
+ {
+ commandNodeMapping.put( source, null );
+ return null;
+ }
+ } catch ( Throwable t )
+ {
+ ProxyServer.getInstance().getLogger().log( Level.SEVERE, "Requirement test for command node " + source + " encountered an exception", t );
+ }
+ }
+
+ ArgumentBuilder destChildBuilder = source.createBuilder();
+ destChildBuilder.requires( sender -> true );
+ if ( destChildBuilder.getRedirect() != null )
+ {
+ if ( commandNodeMapping.containsKey( destChildBuilder.getRedirect() ) )
+ destChildBuilder.redirect( commandNodeMapping.get( destChildBuilder.getRedirect() ) );
+ else
+ destChildBuilder.redirect( filterCommandNode( destChildBuilder.getRedirect(), commandNodeMapping ) );
+ }
+
+ dest = destChildBuilder.build();
+ }
+
+ commandNodeMapping.put( source, dest );
+
+ for ( CommandNode sourceChild : (Collection) source.getChildren() )
+ {
+ CommandNode destChild = filterCommandNode( sourceChild, commandNodeMapping );
+ if ( destChild == null )
+ continue;
+ dest.addChild( destChild );
+ }
+
+ return dest;
+ }
+
@Override
public void handle(ServerData serverData) throws Exception
{