diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index a736e93..ec67181 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -3,17 +3,57 @@ import me.hsgamer.bettergui.BetterGUI; import me.hsgamer.bettergui.manager.MenuCommandManager; import me.hsgamer.hscore.collections.map.CaseInsensitiveStringMap; +import org.bukkit.Bukkit; import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerCommandPreprocessEvent; +import org.bukkit.event.server.TabCompleteEvent; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; import java.util.regex.Pattern; public class CommandListener implements Listener { + private static final Method COMMAND_MAP_METHOD; + private static final Method KNOWN_COMMANDS_METHOD; + private static final Field KNOWN_COMMANDS_FIELD; + + static { + Method commandMapMethod = null; + try { + commandMapMethod = Bukkit.getServer().getClass().getMethod("getCommandMap"); + } catch (Exception e) { + // ignore + } + COMMAND_MAP_METHOD = commandMapMethod; + + Method knownCommandsMethod = null; + Field knownCommandsField = null; + try { + knownCommandsMethod = SimpleCommandMap.class.getDeclaredMethod("getKnownCommands"); + } catch (NoSuchMethodException e) { + try { + knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommandsField.setAccessible(true); + } catch (Exception ex) { + // ignore + } + } catch (Exception e) { + // ignore + } + KNOWN_COMMANDS_METHOD = knownCommandsMethod; + KNOWN_COMMANDS_FIELD = knownCommandsField; + } + private static final Pattern SPACE_PATTERN = Pattern.compile("\\s"); private final AlternativeCommandListener addon; @@ -28,18 +68,33 @@ public void onCommand(PlayerCommandPreprocessEvent event) { } String rawCommand = event.getMessage().substring(1); - if (addon.getConfig().getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == addon.getConfig().isShouldIgnore()) { + String[] split = SPACE_PATTERN.split(rawCommand); + if (split.length == 0) { return; } - String[] split = SPACE_PATTERN.split(rawCommand); String command = split[0]; String[] args = new String[0]; if (split.length > 1) { args = Arrays.copyOfRange(split, 1, split.length); } - Map menuCommand = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); + if (isIgnored(rawCommand)) { + Optional alternativeCommand = findAlternativeCommand(command); + if (alternativeCommand.isPresent()) { + event.setCancelled(true); + try { + alternativeCommand.get().execute(event.getPlayer(), command, args); + } catch (Exception e) { + BetterGUI.getInstance().getLogger().log(Level.WARNING, + "Error executing alternative command: " + command, e); + } + } + return; + } + + Map menuCommand = BetterGUI.getInstance().get(MenuCommandManager.class) + .getRegisteredMenuCommand(); if (addon.getConfig().isCaseInsensitive()) { menuCommand = new CaseInsensitiveStringMap<>(menuCommand); } @@ -50,6 +105,100 @@ public void onCommand(PlayerCommandPreprocessEvent event) { } } + @EventHandler(priority = EventPriority.HIGHEST) + public void onTabComplete(TabCompleteEvent event) { + String buffer = event.getBuffer(); + if (!buffer.startsWith("/")) { + return; + } + + String rawCommand = buffer.substring(1); + if (!isIgnored(rawCommand)) { + return; + } + + String[] split = rawCommand.split(" ", -1); + if (split.length == 0) { + return; + } + + String commandLabel = split[0]; + Optional alternativeCommand = findAlternativeCommand(commandLabel); + if (!alternativeCommand.isPresent()) { + return; + } + + String[] args = new String[0]; + if (split.length > 1) { + args = Arrays.copyOfRange(split, 1, split.length); + } + + try { + List completions = alternativeCommand.get().tabComplete(event.getSender(), commandLabel, args); + if (completions != null) { + event.setCompletions(completions); + } + } catch (Exception e) { + // Keep the current completions if the delegated command cannot complete safely + } + } + + private Optional findAlternativeCommand(String label) { + Map knownCommands = getKnownCommands(); + if (knownCommands == null) { + return Optional.empty(); + } + + Command command = knownCommands.get(label.toLowerCase()); + if (command == null) { + return Optional.empty(); + } + + Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class) + .getRegisteredMenuCommand(); + if (!menuCommands.values().contains(command)) { + return Optional.empty(); + } + + String suffix = ":" + label.toLowerCase(); + for (Map.Entry entry : knownCommands.entrySet()) { + if (entry.getKey().toLowerCase().endsWith(suffix)) { + Command fallbackCommand = entry.getValue(); + if (!menuCommands.values().contains(fallbackCommand)) { + return Optional.of(fallbackCommand); + } + } + } + + return Optional.empty(); + } + + @SuppressWarnings("unchecked") + private Map getKnownCommands() { + if (COMMAND_MAP_METHOD == null) { + return null; + } + try { + CommandMap commandMap = (CommandMap) COMMAND_MAP_METHOD.invoke(Bukkit.getServer()); + if (commandMap == null) { + return null; + } + if (KNOWN_COMMANDS_METHOD != null) { + return (Map) KNOWN_COMMANDS_METHOD.invoke(commandMap); + } else if (KNOWN_COMMANDS_FIELD != null) { + return (Map) KNOWN_COMMANDS_FIELD.get(commandMap); + } + } catch (Exception e) { + // ignore + } + return null; + } + + private boolean isIgnored(String rawCommand) { + return addon.getConfig().getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == addon + .getConfig().isShouldIgnore(); + } + private boolean matchCommand(String rawCommand, String pattern) { boolean caseInsensitive = addon.getConfig().isCaseInsensitive(); if (pattern.endsWith("*")) {