Add basic tab completion support.

This commit adds support for basic tab completion for most of the commands that take arguments. Some completions are somewhat intelligent, e.g. `/ma join` which only lists enabled and "functional" arenas that the given player is permitted to use. Others are just kinda dumb, e.g. `/ma enable` which indiscriminately lists all arenas.

Closes #405
This commit is contained in:
Andreas Troelsen 2019-07-26 22:26:10 +02:00
parent d5676b7b74
commit 81dfb71fe1
16 changed files with 383 additions and 1 deletions

View File

@ -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+.

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
return Collections.emptyList();
}
}

View File

@ -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<String> 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<Command> 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

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> arenas = am.getArenas();
return arenas.stream()
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
.map(Arena::configName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> arenas = am.getArenas();
return arenas.stream()
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
.map(Arena::configName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 2) {
return Collections.emptyList();
}
if (args.length == 1) {
String prefix = args[0].toLowerCase();
List<String> 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<Arena> arenas = am.getArenas();
return arenas.stream()
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
.filter(arena -> start != arena.isRunning())
.map(Arena::configName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Player> players = am.getAllPlayers();
return players.stream()
.filter(p -> p.getDisplayName().toLowerCase().startsWith(prefix))
.map(Player::getDisplayName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
Collection<? extends Player> players = am.getPlugin().getServer().getOnlinePlayers();
return players.stream()
.filter(p -> p.getDisplayName().toLowerCase().startsWith(prefix))
.map(Player::getDisplayName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
Collection<ArenaClass> classes = am.getClasses().values();
return classes.stream()
.filter(cls -> cls.getConfigName().toLowerCase().startsWith(prefix))
.map(ArenaClass::getConfigName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> arenas = am.getArenas();
return arenas.stream()
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
.map(Arena::configName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> arenas = am.getArenas();
return arenas.stream()
.filter(arena -> arena.configName().toLowerCase().startsWith(prefix))
.map(Arena::configName)
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 2) {
return Collections.emptyList();
}
List<Arena> 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<String> settings = arena.getSettings().getValues(false).keySet();
return settings.stream()
.filter(s -> s.toLowerCase().startsWith(prefix))
.sorted()
.collect(Collectors.toList());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> 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,

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> 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());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
Collection<ArenaClass> 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());
}
}

View File

@ -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<String> tab(ArenaMaster am, Player player, String... args) {
if (args.length > 1) {
return Collections.emptyList();
}
String prefix = args[0].toLowerCase();
List<Arena> 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());
}
}