MobArena/src/main/java/com/garbagemule/MobArena/commands/CommandHandler.java

390 lines
14 KiB
Java

package com.garbagemule.MobArena.commands;
import com.garbagemule.MobArena.ConfigError;
import com.garbagemule.MobArena.Messenger;
import com.garbagemule.MobArena.MobArena;
import com.garbagemule.MobArena.Msg;
import com.garbagemule.MobArena.commands.admin.AddRewardCommand;
import com.garbagemule.MobArena.commands.admin.DisableCommand;
import com.garbagemule.MobArena.commands.admin.EnableCommand;
import com.garbagemule.MobArena.commands.admin.ForceCommand;
import com.garbagemule.MobArena.commands.admin.KickCommand;
import com.garbagemule.MobArena.commands.admin.RestoreCommand;
import com.garbagemule.MobArena.commands.setup.AddArenaCommand;
import com.garbagemule.MobArena.commands.setup.AutoGenerateCommand;
import com.garbagemule.MobArena.commands.setup.CheckDataCommand;
import com.garbagemule.MobArena.commands.setup.CheckSpawnsCommand;
import com.garbagemule.MobArena.commands.setup.ClassChestCommand;
import com.garbagemule.MobArena.commands.setup.DeleteItemCommand;
import com.garbagemule.MobArena.commands.setup.EditArenaCommand;
import com.garbagemule.MobArena.commands.setup.ListClassesCommand;
import com.garbagemule.MobArena.commands.setup.LoadItemCommand;
import com.garbagemule.MobArena.commands.setup.RemoveArenaCommand;
import com.garbagemule.MobArena.commands.setup.RemoveContainerCommand;
import com.garbagemule.MobArena.commands.setup.RemoveLeaderboardCommand;
import com.garbagemule.MobArena.commands.setup.RemoveSpawnpointCommand;
import com.garbagemule.MobArena.commands.setup.SaveItemCommand;
import com.garbagemule.MobArena.commands.setup.SettingCommand;
import com.garbagemule.MobArena.commands.setup.SetupCommand;
import com.garbagemule.MobArena.commands.user.ArenaListCommand;
import com.garbagemule.MobArena.commands.user.JoinCommand;
import com.garbagemule.MobArena.commands.user.LeaveCommand;
import com.garbagemule.MobArena.commands.user.NotReadyCommand;
import com.garbagemule.MobArena.commands.user.PickClassCommand;
import com.garbagemule.MobArena.commands.user.PlayerListCommand;
import com.garbagemule.MobArena.commands.user.SpecCommand;
import com.garbagemule.MobArena.commands.user.ReadyCommand;
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, TabCompleter
{
private MobArena plugin;
private Messenger fallbackMessenger;
private Map<String,Command> commands;
public CommandHandler(MobArena plugin) {
this.plugin = plugin;
this.fallbackMessenger = new Messenger("&a[MobArena] ");
registerCommands();
}
@Override
public boolean onCommand(CommandSender sender, org.bukkit.command.Command bcmd, String label, String[] args) {
// Grab the base and arguments.
String base = (args.length > 0 ? args[0] : "").toLowerCase();
String last = (args.length > 0 ? args[args.length - 1] : "").toLowerCase();
// If the player is in a convo (Setup Mode), bail
if (sender instanceof Conversable && ((Conversable) sender).isConversing()) {
return true;
}
// If there's no base argument, show a helpful message.
if (base.equals("")) {
return safeTell(sender, Msg.MISC_HELP);
}
// Reloads are special
if (base.equals("reload") || (base.equals("config") && args.length > 1 && args[1].equals("reload"))) {
return reload(sender);
}
Throwable lastFailureCause = plugin.getLastFailureCause();
if (lastFailureCause != null) {
fallbackMessenger.tell(sender, "MobArena is disabled, because:\n" + ChatColor.RED + lastFailureCause.getMessage());
return true;
}
// The help command is a little special
if (base.equals("?") || base.equals("help")) {
showHelp(sender);
return true;
}
ArenaMaster am = plugin.getArenaMaster();
// Get all commands that match the base.
List<Command> matches = getMatchingCommands(base);
// If there's more than one match, display them.
if (matches.size() > 1) {
am.getGlobalMessenger().tell(sender, Msg.MISC_MULTIPLE_MATCHES);
for (Command cmd : matches) {
showUsage(cmd, sender, false);
}
return true;
}
// If there are no matches at all, notify.
if (matches.size() == 0) {
am.getGlobalMessenger().tell(sender, Msg.MISC_NO_MATCHES);
return true;
}
// Grab the only match.
Command command = matches.get(0);
CommandInfo info = command.getClass().getAnnotation(CommandInfo.class);
// First check if the sender has permission.
if (!sender.hasPermission(info.permission())) {
am.getGlobalMessenger().tell(sender, Msg.MISC_NO_ACCESS);
return true;
}
// Check if the last argument is a ?, in which case, display usage and description
if (last.equals("?") || last.equals("help")) {
showUsage(command, sender, true);
return true;
}
// Otherwise, execute the command!
String[] params = trimFirstArg(args);
if (!command.execute(am, sender, params)) {
showUsage(command, sender, true);
}
return true;
}
private boolean reload(CommandSender sender) {
if (!sender.hasPermission("mobarena.setup.load") && !sender.hasPermission("mobarena.setup.config")) {
return safeTell(sender, Msg.MISC_NO_ACCESS);
}
try {
plugin.reload();
plugin.getArenaMaster().getGlobalMessenger().tell(sender, "Reload complete.");
} catch (ConfigError e) {
fallbackMessenger.tell(sender, "Reload failed due to config-file error:\n" + ChatColor.RED + e.getMessage());
} catch (Exception e) {
fallbackMessenger.tell(sender, "Reload failed:\n" + ChatColor.RED + e.getMessage());
}
return true;
}
private boolean safeTell(CommandSender sender, Msg msg) {
ArenaMaster am = plugin.getArenaMaster();
if (am != null) {
am.getGlobalMessenger().tell(sender, msg);
} else {
fallbackMessenger.tell(sender, msg);
}
return true;
}
/**
* Get all commands that match a given string.
* @param arg the given string
* @return a list of commands whose patterns match the given string
*/
private List<Command> getMatchingCommands(String arg) {
List<Command> result = new ArrayList<>();
// Grab the commands that match the argument.
for (Entry<String,Command> entry : commands.entrySet()) {
if (arg.matches(entry.getKey())) {
result.add(entry.getValue());
}
}
return result;
}
/**
* Show the usage and description messages of a command to a player.
* The usage will only be shown, if the player has permission for the command.
* @param cmd a Command
* @param sender a CommandSender
*/
private void showUsage(Command cmd, CommandSender sender, boolean prefix) {
CommandInfo info = cmd.getClass().getAnnotation(CommandInfo.class);
if (!sender.hasPermission(info.permission())) return;
sender.sendMessage((prefix ? "Usage: " : "") + info.usage() + " " + ChatColor.YELLOW + info.desc());
}
/**
* Remove the first argument of a string. This is because the very first
* element of the arguments array will be the command itself.
* @param args an array of length n
* @return the same array minus the first element, and thus of length n-1
*/
private String[] trimFirstArg(String[] args) {
return Arrays.copyOfRange(args, 1, args.length);
}
/**
* List all the available MobArena commands for the CommandSender.
* @param sender a player or the console
*/
private void showHelp(CommandSender sender) {
StringBuilder user = new StringBuilder();
StringBuilder admin = new StringBuilder();
StringBuilder setup = new StringBuilder();
for (Command cmd : commands.values()) {
CommandInfo info = cmd.getClass().getAnnotation(CommandInfo.class);
if (!sender.hasPermission(info.permission())) continue;
StringBuilder buffy;
if (info.permission().startsWith("mobarena.admin")) {
buffy = admin;
} else if (info.permission().startsWith("mobarena.setup")) {
buffy = setup;
} else {
buffy = user;
}
buffy.append("\n")
.append(ChatColor.RESET).append(info.usage()).append(" ")
.append(ChatColor.YELLOW).append(info.desc());
}
ArenaMaster am = plugin.getArenaMaster();
if (admin.length() == 0 && setup.length() == 0) {
am.getGlobalMessenger().tell(sender, "Available commands: " + user.toString());
} else {
am.getGlobalMessenger().tell(sender, "User commands: " + user.toString());
if (admin.length() > 0) am.getGlobalMessenger().tell(sender, "Admin commands: " + admin.toString());
if (setup.length() > 0) am.getGlobalMessenger().tell(sender, "Setup commands: " + setup.toString());
}
}
@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
* method, but this is neater, albeit more manual work.
*/
private void registerCommands() {
commands = new LinkedHashMap<>();
// mobarena.use
register(JoinCommand.class);
register(LeaveCommand.class);
register(SpecCommand.class);
register(ArenaListCommand.class);
register(PlayerListCommand.class);
register(NotReadyCommand.class);
register(PickClassCommand.class);
register(ReadyCommand.class);
// mobarena.admin
register(EnableCommand.class);
register(DisableCommand.class);
register(ForceCommand.class);
register(KickCommand.class);
register(RestoreCommand.class);
register(AddRewardCommand.class);
// mobarena.setup
register(SetupCommand.class);
register(SettingCommand.class);
register(AddArenaCommand.class);
register(RemoveArenaCommand.class);
register(EditArenaCommand.class);
register(CheckDataCommand.class);
register(RemoveSpawnpointCommand.class);
register(CheckSpawnsCommand.class);
register(RemoveContainerCommand.class);
register(ListClassesCommand.class);
register(ClassChestCommand.class);
register(RemoveLeaderboardCommand.class);
register(AutoGenerateCommand.class);
register(SaveItemCommand.class);
register(DeleteItemCommand.class);
register(LoadItemCommand.class);
}
/**
* Register a command.
* The Command's CommandInfo annotation is queried to find its pattern
* string, which is used to map the commands.
* @param c a Command
*/
public void register(Class<? extends Command> c) {
try {
Command command = c.newInstance();
register(command);
} catch (ReflectiveOperationException e) {
throw new IllegalArgumentException("Failed to instantiate Command class: " + c.getName(), e);
}
}
/**
* Register a command instance.
* <p>
* Adds the given command to MobArena's internal command handler as a
* subcommand, overwriting any existing subcommand mappings. This means
* that the method is safe to call on reloads as long as a reload does
* not change the pattern of a registered command.
*
* @param command the Command instance to register
* @throws IllegalArgumentException if the CommandInfo annotation is
* missing from the class of the Command instance
*/
public void register(Command command) {
Class<?> cls = command.getClass();
CommandInfo info = cls.getAnnotation(CommandInfo.class);
if (info == null) {
throw new IllegalArgumentException("Missing CommandInfo annotation on class " + cls.getName());
}
commands.put(info.pattern(), command);
}
}