From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jake Potrebic <jake.m.potrebic@gmail.com>
Date: Sun, 22 Aug 2021 15:21:57 -0700
Subject: [PATCH] Fix entity type tags suggestions in selectors

This would really be better as a client fix because just to fix it
all EntityArguments have been told to ask the server for completions
when if this was fixed on the client, that wouldn't be needed.

Mojira Issue: https://bugs.mojang.com/browse/MC-235045

diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java
index 3c0d2332207ba638faaaa4280bce18c334a01271..e6c7f62ed379a78645933670299e4fcda8540ed1 100644
--- a/src/main/java/net/minecraft/commands/CommandSourceStack.java
+++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java
@@ -462,4 +462,20 @@ public class CommandSourceStack implements ExecutionCommandSource<CommandSourceS
         return this.source.getBukkitSender(this);
     }
     // CraftBukkit end
+    // Paper start - tell clients to ask server for suggestions for EntityArguments
+    @Override
+    public Collection<String> getSelectedEntities() {
+        if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && this.source instanceof ServerPlayer player) {
+            final Entity cameraEntity = player.getCamera();
+            final double pickDistance = player.gameMode.getGameModeForPlayer().isCreative() ? 6.0F : 4.5F;
+            final Vec3 min = cameraEntity.getEyePosition(1.0F);
+            final Vec3 viewVector = cameraEntity.getViewVector(1.0F);
+            final Vec3 max = min.add(viewVector.x * pickDistance, viewVector.y * pickDistance, viewVector.z * pickDistance);
+            final net.minecraft.world.phys.AABB aabb = cameraEntity.getBoundingBox().expandTowards(viewVector.scale(pickDistance)).inflate(1.0D, 1.0D, 1.0D);
+            final net.minecraft.world.phys.EntityHitResult hitResult = net.minecraft.world.entity.projectile.ProjectileUtil.getEntityHitResult(cameraEntity, min, max, aabb, (e) -> !e.isSpectator() && e.isPickable(), pickDistance * pickDistance);
+            return hitResult != null ? java.util.Collections.singletonList(hitResult.getEntity().getStringUUID()) : SharedSuggestionProvider.super.getSelectedEntities();
+        }
+        return SharedSuggestionProvider.super.getSelectedEntities();
+    }
+    // Paper end - tell clients to ask server for suggestions for EntityArguments
 }
