diff --git a/README.md b/README.md index df71fbe..333d1be 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,6 @@ To use Blade, you simply have to include it as a dependency and shade it into yo If you make any changes or improvements to the project, please consider making a pull request to merge your changes back into the upstream project. This project is in its early stages, if you find any issues please open an issue. -## TODO -- [ ] Fix tab completion - ## Using Blade Maven @@ -24,7 +21,7 @@ Maven com.github.vaperion blade - 1.0.0 + 1.1.0 compile @@ -39,17 +36,18 @@ allprojects { } dependencies { - implementation 'com.github.vaperion:blade:1.0.0' + implementation 'com.github.vaperion:blade:1.1.0' } ``` ### Example code -Initialize Blade: +Initializing Blade: ```java import me.vaperion.blade.Blade; import me.vaperion.blade.command.bindings.impl.BukkitBindings; import me.vaperion.blade.command.container.impl.BukkitCommandContainer; +import me.vaperion.blade.completer.impl.ProtocolLibTabCompleter; import org.bukkit.plugin.java.JavaPlugin; public class ExamplePlugin extends JavaPlugin { @@ -66,6 +64,22 @@ public class ExamplePlugin extends JavaPlugin { } ``` +Setting a custom tab completer: +```java +Blade.of() + ... + .tabCompleter(new ProtocolLibTabCompleter(this)) + ...; +``` + +Registering a type provider without Bindings: +```java +Blade.of() + ... + .bind(Example.class, new BladeProvider() {...}) + ...; +``` + Example commands: ```java import me.vaperion.blade.command.annotation.*; @@ -93,3 +107,59 @@ public class ExampleCommand { } } ``` + +Example custom tab completer with Netty: +```java +import me.vaperion.blade.command.service.BladeCommandService; +import me.vaperion.blade.completer.TabCompleter; +import net.minecraft.server.v1_7_R4.PacketPlayInTabComplete; +import net.minecraft.server.v1_7_R4.PacketPlayOutTabComplete; +import net.minecraft.util.io.netty.channel.ChannelDuplexHandler; +import net.minecraft.util.io.netty.channel.ChannelHandlerContext; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_7_R4.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.plugin.java.JavaPlugin; + +public class CustomTabCompleter implements TabCompleter, Listener { + + private BladeCommandService commandService; + + public CustomTabCompleter(JavaPlugin plugin) { + Bukkit.getServer().getPluginManager().registerEvents(this, plugin); + } + + @Override + public void init(@NotNull BladeCommandService bladeCommandService) { + this.commandService = bladeCommandService; + } + + @EventHandler + public void onJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + ((CraftPlayer) player).getHandle().playerConnection.networkManager.m.pipeline() + .addBefore("packet_handler", "blade_completer", new ChannelDuplexHandler() { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (msg instanceof PacketPlayInTabComplete) { + String commandLine = ((PacketPlayInTabComplete) msg).c(); + if (commandLine.startsWith("/")) { + commandLine = commandLine.substring(1); + + List suggestions = commandService.getCommandCompleter().suggest(commandLine, () -> new BukkitSender(player), (cmd) -> hasPermission(player, cmd)); + if (suggestions != null) { + ctx.writeAndFlush(new PacketPlayOutTabComplete(suggestions.toArray(new String[0]))); + return; + } + } + } + + super.channelRead(ctx, msg); + } + }); + } +} +``` \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3236e6f..a313d3f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.vaperion blade - 1.0.0 + 1.1.0 UTF-8 @@ -19,6 +19,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + @@ -28,36 +32,39 @@ 1.18.16 provided - org.jetbrains annotations 13.0 compile - org.spigotmc spigot-api 1.16.5-R0.1-SNAPSHOT provided + + com.comphenix.protocol + ProtocolLib + 4.6.0 + provided + + clean install org.apache.maven.plugins maven-surefire-plugin 2.22.0 - org.apache.maven.plugins maven-dependency-plugin 3.1.1 - org.apache.maven.plugins maven-compiler-plugin @@ -67,7 +74,6 @@ 1.8 - org.apache.maven.plugins maven-shade-plugin @@ -87,6 +93,4 @@ - - diff --git a/src/main/java/me/vaperion/blade/Blade.java b/src/main/java/me/vaperion/blade/Blade.java index 9f0e295..26af96e 100644 --- a/src/main/java/me/vaperion/blade/Blade.java +++ b/src/main/java/me/vaperion/blade/Blade.java @@ -8,6 +8,7 @@ import me.vaperion.blade.command.bindings.Binding; import me.vaperion.blade.command.container.ContainerCreator; import me.vaperion.blade.command.service.BladeCommandService; +import me.vaperion.blade.completer.TabCompleter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -22,6 +23,7 @@ public class Blade { private final BladeCommandService commandService = new BladeCommandService(); private final String fallbackPrefix; private final ContainerCreator containerCreator; + private final TabCompleter tabCompleter; @Singular("bind0") private final Map, Class>, BladeProvider> customProviderMap; @Singular private final List bindings; @@ -56,10 +58,15 @@ public Blade build() { if (blade.fallbackPrefix != null) blade.commandService.setFallbackPrefix(blade.fallbackPrefix); + if (blade.tabCompleter != null) + blade.commandService.setTabCompleter(blade.tabCompleter); + for (Binding binding : blade.bindings) { binding.bind(blade.commandService); } + blade.commandService.getTabCompleter().init(blade.commandService); + for (Map.Entry, Class>, BladeProvider> entry : blade.customProviderMap.entrySet()) { //noinspection deprecation blade.commandService.bindProviderUnsafely(entry.getKey().getKey(), entry.getValue(), entry.getKey().getValue()); diff --git a/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java b/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java index 84f6f60..6e7e748 100644 --- a/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java +++ b/src/main/java/me/vaperion/blade/command/bindings/impl/BukkitBindings.java @@ -8,6 +8,7 @@ import me.vaperion.blade.command.service.BladeCommandService; import org.bukkit.Bukkit; import org.bukkit.ChatColor; +import org.bukkit.GameMode; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -15,6 +16,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.UUID; import java.util.regex.Pattern; @@ -81,6 +83,37 @@ public List suggest(@NotNull BladeContext context, @NotNull String input return offlinePlayer; }); + + commandService.bindProvider(GameMode.class, new BladeProvider() { + @Nullable + @Override + public GameMode provide(@NotNull BladeContext ctx, @NotNull BladeParameter param, @Nullable String input) throws BladeExitMessage { + if (input == null) return null; + input = input.trim(); + + GameMode mode = getGameMode(input); + + if (mode == null) + throw new BladeExitMessage("No game mode with name " + ChatColor.YELLOW + input + ChatColor.RED + " found."); + + return mode; + } + + @NotNull + @Override + public List suggest(@NotNull BladeContext context, @NotNull String input) throws BladeExitMessage { + input = input.toUpperCase(Locale.ROOT); + List completions = new ArrayList<>(); + + for (GameMode mode : GameMode.values()) { + if (mode.name().startsWith(input)) { + completions.add(mode.name().toLowerCase(Locale.ROOT)); + } + } + + return completions; + } + }); } private boolean isUUID(@NotNull String input) { @@ -99,4 +132,16 @@ private OfflinePlayer getOfflinePlayer(@NotNull String input) { return Bukkit.getOfflinePlayer(input); } + @Nullable + private GameMode getGameMode(String input) { + input = input.toUpperCase(Locale.ROOT); + + for (GameMode mode : GameMode.values()) { + if (mode.name().startsWith(input) || input.equals(String.valueOf(mode.getValue()))) { + return mode; + } + } + + return null; + } } diff --git a/src/main/java/me/vaperion/blade/command/container/BladeCommand.java b/src/main/java/me/vaperion/blade/command/container/BladeCommand.java index 15d7dfa..872a450 100644 --- a/src/main/java/me/vaperion/blade/command/container/BladeCommand.java +++ b/src/main/java/me/vaperion/blade/command/container/BladeCommand.java @@ -31,7 +31,7 @@ public class BladeCommand { private final boolean senderParameter; private final List parameters = new LinkedList<>(); - private final List> providers = new LinkedList<>(); + private final List> providers = new LinkedList<>(), parameterProviders = new LinkedList<>(), flagProviders = new LinkedList<>(); public BladeCommand(BladeCommandService commandService, Object instance, Method method, String[] aliases, Command command, Permission permission) { this.commandService = commandService; @@ -70,8 +70,16 @@ public BladeCommand(BladeCommandService commandService, Object instance, Method bladeParameter = new BladeParameter.CommandParameter(parameterName, parameter.getType(), parameter.getAnnotation(Optional.class), parameter.getAnnotation(Range.class), parameter.isAnnotationPresent(Combined.class)); } + BladeProvider provider = commandService.getCommandResolver().resolveProvider(parameter.getType(), Arrays.asList(parameter.getAnnotations())); + parameters.add(bladeParameter); - providers.add(commandService.getCommandResolver().resolveProvider(parameter.getType(), Arrays.asList(parameter.getAnnotations()))); + providers.add(provider); + + if (bladeParameter instanceof BladeParameter.FlagParameter) + flagProviders.add(provider); + else + parameterProviders.add(provider); + i++; } } diff --git a/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java b/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java index 065c62e..3a3cc6e 100644 --- a/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java +++ b/src/main/java/me/vaperion/blade/command/container/impl/BukkitCommandContainer.java @@ -2,7 +2,6 @@ import lombok.Getter; import me.vaperion.blade.command.annotation.Flag; -import me.vaperion.blade.command.argument.BladeProvider; import me.vaperion.blade.command.container.BladeCommand; import me.vaperion.blade.command.container.BladeParameter; import me.vaperion.blade.command.container.ContainerCreator; @@ -64,14 +63,12 @@ public BukkitCommandContainer(@NotNull BladeCommandService service, @NotNull Bla SimplePluginManager simplePluginManager = (SimplePluginManager) Bukkit.getServer().getPluginManager(); SimpleCommandMap simpleCommandMap = (SimpleCommandMap) COMMAND_MAP.get(simplePluginManager); - simpleCommandMap.register(Optional.ofNullable(this.commandService.getFallbackPrefix()).orElse(alias), this); + simpleCommandMap.register(this.commandService.getFallbackPrefix(), this); } - @NotNull + @Nullable private Tuple resolveCommand(@NotNull String[] arguments) throws BladeExitMessage { - return Optional - .ofNullable(commandService.getCommandResolver().resolveCommand(parentCommand, arguments)) - .orElseThrow(() -> new BladeExitMessage("This command failed to execute as we couldn't find it's registration.")); + return commandService.getCommandResolver().resolveCommand(arguments); } @NotNull @@ -148,19 +145,20 @@ private Tuple checkPermission(@NotNull CommandSender sender, @N } private String[] joinAliasToArgs(String alias, String[] args) { - String[] argsWithAlias = new String[args.length + 1]; - argsWithAlias[0] = alias; - System.arraycopy(args, 0, argsWithAlias, 1, args.length); + String[] aliasParts = alias.split(" "); + String[] argsWithAlias = new String[args.length + aliasParts.length]; + System.arraycopy(aliasParts, 0, argsWithAlias, 0, aliasParts.length); + System.arraycopy(args, 0, argsWithAlias, aliasParts.length, args.length); return argsWithAlias; } @Override - public boolean testPermissionSilent(CommandSender sender) { + public boolean testPermissionSilent(@NotNull CommandSender sender) { return hasPermission(sender, new String[0]); } @Override - public boolean execute(CommandSender sender, String alias, String[] args) { + public boolean execute(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) { BladeCommand command = null; String resolvedAlias = alias; @@ -169,6 +167,8 @@ public boolean execute(CommandSender sender, String alias, String[] args) { if (!permissionResult.getLeft()) throw new BladeExitMessage(permissionResult.getRight()); Tuple resolved = resolveCommand(joinAliasToArgs(alias, args)); + if (resolved == null) throw new BladeExitMessage("This command failed to execute as we couldn't find it's registration."); + command = resolved.getLeft(); resolvedAlias = resolved.getRight(); int offset = Math.min(args.length, resolvedAlias.split(" ").length - 1); @@ -208,20 +208,30 @@ public boolean execute(CommandSender sender, String alias, String[] args) { return false; } + @NotNull @Override - public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + public List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { + if (!commandService.getTabCompleter().isDefault()) return Collections.emptyList(); if (!hasPermission(sender, args)) return Collections.emptyList(); try { - BladeCommand command = resolveCommand(joinAliasToArgs(alias, args)).getLeft(); - BladeContext context = new BladeContext(new BukkitSender(sender), alias, args); + Tuple resolved = resolveCommand(joinAliasToArgs(alias, args)); + if (resolved == null) { + // maybe suggest subcommands? + return Collections.emptyList(); + } + + BladeCommand command = resolved.getLeft(); + String foundAlias = resolved.getRight(); + + List argList = new ArrayList<>(Arrays.asList(args)); + if (foundAlias.split(" ").length > 1) argList.subList(0, foundAlias.split(" ").length - 1).clear(); - Tuple, String> data = commandService.getCommandCompleter().getLastProvider(command, args); - BladeProvider provider = data == null ? null : data.getLeft(); - String argument = data == null ? null : data.getRight(); - if (provider == null) return Collections.emptyList(); + if (argList.isEmpty()) argList.add(""); + String[] actualArguments = argList.toArray(new String[0]); - return provider.suggest(context, argument); + BladeContext context = new BladeContext(new BukkitSender(sender), foundAlias, actualArguments); + return commandService.getCommandCompleter().suggest(context, command, actualArguments); } catch (BladeExitMessage ex) { sender.sendMessage(ChatColor.RED + ex.getMessage()); } catch (Exception ex) { diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java index 097233a..80d190b 100644 --- a/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java +++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandCompleter.java @@ -3,15 +3,16 @@ import lombok.RequiredArgsConstructor; import me.vaperion.blade.command.argument.BladeProvider; import me.vaperion.blade.command.container.BladeCommand; -import me.vaperion.blade.command.container.BladeParameter; +import me.vaperion.blade.command.context.BladeContext; +import me.vaperion.blade.command.context.WrappedSender; import me.vaperion.blade.command.exception.BladeExitMessage; import me.vaperion.blade.utils.Tuple; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; @RequiredArgsConstructor public class BladeCommandCompleter { @@ -19,36 +20,48 @@ public class BladeCommandCompleter { private final BladeCommandService commandService; @Nullable - public Tuple, String> getLastProvider(@NotNull BladeCommand command, @NotNull String[] args) throws BladeExitMessage { - BladeProvider lastProvider = null; - String lastArgument = null; + public List suggest(@NotNull String commandLine, @NotNull Supplier> senderSupplier, @NotNull Function permissionFunction) { + String[] commandParts = commandLine.split(" "); + Tuple resolved = commandService.getCommandResolver().resolveCommand(commandParts); + if (resolved == null) return null; + if (!permissionFunction.apply(resolved.getLeft())) return Collections.emptyList(); + + BladeCommand command = resolved.getLeft(); + String foundAlias = resolved.getRight(); + + List argList = new ArrayList<>(Arrays.asList(commandParts)); + argList.subList(0, Math.max(1, foundAlias.split(" ").length)).clear(); + + if (commandLine.endsWith(" ")) argList.add(""); + String[] actualArguments = argList.toArray(new String[0]); + + BladeContext context = new BladeContext(senderSupplier.get(), foundAlias, actualArguments); + return suggest(context, command, actualArguments); + } + + @NotNull + public List suggest(@NotNull BladeContext context, @NotNull BladeCommand command, @NotNull String[] args) throws BladeExitMessage { try { List argumentList = new ArrayList<>(Arrays.asList(args)); List arguments = command.isQuoted() ? commandService.getCommandParser().combineQuotedArguments(argumentList) : argumentList; - int argIndex = 0, providerIndex = 0; - for (BladeParameter parameter : command.getParameters()) { - boolean flag = false; - - if (parameter instanceof BladeParameter.FlagParameter) { - flag = true; - } else { - if (arguments.size() <= argIndex) return new Tuple<>(lastProvider, lastArgument); - } + Map flags = commandService.getCommandParser().parseFlags(command, arguments); + for (Map.Entry entry : flags.entrySet()) { + arguments.remove("-" + entry.getKey()); - BladeProvider provider = command.getProviders().get(providerIndex); - if (provider == null) - throw new BladeExitMessage("Could not find provider for type '" + parameter.getType().getCanonicalName() + "'."); + boolean isFlag = command.getFlagParameters().stream().anyMatch(flag -> flag.getFlag().value() == entry.getKey()); + if (!isFlag || !"true".equals(entry.getValue())) arguments.remove(entry.getValue()); + } - lastProvider = provider; - lastArgument = (arguments.size() - 1 >= argIndex) ? arguments.get(argIndex) : null; + if (arguments.size() == 0) return Collections.emptyList(); + if (command.getParameterProviders().size() < arguments.size()) return Collections.emptyList(); - if (!flag) argIndex++; - providerIndex++; - } + int index = Math.max(0, arguments.size() - 1); + BladeProvider parameterProvider = command.getParameterProviders().get(index); + String argument = index < arguments.size() ? arguments.get(index) : ""; - return new Tuple<>(lastProvider, lastArgument); + return parameterProvider.suggest(context, argument); } catch (BladeExitMessage ex) { throw ex; } catch (Exception ex) { diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java index da9b6c1..13c4c89 100644 --- a/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java +++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandParser.java @@ -129,7 +129,10 @@ public List combineQuotedArguments(@NotNull List args) { for (int i = 0; i < args.size(); i++) { String arg = args.get(i); - if (arg.isEmpty()) continue; + if (arg.isEmpty()) { + argList.add(arg); + continue; + } char c = arg.charAt(0); if (c == '"' || c == '\'') { @@ -157,7 +160,7 @@ public List combineQuotedArguments(@NotNull List args) { } } - if (!arg.isEmpty()) argList.add(arg); + argList.add(arg); } return argList; diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java index c21d44e..03f865b 100644 --- a/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java +++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandRegistrar.java @@ -12,7 +12,6 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; @RequiredArgsConstructor public class BladeCommandRegistrar { @@ -45,16 +44,15 @@ public void registerClass(@Nullable Object instance, @NotNull Class clazz) { public void registerMethod(@Nullable Object instance, @NotNull Method method, @Nullable BladeCommand parentCommand) throws Exception { Command command = method.getAnnotation(Command.class); Permission permission = method.getAnnotation(Permission.class); + String[] aliases = parentCommand == null ? command.value() : mutateAliases(command.value(), parentCommand.getAliases()); + aliases = Arrays.stream(aliases).map(String::toLowerCase).toArray(String[]::new); - BladeCommand bladeCommand = new BladeCommand(commandService, instance, method, Arrays.stream(aliases).map(String::toLowerCase).toArray(String[]::new), command, permission); + BladeCommand bladeCommand = new BladeCommand(commandService, instance, method, aliases, command, permission); commandService.commands.add(bladeCommand); - List aliasList = Arrays.stream(aliases).collect(Collectors.toCollection(LinkedList::new)); - - for (String alias : aliasList) { - String realAlias = alias; - if (alias.contains(" ")) realAlias = alias.split(" ")[0]; + for (String alias : aliases) { + String realAlias = alias.split(" ")[0]; commandService.aliasCommands.computeIfAbsent(realAlias, $ -> new LinkedList<>()).add(bladeCommand); diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java index a695c5f..9e90863 100644 --- a/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java +++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandResolver.java @@ -9,10 +9,9 @@ import org.jetbrains.annotations.Nullable; import java.lang.annotation.Annotation; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicReference; @RequiredArgsConstructor public class BladeCommandResolver { @@ -20,39 +19,26 @@ public class BladeCommandResolver { private final BladeCommandService commandService; @Nullable - public Tuple resolveCommand(@NotNull BladeCommand parentCommand, @NotNull String[] arguments) { - String cmd = String.join(" ", arguments); + public Tuple resolveCommand(@NotNull String[] input) { + if (input.length == 0) return null; + String[] commandParts = Arrays.copyOf(input, input.length); - do { - AtomicReference commandRef = new AtomicReference<>(); - - for (String alias : parentCommand.getRealAliases()) { - final String finalCmd = cmd; + String baseCommand = commandParts[0].toLowerCase(); + List tree = commandService.aliasCommands.getOrDefault(baseCommand, new ArrayList<>()); - Optional.ofNullable(commandService.aliasCommands.get(alias)) - .ifPresent(list -> { - if (commandRef.get() != null) return; + do { + String checking = String.join(" ", commandParts); - list.forEach(command -> { - for (String commandAlias : command.getAliases()) { - if (commandAlias.equalsIgnoreCase(finalCmd)) { - commandRef.set(command); - break; - } - } - }); - }); + for (BladeCommand subCommand : tree) { + for (String commandAlias : subCommand.getAliases()) { + if (commandAlias.equalsIgnoreCase(checking)) return new Tuple<>(subCommand, commandAlias); + } } - BladeCommand bladeCommand = commandRef.get(); - if (bladeCommand != null) return new Tuple<>(bladeCommand, cmd); - - String[] parts = cmd.split(" "); - if (parts.length <= 1) cmd = ""; - else cmd = String.join(" ", Arrays.copyOfRange(parts, 0, parts.length - 1)); - } while (!cmd.isEmpty()); + commandParts = Arrays.copyOfRange(commandParts, 0, commandParts.length - 1); + } while (commandParts.length > 0); - return new Tuple<>(parentCommand, parentCommand.getRealAliases()[0]); + return null; } @SuppressWarnings("unchecked") diff --git a/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java b/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java index 0ddc895..3c3c5f3 100644 --- a/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java +++ b/src/main/java/me/vaperion/blade/command/service/BladeCommandService.java @@ -9,6 +9,8 @@ import me.vaperion.blade.command.container.BladeProviderContainer; import me.vaperion.blade.command.container.ContainerCreator; import me.vaperion.blade.command.container.ICommandContainer; +import me.vaperion.blade.completer.TabCompleter; +import me.vaperion.blade.completer.impl.DefaultTabCompleter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,8 +26,9 @@ public class BladeCommandService { final Map> aliasCommands = new LinkedHashMap<>(); final Map containerMap = new LinkedHashMap<>(); - @Setter @Getter private String fallbackPrefix = null; + @Setter @Getter private String fallbackPrefix = "blade"; @Setter @Getter private ContainerCreator containerCreator = ContainerCreator.NONE; + @Setter @Getter private TabCompleter tabCompleter = new DefaultTabCompleter(); @Getter private final BladeCommandRegistrar commandRegistrar = new BladeCommandRegistrar(this); @Getter private final BladeCommandResolver commandResolver = new BladeCommandResolver(this); diff --git a/src/main/java/me/vaperion/blade/completer/TabCompleter.java b/src/main/java/me/vaperion/blade/completer/TabCompleter.java new file mode 100644 index 0000000..cd08ba4 --- /dev/null +++ b/src/main/java/me/vaperion/blade/completer/TabCompleter.java @@ -0,0 +1,20 @@ +package me.vaperion.blade.completer; + +import me.vaperion.blade.command.container.BladeCommand; +import me.vaperion.blade.command.service.BladeCommandService; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public interface TabCompleter { + void init(@NotNull BladeCommandService commandService); + + default boolean isDefault() { + return false; + } + + default boolean hasPermission(@NotNull Player player, @NotNull BladeCommand command) { + if ("op".equals(command.getPermission())) return player.isOp(); + if (command.getPermission() == null || command.getPermission().trim().isEmpty()) return true; + return player.hasPermission(command.getPermission()); + } +} diff --git a/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java b/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java new file mode 100644 index 0000000..6d61d8d --- /dev/null +++ b/src/main/java/me/vaperion/blade/completer/impl/DefaultTabCompleter.java @@ -0,0 +1,17 @@ +package me.vaperion.blade.completer.impl; + +import me.vaperion.blade.command.service.BladeCommandService; +import me.vaperion.blade.completer.TabCompleter; +import org.jetbrains.annotations.NotNull; + +public class DefaultTabCompleter implements TabCompleter { + @Override + public void init(@NotNull BladeCommandService commandService) { + // noop + } + + @Override + public boolean isDefault() { + return true; + } +} diff --git a/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java b/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java new file mode 100644 index 0000000..2885227 --- /dev/null +++ b/src/main/java/me/vaperion/blade/completer/impl/ProtocolLibTabCompleter.java @@ -0,0 +1,54 @@ +package me.vaperion.blade.completer.impl; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import me.vaperion.blade.command.context.impl.BukkitSender; +import me.vaperion.blade.command.service.BladeCommandService; +import me.vaperion.blade.completer.TabCompleter; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class ProtocolLibTabCompleter extends PacketAdapter implements TabCompleter { + + private BladeCommandService commandService; + + public ProtocolLibTabCompleter(@NotNull JavaPlugin plugin) { + super(plugin, PacketType.Play.Client.TAB_COMPLETE); + } + + @Override + public void init(@NotNull BladeCommandService commandService) { + this.commandService = commandService; + ProtocolLibrary.getProtocolManager().addPacketListener(this); + } + + @Override + public void onPacketReceiving(PacketEvent event) { + if (event.getPlayer() == null) return; + + Player player = event.getPlayer(); + String commandLine = event.getPacket().getStrings().read(0); + + if (!commandLine.startsWith("/")) return; + else commandLine = commandLine.substring(1); + + List suggestions = commandService.getCommandCompleter().suggest(commandLine, () -> new BukkitSender(player), cmd -> hasPermission(player, cmd)); + if (suggestions == null) return; // if command was not found + + try { + event.setCancelled(true); + PacketContainer tabComplete = new PacketContainer(PacketType.Play.Server.TAB_COMPLETE); + tabComplete.getStringArrays().write(0, suggestions.toArray(new String[0])); + ProtocolLibrary.getProtocolManager().sendServerPacket(player, tabComplete); + } catch (Exception ex) { + System.err.println("An exception was thrown while attempting to tab complete '" + commandLine + "' for player " + player.getName()); + ex.printStackTrace(); + } + } +}