package net.minecraft.client.multiplayer; import com.google.common.collect.Lists; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.Minecraft; import net.minecraft.client.player.LocalPlayer; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.core.BlockPos; import net.minecraft.core.Registry; import net.minecraft.core.RegistryAccess; import net.minecraft.network.protocol.game.ClientboundCustomChatCompletionsPacket; import net.minecraft.network.protocol.game.ServerboundCommandSuggestionPacket; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.level.Level; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.EntityHitResult; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.HitResult.Type; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class ClientSuggestionProvider implements SharedSuggestionProvider { private final ClientPacketListener connection; private final Minecraft minecraft; private int pendingSuggestionsId = -1; @Nullable private CompletableFuture pendingSuggestionsFuture; private final Set customCompletionSuggestions = new HashSet(); public ClientSuggestionProvider(ClientPacketListener connection, Minecraft minecraft) { this.connection = connection; this.minecraft = minecraft; } @Override public Collection getOnlinePlayerNames() { List list = Lists.newArrayList(); for (PlayerInfo playerInfo : this.connection.getOnlinePlayers()) { list.add(playerInfo.getProfile().getName()); } return list; } @Override public Collection getCustomTabSugggestions() { if (this.customCompletionSuggestions.isEmpty()) { return this.getOnlinePlayerNames(); } else { Set set = new HashSet(this.getOnlinePlayerNames()); set.addAll(this.customCompletionSuggestions); return set; } } @Override public Collection getSelectedEntities() { return (Collection)(this.minecraft.hitResult != null && this.minecraft.hitResult.getType() == Type.ENTITY ? Collections.singleton(((EntityHitResult)this.minecraft.hitResult).getEntity().getStringUUID()) : Collections.emptyList()); } @Override public Collection getAllTeams() { return this.connection.scoreboard().getTeamNames(); } @Override public Stream getAvailableSounds() { return this.minecraft.getSoundManager().getAvailableSounds().stream(); } @Override public boolean hasPermission(int permissionLevel) { LocalPlayer localPlayer = this.minecraft.player; return localPlayer != null ? localPlayer.hasPermissions(permissionLevel) : permissionLevel == 0; } @Override public CompletableFuture suggestRegistryElements( ResourceKey> resourceKey, SharedSuggestionProvider.ElementSuggestionType registryKey, SuggestionsBuilder builder, CommandContext context ) { return (CompletableFuture)this.registryAccess().lookup(resourceKey).map(registry -> { this.suggestRegistryElements(registry, registryKey, builder); return builder.buildFuture(); }).orElseGet(() -> this.customSuggestion(context)); } @Override public CompletableFuture customSuggestion(CommandContext context) { if (this.pendingSuggestionsFuture != null) { this.pendingSuggestionsFuture.cancel(false); } this.pendingSuggestionsFuture = new CompletableFuture(); int i = ++this.pendingSuggestionsId; this.connection.send(new ServerboundCommandSuggestionPacket(i, context.getInput())); return this.pendingSuggestionsFuture; } private static String prettyPrint(double doubleValue) { return String.format(Locale.ROOT, "%.2f", doubleValue); } private static String prettyPrint(int intValue) { return Integer.toString(intValue); } @Override public Collection getRelevantCoordinates() { HitResult hitResult = this.minecraft.hitResult; if (hitResult != null && hitResult.getType() == Type.BLOCK) { BlockPos blockPos = ((BlockHitResult)hitResult).getBlockPos(); return Collections.singleton( new SharedSuggestionProvider.TextCoordinates(prettyPrint(blockPos.getX()), prettyPrint(blockPos.getY()), prettyPrint(blockPos.getZ())) ); } else { return SharedSuggestionProvider.super.getRelevantCoordinates(); } } @Override public Collection getAbsoluteCoordinates() { HitResult hitResult = this.minecraft.hitResult; if (hitResult != null && hitResult.getType() == Type.BLOCK) { Vec3 vec3 = hitResult.getLocation(); return Collections.singleton(new SharedSuggestionProvider.TextCoordinates(prettyPrint(vec3.x), prettyPrint(vec3.y), prettyPrint(vec3.z))); } else { return SharedSuggestionProvider.super.getAbsoluteCoordinates(); } } @Override public Set> levels() { return this.connection.levels(); } @Override public RegistryAccess registryAccess() { return this.connection.registryAccess(); } @Override public FeatureFlagSet enabledFeatures() { return this.connection.enabledFeatures(); } public void completeCustomSuggestions(int transaction, Suggestions result) { if (transaction == this.pendingSuggestionsId) { this.pendingSuggestionsFuture.complete(result); this.pendingSuggestionsFuture = null; this.pendingSuggestionsId = -1; } } public void modifyCustomCompletions(ClientboundCustomChatCompletionsPacket.Action action, List entries) { switch (action) { case ADD: this.customCompletionSuggestions.addAll(entries); break; case REMOVE: entries.forEach(this.customCompletionSuggestions::remove); break; case SET: this.customCompletionSuggestions.clear(); this.customCompletionSuggestions.addAll(entries); } } }