Skip to content

Add reflection based init for Commands and Modules #5548

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 82 additions & 43 deletions src/main/java/meteordevelopment/meteorclient/commands/Commands.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,104 @@

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import meteordevelopment.meteorclient.commands.commands.*;
import meteordevelopment.meteorclient.MeteorClient;
import meteordevelopment.meteorclient.pathing.PathManagers;
import meteordevelopment.meteorclient.utils.PostInit;
import net.minecraft.command.CommandSource;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static meteordevelopment.meteorclient.MeteorClient.mc;

public class Commands {
public static final CommandDispatcher<CommandSource> DISPATCHER = new CommandDispatcher<>();
public static final List<Command> COMMANDS = new ArrayList<>();
private static final Set<String> SCAN_PACKAGES = new HashSet<>();
private static final Map<String, Supplier<Boolean>> CONDITIONAL_COMMANDS = new HashMap<>();

static {
registerCommandPackage("meteordevelopment.meteorclient.commands.commands");
}

@PostInit(dependencies = PathManagers.class)
public static void init() {
add(new VClipCommand());
add(new HClipCommand());
add(new DismountCommand());
add(new DisconnectCommand());
add(new DamageCommand());
add(new DropCommand());
add(new EnchantCommand());
add(new FakePlayerCommand());
add(new FriendsCommand());
add(new CommandsCommand());
add(new InventoryCommand());
add(new NbtCommand());
add(new NotebotCommand());
add(new PeekCommand());
add(new EnderChestCommand());
add(new ProfilesCommand());
add(new ReloadCommand());
add(new ResetCommand());
add(new SayCommand());
add(new ServerCommand());
add(new SwarmCommand());
add(new ToggleCommand());
add(new SettingCommand());
add(new SpectateCommand());
add(new GamemodeCommand());
add(new SaveMapCommand());
add(new MacroCommand());
add(new ModulesCommand());
add(new BindsCommand());
add(new GiveCommand());
add(new NameHistoryCommand());
add(new BindCommand());
add(new FovCommand());
add(new RotationCommand());
add(new WaypointCommand());
add(new InputCommand());
add(new WaspCommand());
add(new LocateCommand());

loadCommands();
COMMANDS.sort(Comparator.comparing(Command::getName));
}

/**
* Registers a package to scan for commands.
* This should be called by addons during their initialization.
*/
public static void registerCommandPackage(String packageName) {
SCAN_PACKAGES.add(packageName);
}

/**
* Registers a command to be loaded conditionally.
*
* @param commandClass The class of the command to register
* @param condition A supplier that returns true if the command should be loaded
*/
public static void registerConditionalCommand(Class<? extends Command> commandClass, Supplier<Boolean> condition) {
CONDITIONAL_COMMANDS.put(commandClass.getName(), condition);
}

public static void loadCommands() {
try {
int totalCount = 0;
int skippedCount = 0;

ConfigurationBuilder config = new ConfigurationBuilder()
.forPackages(SCAN_PACKAGES.toArray(new String[0]))
.setScanners(Scanners.SubTypes)
.setParallel(true)
.setExpandSuperTypes(false);

Reflections reflections = new Reflections(config);
Set<Class<? extends Command>> commandClasses = reflections.getSubTypesOf(Command.class);

commandClasses = commandClasses.stream()
.filter(commandClass -> SCAN_PACKAGES.stream().anyMatch(pkg -> commandClass.getName().startsWith(pkg)))
.collect(Collectors.toSet());

for (Class<? extends Command> commandClass : commandClasses) {
String className = commandClass.getName();
if (CONDITIONAL_COMMANDS.containsKey(className) && !CONDITIONAL_COMMANDS.get(className).get()) {
MeteorClient.LOG.info("Skipping conditional command: {}", className);
skippedCount++;
continue;
}

try {
if (Modifier.isAbstract(commandClass.getModifiers()) || commandClass.isInterface()) continue;

Constructor<? extends Command> constructor = commandClass.getDeclaredConstructor();
constructor.setAccessible(true);
Command command = constructor.newInstance();
add(command);
totalCount++;
} catch (NoSuchMethodException ignored) {
MeteorClient.LOG.error("Command {} does not have a no-args constructor", commandClass.getName());
} catch (InvocationTargetException | InstantiationException | IllegalAccessException e) {
MeteorClient.LOG.error("Failed to load command: {}", commandClass.getName(), e);
}
}

MeteorClient.LOG.info("Loaded {} commands ({} skipped)", totalCount, skippedCount);
} catch (Exception e) {
MeteorClient.LOG.error("Failed to load commands", e);
}
}

public static void add(Command command) {
COMMANDS.removeIf(existing -> existing.getName().equals(command.getName()));
command.registerTo(DISPATCHER);
Expand Down
Loading