Compare commits

...

2 Commits

6 changed files with 820 additions and 31 deletions

View File

@ -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.
* <p>
* BungeeCord will not overwrite the modifications made by the listeners.
*
* <h2>Usage example</h2>
* Here is a usage example of this event, to declare a command structure.
* This illustrates the commands /server and /send of Bungee.
* <pre>
* event.getRoot().addChild( LiteralArgumentBuilder.&lt;CommandSender&gt;literal( "server" )
* .requires( sender -&gt; sender.hasPermission( "bungeecord.command.server" ) )
* .executes( a -&gt; 0 )
* .then( RequiredArgumentBuilder.argument( "serverName", StringArgumentType.greedyString() )
* .suggests( SuggestionRegistry.ASK_SERVER )
* )
* .build()
* );
* event.getRoot().addChild( LiteralArgumentBuilder.&lt;CommandSender&gt;literal( "send" )
* .requires( sender -&gt; 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()
* );
* </pre>
*
* <h2>Flag a {@link CommandNode} as executable or not</h2>
* 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.
* <ul>
* <li>
* {@code builder.executes(null)} (default) to mark the node as not
* executable.
* </li>
* <li>
* {@code builder.executes(a -> 0)}, or any non null argument, to mark
* the node as executable (the child arguments are displayed as
* optional).
* </li>
* </ul>
*
* <h2>{@link CommandNode}s suggestions management</h2>
* 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.
* <ul>
* <li>
* {@code builder.suggests(null)} (default) to disable auto-completion
* for this argument.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.ALL_RECIPES)} to suggest
* Minecrafts recipes.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.AVAILABLE_SOUNDS)} to
* suggest Minecrafts default sound identifiers.
* </li>
* <li>
* {@code builder.suggests(SuggestionRegistry.SUMMONABLE_ENTITIES)} to
* suggest Minecrafts default summonable entities identifiers.
* </li>
* <li>
* {@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.
* </li>
* </ul>
*
* <h2>Argument types</h2>
* 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.
*
* <h2>Limitations with brigadier API</h2>
* 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<CommandSender> root;
public CommandsDeclareEvent(Connection sender, Connection receiver, RootCommandNode<CommandSender> 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<CommandSender> getRoot()
{
modified = true;
return root;
}
}

View File

@ -9,7 +9,9 @@ import net.md_5.bungee.api.plugin.Cancellable;
/** /**
* Event called when a player uses tab completion. * Event called when a player uses tab completion.
* @deprecated please use {@link TabCompleteRequestEvent} to support 1.13+ suggestions.
*/ */
@Deprecated
@Data @Data
@ToString(callSuper = true) @ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -0,0 +1,85 @@
package net.md_5.bungee.api.event;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.suggestion.Suggestions;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import net.md_5.bungee.api.connection.Connection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.protocol.ProtocolConstants;
/**
* Event called when a player uses tab completion.
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class TabCompleteRequestEvent extends TargetedEvent implements Cancellable
{
/**
* Cancelled state.
*/
private boolean cancelled;
/**
* The message the player has already entered.
*/
private final String cursor;
/**
* Range corresponding to the last word of {@link #getCursor()}.
* If you want your suggestions to be compatible with 1.12 and older
* clients, you need to {@link #setSuggestions(Suggestions)} with
* a range equals to this one.
* For 1.13 and newer clients, any other range that cover any part of
* {@link #getCursor()} is fine.<br>
* To check if the client supports custom ranges, use
* {@link #supportsCustomRange()}.
*/
private final StringRange legacyCompatibleRange;
/**
* The suggestions that will be sent to the client. If this list is empty,
* the request will be forwarded to the server.
*/
private Suggestions suggestions;
public TabCompleteRequestEvent(Connection sender, Connection receiver, String cursor, StringRange legacyCompatibleRange, Suggestions suggestions)
{
super( sender, receiver );
this.cursor = cursor;
this.legacyCompatibleRange = legacyCompatibleRange;
this.suggestions = suggestions;
}
/**
* Sets the suggestions that will be sent to the client.
* If this list is empty, the request will be forwarded to the server.
* @param suggestions the new Suggestions. Cannot be null.
* @throws IllegalArgumentException if the client is on 1.12 or lower and
* {@code suggestions.getRange()} is not equals to {@link #legacyCompatibleRange}.
*/
public void setSuggestions(Suggestions suggestions)
{
Preconditions.checkNotNull( suggestions );
Preconditions.checkArgument( supportsCustomRange() || legacyCompatibleRange.equals( suggestions.getRange() ),
"Clients on 1.12 or lower versions don't support the provided range for tab-completion: " + suggestions.getRange()
+ ". Please use TabCompleteRequestEvent.getLegacyCompatibleRange() for legacy clients." );
this.suggestions = suggestions;
}
/**
* Convenient method to tell if the client supports custom range for
* suggestions.
* If the client is on 1.13 or above, this methods returns true, and any
* range can be used for {@link #setSuggestions(Suggestions)}. Otherwise,
* it returns false and the defined range must be equals to
* {@link #legacyCompatibleRange}.
* @return true if the client is on 1.13 or newer version, false otherwise.
*/
public boolean supportsCustomRange()
{
return ( (ProxiedPlayer) getSender() ).getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13;
}
}

