diff --git a/changelog.md b/changelog.md index 15b13f6..666d857 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ These changes will (most likely) be included in the next version. ## [Unreleased] - Extended and upgraded potions are now supported in the item syntax by prepending `long_` or `strong_` to the data portion of a potion item (e.g. `potion:strong_instant_heal:1` will yield a Potion of Healing II). Check the wiki for details. +- MobArena now has basic tab completion support for most of the commands that take arguments. - Tridents and crossbows are now considered weapons with regards to the `unbreakable-weapons` flag. All class items that have durability now have their unbreakable flag set to true unless the `unbreakable-weapons` and/or `unbreakable-armor` flags are set to `false`. - Leaderboards now work again on servers running Minecraft 1.14+. - Class chests (non-linked) now work again on servers running Minecraft 1.14+. diff --git a/src/main/java/com/garbagemule/MobArena/commands/Command.java b/src/main/java/com/garbagemule/MobArena/commands/Command.java index f4879db..7fba974 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/Command.java +++ b/src/main/java/com/garbagemule/MobArena/commands/Command.java @@ -2,6 +2,10 @@ package com.garbagemule.MobArena.commands; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; public interface Command { @@ -25,4 +29,16 @@ public interface Command * command handler should print the usage message to the sender */ boolean execute(ArenaMaster am, CommandSender sender, String... args); + + /** + * Tab complete the given arguments. + * + * @param am an ArenaMaster instance + * @param player the sender + * @param args array of arguments + * @return a list of possible completions, or null + */ + default List tab(ArenaMaster am, Player player, String... args) { + return Collections.emptyList(); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java b/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java index 589e2a7..8b7fc54 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java +++ b/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java @@ -34,16 +34,20 @@ import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.ChatColor; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.conversations.Conversable; +import org.bukkit.entity.Player; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; -public class CommandHandler implements CommandExecutor +public class CommandHandler implements CommandExecutor, TabCompleter { private MobArena plugin; private Messenger fallbackMessenger; @@ -237,6 +241,60 @@ public class CommandHandler implements CommandExecutor } } + @Override + public List onTabComplete(CommandSender sender, org.bukkit.command.Command bcmd, String alias, String[] args) { + // Only players can tab complete + if (!(sender instanceof Player)) { + return null; + } + Player player = (Player) sender; + + // If the player is in a convo (Setup Mode), bail + if (player.isConversing()) { + return null; + } + + // Grab the base argument. + String base = (args.length > 0 ? args[0] : "").toLowerCase(); + + // If there's no base argument, show it all + if (base.equals("")) { + return commands.values() + .stream() + .map(cmd -> cmd.getClass().getAnnotation(CommandInfo.class)) + .filter(info -> info != null && player.hasPermission(info.permission())) + .map(CommandInfo::name) + .sorted() + .collect(Collectors.toList()); + } + + // Reloads are terminal + if (base.equals("reload") || (base.equals("config") && args.length > 1 && args[1].equals("reload"))) { + return Collections.emptyList(); + } + + // If we only have the base, terminate + if (args.length == 1) { + return commands.values().stream() + .map(cmd -> cmd.getClass().getAnnotation(CommandInfo.class)) + .filter(info -> info.name().startsWith(base)) + .map(CommandInfo::name) + .sorted() + .collect(Collectors.toList()); + } + + // Otherwise, find the command + List matches = getMatchingCommands(base); + if (matches.size() != 1) { + return Collections.emptyList(); + } + + // And pass completion + Command command = matches.get(0); + String[] params = trimFirstArg(args); + return command.tab(plugin.getArenaMaster(), player, params); + } + /** * Register all the commands directly. * This could also be done with a somewhat dirty classloader/resource reader diff --git a/src/main/java/com/garbagemule/MobArena/commands/admin/DisableCommand.java b/src/main/java/com/garbagemule/MobArena/commands/admin/DisableCommand.java index f4039e9..d8ec5e2 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/admin/DisableCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/admin/DisableCommand.java @@ -7,6 +7,11 @@ import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "disable", @@ -50,4 +55,20 @@ public class DisableCommand implements Command arena.getPlugin().saveConfig(); arena.getGlobalMessenger().tell(sender, "Arena '" + arena.configName() + "' " + ChatColor.RED + "disabled"); } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/admin/EnableCommand.java b/src/main/java/com/garbagemule/MobArena/commands/admin/EnableCommand.java index 9ed9157..57b3a99 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/admin/EnableCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/admin/EnableCommand.java @@ -7,6 +7,11 @@ import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "enable", @@ -50,4 +55,20 @@ public class EnableCommand implements Command arena.getPlugin().saveConfig(); arena.getGlobalMessenger().tell(sender, "Arena '" + arena.configName() + "' " + ChatColor.GREEN + "enabled"); } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/admin/ForceCommand.java b/src/main/java/com/garbagemule/MobArena/commands/admin/ForceCommand.java index dbf5e81..c1b457e 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/admin/ForceCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/admin/ForceCommand.java @@ -6,6 +6,12 @@ import com.garbagemule.MobArena.commands.CommandInfo; import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "force", @@ -82,4 +88,38 @@ public class ForceCommand implements Command } return false; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 2) { + return Collections.emptyList(); + } + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List result = new ArrayList<>(2); + if ("end".startsWith(prefix)) { + result.add("end"); + } + if ("start".startsWith(prefix)) { + result.add("start"); + } + return result; + } + + if (!args[0].equals("end") && !args[0].equals("start")) { + return Collections.emptyList(); + } + + boolean start = args[0].equals("start"); + String prefix = args[1].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .filter(arena -> start != arena.isRunning()) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/admin/KickCommand.java b/src/main/java/com/garbagemule/MobArena/commands/admin/KickCommand.java index e69d87c..57c0511 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/admin/KickCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/admin/KickCommand.java @@ -7,6 +7,10 @@ import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CommandInfo( name = "kick", pattern = "kick|kcik", @@ -36,4 +40,20 @@ public class KickCommand implements Command am.getGlobalMessenger().tell(bp, "You were kicked by " + sender.getName() + "."); return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List players = am.getAllPlayers(); + + return players.stream() + .filter(p -> p.getDisplayName().toLowerCase().startsWith(prefix)) + .map(Player::getDisplayName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/admin/RestoreCommand.java b/src/main/java/com/garbagemule/MobArena/commands/admin/RestoreCommand.java index 7af13cd..46cfdac 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/admin/RestoreCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/admin/RestoreCommand.java @@ -7,6 +7,11 @@ import com.garbagemule.MobArena.util.inventory.InventoryManager; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CommandInfo( name = "restore", pattern = "restore", @@ -38,4 +43,20 @@ public class RestoreCommand implements Command } return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + Collection players = am.getPlugin().getServer().getOnlinePlayers(); + + return players.stream() + .filter(p -> p.getDisplayName().toLowerCase().startsWith(prefix)) + .map(Player::getDisplayName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/ClassChestCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/ClassChestCommand.java index b329403..036c4c3 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/setup/ClassChestCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/ClassChestCommand.java @@ -13,7 +13,11 @@ import org.bukkit.block.Block; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; @CommandInfo( name = "classchest", @@ -58,4 +62,20 @@ public class ClassChestCommand implements Command { am.loadClasses(); return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + Collection classes = am.getClasses().values(); + + return classes.stream() + .filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix)) + .map(ArenaClass::getConfigName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/EditArenaCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/EditArenaCommand.java index f71a07e..9241817 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/setup/EditArenaCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/EditArenaCommand.java @@ -6,6 +6,11 @@ import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "editarena", @@ -52,4 +57,20 @@ public class EditArenaCommand implements Command if (arena.inEditMode()) am.getGlobalMessenger().tell(sender, "Remember to turn it back off after editing!"); return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/RemoveArenaCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/RemoveArenaCommand.java index bdeaa10..20549f8 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/setup/RemoveArenaCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/RemoveArenaCommand.java @@ -5,6 +5,11 @@ import com.garbagemule.MobArena.commands.CommandInfo; import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "removearena", @@ -34,4 +39,20 @@ public class RemoveArenaCommand implements Command am.getGlobalMessenger().tell(sender, "Arena '" + arena.configName() + "' deleted."); return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/SettingCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/SettingCommand.java index dbadf6e..37104e5 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/setup/SettingCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/SettingCommand.java @@ -6,8 +6,13 @@ import com.garbagemule.MobArena.framework.Arena; import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @CommandInfo( name = "setting", @@ -95,4 +100,37 @@ public class SettingCommand implements Command { am.getGlobalMessenger().tell(sender, buffy.toString()); return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 2) { + return Collections.emptyList(); + } + + List arenas = am.getArenas(); + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .filter(arena -> !arena.isRunning()) + .map(Arena::configName) + .collect(Collectors.toList()); + } + + Arena arena = am.getArenaWithName(args[0]); + if (arena == null) { + return Collections.emptyList(); + } + + String prefix = args[1].toLowerCase(); + + Collection settings = arena.getSettings().getValues(false).keySet(); + + return settings.stream() + .filter(s -> s.toLowerCase().startsWith(prefix)) + .sorted() + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/setup/SetupCommand.java b/src/main/java/com/garbagemule/MobArena/commands/setup/SetupCommand.java index 6788140..02e795f 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/setup/SetupCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/setup/SetupCommand.java @@ -38,7 +38,9 @@ import org.bukkit.inventory.meta.ItemMeta; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; @CommandInfo( name = "setup", @@ -88,6 +90,22 @@ public class SetupCommand implements Command, Listener { return true; } + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getArenas(); + + return arenas.stream() + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .map(Arena::configName) + .collect(Collectors.toList()); + } + /** * The internal Setup class has three roles; it is the prompt and the * abandon listener for the Conversation initiated by the setup command, diff --git a/src/main/java/com/garbagemule/MobArena/commands/user/JoinCommand.java b/src/main/java/com/garbagemule/MobArena/commands/user/JoinCommand.java index eac4d0c..992bc0a 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/user/JoinCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/user/JoinCommand.java @@ -10,6 +10,10 @@ import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CommandInfo( name = "join", pattern = "j|jo.*|j.*n", @@ -77,4 +81,22 @@ public class JoinCommand implements Command arena.playerJoin(player, player.getLocation()); } } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getPermittedArenas(player); + + return arenas.stream() + .filter(Arena::isEnabled) + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .filter(arena -> arena.getRegion().isSetup()) + .map(Arena::configName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/user/PickClassCommand.java b/src/main/java/com/garbagemule/MobArena/commands/user/PickClassCommand.java index dac902a..ea3a6cd 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/user/PickClassCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/user/PickClassCommand.java @@ -13,6 +13,11 @@ import com.garbagemule.MobArena.util.ClassChests; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CommandInfo( name = "class", pattern = "(pick)?class", @@ -101,4 +106,21 @@ public class PickClassCommand implements Command } return true; } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + Collection classes = am.getClasses().values(); + + return classes.stream() + .filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix)) + .filter(cls -> cls.hasPermission(player)) + .map(ArenaClass::getConfigName) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/garbagemule/MobArena/commands/user/SpecCommand.java b/src/main/java/com/garbagemule/MobArena/commands/user/SpecCommand.java index 605e29c..1d93f0c 100644 --- a/src/main/java/com/garbagemule/MobArena/commands/user/SpecCommand.java +++ b/src/main/java/com/garbagemule/MobArena/commands/user/SpecCommand.java @@ -10,6 +10,10 @@ import com.garbagemule.MobArena.framework.ArenaMaster; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + @CommandInfo( name = "spec", pattern = "s|spec.*", @@ -71,4 +75,22 @@ public class SpecCommand implements Command arena.playerSpec(player, player.getLocation()); } } + + @Override + public List tab(ArenaMaster am, Player player, String... args) { + if (args.length > 1) { + return Collections.emptyList(); + } + + String prefix = args[0].toLowerCase(); + + List arenas = am.getPermittedArenas(player); + + return arenas.stream() + .filter(Arena::isEnabled) + .filter(arena -> arena.configName().toLowerCase().startsWith(prefix)) + .filter(arena -> arena.getRegion().isSetup()) + .map(Arena::configName) + .collect(Collectors.toList()); + } }