PandaCord/Waterfall-Proxy-Patches/0005-Add-CommandsDeclareEvent-to-declare-commands-with-br.patch

1049 lines
40 KiB
Diff
Raw Permalink Normal View History

From 676b064e31a857821c9a08c6329a148b3739fff4 Mon Sep 17 00:00:00 2001
From: Marc Baloup <marc.baloup@laposte.net>
Date: Mon, 8 Jun 2020 00:51:21 +0200
Subject: [PATCH] Add CommandsDeclareEvent to declare commands with brigadier
API
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.
+ * <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;
+ }
+}
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 26edbca8..323d479c 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;
@@ -36,6 +37,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
+import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@@ -126,7 +128,7 @@ public class Commands extends DefinedPacket
}
@Override
- public void write(ByteBuf buf)
+ public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
Map<CommandNode, Integer> indexMap = new LinkedHashMap<>();
Deque<CommandNode> nodeQueue = new ArrayDeque<>();
@@ -210,7 +212,7 @@ public class Commands extends DefinedPacket
ArgumentCommandNode argumentNode = (ArgumentCommandNode) node;
writeString( argumentNode.getName(), buf );
- ArgumentRegistry.write( argumentNode.getType(), buf );
+ ArgumentRegistry.write( argumentNode.getType(), buf, protocolVersion );
if ( argumentNode.getCustomSuggestions() != null )
{
@@ -304,11 +306,12 @@ public class Commands extends DefinedPacket
}
@Data
- private static class ArgumentRegistry
+ public static class ArgumentRegistry
{
private static final Map<String, ArgumentSerializer> PROVIDERS = new HashMap<>();
private static final Map<Class<?>, ProperArgumentSerializer<?>> PROPER_PROVIDERS = new HashMap<>();
+ private static final Map<String, ProtocolConversionProvider> CONVERSION_PROVIDERS = new HashMap<>();
//
private static final ArgumentSerializer<Void> VOID = new ArgumentSerializer<Void>()
{
@@ -323,18 +326,23 @@ public class Commands extends DefinedPacket
{
}
};
- private static final ArgumentSerializer<Boolean> BOOLEAN = new ArgumentSerializer<Boolean>()
+ private static final ProperArgumentSerializer<BoolArgumentType> BOOLEAN = new ProperArgumentSerializer<BoolArgumentType>()
{
@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 String getKey()
+ {
+ return "brigadier:bool";
}
};
private static final ArgumentSerializer<Byte> BYTE = new ArgumentSerializer<Byte>()
@@ -351,7 +359,7 @@ public class Commands extends DefinedPacket
buf.writeByte( t );
}
};
- private static final ArgumentSerializer<FloatArgumentType> FLOAT = new ArgumentSerializer<FloatArgumentType>()
+ private static final ProperArgumentSerializer<FloatArgumentType> FLOAT = new ProperArgumentSerializer<FloatArgumentType>()
{
@Override
protected FloatArgumentType read(ByteBuf buf)
@@ -379,8 +387,14 @@ public class Commands extends DefinedPacket
buf.writeFloat( t.getMaximum() );
}
}
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:float";
+ }
};
- private static final ArgumentSerializer<DoubleArgumentType> DOUBLE = new ArgumentSerializer<DoubleArgumentType>()
+ private static final ProperArgumentSerializer<DoubleArgumentType> DOUBLE = new ProperArgumentSerializer<DoubleArgumentType>()
{
@Override
protected DoubleArgumentType read(ByteBuf buf)
@@ -408,8 +422,14 @@ public class Commands extends DefinedPacket
buf.writeDouble( t.getMaximum() );
}
}
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:double";
+ }
};
- private static final ArgumentSerializer<IntegerArgumentType> INTEGER = new ArgumentSerializer<IntegerArgumentType>()
+ private static final ProperArgumentSerializer<IntegerArgumentType> INTEGER = new ProperArgumentSerializer<IntegerArgumentType>()
{
@Override
protected IntegerArgumentType read(ByteBuf buf)
@@ -437,8 +457,14 @@ public class Commands extends DefinedPacket
buf.writeInt( t.getMaximum() );
}
}
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:integer";
+ }
};
- private static final ArgumentSerializer<LongArgumentType> LONG = new ArgumentSerializer<LongArgumentType>()
+ private static final ProperArgumentSerializer<LongArgumentType> LONG = new ProperArgumentSerializer<LongArgumentType>()
{
@Override
protected LongArgumentType read(ByteBuf buf)
@@ -466,6 +492,12 @@ public class Commands extends DefinedPacket
buf.writeLong( t.getMaximum() );
}
}
+
+ @Override
+ protected String getKey()
+ {
+ return "brigadier:long";
+ }
};
private static final ProperArgumentSerializer<StringArgumentType> STRING = new ProperArgumentSerializer<StringArgumentType>()
{
@@ -501,11 +533,31 @@ public class Commands extends DefinedPacket
static
{
- PROVIDERS.put( "brigadier:bool", VOID );
+ PROVIDERS.put( "brigadier:bool", BOOLEAN );
+ PROPER_PROVIDERS.put( BoolArgumentType.class, BOOLEAN );
+
PROVIDERS.put( "brigadier:float", FLOAT );
+ PROPER_PROVIDERS.put( FloatArgumentType.class, FLOAT );
+
PROVIDERS.put( "brigadier:double", DOUBLE );
+ PROPER_PROVIDERS.put( DoubleArgumentType.class, DOUBLE );
+
PROVIDERS.put( "brigadier:integer", INTEGER );
- PROVIDERS.put( "brigadier:long", LONG );
+ PROPER_PROVIDERS.put( IntegerArgumentType.class, INTEGER );
+
+ PROVIDERS.put( "brigadier:long", LONG ); // 1.14+
+ PROPER_PROVIDERS.put( LongArgumentType.class, LONG );
+ CONVERSION_PROVIDERS.put( "brigadier:long", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
+ {
+ LongArgumentType type = (LongArgumentType) originalType;
+ int min = (int) Math.max( Integer.MIN_VALUE, Math.min( Integer.MAX_VALUE, type.getMinimum() ) );
+ int max = (int) Math.max( Integer.MIN_VALUE, Math.min( Integer.MAX_VALUE, type.getMaximum() ) );
+ return IntegerArgumentType.integer( min, max );
+ }
+ return originalType;
+ } );
PROVIDERS.put( "brigadier:string", STRING );
PROPER_PROVIDERS.put( StringArgumentType.class, STRING );
@@ -523,9 +575,37 @@ public class Commands extends DefinedPacket
PROVIDERS.put( "minecraft:color", VOID );
PROVIDERS.put( "minecraft:component", VOID );
PROVIDERS.put( "minecraft:message", VOID );
- PROVIDERS.put( "minecraft:nbt_compound_tag", VOID ); // 1.14
- PROVIDERS.put( "minecraft:nbt_tag", VOID ); // 1.14
+
+ PROVIDERS.put( "minecraft:nbt_compound_tag", VOID ); // 1.14+, replaces minecraft:nbt
+ CONVERSION_PROVIDERS.put( "minecraft:nbt_compound_tag", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
+ {
+ return minecraftNBT();
+ }
+ return originalType;
+ } );
+
+ PROVIDERS.put( "minecraft:nbt_tag", VOID ); // 1.14+
+ CONVERSION_PROVIDERS.put( "minecraft:nbt_tag", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
+ {
+ return minecraftNBT();
+ }
+ return originalType;
+ } );
+
PROVIDERS.put( "minecraft:nbt", VOID ); // 1.13
+ CONVERSION_PROVIDERS.put( "minecraft:nbt", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_14 )
+ {
+ return minecraftNBTCompoundTag();
+ }
+ return originalType;
+ } );
+
PROVIDERS.put( "minecraft:nbt_path", VOID );
PROVIDERS.put( "minecraft:objective", VOID );
PROVIDERS.put( "minecraft:objective_criteria", VOID );
@@ -546,11 +626,416 @@ public class Commands extends DefinedPacket
PROVIDERS.put( "minecraft:item_enchantment", VOID );
PROVIDERS.put( "minecraft:entity_summon", VOID );
PROVIDERS.put( "minecraft:dimension", VOID );
- PROVIDERS.put( "minecraft:time", VOID ); // 1.14
- PROVIDERS.put( "minecraft:uuid", VOID ); // 1.16
- PROVIDERS.put( "minecraft:test_argument", VOID ); // 1.16, debug
- PROVIDERS.put( "minecraft:test_class", VOID ); // 1.16, debug
- PROVIDERS.put( "minecraft:angle", VOID ); // 1.16.2
+
+ PROVIDERS.put( "minecraft:time", VOID ); // 1.14+
+ CONVERSION_PROVIDERS.put( "minecraft:time", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_14 )
+ {
+ return StringArgumentType.word();
+ }
+ return originalType;
+ } );
+
+ PROVIDERS.put( "minecraft:uuid", VOID ); // 1.16+
+ CONVERSION_PROVIDERS.put( "minecraft:uuid", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_16 )
+ {
+ return StringArgumentType.word();
+ }
+ return originalType;
+ } );
+
+ PROVIDERS.put( "minecraft:test_argument", VOID ); // 1.16+, debug
+ PROVIDERS.put( "minecraft:test_class", VOID ); // 1.16+, debug
+ PROVIDERS.put( "minecraft:angle", VOID ); // 1.16.2+
+ CONVERSION_PROVIDERS.put( "minecraft:angle", ( originalType, protocolVersion ) ->
+ {
+ if ( protocolVersion < ProtocolConstants.MINECRAFT_1_16_2 )
+ {
+ return FloatArgumentType.floatArg();
+ }
+ return originalType;
+ } );
+ }
+
+ /**
+ * 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 );
+ }
+
+ 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(String key, ByteBuf buf)
@@ -562,8 +1047,10 @@ public class Commands extends DefinedPacket
return val != null && PROPER_PROVIDERS.containsKey( val.getClass() ) ? (ArgumentType<?>) val : new DummyType( key, reader, val );
}
- private static void write(ArgumentType<?> arg, ByteBuf buf)
+ private static void write(ArgumentType<?> arg, ByteBuf buf, int protocolVersion)
{
+ arg = convertToVersion( arg, protocolVersion );
+
ProperArgumentSerializer proper = PROPER_PROVIDERS.get( arg.getClass() );
if ( proper != null )
{
@@ -579,6 +1066,34 @@ public class Commands extends DefinedPacket
}
}
+ // Convert the provided argument type to a new one compatible with the provided protocol version
+ private static ArgumentType<?> convertToVersion(ArgumentType<?> arg, int protocolVersion)
+ {
+ ProperArgumentSerializer proper = PROPER_PROVIDERS.get( arg.getClass() );
+ String key;
+ if ( proper != null )
+ {
+ key = proper.getKey();
+ } else
+ {
+ Preconditions.checkArgument( arg instanceof DummyType, "Non dummy arg " + arg.getClass() );
+ key = ( (DummyType) arg ).key;
+ }
+
+ ProtocolConversionProvider converter = CONVERSION_PROVIDERS.get( key );
+
+ if ( converter != null )
+ {
+ return converter.convert( arg, protocolVersion );
+ }
+ return arg;
+ }
+
+ private interface ProtocolConversionProvider
+ {
+ public ArgumentType<?> convert(ArgumentType<?> originalType, int protocolVersion);
+ }
+
@Data
private static class DummyType<T> implements ArgumentType<T>
{
@@ -612,17 +1127,39 @@ public class Commands extends DefinedPacket
@Data
public static class SuggestionRegistry
{
-
+ /**
+ * Tells the client to ask suggestions to the server.
+ */
public static final SuggestionProvider ASK_SERVER = new DummyProvider( "minecraft:ask_server" );
+
+ /**
+ * Tells the client to suggest all the available recipes. The suggestions are stored client side.
+ */
+ public static final SuggestionProvider ALL_RECIPES = new DummyProvider( "minecraft:all_recipes" );
+
+ /**
+ * Tells the client to suggest all the available sounds. The suggestions are stored client side.
+ */
+ public static final SuggestionProvider AVAILABLE_SOUNDS = new DummyProvider( "minecraft:available_sounds" );
+
+ /**
+ * Tells the client to suggest all the available biomes. The suggestions are stored client side.
+ */
+ public static final SuggestionProvider AVAILABLE_BIOMES = new DummyProvider( "minecraft:available_biomes" );
+
+ /**
+ * Tells the client to suggest all the available entities. The suggestions are stored client side.
+ */
+ public static final SuggestionProvider SUMMONABLE_ENTITIES = new DummyProvider( "minecraft:summonable_entities" );
private static final Map<String, SuggestionProvider<DummyProvider>> PROVIDERS = new HashMap<>();
static
{
PROVIDERS.put( "minecraft:ask_server", ASK_SERVER );
- registerDummy( "minecraft:all_recipes" );
- registerDummy( "minecraft:available_sounds" );
- registerDummy( "minecraft:available_biomes" );
- registerDummy( "minecraft:summonable_entities" );
+ PROVIDERS.put( "minecraft:all_recipes", ALL_RECIPES );
+ PROVIDERS.put( "minecraft:available_sounds", AVAILABLE_SOUNDS );
+ PROVIDERS.put( "minecraft:available_biomes", AVAILABLE_BIOMES );
+ PROVIDERS.put( "minecraft:summonable_entities", SUMMONABLE_ENTITIES );
}
private static void registerDummy(String name)
@@ -640,9 +1177,13 @@ public class Commands extends DefinedPacket
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
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 72538ddb..4379a6c5 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,12 +6,15 @@ 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;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
+import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
+import com.mojang.brigadier.tree.RootCommandNode;
import io.github.waterfallmc.waterfall.event.ProxyDefineCommandsEvent; // Waterfall
import io.netty.buffer.ByteBuf;
@@ -21,10 +24,13 @@ 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.HashMap; // Waterfall
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects; // Waterfall
+import java.util.logging.Level;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.ServerConnection.KeepAliveData;
@@ -35,6 +41,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;
@@ -689,6 +696,11 @@ public class DownstreamBridge extends PacketHandler
{
boolean modified = false;
+ CommandsDeclareEvent commandsDeclareEvent = new CommandsDeclareEvent( server, con, commands.getRoot() );
+ bungee.getPluginManager().callEvent( commandsDeclareEvent );
+
+ modified = commandsDeclareEvent.isModified();
+
// Waterfall start
Map<String, Command> commandMap = new HashMap<>();
for ( Map.Entry<String, Command> commandEntry : bungee.getPluginManager().getCommands() ) {
@@ -721,11 +733,65 @@ public class DownstreamBridge extends PacketHandler
if ( modified )
{
+ commands.setRoot( (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<CommandNode, CommandNode> commandNodeMapping)
+ {
+ CommandNode dest;
+ if ( source instanceof RootCommandNode )
+ {
+ dest = new 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
public String toString()
{
--
2.32.0.windows.2