diff --git a/api/src/main/java/de/epiceric/shopchest/api/ShopChest.java b/api/src/main/java/de/epiceric/shopchest/api/ShopChest.java index 26ef90d..c454823 100644 --- a/api/src/main/java/de/epiceric/shopchest/api/ShopChest.java +++ b/api/src/main/java/de/epiceric/shopchest/api/ShopChest.java @@ -3,6 +3,7 @@ package de.epiceric.shopchest.api; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; +import de.epiceric.shopchest.api.command.ShopCommand; import de.epiceric.shopchest.api.player.ShopPlayer; /** @@ -34,4 +35,11 @@ public abstract class ShopChest extends JavaPlugin { * @return the formatted amount */ public abstract String formatEconomy(double amount); + + /** + * Gets the main command of this plugin + * + * @return the shop command + */ + public abstract ShopCommand getShopCommand(); } \ No newline at end of file diff --git a/api/src/main/java/de/epiceric/shopchest/api/command/ShopCommand.java b/api/src/main/java/de/epiceric/shopchest/api/command/ShopCommand.java new file mode 100644 index 0000000..4810e99 --- /dev/null +++ b/api/src/main/java/de/epiceric/shopchest/api/command/ShopCommand.java @@ -0,0 +1,138 @@ +package de.epiceric.shopchest.api.command; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.bukkit.command.CommandSender; + +/** + * Represents the plugin's main command + * + * @since 1.13 + */ +public abstract class ShopCommand { + private List subCommands = new ArrayList<>(); + + /** + * Gets the command's name + * + * @return the name + * @since 1.13 + */ + public abstract String getName(); + + /** + * Adds a sub command to this command + * + * @param subCommand the sub command + * @return {@code true} if the sub command has been registered, {@code false} if + * the name has already been taken + * @since 1.13 + */ + public final boolean addSubCommand(SubCommand subCommand) { + boolean nameTaken = subCommands.stream().filter(sub -> sub.getName().equalsIgnoreCase(subCommand.getName())) + .findAny().isPresent(); + + if (nameTaken) { + return false; + } + + this.subCommands.add(subCommand); + return true; + } + + /** + * Removes a sub command from this command + * + * @param subCommand the sub command + * @since 1.13 + */ + public final void removeSubCommand(SubCommand subCommand) { + this.subCommands.remove(subCommand); + } + + /** + * Called when this command is executed + *

+ * Sub commands are handled here and their respective {@code onExecute} method + * is called from here. + *

+ * The first argument {@code args[0]} is the name of the sub command (if + * entered). + * + * @param sender the command sender + * @param args the arguments + * @since 1.13 + */ + public final void onExecute(CommandSender sender, String... args) { + if (args.length > 0) { + Optional optional = subCommands.stream() + .filter(sub -> sub.canExecute(sender) && sub.isPermitted(sender)) + .filter(sub -> sub.getName().equalsIgnoreCase(args[0])).findAny(); + + if (!optional.isPresent()) { + sendUsage(sender); + return; + } + + optional.ifPresent(sub -> { + if (args.length > 1) { + sub.onExecute(sender, Arrays.copyOfRange(args, 1, args.length)); + } else { + sub.onExecute(sender); + } + }); + } else { + sendUsage(sender); + } + } + + /** + * Called when a sender tab completes an argument in this command + *

+ * Only tab completion for the name of the sub command is handled here. + * Everything else has to be implemented in the sub commands respective + * {@code onTabComplete} method. + *

+ * The first argument {@code args[0]} is the name of the sub command (if + * entered). + * + * @param sender the command sender + * @param args the arguments + * @return the tab completions + * @since 1.13 + */ + public final List onTabComplete(CommandSender sender, String... args) { + if (args.length == 1) { + return subCommands.stream() + .filter(sub -> sub.canExecute(sender) && sub.isPermitted(sender)) + .filter(sub -> sub.getName().toLowerCase(Locale.US).startsWith(args[0].toLowerCase(Locale.US))) + .map(SubCommand::getName).collect(Collectors.toList()); + } else if (args.length > 1) { + return subCommands.stream() + .filter(sub -> sub.canExecute(sender) && sub.isPermitted(sender)) + .filter(sub -> sub.getName().equalsIgnoreCase(args[0])).findAny() + .map(sub -> sub.onTabComplete(sender, args)) + .orElse(new ArrayList<>()); + } + + return new ArrayList<>(); + } + + /** + * Sends the help messages for all sub commands to the given sender + * + * @param sender the command sender + * @since 1.13 + */ + public void sendUsage(CommandSender sender) { + String format = "[green] /%s %s [white] - [yellow] %s"; // TODO: i18n + subCommands.stream().filter(sub -> sub.canExecute(sender) && sub.isPermitted(sender)) + .forEach(sub -> sender.sendMessage(String.format(format, sub.getName(), sub.getDescription()))); + } + +} \ No newline at end of file diff --git a/api/src/main/java/de/epiceric/shopchest/api/command/SubCommand.java b/api/src/main/java/de/epiceric/shopchest/api/command/SubCommand.java new file mode 100644 index 0000000..6134590 --- /dev/null +++ b/api/src/main/java/de/epiceric/shopchest/api/command/SubCommand.java @@ -0,0 +1,114 @@ +package de.epiceric.shopchest.api.command; + +import java.util.List; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * Represents a sub command for the plugin's main command + * + * @see ShopCommand#addSubCommand(SubCommand) + * @since 1.13 + */ +public abstract class SubCommand { + private final String name; + private final boolean onlyPlayer; + + /** + * Creates a sub command with the given name + *

+ * The sub command has to be added to the main command via + * {@link ShopCommand#addSubCommand(SubCommand)}. + * + * @param name the name + * @param onlyPlayer whether only players can run this sub command + * @see ShopCommand#addSubCommand(SubCommand) + * @since 1.13 + */ + public SubCommand(String name, boolean onlyPlayer) { + this.name = name; + this.onlyPlayer = onlyPlayer; + } + + /** + * Gets the name of this sub command + * + * @return the name + * @since 1.13 + */ + public String getName() { + return name; + } + + /** + * Gets the permission the command sender needs to run this sub command + *

+ * If no permission is needed, this should return an empty string. + * + * @return the permission or an empty string + * @since 1.13 + */ + public String getPermission() { + return ""; + } + + /** + * Gets whether the given sender is permitted to run this sub command + * + * @param sender the sender + * @return whether the sender is permitted + */ + public boolean isPermitted(CommandSender sender) { + return getPermission() == null || getPermission().isEmpty() || sender.hasPermission(getPermission()); + } + + /** + * Gets whether the given sender can run this sub command + *

+ * This checks whether the sender is a player if the command can only be run by + * players. This does not check for permission. + * + * @param sender the sender + * @return whether the sender can run this sub command + * @since 1.13 + */ + public boolean canExecute(CommandSender sender) { + return sender instanceof Player || !onlyPlayer; + } + + /** + * Gets the description of this sub command + * + * @return the description + * @since 1.13 + */ + public abstract String getDescription(); + + /** + * Called when this sub command is executed + *

+ * The first argument {@code args[0]} is not the sub command itself, but the + * argument after it (if entered).. + * + * @param sender the sender + * @param args the arguments of the sub command + * @since 1.13 + */ + public abstract void onExecute(CommandSender sender, String... args); + + /** + * Called when a sender tab completes an argument in this sub command + *

+ * The tab completion for the sub command itself is already handled. The first + * argument {@code args[0]} is not the sub command itself, but the argument + * after it (if entered).. + * + * @param sender the command sender + * @param args the arguments of the sub command + * @return the tab completions + * @since 1.13 + */ + public abstract List onTabComplete(CommandSender sender, String... args); + +} \ No newline at end of file