package autismclient.commands; import autismclient.AutismClientAddon; import autismclient.api.AddonRegistrationResult; import autismclient.modules.PackHideState; import autismclient.util.AutismClientMessaging; import autismclient.util.AutismCompatManager; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.exceptions.CommandSyntaxException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Locale; import java.util.Set; public final class AutismCommands { private static final CommandDispatcher DISPATCHER = new CommandDispatcher<>(); private static final List ALL = new ArrayList<>(); private static final Map BY_NAME = new LinkedHashMap<>(); private static final Map ADDON_COMMAND_OWNERS = new LinkedHashMap<>(); private static final Map ADDON_COMMAND_OBJECT_OWNERS = new IdentityHashMap<>(); private static final Set DISABLED_ADDON_COMMAND_NAMES = new HashSet<>(); private static boolean initialized = false; private AutismCommands() {} public static synchronized void init() { if (initialized) return; initialized = true; register(new autismclient.commands.impl.ToggleCommand()); register(new autismclient.commands.impl.MacroCommand()); register(new autismclient.commands.impl.DelayCommand()); register(new autismclient.commands.impl.SendCommand()); register(new autismclient.commands.impl.VClipCommand()); register(new autismclient.commands.impl.HClipCommand()); register(new autismclient.commands.impl.NbtCommand()); register(new autismclient.commands.impl.ServerCommand()); register(new autismclient.commands.impl.PluginsCommand()); register(new autismclient.commands.impl.PrefixCommand()); register(new autismclient.commands.impl.CommandsCommand()); register(new autismclient.commands.impl.HelpCommand()); register(new autismclient.commands.impl.ModulesCommand()); register(new autismclient.commands.impl.BindsCommand()); register(new autismclient.commands.impl.BindCommand()); register(new autismclient.commands.impl.DismountCommand()); register(new autismclient.commands.impl.DisconnectCommand()); register(new autismclient.commands.impl.SayCommand()); register(new autismclient.commands.impl.DropCommand()); register(new autismclient.commands.impl.GamemodeCommand()); register(new autismclient.commands.impl.GiveCommand()); register(new autismclient.commands.impl.DamageCommand()); register(new autismclient.commands.impl.XCarryCommand()); } private static void register(Command command) { registerWithName(command, command.name(), true); } private static List registerWithName(Command command, String primaryName, boolean registerAliases) { List registeredNames = new ArrayList<>(); ALL.add(command); LiteralArgumentBuilder primary = LiteralArgumentBuilder.literal(primaryName); command.build(primary); DISPATCHER.register(primary); BY_NAME.put(primaryName.toLowerCase(Locale.ROOT), command); DISABLED_ADDON_COMMAND_NAMES.remove(primaryName.toLowerCase(Locale.ROOT)); registeredNames.add(primaryName.toLowerCase(Locale.ROOT)); if (!registerAliases) return registeredNames; for (String alias : command.aliases()) { if (alias == null || alias.isBlank()) continue; if (BY_NAME.containsKey(alias.toLowerCase(Locale.ROOT))) continue; LiteralArgumentBuilder aliasBuilder = LiteralArgumentBuilder.literal(alias); command.build(aliasBuilder); DISPATCHER.register(aliasBuilder); BY_NAME.put(alias.toLowerCase(Locale.ROOT), command); DISABLED_ADDON_COMMAND_NAMES.remove(alias.toLowerCase(Locale.ROOT)); registeredNames.add(alias.toLowerCase(Locale.ROOT)); } return registeredNames; } public static synchronized void registerAddonCommand(Command command, String addonId) { registerAddonCommandDetailed(command, addonId); } public static synchronized AddonRegistrationResult registerAddonCommandDetailed(Command command, String addonId) { if (command == null) return AddonRegistrationResult.rejected("command", "", "command was null"); if (addonId == null || addonId.isBlank()) { return rejectAddonCommand(addonId, "", "registration outside an addon lifecycle"); } String name = command.name(); if (name == null || name.isBlank()) { return rejectAddonCommand(addonId, "", "blank command name"); } List registered; boolean collision = BY_NAME.containsKey(name.toLowerCase(Locale.ROOT)); if (collision) { String namespaced = (addonId == null || addonId.isBlank() ? "addon" : addonId) + ":" + name; AutismClientAddon.LOG.warn("[Commands] Command name '{}' from addon '{}' collides with an existing " + "command; registering it as '{}' instead", name, addonId, namespaced); registered = registerWithName(command, namespaced, false); } else { registered = registerWithName(command, name, true); } ADDON_COMMAND_OBJECT_OWNERS.put(command, addonId); for (String registeredName : registered) { ADDON_COMMAND_OWNERS.put(registeredName, addonId); } String id = registered.isEmpty() ? name : registered.get(0); autismclient.addons.AddonManager.recordAcceptedRegistration("command", id); return AddonRegistrationResult.accepted("command", id); } private static AddonRegistrationResult rejectAddonCommand(String addonId, String id, String reason) { AutismClientAddon.LOG.warn("[Commands] Rejecting addon command '{}': {}", id, reason); autismclient.addons.AddonManager.recordRejectedRegistration(addonId, "command", id, reason); return AddonRegistrationResult.rejected("command", id, reason); } public static synchronized void unregisterAddonCommands(String addonId) { if (addonId == null || addonId.isBlank()) return; List removeNames = new ArrayList<>(); for (Map.Entry owner : ADDON_COMMAND_OWNERS.entrySet()) { if (addonId.equals(owner.getValue())) removeNames.add(owner.getKey()); } for (String name : removeNames) { BY_NAME.remove(name); ADDON_COMMAND_OWNERS.remove(name); DISABLED_ADDON_COMMAND_NAMES.add(name); } ALL.removeIf(command -> addonId.equals(ADDON_COMMAND_OBJECT_OWNERS.get(command))); ADDON_COMMAND_OBJECT_OWNERS.entrySet().removeIf(entry -> addonId.equals(entry.getValue())); } public static CommandDispatcher dispatcher() { return DISPATCHER; } public static List all() { return Collections.unmodifiableList(ALL); } public static Command find(String nameOrAlias) { if (nameOrAlias == null) return null; return BY_NAME.get(nameOrAlias.trim().toLowerCase(Locale.ROOT)); } public static String effectivePrefix() { return AutismCompatManager.effectiveCommandPrefix(); } public static boolean isAutismCommandMessage(String message) { if (commandsBlockedByPanic()) return false; if (message == null || message.isBlank()) return false; String trimmed = message.trim(); String prefix = effectivePrefix(); return !prefix.isEmpty() && trimmed.startsWith(prefix); } public static boolean commandsBlockedByPanic() { return PackHideState.isActive(); } public static boolean isBlockedPanicCommandMessage(String message) { return commandsBlockedByPanic() && isAutismCommandMessage(message); } public static String commandBody(String message) { if (!isAutismCommandMessage(message)) return ""; String trimmed = message.trim(); int prefixLength = effectivePrefix().length(); return trimmed.length() <= prefixLength ? "" : trimmed.substring(prefixLength).trim(); } public static boolean dispatch(String body) { if (commandsBlockedByPanic()) return true; if (body == null) return false; String trimmed = body.trim(); if (trimmed.isEmpty()) return false; if (DISABLED_ADDON_COMMAND_NAMES.contains(firstToken(trimmed).toLowerCase(Locale.ROOT))) { sendSyntaxError(trimmed, CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand().create()); return true; } try { DISPATCHER.execute(trimmed, AutismCommandSource.INSTANCE); return true; } catch (CommandSyntaxException e) { sendSyntaxError(trimmed, e); return true; } catch (Exception e) { AutismClientAddon.LOG.warn("[Commands] dispatch failed for '{}'", trimmed, e); AutismClientMessaging.sendPrefixed("§cCommand error: " + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage())); return true; } } private static void sendSyntaxError(String body, CommandSyntaxException e) { String first = firstToken(body); Command command = find(first); String prefix = effectivePrefix(); if (command == null) { AutismClientMessaging.sendPrefixed("§cUnknown AUTISM command: §f" + first); AutismClientMessaging.sendPrefixed("§7Use §f" + prefix + "commands §7or §f" + prefix + "help§7."); return; } String message = e.getMessage(); if (message == null || message.isBlank()) message = "Incomplete or invalid command."; AutismClientMessaging.sendPrefixed("§c" + message); AutismClientMessaging.sendPrefixed("§7Use §f" + prefix + "help " + command.name() + "§7."); } private static String firstToken(String body) { if (body == null) return ""; String trimmed = body.trim(); if (trimmed.isEmpty()) return ""; int space = trimmed.indexOf(' '); return space < 0 ? trimmed : trimmed.substring(0, space); } }