diff --git a/src/main/java/com/craftyn/casinoslots/CasinoManager.java b/src/main/java/com/craftyn/casinoslots/CasinoManager.java new file mode 100644 index 0000000..95281ef --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/CasinoManager.java @@ -0,0 +1,13 @@ +package com.craftyn.casinoslots; + +public class CasinoManager { + private CasinoSlots plugin; + + public CasinoManager(CasinoSlots plugin) { + this.plugin = plugin; + } + + public CasinoSlots getPlugin() { + return this.plugin; + } +} diff --git a/src/main/java/com/craftyn/casinoslots/command/Command.java b/src/main/java/com/craftyn/casinoslots/command/Command.java new file mode 100644 index 0000000..8d0bb77 --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/command/Command.java @@ -0,0 +1,35 @@ +package com.craftyn.casinoslots.command; + +import java.util.List; + +import org.bukkit.command.CommandSender; + +import com.craftyn.casinoslots.CasinoManager; + +/** + * The base of all the commands. + * + * @author graywolf336 + * @since 3.0.0 + * @version 1.0.0 + */ +public interface Command { + /** + * Execute the command given the arguments, returning whether the command handled it or not. + * + *

+ * + * When the method returns false, the usage message is printed to the sender. If the method + * handles the command in any way, such as sending a message to the sender or actually doing + * something, then it should return true so that the sender of the command doesn't get the + * usage message. + * + * @param cm An instance of the {@link CasinoManager} + * @param sender The {@link CommandSender sender} of the command + * @param args The args, in an array + * @return True if the method handled it in any way, false if we should send the usage message. + */ + public boolean execute(CasinoManager cm, CommandSender sender, String... args) throws Exception; + + public List provideTabCompletions(CasinoManager cm, CommandSender sender, String... args) throws Exception; +} diff --git a/src/main/java/com/craftyn/casinoslots/command/CommandHandler.java b/src/main/java/com/craftyn/casinoslots/command/CommandHandler.java new file mode 100644 index 0000000..d7abeef --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/command/CommandHandler.java @@ -0,0 +1,224 @@ +package com.craftyn.casinoslots.command; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import com.craftyn.casinoslots.CasinoManager; +import com.craftyn.casinoslots.CasinoSlots; +import com.craftyn.casinoslots.enums.Lang; +import com.craftyn.casinoslots.enums.Settings; + +/** + * Where all the commands are registered, handled, and processed. + * + * @author graywolf336 + * @since 3.0.0 + * @version 1.0.0 + * + */ +public class CommandHandler { + private LinkedHashMap commands; + + public CommandHandler(CasinoSlots plugin) { + commands = new LinkedHashMap(); + loadCommands(); + + plugin.debug("Loaded " + commands.size() + " commands."); + } + + public List parseTabComplete(CasinoManager cm, CommandSender sender, String[] args) throws Exception { + if(args[0].isEmpty() || args.length == 1) { + List results = new ArrayList(); + String arg0 = args[0].toLowerCase(); + + for(Command c : commands.values()) { + CommandInfo i = c.getClass().getAnnotation(CommandInfo.class); + + // Skip if the command requires a player and the sender isn't a player + if(i.needsPlayer() && !(sender instanceof Player)) continue; + + // If the sender has permission to the command + // and the first argument (sub command) is empty OR + // the first argument matches the command or starts with the string + if(sender.hasPermission(i.permission()) && (arg0.isEmpty() || (arg0.toLowerCase().matches(i.pattern()) || i.pattern().startsWith(arg0)))) { + results.add(i.pattern().split("\\|")[0]); + } + } + + //Sort the results before adding the player names + Collections.sort(results); + + return results; + }else { + String arg0 = args[0].toLowerCase(); + + for(Command c : commands.values()) { + CommandInfo i = c.getClass().getAnnotation(CommandInfo.class); + + if(!arg0.toLowerCase().matches(i.pattern())) continue; + + // Sender provided too many arguments which means there + // is nothing to tab complete + if(i.maxArgs() != -1 && i.maxArgs() < args.length - 1) continue; + + // Skip if the command requires a player and the sender isn't a player + if(i.needsPlayer() && !(sender instanceof Player)) continue; + + // If the sender doesn't have permission, we won't send them further + if(!sender.hasPermission(i.permission())) continue; + + return c.provideTabCompletions(cm, sender, args); + } + } + + return Collections.emptyList(); + } + + /** + * Handles the given command and checks that the command is in valid form. + * + *

+ * + * It checks in the following order: + *

    + *
  1. If they have permission for it, if they don't then we send them a message stating so.
  2. + *
  3. If the command needs a player instance, if so we send a message stating that.
  4. + *
  5. If the required minimum arguments have been passed, if not sends the usage.
  6. + *
  7. If the required maximum arguments have been passed (if there is a max, -1 if no max), if not sends the usage.
  8. + *
  9. Then executes, upon failed execution it sends the usage command.
  10. + *
+ * + * @param cm The instance of {@link CasinoManager}. + * @param sender The sender of the command. + * @param args The arguments passed to the command. + */ + public boolean parseCommand(CasinoManager cm, CommandSender sender, String[] args) { + Command c = null; + + // If they didn't provide any arguments (aka just: /casinoslots) then we will need to send them some help + if(args.length == 0) { + // TODO: Create the help page(s) + c = getMatches("casino").get(0); + }else { + // /casino add + + // Get the matches from the first argument passed + List matches = getMatches(args[0].toLowerCase()); + + if(matches.isEmpty()) { + // No matches found, thus it is likely they don't know what they're trying to do + c = getMatches("casino").get(0); + } else if(matches.size() > 1) { + // If we found more than one match + // then let's send the usage of each match to the sender + for(Command cmd : matches) + showUsage(sender, cmd); + + return true; + }else { + // Only one match was found, so let's continue + c = matches.get(0); + } + } + + CommandInfo i = c.getClass().getAnnotation(CommandInfo.class); + + // First, let's check if the sender has permission for the command. + if(!i.permission().isEmpty() && !sender.hasPermission(i.permission())) { + cm.getPlugin().debug("Sender has no permission: " + i.permission()); + sender.sendMessage(Lang.NOPERMISSION.get() + (Settings.DEBUG.asBoolean() ? " (" + i.permission() + ")" : "")); + return true; + } + + // Next, let's check if we need a player and then if the sender is actually a player + if(i.needsPlayer() && !(sender instanceof Player)) { + cm.getPlugin().debug("Sender is not a player."); + sender.sendMessage(Lang.PLAYERCONTEXTREQUIRED.get()); + return true; + } + + // Now, let's check the size of the arguments passed. + // If it is shorter than the minimum required args, let's show the usage. + // The reason we are subtracting one is because the command is + // `/casino ` and the subcommand is viewed as an argument + if(args.length - 1 < i.minimumArgs()) { + cm.getPlugin().debug("Sender didn't provide enough arguments."); + showUsage(sender, c); + return true; + } + + // Then, if the maximumArgs doesn't equal -1, we need to check + // if the size of the arguments passed is greater than the maximum args. + // The reason we are subtracting one is because the command is + // `/casino ` and the subcommand is viewed as an argument + if(i.maxArgs() != -1 && i.maxArgs() < args.length - 1) { + cm.getPlugin().debug("Sender provided too many arguments."); + showUsage(sender, c); + return true; + } + + // Since everything has been checked and we're all clear, let's execute it. + // But if get back false, let's show the usage message. + try { + if(!c.execute(cm, sender, args)) { + showUsage(sender, c); + return true; + }else { + return true; + } + } catch (Exception e) { + if(Settings.DEBUG.asBoolean()) { + e.printStackTrace(); + } + + cm.getPlugin().getLogger().severe("An error occured while handling the command: " + i.usage()); + showUsage(sender, c); + return true; + } + } + + private List getMatches(String command) { + List result = new ArrayList(); + + for(Entry entry : commands.entrySet()) { + if(command.matches(entry.getKey())) { + result.add(entry.getValue()); + } + } + + return result; + } + + /** + * Shows the usage information to the sender, if they have permission. + * + * @param sender The sender of the command + * @param command The command to send usage of. + */ + private void showUsage(CommandSender sender, Command command) { + CommandInfo info = command.getClass().getAnnotation(CommandInfo.class); + if(!sender.hasPermission(info.permission())) return; + + sender.sendMessage(info.usage()); + } + + private void loadCommands() { + } + + private void load(Class c) { + CommandInfo info = c.getAnnotation(CommandInfo.class); + if(info == null) return; + + try { + commands.put(info.pattern(), c.newInstance()); + }catch(Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/craftyn/casinoslots/command/CommandInfo.java b/src/main/java/com/craftyn/casinoslots/command/CommandInfo.java new file mode 100644 index 0000000..cc8b42e --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/command/CommandInfo.java @@ -0,0 +1,77 @@ +package com.craftyn.casinoslots.command; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Contains a definition of all the annotations {@link Command commands} should have, if a {@link Command command} doesn't have any then it isn't registered. + * + *

+ * + * This helps make loading and registering and verifying commands a lot + * easier, makes sure that before we really process a command that it + * is structured correctly and all the right information is passed. If + * the command takes a variety of options, then that command obviously + * has to handle it. We just make sure that the minimum amount of + * arguments is met and same with the maximum amount, if there is a max. + * We also check if the commands needs a player or not, if so and the + * sender is something other than a player we send a message stating that + * a player context is required. The pattern is just used to match up + * the command used to it's registered value, in regex form. We check + * the permission stated and determine if the sender has it or not, if + * they don't then we send a message stating they don't have permission + * for that command. Finally we have the usage string, which is sent + * when the sender of the command sends an incorrectly formatted + * command. The order of checking is as defined in {@link CommandHandler#handleCommand(org.bukkit.command.CommandSender, String, String[]) CommandHandler.handleCommand}. + * + * @author graywolf336 + * @since 3.0.0 + * @version 1.0.0 + * + */ +@Retention (RetentionPolicy.RUNTIME) +public @interface CommandInfo { + /** + * Gets the maximum amount of arguments required, -1 if no maximum + * (ex: Jailing someone with a reason or editing a reason). + * + * @return The maximum number of arguments required, -1 if no maximum. + */ + public int maxArgs(); + + /** + * Gets the minimum amount of arguments required. + * + * @return The minimum number of arguments required. + */ + public int minimumArgs(); + + /** + * Whether the command needs a player context or not. + * + * @return True if requires a player, false if not. + */ + public boolean needsPlayer(); + + /** + * A regex pattern that allows for alternatives to + * the command (ex: /casino or /cs). + * + * @return The regex pattern to match. + */ + public String pattern(); + + /** + * Gets the permission required to execute this command. + * + * @return The permission required. + */ + public String permission(); + + /** + * Gets the usage message for this command. + * + * @return The usage message. + */ + public String usage(); +} diff --git a/src/main/java/com/craftyn/casinoslots/command/commands/CasinoAddCommand.java b/src/main/java/com/craftyn/casinoslots/command/commands/CasinoAddCommand.java new file mode 100644 index 0000000..532f5fa --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/command/commands/CasinoAddCommand.java @@ -0,0 +1,30 @@ +package com.craftyn.casinoslots.command.commands; + +import java.util.Collections; +import java.util.List; + +import org.bukkit.command.CommandSender; + +import com.craftyn.casinoslots.CasinoManager; +import com.craftyn.casinoslots.command.Command; +import com.craftyn.casinoslots.command.CommandInfo; + +@CommandInfo( + maxArgs = 3, + minimumArgs = 2, + needsPlayer = true, + pattern = "add", + permission = "casinoslots.command.add", + usage = "/casino add [name] [type]" + ) +public class CasinoAddCommand implements Command { + public boolean execute(CasinoManager cm, CommandSender sender, String... args) throws Exception { + // TODO Auto-generated method stub + return false; + } + + public List provideTabCompletions(CasinoManager cm, CommandSender sender, String... args) throws Exception { + // TODO Auto-generated method stub + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/craftyn/casinoslots/enums/Lang.java b/src/main/java/com/craftyn/casinoslots/enums/Lang.java new file mode 100644 index 0000000..24ba418 --- /dev/null +++ b/src/main/java/com/craftyn/casinoslots/enums/Lang.java @@ -0,0 +1,93 @@ +package com.craftyn.casinoslots.enums; + +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.YamlConfiguration; + +public enum Lang { + /** The message sent whenever the sender/player doesn't have permission. */ + NOPERMISSION("general"), + /** The message sent whenever the sender/player supplies a number format that is incorrect. */ + NUMBERFORMATINCORRECT("general"), + /** The message sent whenever something is done that needs a player but doesn't have it. */ + PLAYERCONTEXTREQUIRED("general"), + /** The message sent to the sender when the plugin data has been reloaded. */ + PLUGINRELOADED("general"), + /** The message sent to the sender of a command when the plugin didn't start correct. */ + PLUGINNOTLOADED("general"), + /** The message sent whenever someone does a command we don't know. */ + UNKNOWNCOMMAND("general"); + + private String section, name, path; + private static YamlConfiguration lang; + + Lang(String section) { + this.section = section; + this.name = toString().toLowerCase(); + this.path = "language." + this.section + "." + this.name; + } + + Lang(String section, String name) { + this.section = section; + this.name = name; + this.path = "language." + this.section + "." + this.name; + } + + /** + * Sets the {@link YamlConfiguration} instance to use. + * + * @param file + * of the language to use. + */ + public static void setFile(YamlConfiguration file) { + lang = file; + } + + /** Gets the {@link YamlConfiguration} instance. */ + public static YamlConfiguration getFile() { + return lang; + } + + /** Writes any new language settings to the language file in storage. */ + public static boolean writeNewLanguage(YamlConfiguration newLang) { + boolean anything = false; + + for(Lang l : values()) { + if(!lang.contains(l.path)) { + lang.set(l.path, newLang.getString(l.path)); + anything = true; + } + } + + return anything; + } + + /** Returns the message in the language, no variables are replaced. */ + public String get() { + return get(new String[] {}); + } + + /** Returns the message in the language, no variables are replaced. */ + public String get(Lang langString) { + return get(new String[] { langString.get() }); + } + + /** + * Returns the message in the language, with the provided variables being replaced. + * + * @param variables + * All the variables to replace, in order from 0 to however many. + * @return The message as a colorful message or an empty message if that + * isn't defined in the language file. + */ + public String get(String... variables) { + String message = lang.getString(path); + + if (message == null) return ""; + + for (int i = 0; i < variables.length; i++) { + message = message.replaceAll("%" + i + "%", variables[i]); + } + + return ChatColor.translateAlternateColorCodes('&', message); + } +}