Created
June 9, 2015 02:43
-
-
Save aikar/15eab53102152daf43e6 to your computer and use it in GitHub Desktop.
Empire Minecraft's Command System
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface CommandAlias { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface CommandCompletion { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface CommandPermission { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Default { | |
String value() default ""; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Optional { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Split { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Subcommand { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Syntax { | |
String value(); | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.annotation; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
@Retention(RetentionPolicy.RUNTIME) | |
public @interface Values { | |
String value(); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.contexts; | |
import com.empireminecraft.commands.InvalidCommandArgument; | |
import com.empireminecraft.data.EmpireUser; | |
import com.empireminecraft.systems.db.EmpireDb; | |
import com.empireminecraft.util.Util; | |
import lombok.Data; | |
import org.bukkit.entity.Player; | |
import java.sql.SQLException; | |
@Data public class AnyPlayer { | |
final EmpireUser user; | |
public Player getPlayer() { | |
return user.player; | |
} | |
public static ContextResolver<AnyPlayer> getResolver() { | |
return (p, sender, args, isLast) -> { | |
String who = args.remove(0); | |
final String origwho = who; | |
EmpireUser user = EmpireUser.getUser(who); | |
if (user == null) { | |
try { | |
// TODO: cache? | |
who = EmpireDb.getFirstColumn( | |
"SELECT name FROM user WHERE name LIKE ? ORDER BY name LIMIT 1", | |
Util.simplifyString(who) + "%"); | |
if (who != null) { | |
user = EmpireUser.getUser(who); | |
} | |
} catch (SQLException e) { | |
Util.printException(e); | |
} | |
} | |
if (user == null) { | |
throw new InvalidCommandArgument("Could not find a player with the username " + origwho); | |
} | |
return new AnyPlayer(user); | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.contexts; | |
import com.empireminecraft.commands.contexts.ContextResolver.SenderAwareContextResolver; | |
import com.empireminecraft.commands.InvalidCommandArgument; | |
import com.empireminecraft.commands.annotation.Split; | |
import com.empireminecraft.commands.annotation.Values; | |
import com.empireminecraft.data.EmpireUser; | |
import com.empireminecraft.systems.residence.protection.ClaimedResidence; | |
import com.empireminecraft.util.RegexPatterns; | |
import com.empireminecraft.util.UserUtil; | |
import com.empireminecraft.util.Util; | |
import com.google.common.collect.Maps; | |
import org.bukkit.command.CommandSender; | |
import org.bukkit.configuration.InvalidConfigurationException; | |
import org.bukkit.entity.Player; | |
import org.spigotmc.SneakyThrow; | |
import java.util.Map; | |
public final class CommandContexts { | |
static final Map<Class<?>, ContextResolver<?>> contextMap = Maps.newHashMap(); | |
private CommandContexts() {} | |
public static void registerContexts() { | |
registerSenderAwareContext(ClaimedResidence.class, ClaimedResidence.getResolver()); | |
registerContext(Integer.class, (p, sender, args, isLast) -> { | |
try { | |
return Integer.parseInt(args.remove(0)); | |
} catch (NumberFormatException e) { | |
throw new InvalidCommandArgument("Must be a number"); | |
} | |
}); | |
registerContext(Long.class, (p, sender, args, isLast) -> { | |
try { | |
return Long.parseLong(args.remove(0)); | |
} catch (NumberFormatException e) { | |
throw new InvalidCommandArgument("Must be a number"); | |
} | |
}); | |
registerContext(Float.class, (p, sender, args, isLast) -> { | |
try { | |
return Float.parseFloat(args.remove(0)); | |
} catch (NumberFormatException e) { | |
throw new InvalidCommandArgument("Must be a number"); | |
} | |
}); | |
registerContext(Double.class, (p, sender, args, isLast) -> { | |
try { | |
return Double.parseDouble(args.remove(0)); | |
} catch (NumberFormatException e) { | |
throw new InvalidCommandArgument("Must be a number"); | |
} | |
}); | |
registerContext(Boolean.class, (p, sender, args, isLast) -> { | |
switch (args.remove(0)) { | |
case "t": | |
case "true": | |
case "on": | |
case "y": | |
case "yes": | |
case "1": | |
return true; | |
} | |
return false; | |
}); | |
registerContext(String.class, (p, sender, args, isLast) -> { | |
final Values values = p.getAnnotation(Values.class); | |
if (values != null) { | |
return args.remove(0); | |
} | |
if (isLast) { | |
return Util.join(args); | |
} | |
return args.remove(0); | |
}); | |
registerContext(String[].class, (p, sender, args, isLast) -> { | |
Split split = p.getAnnotation(Split.class); | |
if (split != null) { | |
return RegexPatterns.getPattern(split.value()).split(args.remove(0)); | |
} else if (!isLast) { | |
SneakyThrow.sneaky(new InvalidConfigurationException("Weird Command signature... String[] should be last or @Split")); | |
} | |
String[] result = args.toArray(new String[args.size()]); | |
args.clear(); | |
return result; | |
}); | |
registerContext(EmpireUser.class, (p, sender, args, isLast) -> { | |
String who = args.remove(0); | |
if (!EmpireUser.validateUsername(who)) { | |
throw new InvalidCommandArgument("Invalid Minecraft Name Format. 3-16 characters"); | |
} | |
EmpireUser user = EmpireUser.getUser(who); | |
if (user == null) { | |
throw new InvalidCommandArgument("Could not find user " + who); | |
} | |
return user; | |
}); | |
registerContext(OnlinePlayer.class, (p, sender, args, isLast) -> { | |
Player player = UserUtil.findPlayerSmart(sender, args.remove(0)); | |
if (player == null) { | |
throw new InvalidCommandArgument(); | |
} | |
return new OnlinePlayer(player); | |
}); | |
registerContext(AnyPlayer.class, AnyPlayer.getResolver()); | |
registerSenderAwareContext(CommandSender.class, (p, sender, args, isLast) -> sender); | |
registerSenderAwareContext(Player.class, (p, sender, args, isLast) -> sender instanceof Player ? (Player) sender : null); | |
} | |
public static <T> void registerSenderAwareContext(Class<T> context, SenderAwareContextResolver<T> supplier) { | |
contextMap.put(context, supplier); | |
} | |
public static <T> void registerContext(Class<T> context, ContextResolver<T> supplier) { | |
contextMap.put(context, supplier); | |
} | |
public static ContextResolver<?> getResolver(Class<?> type) { | |
Class<?> rootType = type; | |
do { | |
if (type == Object.class) { | |
break; | |
} | |
final ContextResolver<?> resolver = contextMap.get(type); | |
if (resolver != null) { | |
return resolver; | |
} | |
} while ((type = type.getSuperclass()) != null); | |
Util.printException(new InvalidConfigurationException("No context resolver defined for " + rootType.getName())); | |
return null; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.contexts; | |
import com.empireminecraft.commands.InvalidCommandArgument; | |
import org.bukkit.command.CommandSender; | |
import java.lang.reflect.Parameter; | |
import java.util.List; | |
@FunctionalInterface | |
public interface ContextResolver <C> { | |
C getContext(Parameter p, CommandSender sender, List<String> args, boolean isLast) throws InvalidCommandArgument; | |
interface SenderAwareContextResolver <C> extends ContextResolver <C> {} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands.contexts; | |
import com.empireminecraft.data.EmpireUser; | |
import lombok.Data; | |
import org.bukkit.entity.Player; | |
@Data public class OnlinePlayer { | |
public final Player player; | |
public EmpireUser getUser() { | |
return EmpireUser.getUser(player); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands; | |
import com.empireminecraft.commands.annotation.CommandAlias; | |
import com.empireminecraft.commands.annotation.CommandPermission; | |
import com.empireminecraft.commands.annotation.Default; | |
import com.empireminecraft.commands.annotation.Subcommand; | |
import com.empireminecraft.config.EmpireServer; | |
import com.empireminecraft.config.PlayerSettingKey; | |
import com.empireminecraft.config.language.MiscLang; | |
import com.empireminecraft.data.EmpireUser; | |
import com.empireminecraft.features.items.CustomItems; | |
import com.empireminecraft.features.survival.mobs.CustomMob; | |
import com.empireminecraft.systems.db.EmpireDb; | |
import com.empireminecraft.util.RegexPatterns; | |
import com.empireminecraft.util.UserUtil; | |
import com.empireminecraft.util.Util; | |
import com.google.common.collect.ImmutableList; | |
import com.google.common.collect.Lists; | |
import com.google.common.collect.Sets; | |
import lombok.AllArgsConstructor; | |
import org.apache.commons.lang.StringUtils; | |
import org.bukkit.Bukkit; | |
import org.bukkit.command.Command; | |
import org.bukkit.command.CommandSender; | |
import org.bukkit.configuration.InvalidConfigurationException; | |
import org.bukkit.entity.EntityType; | |
import org.bukkit.entity.Player; | |
import org.bukkit.util.StringUtil; | |
import org.spigotmc.SneakyThrow; | |
import java.lang.reflect.Method; | |
import java.sql.SQLException; | |
import java.util.*; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
public abstract class EmpireCommand extends Command { | |
private final Map<String, RegisteredCommand> subCommands = new HashMap<>(); | |
private final Map<String, RegisteredCommand> aliases = new HashMap<>(); | |
protected String origCommandLabel; | |
protected String[] origArgs; | |
public EmpireCommand() { | |
this(null); | |
} | |
public EmpireCommand(String cmd) { | |
super(cmd); | |
CommandAlias rootCmdAlias = this.getClass().getAnnotation(CommandAlias.class); | |
if (cmd == null) { | |
if (rootCmdAlias == null) { | |
cmd = "__" + this.getClass().getSimpleName(); | |
} else { | |
cmd = RegexPatterns.PIPE.split(rootCmdAlias.value())[0]; | |
} | |
setName(cmd); | |
setLabel(cmd); | |
} | |
this.description = cmd + " commands"; | |
this.usageMessage = "/" + cmd; | |
final CommandPermission perm = this.getClass().getAnnotation(CommandPermission.class); | |
if (perm != null) { | |
this.setPermission(perm.value()); | |
} | |
Set<Method> methods = Sets.newHashSet(); | |
Collections.addAll(methods, this.getClass().getDeclaredMethods()); | |
Collections.addAll(methods, this.getClass().getMethods()); | |
boolean foundDefault = false; | |
for (Method method : methods) { | |
method.setAccessible(true); | |
String sublist = null; | |
final Subcommand sub = method.getAnnotation(Subcommand.class); | |
final Default def = method.getAnnotation(Default.class); | |
final CommandAlias commandAliases = method.getAnnotation(CommandAlias.class); | |
if (def != null) { | |
if (!foundDefault) { | |
registerSubcommand(method, "__default"); | |
foundDefault = true; | |
} else { | |
SneakyThrow.sneaky(new InvalidConfigurationException("Multiple @Default commands")); | |
} | |
} | |
if (sub != null) { | |
sublist = sub.value(); | |
} else if (commandAliases != null) { | |
sublist = commandAliases.value(); | |
} | |
if (sublist != null) { | |
registerSubcommand(method, sublist); | |
} | |
} | |
try { | |
Method unknown = this.getClass().getMethod("onUnknown", CommandSender.class, String.class, String[].class); | |
unknown.setAccessible(true); | |
registerSubcommand(unknown, "__unknown"); | |
} catch (NoSuchMethodException ignored) {} | |
List<String> cmdList = new ArrayList<>(); | |
cmdList.addAll(this.aliases.keySet()); | |
if (rootCmdAlias != null) { | |
Collections.addAll(cmdList, RegexPatterns.PIPE.split(rootCmdAlias.value())); | |
} | |
cmdList.remove(cmd); | |
if (!cmdList.isEmpty()) { | |
this.setAliases(cmdList); | |
} | |
Bukkit.getServer().getCommandMap().register("empire", this); | |
} | |
private void registerSubcommand(Method method, final String subCommand) { | |
final String[] subCommandParts = RegexPatterns.SPACE.split(subCommand); | |
List<String> cmdList = getSubCommandPossibilityList(subCommandParts); | |
// Strip pipes off for auto complete addition | |
for (int i = 0; i < subCommandParts.length; i++) { | |
subCommandParts[i] = RegexPatterns.PIPE.split(subCommandParts[i])[0]; | |
} | |
String prefSubCommand = StringUtils.join(subCommandParts, " "); | |
final CommandAlias cmdAlias = method.getAnnotation(CommandAlias.class); | |
final String[] aliasNames = cmdAlias != null ? RegexPatterns.PIPE.split(cmdAlias.value().toLowerCase()) : null; | |
String cmdName = aliasNames != null ? aliasNames[0] : getLabel() + " "; | |
RegisteredCommand cmd = new RegisteredCommand(this, cmdName, method, prefSubCommand); | |
for (String subcmd : cmdList) { | |
subCommands.put(subcmd, cmd); | |
} | |
if (aliasNames != null) { | |
for (String name : aliasNames) { | |
aliases.put(name.toLowerCase(), cmd); | |
} | |
} | |
} | |
/** | |
* Takes a string like "foo|bar baz|qux" and generates a list of | |
* - foo baz | |
* - foo qux | |
* - bar baz | |
* - bar qux | |
* | |
* For every possible sub command combination | |
* | |
* @param subCommandParts | |
* @return List of all sub command possibilities | |
*/ | |
private static List<String> getSubCommandPossibilityList(String[] subCommandParts) { | |
int i = 0; | |
ArrayList<String> current = null; | |
while (true) { | |
ArrayList<String> newList = new ArrayList<>(); | |
if (i < subCommandParts.length) { | |
for (String s1 : RegexPatterns.PIPE.split(subCommandParts[i])) { | |
if (current != null) { | |
newList.addAll(current.stream().map(s -> s + " " + s1).collect(Collectors.toList())); | |
} else { | |
newList.add(s1); | |
} | |
} | |
} | |
if (i + 1 < subCommandParts.length) { | |
current = newList; | |
i = i + 1; | |
continue; | |
} | |
return newList; | |
} | |
} | |
@Override | |
public final boolean execute(CommandSender sender, String commandLabel, String[] args) { | |
if (!testPermission(sender)) { | |
return false; | |
} | |
commandLabel = commandLabel.toLowerCase(); | |
if (preCommand(sender, commandLabel, args)) { | |
return false; | |
} | |
origCommandLabel = commandLabel; | |
origArgs = args; | |
final RegisteredCommand alias = aliases.get(commandLabel); | |
if (alias != null) { | |
executeCommand(sender, args, alias); | |
return false; | |
} | |
if (args.length == 0) { | |
onDefault(sender, commandLabel); | |
return false; | |
} | |
CommandSearch cmd = findSubCommand(args); | |
if (cmd != null) { | |
final String[] execargs = Arrays.copyOfRange(args, cmd.argIndex, args.length); | |
executeCommand(sender, execargs, cmd.cmd); | |
return false; | |
} | |
if (!onUnknown(sender, commandLabel, args)) { | |
help(sender, args); | |
} | |
return false; | |
} | |
private CommandSearch findSubCommand(String[] args) { | |
for (int i = args.length; i >= 0; i--) { | |
String checkSub = StringUtils.join(args, " ", 0, i).toLowerCase(); | |
final RegisteredCommand cmd = subCommands.get(checkSub); | |
if (cmd != null) { | |
return new CommandSearch(cmd, i); | |
} | |
} | |
return null; | |
} | |
private static void executeCommand(CommandSender sender, String[] args, RegisteredCommand cmd) { | |
if (cmd.perm == null || sender.hasPermission(cmd.perm.value())) { | |
List<String> sargs = Lists.newArrayList(args); | |
cmd.invoke(sender, sargs); | |
} else { | |
Util.sendMsg(sender, MiscLang.COMMAND_PERM_ERROR); | |
} | |
} | |
public boolean canExecute(CommandSender sender, RegisteredCommand cmd) { | |
return true; | |
} | |
@Override | |
public List<String> tabComplete(CommandSender sender, String commandLabel, String[] args) | |
throws IllegalArgumentException { | |
commandLabel = commandLabel.toLowerCase(); | |
RegisteredCommand cmd = aliases.get(commandLabel); | |
if (cmd == null) { | |
final CommandSearch search = findSubCommand(args); | |
if (search != null) { | |
cmd = search.cmd; | |
args = Arrays.copyOfRange(args, search.argIndex, args.length); | |
} | |
} | |
String argString = StringUtils.join(args, " ").toLowerCase(); | |
final String[] split = RegexPatterns.SPACE.split(argString+"!"); // add ! incase ends in space | |
if (cmd != null) { | |
return completeCommand(sender, cmd, args, commandLabel); | |
} | |
final List<String> cmds = new ArrayList<>(); | |
// TODO: Why isn't /promoadmin test <tab> finding it? -- What did I mean by this? | |
for (Map.Entry<String, RegisteredCommand> entry : subCommands.entrySet()) { | |
final String key = entry.getKey(); | |
if (key.startsWith(argString) && !"__unknown".equals(key)) { | |
String prefCommand = entry.getValue().prefSubCommand; | |
final String[] psplit = RegexPatterns.SPACE.split(prefCommand); | |
cmds.add(psplit[split.length - 1]); | |
} | |
} | |
final RegisteredCommand unknownCommand = subCommands.get("__unknown"); | |
if (cmds.isEmpty() && unknownCommand != null) { | |
return completeCommand(sender, unknownCommand, args, commandLabel); | |
} | |
return filterTabComplete(args[split.length-1], cmds); | |
} | |
private List<String> completeCommand(CommandSender sender, RegisteredCommand cmd, | |
String[] args, String commandLabel) { | |
if (args.length >= cmd.nonSenderAwareResolvers) { | |
return ImmutableList.of(); | |
} | |
if (args.length != 1 || cmd.complete == null | |
|| "@players".equals(cmd.complete.value())) { | |
return super.tabComplete(sender, commandLabel, args); | |
} | |
final List<String> cmds = new ArrayList<>(); | |
String[] complete = RegexPatterns.COLON.split(cmd.complete.value(), 2); | |
String last = args.length > 0 ? args[args.length - 1] : null; | |
switch (complete[0]) { | |
case "@bannedplayers": | |
if (!(sender instanceof Player) || UserUtil.isModerator((Player) sender)) { | |
try { | |
cmds.addAll(EmpireDb.<String>getFirstColumnResults( | |
"SELECT u.name FROM user_ban b JOIN user u ON b.user_id = u.user_id WHERE " + | |
"u.name LIKE ?", last + "%")); | |
} catch (SQLException e) { | |
e.printStackTrace(); | |
} | |
} else { | |
return super.tabComplete(sender, commandLabel, args); | |
} | |
break; | |
case "@allplayers": | |
List<String> results = EmpireUser.lookupNames(last, complete.length > 1 ? Util.parseInt(complete[1]) : null); | |
cmds.addAll(Bukkit.getOnlinePlayers().stream().map(Player::getName).collect(Collectors.toList())); | |
if (results != null) { | |
cmds.addAll(results); | |
} else { | |
return super.tabComplete(sender, commandLabel, args); | |
} | |
break; | |
case "@rareitems": | |
cmds.addAll(CustomItems.customItems.keySet()); | |
break; | |
case "@mobs": | |
for (EntityType entityType : EntityType.values()) { | |
cmds.add(Util.simplifyString(entityType.getName())); | |
} | |
final Stream<CustomMob> stream = CustomMob.allTypes.values().stream(); | |
final Stream<String> mobNames = stream.map(customMob -> Util.simplifyString(customMob.customType)); | |
cmds.addAll(mobNames.collect(Collectors.toList())); | |
break; | |
case "@psetting": | |
for (PlayerSettingKey key : PlayerSettingKey.values()) { | |
cmds.add(key.key); | |
} | |
break; | |
case "@staffplayers": | |
List<String> staff = EmpireUser.lookupStaff(last); | |
if (staff != null) { | |
cmds.addAll(staff); | |
} else { | |
return super.tabComplete(sender, commandLabel, args); | |
} | |
break; | |
case "@servers": | |
for (EmpireServer server : EmpireServer.values()) { | |
cmds.add(server.label); | |
} | |
break; | |
default: | |
Collections.addAll(cmds, RegexPatterns.PIPE.split(cmd.complete.value())); | |
} | |
return filterTabComplete(args[args.length - 1], cmds); | |
} | |
private static List<String> filterTabComplete(String arg, List<String> cmds) { | |
List<String> list = new ArrayList<>(); | |
for (String cmd : cmds) { | |
if (cmd != null && (arg.isEmpty() || StringUtil.startsWithIgnoreCase(cmd, arg))) { | |
list.add(cmd); | |
} | |
} | |
return list; | |
} | |
public void help(CommandSender sender, String[] args) { | |
} | |
public void onDefault(CommandSender sender, String commandLabel) { | |
executeDefault(sender); | |
} | |
public boolean onUnknown(CommandSender sender, String commandLabel, String[] args) { | |
if (!executeDefault(sender, args)) { | |
help(sender, args); | |
} | |
return true; | |
} | |
public boolean executeDefault(CommandSender sender, String... args) { | |
final RegisteredCommand def = subCommands.get("__default"); | |
if (def != null) { | |
executeCommand(sender, args, def); | |
return true; | |
} | |
return false; | |
} | |
public boolean preCommand(CommandSender sender, String commandLabel, String[] args) { | |
return false; | |
} | |
public void doHelp(CommandSender sender, String... args) { | |
help(sender, args); | |
} | |
public void showSyntax(CommandSender sender, RegisteredCommand cmd) { | |
Util.sendMsg(sender, "&cUsage: /" + cmd.command + " " + cmd.syntax); | |
} | |
@AllArgsConstructor static class CommandSearch { RegisteredCommand cmd; int argIndex; } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands; | |
public class InvalidCommandArgument extends Exception { | |
public InvalidCommandArgument() { | |
} | |
public InvalidCommandArgument(String message) { | |
super(message, null, false, false); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.empireminecraft.commands; | |
import com.empireminecraft.commands.annotation.*; | |
import com.empireminecraft.commands.contexts.CommandContexts; | |
import com.empireminecraft.commands.contexts.ContextResolver; | |
import com.empireminecraft.commands.contexts.ContextResolver.SenderAwareContextResolver; | |
import com.empireminecraft.util.RegexPatterns; | |
import com.empireminecraft.util.Util; | |
import com.google.common.collect.Lists; | |
import org.bukkit.command.CommandSender; | |
import org.bukkit.configuration.InvalidConfigurationException; | |
import org.spigotmc.SneakyThrow; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Parameter; | |
import java.util.List; | |
public class RegisteredCommand { | |
public final EmpireCommand scope; | |
public final String command; | |
public final Method method; | |
public final String prefSubCommand; | |
public final Parameter[] parameters; | |
public final ContextResolver<?>[] resolvers; | |
public final String syntax; | |
public final CommandPermission perm; | |
public final CommandCompletion complete; | |
public final int nonSenderAwareResolvers; | |
RegisteredCommand(EmpireCommand scope, String command, Method method, String prefSubCommand) { | |
this.scope = scope; | |
if ("__unknown".equals(prefSubCommand)) { | |
prefSubCommand = ""; | |
} | |
this.command = command + (method.getAnnotation(CommandAlias.class) == null && !prefSubCommand.isEmpty() ? " " + prefSubCommand : ""); | |
this.method = method; | |
this.prefSubCommand = prefSubCommand; | |
this.perm = method.getAnnotation(CommandPermission.class); | |
this.complete = method.getAnnotation(CommandCompletion.class); | |
this.parameters = method.getParameters(); | |
this.resolvers = new ContextResolver[this.parameters.length]; | |
final Syntax syntaxStr = method.getAnnotation(Syntax.class); | |
if (syntaxStr != null) { | |
this.syntax = syntaxStr.value(); | |
} else { | |
StringBuilder syntaxB = new StringBuilder(64); | |
for (Parameter p : parameters) { | |
if (!CommandSender.class.isAssignableFrom(p.getType())) { | |
if (p.getAnnotation(Default.class) != null || p.getAnnotation(Optional.class) != null) { | |
syntaxB.append('[').append(p.getName()).append("] "); | |
} else { | |
syntaxB.append('<').append(p.getName()).append("> "); | |
} | |
} | |
} | |
this.syntax = syntaxB.toString(); | |
} | |
int nonSenderAwareResolvers = 0; | |
for (int i = 0; i < parameters.length; i++) { | |
final Parameter parameter = parameters[i]; | |
final Class<?> type = parameter.getType(); | |
final ContextResolver<?> resolver = CommandContexts.getResolver(type); | |
if (resolver != null) { | |
resolvers[i] = resolver; | |
if (!(resolver instanceof SenderAwareContextResolver)) { | |
nonSenderAwareResolvers++; | |
} | |
} else { | |
SneakyThrow.sneaky(new InvalidConfigurationException( | |
"Parameter " + type.getSimpleName() + " of " + this.command + " has no resolver")); | |
} | |
} | |
this.nonSenderAwareResolvers = nonSenderAwareResolvers; | |
} | |
public void invoke(CommandSender sender, List<String> args) { | |
if (!scope.canExecute(sender, this)) { | |
return; | |
} | |
List<Object> passedArgs = Lists.newArrayList(); | |
for (int i = 0; i < parameters.length; i++) { | |
boolean isLast = i == parameters.length - 1; | |
final Parameter parameter = parameters[i]; | |
final Class<?> type = parameter.getType(); | |
final ContextResolver<?> resolver = resolvers[i]; | |
if (resolver != null) { | |
try { | |
if (args.isEmpty() && !(isLast && type == String[].class)) { | |
Default def = parameter.getAnnotation(Default.class); | |
Optional opt = parameter.getAnnotation(Optional.class); | |
if (isLast && def != null) { | |
args.add(def.value()); | |
} else if (isLast && opt != null) { | |
passedArgs.add(null); | |
continue; | |
} else if (!(resolver instanceof SenderAwareContextResolver)) { | |
scope.showSyntax(sender, this); | |
return; | |
} | |
} else { | |
final Values values = parameter.getAnnotation(Values.class); | |
if (values != null) { | |
final String[] split = RegexPatterns.PIPE.split(values.value()); | |
String arg = args.get(0); | |
if (Util.indexOf(arg, split) == -1) { | |
throw new InvalidCommandArgument("Must be one of: " + Util.join(split, ", ")); | |
} | |
} | |
} | |
passedArgs.add(resolver.getContext(parameter, sender, args, isLast)); | |
} catch (InvalidCommandArgument e) { | |
if (e.getMessage() != null) { | |
Util.sendMsg(sender, "&cInvalid Argument: " + e.getMessage()); | |
} | |
scope.showSyntax(sender, this); | |
return; | |
} | |
} else { | |
Util.sendMsg(sender, "&cUnexpected Error. Staff have been notified of the bug. Please try again."); | |
return; | |
} | |
} | |
try { | |
method.invoke(scope, passedArgs.toArray()); | |
} catch (InvocationTargetException e) { | |
Util.sendMsg(sender, "&cI'm sorry, but there was an error performing this command."); | |
Util.printException("Exception in command: " + command + " " + Util.join(args), e.getCause()); | |
} catch (IllegalAccessException e) { | |
Util.printException(e); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment