From 262747073a9219698e26c860111c6eae07f7bdba Mon Sep 17 00:00:00 2001 From: Poy Date: Sat, 6 Jun 2026 19:03:42 +0200 Subject: [PATCH 1/5] feat: add TabCompleteListener and delegate tab completion using CommandMap --- pom.xml | 2 +- .../AlternativeCommandListener.java | 18 +++ .../CommandListener.java | 13 +- .../TabCompleteListener.java | 122 ++++++++++++++++++ 4 files changed, 142 insertions(+), 13 deletions(-) create mode 100644 src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java diff --git a/pom.xml b/pom.xml index 74ddfae..9995656 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ AlternativeCommandListener - 3.1 + 3.1-SNAPSHOT jar AlternativeCommandListener diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java index b8539fc..4be25e7 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java @@ -14,15 +14,18 @@ public final class AlternativeCommandListener implements Expansion, GetPlugin, Reloadable, DataFolder { private final CommandConfig config = ConfigGenerator.newInstance(CommandConfig.class, new BukkitConfig(new File(getDataFolder(), "config.yml"))); private final CommandListener commandListener = new CommandListener(this); + private final TabCompleteListener tabCompleteListener = new TabCompleteListener(this); @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(commandListener, getPlugin()); + Bukkit.getPluginManager().registerEvents(tabCompleteListener, getPlugin()); } @Override public void onDisable() { HandlerList.unregisterAll(commandListener); + HandlerList.unregisterAll(tabCompleteListener); } @Override @@ -33,4 +36,19 @@ public void onReload() { public CommandConfig getConfig() { return config; } + + public boolean isIgnored(String rawCommand) { + return config.getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == config.isShouldIgnore(); + } + + private boolean matchCommand(String rawCommand, String pattern) { + boolean caseInsensitive = config.isCaseInsensitive(); + if (pattern.endsWith("*")) { + String prefix = pattern.substring(0, pattern.length() - 1); + return caseInsensitive + ? rawCommand.regionMatches(true, 0, prefix, 0, prefix.length()) + : rawCommand.startsWith(prefix); + } + return caseInsensitive ? pattern.equalsIgnoreCase(rawCommand) : pattern.equals(rawCommand); + } } diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index a736e93..a895f74 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -28,7 +28,7 @@ public void onCommand(PlayerCommandPreprocessEvent event) { } String rawCommand = event.getMessage().substring(1); - if (addon.getConfig().getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == addon.getConfig().isShouldIgnore()) { + if (addon.isIgnored(rawCommand)) { return; } @@ -49,15 +49,4 @@ public void onCommand(PlayerCommandPreprocessEvent event) { menuCommand.get(command).execute(event.getPlayer(), command, args); } } - - private boolean matchCommand(String rawCommand, String pattern) { - boolean caseInsensitive = addon.getConfig().isCaseInsensitive(); - if (pattern.endsWith("*")) { - String prefix = pattern.substring(0, pattern.length() - 1); - return caseInsensitive - ? rawCommand.regionMatches(true, 0, prefix, 0, prefix.length()) - : rawCommand.startsWith(prefix); - } - return caseInsensitive ? pattern.equalsIgnoreCase(rawCommand) : pattern.equals(rawCommand); - } } diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java new file mode 100644 index 0000000..43533c2 --- /dev/null +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java @@ -0,0 +1,122 @@ +package me.hsgamer.bettergui.alternativecommandlistener; + +import me.hsgamer.bettergui.BetterGUI; +import me.hsgamer.bettergui.manager.MenuCommandManager; +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.server.TabCompleteEvent; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public class TabCompleteListener implements Listener { + private final AlternativeCommandListener addon; + + public TabCompleteListener(AlternativeCommandListener addon) { + this.addon = addon; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onTabComplete(TabCompleteEvent event) { + String buffer = event.getBuffer(); + if (!buffer.startsWith("/")) { + return; + } + + String rawCommand = buffer.substring(1); + if (!addon.isIgnored(rawCommand)) { + return; + } + + String[] split = rawCommand.split(" ", -1); + if (split.length == 0) { + return; + } + + String commandLabel = split[0]; + Command originalCommand = getOriginalCommand(commandLabel); + if (originalCommand == null) { + return; + } + + String[] args = new String[0]; + if (split.length > 1) { + args = Arrays.copyOfRange(split, 1, split.length); + } + + try { + List completions = originalCommand.tabComplete(event.getSender(), commandLabel, args); + if (completions != null) { + event.setCompletions(completions); + } + } catch (Exception e) { + // Defensive coding to avoid breaking other functionality + } + } + + private Command getOriginalCommand(String label) { + CommandMap commandMap = getCommandMap(); + if (commandMap == null) { + return null; + } + Command command = commandMap.getCommand(label); + if (command == null) { + return null; + } + + Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); + if (!menuCommands.values().contains(command)) { + return command; + } + + Map knownCommands = getKnownCommands(commandMap); + if (knownCommands == null) { + return null; + } + + String suffix = ":" + label.toLowerCase(java.util.Locale.ENGLISH); + for (Map.Entry entry : knownCommands.entrySet()) { + String key = entry.getKey().toLowerCase(java.util.Locale.ENGLISH); + if (key.endsWith(suffix)) { + Command fallbackCommand = entry.getValue(); + if (!menuCommands.values().contains(fallbackCommand)) { + return fallbackCommand; + } + } + } + + return null; + } + + private CommandMap getCommandMap() { + try { + return (CommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer()); + } catch (Exception e) { + try { + Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); + field.setAccessible(true); + return (CommandMap) field.get(Bukkit.getPluginManager()); + } catch (Exception ex) { + return null; + } + } + } + + @SuppressWarnings("unchecked") + private Map getKnownCommands(CommandMap commandMap) { + try { + Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); + field.setAccessible(true); + return (Map) field.get(commandMap); + } catch (Exception e) { + return null; + } + } +} From 76163e50949552b893fdca8bdcbbd739c608676f Mon Sep 17 00:00:00 2001 From: Poy Date: Sat, 6 Jun 2026 19:12:05 +0200 Subject: [PATCH 2/5] fix: delegate command execution of ignored commands using CommandResolver --- .../AlternativeCommandListener.java | 5 ++ .../CommandListener.java | 19 ++++- .../CommandResolver.java | 75 +++++++++++++++++++ .../TabCompleteListener.java | 73 +----------------- 4 files changed, 101 insertions(+), 71 deletions(-) create mode 100644 src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java index 4be25e7..b145dd6 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java @@ -15,6 +15,7 @@ public final class AlternativeCommandListener implements Expansion, GetPlugin, R private final CommandConfig config = ConfigGenerator.newInstance(CommandConfig.class, new BukkitConfig(new File(getDataFolder(), "config.yml"))); private final CommandListener commandListener = new CommandListener(this); private final TabCompleteListener tabCompleteListener = new TabCompleteListener(this); + private final CommandResolver commandResolver = new CommandResolver(); @Override public void onEnable() { @@ -37,6 +38,10 @@ public CommandConfig getConfig() { return config; } + public CommandResolver getCommandResolver() { + return commandResolver; + } + public boolean isIgnored(String rawCommand) { return config.getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == config.isShouldIgnore(); } diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index a895f74..c2be644 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -11,6 +11,8 @@ import java.util.Arrays; import java.util.Map; +import java.util.Optional; +import java.util.logging.Level; import java.util.regex.Pattern; public class CommandListener implements Listener { @@ -28,17 +30,30 @@ public void onCommand(PlayerCommandPreprocessEvent event) { } String rawCommand = event.getMessage().substring(1); - if (addon.isIgnored(rawCommand)) { + 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); } + if (addon.isIgnored(rawCommand)) { + Optional alternativeCommand = addon.getCommandResolver().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); diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java new file mode 100644 index 0000000..32ed189 --- /dev/null +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java @@ -0,0 +1,75 @@ +package me.hsgamer.bettergui.alternativecommandlistener; + +import me.hsgamer.bettergui.BetterGUI; +import me.hsgamer.bettergui.manager.MenuCommandManager; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Optional; + +public final class CommandResolver { + + public Optional findAlternativeCommand(String label) { + CommandMap commandMap = getCommandMap(); + if (commandMap == null) { + return Optional.empty(); + } + + Command command = commandMap.getCommand(label); + if (command == null) { + return Optional.empty(); + } + + Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); + if (!menuCommands.values().contains(command)) { + return Optional.of(command); + } + + Map knownCommands = getKnownCommands(commandMap); + if (knownCommands == null) { + return Optional.empty(); + } + + String suffix = ":" + label.toLowerCase(java.util.Locale.ENGLISH); + for (Map.Entry entry : knownCommands.entrySet()) { + String key = entry.getKey().toLowerCase(java.util.Locale.ENGLISH); + if (key.endsWith(suffix)) { + Command fallbackCommand = entry.getValue(); + if (!menuCommands.values().contains(fallbackCommand)) { + return Optional.of(fallbackCommand); + } + } + } + + return Optional.empty(); + } + + private CommandMap getCommandMap() { + try { + return (CommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer()); + } catch (Exception e) { + try { + Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); + field.setAccessible(true); + return (CommandMap) field.get(Bukkit.getPluginManager()); + } catch (Exception ex) { + return null; + } + } + } + + @SuppressWarnings("unchecked") + private Map getKnownCommands(CommandMap commandMap) { + try { + Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); + field.setAccessible(true); + return (Map) field.get(commandMap); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java index 43533c2..c424cf8 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java @@ -1,20 +1,14 @@ package me.hsgamer.bettergui.alternativecommandlistener; -import me.hsgamer.bettergui.BetterGUI; -import me.hsgamer.bettergui.manager.MenuCommandManager; -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.server.TabCompleteEvent; -import java.lang.reflect.Field; import java.util.Arrays; import java.util.List; -import java.util.Map; +import java.util.Optional; public class TabCompleteListener implements Listener { private final AlternativeCommandListener addon; @@ -41,8 +35,8 @@ public void onTabComplete(TabCompleteEvent event) { } String commandLabel = split[0]; - Command originalCommand = getOriginalCommand(commandLabel); - if (originalCommand == null) { + Optional originalCommand = addon.getCommandResolver().findAlternativeCommand(commandLabel); + if (!originalCommand.isPresent()) { return; } @@ -52,7 +46,7 @@ public void onTabComplete(TabCompleteEvent event) { } try { - List completions = originalCommand.tabComplete(event.getSender(), commandLabel, args); + List completions = originalCommand.get().tabComplete(event.getSender(), commandLabel, args); if (completions != null) { event.setCompletions(completions); } @@ -60,63 +54,4 @@ public void onTabComplete(TabCompleteEvent event) { // Defensive coding to avoid breaking other functionality } } - - private Command getOriginalCommand(String label) { - CommandMap commandMap = getCommandMap(); - if (commandMap == null) { - return null; - } - Command command = commandMap.getCommand(label); - if (command == null) { - return null; - } - - Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); - if (!menuCommands.values().contains(command)) { - return command; - } - - Map knownCommands = getKnownCommands(commandMap); - if (knownCommands == null) { - return null; - } - - String suffix = ":" + label.toLowerCase(java.util.Locale.ENGLISH); - for (Map.Entry entry : knownCommands.entrySet()) { - String key = entry.getKey().toLowerCase(java.util.Locale.ENGLISH); - if (key.endsWith(suffix)) { - Command fallbackCommand = entry.getValue(); - if (!menuCommands.values().contains(fallbackCommand)) { - return fallbackCommand; - } - } - } - - return null; - } - - private CommandMap getCommandMap() { - try { - return (CommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer()); - } catch (Exception e) { - try { - Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); - field.setAccessible(true); - return (CommandMap) field.get(Bukkit.getPluginManager()); - } catch (Exception ex) { - return null; - } - } - } - - @SuppressWarnings("unchecked") - private Map getKnownCommands(CommandMap commandMap) { - try { - Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); - field.setAccessible(true); - return (Map) field.get(commandMap); - } catch (Exception e) { - return null; - } - } } From 32a1860e0c9dd0b0b709689ff098b2d5f708f1a1 Mon Sep 17 00:00:00 2001 From: Poy Date: Sat, 6 Jun 2026 19:53:46 +0200 Subject: [PATCH 3/5] refactor: merge reflective command map lookups into a single getKnownCommands method --- pom.xml | 2 +- .../AlternativeCommandListener.java | 23 ---- .../CommandListener.java | 105 +++++++++++++++++- .../CommandResolver.java | 75 ------------- .../TabCompleteListener.java | 57 ---------- 5 files changed, 104 insertions(+), 158 deletions(-) delete mode 100644 src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java delete mode 100644 src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java diff --git a/pom.xml b/pom.xml index 9995656..74ddfae 100644 --- a/pom.xml +++ b/pom.xml @@ -11,7 +11,7 @@ AlternativeCommandListener - 3.1-SNAPSHOT + 3.1 jar AlternativeCommandListener diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java index b145dd6..b8539fc 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/AlternativeCommandListener.java @@ -14,19 +14,15 @@ public final class AlternativeCommandListener implements Expansion, GetPlugin, Reloadable, DataFolder { private final CommandConfig config = ConfigGenerator.newInstance(CommandConfig.class, new BukkitConfig(new File(getDataFolder(), "config.yml"))); private final CommandListener commandListener = new CommandListener(this); - private final TabCompleteListener tabCompleteListener = new TabCompleteListener(this); - private final CommandResolver commandResolver = new CommandResolver(); @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(commandListener, getPlugin()); - Bukkit.getPluginManager().registerEvents(tabCompleteListener, getPlugin()); } @Override public void onDisable() { HandlerList.unregisterAll(commandListener); - HandlerList.unregisterAll(tabCompleteListener); } @Override @@ -37,23 +33,4 @@ public void onReload() { public CommandConfig getConfig() { return config; } - - public CommandResolver getCommandResolver() { - return commandResolver; - } - - public boolean isIgnored(String rawCommand) { - return config.getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == config.isShouldIgnore(); - } - - private boolean matchCommand(String rawCommand, String pattern) { - boolean caseInsensitive = config.isCaseInsensitive(); - if (pattern.endsWith("*")) { - String prefix = pattern.substring(0, pattern.length() - 1); - return caseInsensitive - ? rawCommand.regionMatches(true, 0, prefix, 0, prefix.length()) - : rawCommand.startsWith(prefix); - } - return caseInsensitive ? pattern.equalsIgnoreCase(rawCommand) : pattern.equals(rawCommand); - } } diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index c2be644..c6ea0df 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -3,13 +3,18 @@ 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.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.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.logging.Level; @@ -41,8 +46,8 @@ public void onCommand(PlayerCommandPreprocessEvent event) { args = Arrays.copyOfRange(split, 1, split.length); } - if (addon.isIgnored(rawCommand)) { - Optional alternativeCommand = addon.getCommandResolver().findAlternativeCommand(command); + if (isIgnored(rawCommand)) { + Optional alternativeCommand = findAlternativeCommand(command); if (alternativeCommand.isPresent()) { event.setCancelled(true); try { @@ -64,4 +69,100 @@ public void onCommand(PlayerCommandPreprocessEvent event) { menuCommand.get(command).execute(event.getPlayer(), command, args); } } + + @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) { + // Defensive coding + } + } + + 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() { + try { + Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); + field.setAccessible(true); + Object commandMap = field.get(Bukkit.getPluginManager()); + Field knownField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownField.setAccessible(true); + return (Map) knownField.get(commandMap); + } catch (Exception e) { + 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("*")) { + String prefix = pattern.substring(0, pattern.length() - 1); + return caseInsensitive + ? rawCommand.regionMatches(true, 0, prefix, 0, prefix.length()) + : rawCommand.startsWith(prefix); + } + return caseInsensitive ? pattern.equalsIgnoreCase(rawCommand) : pattern.equals(rawCommand); + } } diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java deleted file mode 100644 index 32ed189..0000000 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandResolver.java +++ /dev/null @@ -1,75 +0,0 @@ -package me.hsgamer.bettergui.alternativecommandlistener; - -import me.hsgamer.bettergui.BetterGUI; -import me.hsgamer.bettergui.manager.MenuCommandManager; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandMap; -import org.bukkit.command.SimpleCommandMap; - -import java.lang.reflect.Field; -import java.util.Map; -import java.util.Optional; - -public final class CommandResolver { - - public Optional findAlternativeCommand(String label) { - CommandMap commandMap = getCommandMap(); - if (commandMap == null) { - return Optional.empty(); - } - - Command command = commandMap.getCommand(label); - if (command == null) { - return Optional.empty(); - } - - Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); - if (!menuCommands.values().contains(command)) { - return Optional.of(command); - } - - Map knownCommands = getKnownCommands(commandMap); - if (knownCommands == null) { - return Optional.empty(); - } - - String suffix = ":" + label.toLowerCase(java.util.Locale.ENGLISH); - for (Map.Entry entry : knownCommands.entrySet()) { - String key = entry.getKey().toLowerCase(java.util.Locale.ENGLISH); - if (key.endsWith(suffix)) { - Command fallbackCommand = entry.getValue(); - if (!menuCommands.values().contains(fallbackCommand)) { - return Optional.of(fallbackCommand); - } - } - } - - return Optional.empty(); - } - - private CommandMap getCommandMap() { - try { - return (CommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer()); - } catch (Exception e) { - try { - Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); - field.setAccessible(true); - return (CommandMap) field.get(Bukkit.getPluginManager()); - } catch (Exception ex) { - return null; - } - } - } - - @SuppressWarnings("unchecked") - private Map getKnownCommands(CommandMap commandMap) { - try { - Field field = SimpleCommandMap.class.getDeclaredField("knownCommands"); - field.setAccessible(true); - return (Map) field.get(commandMap); - } catch (Exception e) { - return null; - } - } -} diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java deleted file mode 100644 index c424cf8..0000000 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/TabCompleteListener.java +++ /dev/null @@ -1,57 +0,0 @@ -package me.hsgamer.bettergui.alternativecommandlistener; - -import org.bukkit.command.Command; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.server.TabCompleteEvent; - -import java.util.Arrays; -import java.util.List; -import java.util.Optional; - -public class TabCompleteListener implements Listener { - private final AlternativeCommandListener addon; - - public TabCompleteListener(AlternativeCommandListener addon) { - this.addon = addon; - } - - @EventHandler(priority = EventPriority.HIGHEST) - public void onTabComplete(TabCompleteEvent event) { - String buffer = event.getBuffer(); - if (!buffer.startsWith("/")) { - return; - } - - String rawCommand = buffer.substring(1); - if (!addon.isIgnored(rawCommand)) { - return; - } - - String[] split = rawCommand.split(" ", -1); - if (split.length == 0) { - return; - } - - String commandLabel = split[0]; - Optional originalCommand = addon.getCommandResolver().findAlternativeCommand(commandLabel); - if (!originalCommand.isPresent()) { - return; - } - - String[] args = new String[0]; - if (split.length > 1) { - args = Arrays.copyOfRange(split, 1, split.length); - } - - try { - List completions = originalCommand.get().tabComplete(event.getSender(), commandLabel, args); - if (completions != null) { - event.setCompletions(completions); - } - } catch (Exception e) { - // Defensive coding to avoid breaking other functionality - } - } -} From d0e64a81b48b22682848134a7ce829c109f79211 Mon Sep 17 00:00:00 2001 From: Poy Date: Sat, 6 Jun 2026 19:56:06 +0200 Subject: [PATCH 4/5] fix: comment --- .../CommandListener.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index c6ea0df..1c647bb 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -53,13 +53,15 @@ public void onCommand(PlayerCommandPreprocessEvent event) { try { alternativeCommand.get().execute(event.getPlayer(), command, args); } catch (Exception e) { - BetterGUI.getInstance().getLogger().log(Level.WARNING, "Error executing alternative command: " + command, e); + BetterGUI.getInstance().getLogger().log(Level.WARNING, + "Error executing alternative command: " + command, e); } } return; } - Map menuCommand = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); + Map menuCommand = BetterGUI.getInstance().get(MenuCommandManager.class) + .getRegisteredMenuCommand(); if (addon.getConfig().isCaseInsensitive()) { menuCommand = new CaseInsensitiveStringMap<>(menuCommand); } @@ -104,7 +106,7 @@ public void onTabComplete(TabCompleteEvent event) { event.setCompletions(completions); } } catch (Exception e) { - // Defensive coding + // Safe fallback } } @@ -119,7 +121,8 @@ private Optional findAlternativeCommand(String label) { return Optional.empty(); } - Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class).getRegisteredMenuCommand(); + Map menuCommands = BetterGUI.getInstance().get(MenuCommandManager.class) + .getRegisteredMenuCommand(); if (!menuCommands.values().contains(command)) { return Optional.empty(); } @@ -152,7 +155,8 @@ private Map getKnownCommands() { } private boolean isIgnored(String rawCommand) { - return addon.getConfig().getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == addon.getConfig().isShouldIgnore(); + return addon.getConfig().getIgnoredCommands().stream().anyMatch(s -> matchCommand(rawCommand, s)) == addon + .getConfig().isShouldIgnore(); } private boolean matchCommand(String rawCommand, String pattern) { From 6522fab0c15cb828ccea4fff92468563522692eb Mon Sep 17 00:00:00 2001 From: Poy Date: Sun, 7 Jun 2026 12:03:41 +0200 Subject: [PATCH 5/5] feat: add CommandListener to intercept and delegate command execution and tab completion --- .../CommandListener.java | 56 ++++++++++++++++--- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java index 1c647bb..ec67181 100644 --- a/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java +++ b/src/main/java/me/hsgamer/bettergui/alternativecommandlistener/CommandListener.java @@ -5,6 +5,7 @@ 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; @@ -13,6 +14,7 @@ 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; @@ -21,6 +23,37 @@ 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; @@ -106,7 +139,7 @@ public void onTabComplete(TabCompleteEvent event) { event.setCompletions(completions); } } catch (Exception e) { - // Safe fallback + // Keep the current completions if the delegated command cannot complete safely } } @@ -142,16 +175,23 @@ private Optional findAlternativeCommand(String label) { @SuppressWarnings("unchecked") private Map getKnownCommands() { + if (COMMAND_MAP_METHOD == null) { + return null; + } try { - Field field = Bukkit.getPluginManager().getClass().getDeclaredField("commandMap"); - field.setAccessible(true); - Object commandMap = field.get(Bukkit.getPluginManager()); - Field knownField = SimpleCommandMap.class.getDeclaredField("knownCommands"); - knownField.setAccessible(true); - return (Map) knownField.get(commandMap); + 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) { - return null; + // ignore } + return null; } private boolean isIgnored(String rawCommand) {