diff --git a/src/main/java/meteordevelopment/meteorclient/commands/Commands.java b/src/main/java/meteordevelopment/meteorclient/commands/Commands.java index 8764f20636..c80c9054ad 100644 --- a/src/main/java/meteordevelopment/meteorclient/commands/Commands.java +++ b/src/main/java/meteordevelopment/meteorclient/commands/Commands.java @@ -67,6 +67,7 @@ public static void init() { add(new InputCommand()); add(new WaspCommand()); add(new LocateCommand()); + add(new HelpCommand()); COMMANDS.sort(Comparator.comparing(Command::getName)); diff --git a/src/main/java/meteordevelopment/meteorclient/commands/arguments/CommandArgumentType.java b/src/main/java/meteordevelopment/meteorclient/commands/arguments/CommandArgumentType.java new file mode 100644 index 0000000000..a04494b394 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/arguments/CommandArgumentType.java @@ -0,0 +1,78 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.Commands; +import net.minecraft.command.CommandSource; +import net.minecraft.text.Text; + +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class CommandArgumentType implements ArgumentType { + private static final CommandArgumentType INSTANCE = new CommandArgumentType(); + private static final DynamicCommandExceptionType NO_SUCH_COMMAND = new DynamicCommandExceptionType(name -> Text.literal("Command with name " + name + " doesn't exist.")); + private static final Collection EXAMPLES = Commands.COMMANDS.stream().limit(3).map(Command::getName).collect(Collectors.toList()); + + private CommandArgumentType() { + } + + public static CommandArgumentType create() { + return INSTANCE; + } + + public static Command get(CommandContext context) { + return context.getArgument("command", Command.class); + } + + @Override + public Command parse(StringReader reader) throws CommandSyntaxException { + String name = reader.readString(); + + Command command = Commands.get(name); + + if (command == null) { + for (Command c : Commands.COMMANDS) { + for (String alias : c.getAliases()) { + if (alias.equals(name)) { + command = c; + break; + } + } + if (command != null) break; + } + } + + if (command == null) throw NO_SUCH_COMMAND.create(name); + return command; + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + Set suggestions = new LinkedHashSet<>(); + for (Command c : Commands.COMMANDS) { + suggestions.add(c.getName()); + suggestions.addAll(c.getAliases()); + } + return CommandSource.suggestMatching(suggestions, builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } +} diff --git a/src/main/java/meteordevelopment/meteorclient/commands/commands/HelpCommand.java b/src/main/java/meteordevelopment/meteorclient/commands/commands/HelpCommand.java new file mode 100644 index 0000000000..cbcd031445 --- /dev/null +++ b/src/main/java/meteordevelopment/meteorclient/commands/commands/HelpCommand.java @@ -0,0 +1,85 @@ +/* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client). + * Copyright (c) Meteor Development. + */ + +package meteordevelopment.meteorclient.commands.commands; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import meteordevelopment.meteorclient.commands.Command; +import meteordevelopment.meteorclient.commands.Commands; +import meteordevelopment.meteorclient.commands.arguments.CommandArgumentType; +import meteordevelopment.meteorclient.systems.config.Config; +import meteordevelopment.meteorclient.utils.player.ChatUtils; +import net.minecraft.command.CommandSource; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.util.Map; + +public class HelpCommand extends Command { + public HelpCommand() { + super("help", "Shows you what a command does."); + } + + @Override + public void build(LiteralArgumentBuilder builder) { + builder.then(argument("command", CommandArgumentType.create()).executes(context -> { + showHelp(CommandArgumentType.get(context)); + return SINGLE_SUCCESS; + })); + + builder.executes(context -> { + showHelp(this); + return SINGLE_SUCCESS; + }); + } + + private void showHelp(Command cmd) { + MutableText msg = Text.literal(""); + + msg.append(Text.literal("Help for ").formatted(Formatting.GRAY).append(Text.literal(cmd.getName()).formatted(Formatting.YELLOW))); + + msg.append(Text.literal("\n")).append(Text.literal("Description: ").formatted(Formatting.GRAY).append(Text.literal(cmd.getDescription()).formatted(Formatting.WHITE))); + + msg.append(Text.literal("\n")).append(Text.literal("Aliases: ").formatted(Formatting.GRAY)); + + if (cmd.getAliases().isEmpty()) { + msg.append(Text.literal("None").formatted(Formatting.DARK_GRAY)); + } else { + msg.append(Text.literal(String.join(", ", cmd.getAliases())).formatted(Formatting.AQUA)); + } + + msg.append(getUsageText(cmd)); + + ChatUtils.sendMsg(msg); + } + + private MutableText getUsageText(Command cmd) { + CommandSource source = mc.getNetworkHandler().getCommandSource(); + CommandNode root = Commands.DISPATCHER.getRoot(); + CommandNode node = root.getChild(cmd.getName()); + + MutableText usagesText = Text.literal(""); + + if (node != null) { + Map, String> usages = Commands.DISPATCHER.getSmartUsage(node, source); + + boolean first = true; + for (String usage : usages.values()) { + if (!first) usagesText.append(Text.literal("\n")); + first = false; + + usagesText.append(Text.literal(Config.get().prefix.get()).formatted(Formatting.DARK_GRAY).append(Text.literal(usage).formatted(Formatting.GREEN))); + } + } + + if (usagesText.getString().isEmpty()) { + usagesText.append(Text.literal(cmd.toString()).formatted(Formatting.GREEN)); + } + + return Text.literal("\nUsage:\n").formatted(Formatting.GRAY).append(usagesText); + } +}