diff --git a/src/main/java/net/minecraft/commands/Commands.java b/src/main/java/net/minecraft/commands/Commands.java
index e1aa1e1f23512fc7d2267ff8e75358b67cb419a3..aa2fca6917fb67fe0e9ba067d11487c3a274f675 100644
--- a/src/main/java/net/minecraft/commands/Commands.java
+++ b/src/main/java/net/minecraft/commands/Commands.java
@@ -517,6 +517,7 @@ public class Commands {
     private void fillUsableCommands(CommandNode<CommandSourceStack> tree, CommandNode<SharedSuggestionProvider> result, CommandSourceStack source, Map<CommandNode<CommandSourceStack>, CommandNode<SharedSuggestionProvider>> resultNodes) {
         Iterator iterator = tree.getChildren().iterator();
 
+        boolean registeredAskServerSuggestionsForTree = false; // Paper - tell clients to ask server for suggestions for EntityArguments
         while (iterator.hasNext()) {
             CommandNode<CommandSourceStack> commandnode2 = (CommandNode) iterator.next();
             // Paper start - Brigadier API
@@ -543,6 +544,12 @@ public class Commands {
 
                     if (requiredargumentbuilder.getSuggestionsProvider() != null) {
                         requiredargumentbuilder.suggests(SuggestionProviders.safelySwap(requiredargumentbuilder.getSuggestionsProvider()));
+                        // Paper start - tell clients to ask server for suggestions for EntityArguments
+                        registeredAskServerSuggestionsForTree = requiredargumentbuilder.getSuggestionsProvider() == net.minecraft.commands.synchronization.SuggestionProviders.ASK_SERVER;
+                    } else if (io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && !registeredAskServerSuggestionsForTree && requiredargumentbuilder.getType() instanceof net.minecraft.commands.arguments.EntityArgument) {
+                        requiredargumentbuilder.suggests(requiredargumentbuilder.getType()::listSuggestions);
+                        registeredAskServerSuggestionsForTree = true; // You can only
+                        // Paper end - tell clients to ask server for suggestions for EntityArguments
                     }
                 }
 
diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
index 2043001c16b3530c2d3f52efda10bcad424881c0..7976885b902a6ce7d80f31e49448c99452eb9765 100644
--- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
+++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java
@@ -131,7 +131,7 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
             final boolean permission = object instanceof CommandSourceStack stack
                     ? stack.bypassSelectorPermissions || stack.hasPermission(2, "minecraft.command.selector")
                     : icompletionprovider.hasPermission(2);
-            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission);
+            EntitySelectorParser argumentparserselector = new EntitySelectorParser(stringreader, permission, true); // Paper - tell clients to ask server for suggestions for EntityArguments
             // Paper end - Fix EntityArgument suggestion permissions
 
             try {
@@ -141,7 +141,19 @@ public class EntityArgument implements ArgumentType<EntitySelector> {
             }
 
             return argumentparserselector.fillSuggestions(suggestionsbuilder, (suggestionsbuilder1) -> {
-                Collection<String> collection = icompletionprovider.getOnlinePlayerNames();
+                // Paper start - tell clients to ask server for suggestions for EntityArguments
+                final Collection<String> collection;
+                if (icompletionprovider instanceof CommandSourceStack commandSourceStack && commandSourceStack.getEntity() instanceof ServerPlayer sourcePlayer) {
+                    collection = new java.util.ArrayList<>();
+                    for (final ServerPlayer player : commandSourceStack.getServer().getPlayerList().getPlayers()) {
+                        if (sourcePlayer.getBukkitEntity().canSee(player.getBukkitEntity())) {
+                            collection.add(player.getGameProfile().getName());
+                        }
+                    }
+                } else {
+                    collection = icompletionprovider.getOnlinePlayerNames();
+                }
+                // Paper end - tell clients to ask server for suggestions for EntityArguments
                 Iterable<String> iterable = this.playersOnly ? collection : Iterables.concat(collection, icompletionprovider.getSelectedEntities());
 
                 SharedSuggestionProvider.suggest((Iterable) iterable, suggestionsbuilder1);
diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
index 3d897ec6920eff6176ddac9f0442a997b9ef14fd..abaf1ea340c69c8bee80e64567b44b5ce66d5fa3 100644
--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
+++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelectorParser.java
@@ -113,12 +113,19 @@ public class EntitySelectorParser {
     private boolean hasScores;
     private boolean hasAdvancements;
     private boolean usesSelectors;
+    public boolean parsingEntityArgumentSuggestions; // Paper - tell clients to ask server for suggestions for EntityArguments
 
     public EntitySelectorParser(StringReader reader) {
         this(reader, true);
     }
 
     public EntitySelectorParser(StringReader reader, boolean atAllowed) {
+        // Paper start - tell clients to ask server for suggestions for EntityArguments
+        this(reader, atAllowed, false);
+    }
+    public EntitySelectorParser(StringReader reader, boolean atAllowed, boolean parsingEntityArgumentSuggestions) {
+        this.parsingEntityArgumentSuggestions = parsingEntityArgumentSuggestions;
+        // Paper end - tell clients to ask server for suggestions for EntityArguments
         this.distance = MinMaxBounds.Doubles.ANY;
         this.level = MinMaxBounds.Ints.ANY;
         this.rotX = WrappedMinMaxBounds.ANY;
diff --git a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
index 44982ff2cdd6e03f0b9ce8c3cc87561ef183ef06..626fe7a45c2edba68eb201974e9f8f5eebf75cc0 100644
--- a/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
+++ b/src/main/java/net/minecraft/commands/arguments/selector/options/EntitySelectorOptions.java
@@ -76,6 +76,19 @@ public class EntitySelectorOptions {
     public static final DynamicCommandExceptionType ERROR_ENTITY_TYPE_INVALID = new DynamicCommandExceptionType(
         entity -> Component.translatableEscape("argument.entity.options.type.invalid", entity)
     );
+    // Paper start - tell clients to ask server for suggestions for EntityArguments
+    public static final DynamicCommandExceptionType ERROR_ENTITY_TAG_INVALID = new DynamicCommandExceptionType((object) -> {
+        return io.papermc.paper.adventure.PaperAdventure
+            .asVanilla(net.kyori.adventure.text.Component
+                .text("Invalid or unknown entity type tag '" + object + "'")
+                .hoverEvent(net.kyori.adventure.text.event.HoverEvent
+                    .showText(net.kyori.adventure.text.Component
+                        .text("You can disable this error in 'paper.yml'")
+                    )
+                )
+            );
+    });
+    // Paper end - tell clients to ask server for suggestions for EntityArguments
 
     private static void register(String id, EntitySelectorOptions.Modifier handler, Predicate<EntitySelectorParser> condition, Component description) {
         OPTIONS.put(id, new EntitySelectorOptions.Option(handler, condition, description));
@@ -297,6 +310,12 @@ public class EntitySelectorOptions {
 
                         if (reader.isTag()) {
                             TagKey<EntityType<?>> tagKey = TagKey.create(Registries.ENTITY_TYPE, ResourceLocation.read(reader.getReader()));
+                            // Paper start - tell clients to ask server for suggestions for EntityArguments; throw error if invalid entity tag (only on suggestions to keep cmd success behavior)
+                            if (reader.parsingEntityArgumentSuggestions && io.papermc.paper.configuration.GlobalConfiguration.get().commands.fixTargetSelectorTagCompletion && net.minecraft.core.registries.BuiltInRegistries.ENTITY_TYPE.getTag(tagKey).isEmpty()) {
+                                reader.getReader().setCursor(i);
+                                throw ERROR_ENTITY_TAG_INVALID.createWithContext(reader.getReader(), tagKey);
+                            }
+                            // Paper end - tell clients to ask server for suggestions for EntityArguments
                             reader.addPredicate(entity -> entity.getType().is(tagKey) != bl);
                         } else {
                             ResourceLocation resourceLocation = ResourceLocation.read(reader.getReader());