View File

@ -4,6 +4,7 @@ import com.google.common.base.Preconditions;
import com.mojang.brigadier.Command; import com.mojang.brigadier.Command;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.BoolArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType; import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType; import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType; import com.mojang.brigadier.arguments.IntegerArgumentType;
@ -303,7 +304,7 @@ public class Commands extends DefinedPacket
} }
@Data @Data
private static class ArgumentRegistry public static class ArgumentRegistry
{ {
private static final Map<String, ArgumentSerializer> PROVIDERS = new HashMap<>(); private static final Map<String, ArgumentSerializer> PROVIDERS = new HashMap<>();
@ -325,18 +326,29 @@ public class Commands extends DefinedPacket
{ {
} }
}; };
private static final ArgumentSerializer<Boolean> BOOLEAN = new ArgumentSerializer<Boolean>() private static final ProperArgumentSerializer<BoolArgumentType> BOOLEAN = new ProperArgumentSerializer<BoolArgumentType>()
{ {
@Override @Override
protected Boolean read(ByteBuf buf) protected BoolArgumentType read(ByteBuf buf)
{ {
return buf.readBoolean(); return BoolArgumentType.bool();
} }
@Override @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> BYTE = new ArgumentSerializer<Byte>() private static final ArgumentSerializer<Byte> BYTE = new ArgumentSerializer<Byte>()
@ -353,7 +365,7 @@ public class Commands extends DefinedPacket
buf.writeByte( t ); buf.writeByte( t );
} }
}; };
private static final ArgumentSerializer<FloatArgumentType> FLOAT_RANGE = new ArgumentSerializer<FloatArgumentType>() private static final ProperArgumentSerializer<FloatArgumentType> FLOAT_RANGE = new ProperArgumentSerializer<FloatArgumentType>()
{ {
@Override @Override
protected FloatArgumentType read(ByteBuf buf) protected FloatArgumentType read(ByteBuf buf)
@ -381,8 +393,20 @@ public class Commands extends DefinedPacket
buf.writeFloat( t.getMaximum() ); buf.writeFloat( t.getMaximum() );
} }
} }
@Override
protected int getIntKey()
{
return 1;
}
@Override
protected String getKey()
{
return "brigadier:float";
}
}; };
private static final ArgumentSerializer<DoubleArgumentType> DOUBLE_RANGE = new ArgumentSerializer<DoubleArgumentType>() private static final ProperArgumentSerializer<DoubleArgumentType> DOUBLE_RANGE = new ProperArgumentSerializer<DoubleArgumentType>()
{ {
@Override @Override
protected DoubleArgumentType read(ByteBuf buf) protected DoubleArgumentType read(ByteBuf buf)
@ -410,8 +434,20 @@ public class Commands extends DefinedPacket
buf.writeDouble( t.getMaximum() ); buf.writeDouble( t.getMaximum() );
} }
} }
@Override
protected int getIntKey()
{
return 2;
}
@Override
protected String getKey()
{
return "brigadier:double";
}
}; };
private static final ArgumentSerializer<IntegerArgumentType> INTEGER_RANGE = new ArgumentSerializer<IntegerArgumentType>() private static final ProperArgumentSerializer<IntegerArgumentType> INTEGER_RANGE = new ProperArgumentSerializer<IntegerArgumentType>()
{ {
@Override @Override
protected IntegerArgumentType read(ByteBuf buf) protected IntegerArgumentType read(ByteBuf buf)
@ -439,6 +475,18 @@ public class Commands extends DefinedPacket
buf.writeInt( t.getMaximum() ); buf.writeInt( t.getMaximum() );
} }
} }
@Override
protected int getIntKey()
{
return 3;
}
@Override
protected String getKey()
{
return "brigadier:integer";
}
}; };
private static final ArgumentSerializer<Integer> INTEGER = new ArgumentSerializer<Integer>() private static final ArgumentSerializer<Integer> INTEGER = new ArgumentSerializer<Integer>()
{ {
@ -454,7 +502,7 @@ public class Commands extends DefinedPacket
buf.writeInt( t ); buf.writeInt( t );
} }
}; };
private static final ArgumentSerializer<LongArgumentType> LONG_RANGE = new ArgumentSerializer<LongArgumentType>() private static final ProperArgumentSerializer<LongArgumentType> LONG_RANGE = new ProperArgumentSerializer<LongArgumentType>()
{ {
@Override @Override
protected LongArgumentType read(ByteBuf buf) protected LongArgumentType read(ByteBuf buf)
@ -482,6 +530,18 @@ public class Commands extends DefinedPacket
buf.writeLong( t.getMaximum() ); buf.writeLong( t.getMaximum() );
} }
} }
@Override
protected int getIntKey()
{
return 4;
}
@Override
protected String getKey()
{
return "brigadier:long";
}
}; };
private static final ProperArgumentSerializer<StringArgumentType> STRING = new ProperArgumentSerializer<StringArgumentType>() private static final ProperArgumentSerializer<StringArgumentType> STRING = new ProperArgumentSerializer<StringArgumentType>()
{ {
@ -537,11 +597,20 @@ public class Commands extends DefinedPacket
static static
{ {
register( "brigadier:bool", VOID ); register( "brigadier:bool", BOOLEAN );
PROPER_PROVIDERS.put( BoolArgumentType.class, BOOLEAN );
register( "brigadier:float", FLOAT_RANGE ); register( "brigadier:float", FLOAT_RANGE );
PROPER_PROVIDERS.put( FloatArgumentType.class, FLOAT_RANGE );
register( "brigadier:double", DOUBLE_RANGE ); register( "brigadier:double", DOUBLE_RANGE );
PROPER_PROVIDERS.put( DoubleArgumentType.class, DOUBLE_RANGE );
register( "brigadier:integer", INTEGER_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 ); register( "brigadier:string", STRING );
PROPER_PROVIDERS.put( StringArgumentType.class, STRING ); PROPER_PROVIDERS.put( StringArgumentType.class, STRING );
@ -756,6 +825,404 @@ public class Commands extends DefinedPacket
return serializer; 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) private static ArgumentType<?> read(ByteBuf buf, int protocolVersion)
{ {
Object key; Object key;
@ -879,9 +1346,13 @@ public class Commands extends DefinedPacket
private static String getKey(SuggestionProvider<DummyProvider> provider) private static String getKey(SuggestionProvider<DummyProvider> 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 @Data

View File

@ -6,6 +6,7 @@ import com.google.common.collect.Lists;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.StringRange; import com.mojang.brigadier.context.StringRange;
@ -19,9 +20,12 @@ import io.netty.channel.unix.DomainSocketAddress;
import java.io.DataInput; import java.io.DataInput;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.ServerConnection; import net.md_5.bungee.ServerConnection;
@ -35,6 +39,7 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.CommandsDeclareEvent;
import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerDisconnectEvent; import net.md_5.bungee.api.event.ServerDisconnectEvent;
@ -729,6 +734,11 @@ public class DownstreamBridge extends PacketHandler
{ {
boolean modified = false; boolean modified = false;
CommandsDeclareEvent commandsDeclareEvent = new CommandsDeclareEvent( server, con, commands.getRoot() );
bungee.getPluginManager().callEvent( commandsDeclareEvent );
modified = commandsDeclareEvent.isModified();
for ( Map.Entry<String, Command> command : bungee.getPluginManager().getCommands() ) for ( Map.Entry<String, Command> command : bungee.getPluginManager().getCommands() )
{ {
if ( !bungee.getDisabledCommands().contains( command.getKey() ) && commands.getRoot().getChild( command.getKey() ) == null && command.getValue().hasPermission( con ) ) if ( !bungee.getDisabledCommands().contains( command.getKey() ) && commands.getRoot().getChild( command.getKey() ) == null && command.getValue().hasPermission( con ) )
@ -745,11 +755,65 @@ public class DownstreamBridge extends PacketHandler
if ( modified ) if ( modified )
{ {
commands.setRoot( (com.mojang.brigadier.tree.RootCommandNode) filterCommandNode( commands.getRoot(), new IdentityHashMap<>() ) );
con.unsafe().sendPacket( commands ); con.unsafe().sendPacket( commands );
throw CancelSendSignal.INSTANCE; 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<CommandNode, CommandNode> 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<CommandNode>) source.getChildren() )
{
CommandNode destChild = filterCommandNode( sourceChild, commandNodeMapping );
if ( destChild == null )
continue;
dest.addChild( destChild );
}
return dest;
}
@Override @Override
public void handle(ServerData serverData) throws Exception public void handle(ServerData serverData) throws Exception
{ {

View File

@ -6,7 +6,6 @@ import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.Suggestions;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
@ -20,6 +19,7 @@ import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.SettingsChangedEvent; import net.md_5.bungee.api.event.SettingsChangedEvent;
import net.md_5.bungee.api.event.TabCompleteEvent; import net.md_5.bungee.api.event.TabCompleteEvent;
import net.md_5.bungee.api.event.TabCompleteRequestEvent;
import net.md_5.bungee.entitymap.EntityMap; import net.md_5.bungee.entitymap.EntityMap;
import net.md_5.bungee.forge.ForgeConstants; import net.md_5.bungee.forge.ForgeConstants;
import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.ChannelWrapper;
@ -225,32 +225,42 @@ public class UpstreamBridge extends PacketHandler
TabCompleteEvent tabCompleteEvent = new TabCompleteEvent( con, con.getServer(), tabComplete.getCursor(), suggestions ); TabCompleteEvent tabCompleteEvent = new TabCompleteEvent( con, con.getServer(), tabComplete.getCursor(), suggestions );
bungee.getPluginManager().callEvent( tabCompleteEvent ); bungee.getPluginManager().callEvent( tabCompleteEvent );
if ( tabCompleteEvent.isCancelled() ) List<String> legacyResults = tabCompleteEvent.getSuggestions();
int start = tabComplete.getCursor().lastIndexOf( ' ' ) + 1;
int end = tabComplete.getCursor().length();
StringRange lastArgumentRange = StringRange.between( start, end );
List<Suggestion> brigadier = new ArrayList<>( legacyResults.size() );
for ( String s : legacyResults )
{
brigadier.add( new Suggestion( lastArgumentRange, s ) );
}
TabCompleteRequestEvent tabCompleteRequestEvent = new TabCompleteRequestEvent( con, con.getServer(), tabComplete.getCursor(), lastArgumentRange, new Suggestions( lastArgumentRange, brigadier ) );
tabCompleteRequestEvent.setCancelled( tabCompleteEvent.isCancelled() );
bungee.getPluginManager().callEvent( tabCompleteRequestEvent );
if ( tabCompleteRequestEvent.isCancelled() )
{ {
throw CancelSendSignal.INSTANCE; throw CancelSendSignal.INSTANCE;
} }
List<String> results = tabCompleteEvent.getSuggestions(); Suggestions brigadierResults = tabCompleteRequestEvent.getSuggestions();
if ( !results.isEmpty() )
if ( !brigadierResults.isEmpty() )
{ {
// Unclear how to handle 1.13 commands at this point. Because we don't inject into the command packets we are unlikely to get this far unless
// Bungee plugins are adding results for commands they don't own anyway
if ( con.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_13 ) if ( con.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_13 )
{ {
List<String> results = new ArrayList<>( brigadierResults.getList().size() );
for ( Suggestion s : brigadierResults.getList() )
{
results.add( s.getText() );
}
con.unsafe().sendPacket( new TabCompleteResponse( results ) ); con.unsafe().sendPacket( new TabCompleteResponse( results ) );
} else } else
{ {
int start = tabComplete.getCursor().lastIndexOf( ' ' ) + 1; con.unsafe().sendPacket( new TabCompleteResponse( tabComplete.getTransactionId(), brigadierResults ) );
int end = tabComplete.getCursor().length();
StringRange range = StringRange.between( start, end );
List<Suggestion> brigadier = new LinkedList<>();
for ( String s : results )
{
brigadier.add( new Suggestion( range, s ) );
}
con.unsafe().sendPacket( new TabCompleteResponse( tabComplete.getTransactionId(), new Suggestions( range, brigadier ) ) );
} }
throw CancelSendSignal.INSTANCE; throw CancelSendSignal.INSTANCE;
} }