package de.oppermann.bastian.spleef.util.command;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import de.oppermann.bastian.spleef.SpleefMain;
import de.oppermann.bastian.spleef.util.ChatUtil;
import de.oppermann.bastian.spleef.util.CraftBukkitUtil;
import de.oppermann.bastian.spleef.util.Language;
import de.oppermann.bastian.spleef.util.Validator;
/**
* Does not require the plugin.yml so it's cool. :)
*
* @author Bastian Oppermann
*/
public class SpleefCommand extends Command {
private static final HashMap<String, SpleefCommand> COMMANDS = new HashMap<>(); // the commands
private static final String VERSION; // the craftbukkit version
static {
String path = Bukkit.getServer().getClass().getPackage().getName();
VERSION = path.substring(path.lastIndexOf(".") + 1, path.length());
}
protected final JavaPlugin PLUGIN;
protected final String COMMAND;
protected final String PERMISSION;
protected final List<AbstractArgument> ARGUMENTS = new ArrayList<AbstractArgument>();
/*
* @see #createIfNotExist(String, String)
*/
private SpleefCommand(JavaPlugin plugin, String command, String description, String permission, String... aliases) {
super(command);
Validator.validateNotNull(plugin, "plugin");
Validator.validateNotNull(description, "description");
Validator.validateNotNull(permission, "permission");
this.PLUGIN = plugin;
this.COMMAND = command;
this.PERMISSION = permission;
super.setDescription(description);
List<String> aliasList = new ArrayList<String>();
for (String alias : aliases) {
aliasList.add(alias);
}
super.setAliases(aliasList);
this.register();
COMMANDS.put(command.toLowerCase(), this);
}
private void register() {
try {
Field f = Class.forName("org.bukkit.craftbukkit." + SpleefCommand.VERSION + ".CraftServer").getDeclaredField("commandMap");
f.setAccessible(true);
CommandMap map = (CommandMap) f.get(Bukkit.getServer());
map.register(this.PLUGIN.getName(), this);
} catch (Exception exc) {
exc.printStackTrace();
}
}
/*
* (non-Javadoc)
* @see org.bukkit.command.Command#tabComplete(org.bukkit.command.CommandSender, java.lang.String, java.lang.String[])
*/
@Override
public List<String> tabComplete(CommandSender sender, String command, String[] args) throws IllegalArgumentException {
return onTabComplete(sender, this, command, args);
}
/*
* (non-Javadoc)
* @see org.bukkit.command.Command#execute(org.bukkit.command.CommandSender, java.lang.String, java.lang.String[])
*/
@Override
public boolean execute(CommandSender cs, String label, String[] args) {
return onCommand(cs, this, label, args);
}
/**
* Adds an argument to the command.
*/
public void registerArgument(AbstractArgument arg) {
Validator.validateNotNull(arg, "arg");
ARGUMENTS.add(arg);
}
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
// show help topic
if (args.length == 1 && args[0].equalsIgnoreCase("?")) { // TODO customizable command (the ?)
ChatUtil.sendLine(sender, ChatColor.GREEN, Language.HELP_HEADLINE.toString().replace("%cmd%", COMMAND));
for (AbstractArgument arg : ARGUMENTS) {
if (!(sender instanceof Player) || ((Player) sender).hasPermission(arg.getPermission())) {
arg.getCommandHelp().send(sender, COMMAND);
}
}
ChatUtil.sendLine(sender, ChatColor.GREEN);
return true; // always return true cause false is ugly
} else { // if it's not the help
CommandResult result;
if (sender instanceof Player) { // if the sender is a player
Player player = (Player) sender;
result = onPlayerCommand(player, cmd, args);
} else {
result = onServerCommand(sender, cmd, args);
}
if (result != null && result.getText() != null) {
sender.sendMessage(result.getText().replace("%cmd%", cmd.getName()).replace("%player%", sender.getName()));
}
return true; // always return true cause false is ugly
}
}
private List<String> onTabComplete(CommandSender sender, Command cmd, String label, String[] args) {
if (!(sender instanceof Player)) { // tab complete only for players (poor console :()
return null;
}
List<String> list = new ArrayList<String>();
// add help topic
if (args.length == 1) {
if ("?".startsWith(args[0])) { // TODO customizable command (the ?)
list.add("?");
}
}
Player player = (Player) sender;
list = onTabComplete(player, cmd, args, list);
if (list == null) { // no list -> no tabcomplete
return null;
}
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String str = it.next();
if (!str.toLowerCase().startsWith(args[args.length-1].toLowerCase())) {
it.remove();
}
}
Collections.sort(list); // sort the list
return list;
}
private List<String> onTabComplete(Player player, Command cmd, String[] args, List<String> list) {
for (AbstractArgument arg : ARGUMENTS) { // iterate throw all arguments
if (player.hasPermission(arg.getPermission())) { // if the player does not have permissions for the argument
if (args.length <= arg.getNeededArguments() || arg.getNeededArguments() < 0) {
if (args.length <= arg.getFixedArguments().length) {
if (arg.getFixedArguments()[args.length - 1].toLowerCase().startsWith(args[args.length - 1].toLowerCase())) {
list.add(arg.getFixedArguments()[args.length - 1]);
}
} else {
if (arg.getFixedArguments().length != 0 && arg.getFixedArguments()[arg.getFixedArguments().length - 1].toLowerCase().startsWith(args[arg.getFixedArguments().length - 1].toLowerCase())) {
list.addAll(arg.onTabComplete(player, args));
} else if (arg.getFixedArguments().length == 0) {
list.addAll(arg.onTabComplete(player, args));
}
}
}
}
}
return list;
}
private CommandResult onPlayerCommand(Player player, Command cmd, String[] args) {
for (AbstractArgument arg : ARGUMENTS) {
if (arg.checkArgs(args)) {
return arg.executeForPlayer(player, cmd, args);
}
}
return CommandResult.ERROR;
}
private CommandResult onServerCommand(CommandSender sender, Command cmd, String[] args) {
for (AbstractArgument arg : ARGUMENTS) {
if (arg.checkArgs(args)) {
return arg.executeForServer(sender, cmd, args);
}
}
return CommandResult.ERROR;
}
/**
* Gets the permission of this command.
*
* @return The permission.
*/
@Override
public String getPermission() {
return PERMISSION;
}
/**
* Unregisters the command.
*/
public void unregister() {
try {
Field fMap = Command.class.getDeclaredField("commandMap");
fMap.setAccessible(true);
CommandMap map = (CommandMap) fMap.get(this);
this.unregister(map);
Field fKnownCommands = map.getClass().getDeclaredField("knownCommands");
fKnownCommands.setAccessible(true);
@SuppressWarnings("unchecked")
HashMap<String, Command> knownCommands = (HashMap<String, Command>) fKnownCommands.get(map);
for (Entry<String, Command> entry : knownCommands.entrySet()) {
if (entry.getValue() == this) {
knownCommands.remove(entry.getKey());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*========== static stuff ==========*/
/**
* Checks if cmdName is a {@link SpleefCommand}.
*/
public static boolean isSpleefCommand(String cmdName) {
Validator.validateNotNull(cmdName, "cmdName");
return COMMANDS.containsKey(cmdName.toLowerCase());
}
/**
* Gets a {@link SpleefCommand} by its name.
*
* @param cmdName The name of the command without slash.
* @param cmdPermission The permission to use this command.
*/
public static SpleefCommand getSpleefCommand(String cmdName) {
Validator.validateNotNull(cmdName, "cmdName");
if (COMMANDS.containsKey(cmdName.toLowerCase())) {
return COMMANDS.get(cmdName.toLowerCase());
} else {
return null;
}
}
private static SpleefCommand createSpleefCommand(String cmdName, String cmdPermission) {
Validator.validateNotNull(cmdName, "cmdName");
Validator.validateNotNull(cmdPermission, "cmdPermission");
if (COMMANDS.containsKey(cmdName.toLowerCase())) {
return COMMANDS.get(cmdName.toLowerCase());
} else {
return new SpleefCommand(SpleefMain.getInstance(), cmdName, "A spleef command", cmdPermission, new String[0]);
}
}
/**
* Creates a new {@link SpleefCommand} if there is no {@link SpleefCommand} for this command.
*
* @param cmdName The name of the command without slash.
* @param cmdPermission The permission to use this command.
*/
public static SpleefCommand createIfNotExist(String cmdName, String cmdPermission) {
Validator.validateNotNull(cmdName, "cmdName");
Validator.validateNotNull(cmdPermission, "cmdPermission");
SpleefCommand command = SpleefCommand.getSpleefCommand(cmdName);
if (command == null) {
command = SpleefCommand.createSpleefCommand(cmdName, cmdPermission);
}
return command;
}
/**
* Gets all commands.
*/
public static Set<Entry<String, SpleefCommand>> getAllCommands() {
return COMMANDS.entrySet();
}
public static class CommandResult {
/**
* Return this if nothing more should happen.
*/
public final static CommandResult SUCCESS = new CommandResult(null);
/**
* Return this if the player does not have the required permission.
*/
public final static CommandResult NO_PERMISSION = new CommandResult(Language.NO_PERMISSION.toString());
/**
* If you use variable arguments instead of fixed return this if the player/server use them the wrong way.
*/
public final static CommandResult ERROR = new CommandResult(Language.WRONG_USAGE.toString());
/**
* If you do not want that the server can use an arguent return this in the
* {@link AbstractArgument#executeForServer(CommandSender, Command, String[])} method.
*/
public final static CommandResult ONLY_PLAYER = new CommandResult(Language.ONLY_PLAYER.toString());
private final String TEXT;
/**
* Class constructor.
*
* @param text The text the player should receive.<p>
* <code>null</code> if the player should receive nothing.<p>
* %cmd% will be replaced with the command without slash.
*/
public CommandResult(String text) {
this.TEXT = text;
}
/**
* Gets the text.
*
* @return The text.
*/
public String getText() {
return TEXT;
}
}
/**
* Helps to keep an uniform style.
* <p>
* This is what is shown in the help-page (/command ?).
*/
public static class CommandHelp {
private final String CMD;
private final String DESCRIPTION;
private final String FULL_TEXT;
/**
* Class constructor.
*
* @param cmd The command/argument name <u>with</u> a slash ( / ).
* @param description The description of the command/argument.
*/
public CommandHelp(String cmd, String description) {
Validator.validateNotNull(cmd, "cmd");
Validator.validateNotNull(description, "description");
this.CMD = cmd;
this.DESCRIPTION = description;
this.FULL_TEXT = ChatColor.GOLD + CMD + ChatColor.GRAY + " - " + DESCRIPTION; // TODO customizable colors
}
/**
* Send's the helptext to the player.
*
* @param sender The reciever of the helptext.
* @param command The command.
*/
public void send(CommandSender sender, String command) {
Validator.validateNotNull(sender, "sender");
Validator.validateNotNull(command, "command");
if (sender instanceof Player) {
String shortDescription = getText().replace("%cmd%", command);
int length = ChatUtil.getStringWidth(shortDescription);
while (length > 1255) {
shortDescription = shortDescription.substring(0, shortDescription.length() - 2); // TODO use StringBuilder for better performance
length = ChatUtil.getStringWidth(shortDescription);
if (length <= 1255) {
shortDescription += "...";
}
}
StringBuilder strBuilder = new StringBuilder();
// TODO customizable colors
strBuilder.append(ChatColor.GOLD.toString()).append(CMD.replace("%cmd%", command)).append("\n").append(ChatColor.RED.toString());
int partLength = 0;
boolean firstIteration = true;
for (String part : DESCRIPTION.split(" ")) {
if (partLength > 1000) { // TODO customizable length
strBuilder.append("\n").append(ChatColor.RED.toString()).append(part); // TODO customizable colors
partLength = 0;
} else {
if (firstIteration) {
strBuilder.append(part);
firstIteration = false;
} else {
strBuilder.append(" ").append(part);
}
}
partLength += ChatUtil.getStringWidth(part);
}
String suggestCommand = command;
final String JSON =
"{\"text\":\"\",\"extra\":[{\"text\":\""
+ shortDescription +
"\",\"clickEvent\":{\"action\":\"suggest_command\",\"value\":\"/"
+ suggestCommand +
"\"},\"hoverEvent\":{\"action\":\"show_text\",\"value\":{\"text\":\"\",\"extra\":[{\"text\":\""
+ strBuilder.toString() +
"\"}]}}}]}";
if (!(sender instanceof Player) || !CraftBukkitUtil.sendJSONText((Player) sender, JSON)) {
sender.sendMessage(shortDescription);
}
} else {
sender.sendMessage(getText().replace("%cmd%", command));
} // end if
}
/**
* Gets the complete text which the player will see.
*
* @return The complete text which the player will see.
*/
public String getText() {
return FULL_TEXT;
}
}
}