PandaCord/Waterfall-Proxy-Patches/0005-Add-CommandsDeclareEvent-to-declare-commands-with-br.patch
2021-08-13 20:23:24 +02:00

1049 lines
40 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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