Resource Easier way to make commands

Discussion in 'Spigot Plugin Development' started by zLegendXP, Oct 9, 2018.

?

Did you find this helpful

  1. Yes

    5 vote(s)
    21.7%
  2. No

    5 vote(s)
    21.7%
  3. Somewhat useful

    13 vote(s)
    56.5%
  1. Hello!
    As my first resource, I want to show all of you how to make commands in the easiest possible way.
    Why would you want this?
    If you're making a plugin that has a lot of individual commands, this can save time and make life easier.
    What does it do?
    • It makes it so you don't have to register them in your plugin.yml
    • Allows you can create useful methods in your super class
    • By default tab completion won't return a list of online players
    • Makes it so the onCommand method doesn't return a boolean
    • Automatically does player and permission checks
    • Makes the onCommand and onTabComplete parameters shorter
    • You can make sub-commands
    • Simplifies command registry
    How is this done?
    You need to create this class:
    Code (Java):

    package me.legendxp.ezcommand;

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;

    import org.bukkit.command.Command;
    import org.bukkit.command.CommandSender;
    import org.bukkit.entity.Player;

    import net.md_5.bungee.api.ChatColor;

    public abstract class EzCommand extends Command {

        private CommandInfo info = getClass().getAnnotation(CommandInfo.class);

        protected EzCommand() {
            super("");
            setName(info.name());
            setDescription(info.desc());
            setPermission(info.perm());
            setAliases(Arrays.asList(info.aliases()));
            setPermissionMessage("You do not have permission to run this command");
        }

        public String getName() {
            return info.name();
        }

        public String getDescription() {
            return info.desc();
        }

        public String getPermission() {
            return info.perm();
        }

        public String getUsage() {
            return Arrays.toString(info.usage());
        }

        public List<String> getAliases() {
            return Arrays.asList(info.aliases());
        }

        //Easier way to message player in sub-class
        public void msg(CommandSender cs, String... msgs) {
            for(String msg : msgs) {
                cs.sendMessage(msg);
            }
        }

        //Easier way to message player in sub-class
        public void info(CommandSender cs, String... msgs) {
            for(String msg : msgs) {
                cs.sendMessage(ChatColor.AQUA + msg);
            }
        }

        //Easier way to message player in sub-class
        public void severe(CommandSender cs, String... msgs) {
            for(String msg : msgs) {
                cs.sendMessage(ChatColor.RED + msg);
            }
        }

        public abstract void onCommand(Player p, String[] args);
        public abstract List<String> onTabComplete(Player p, String[] args);

        public boolean execute(CommandSender sender, String label, String[] args) {
            if(!(sender instanceof Player)) {
                severe(sender, "Only players can use this command");
                return true;
            }

            Player p = (Player) sender;

            //If the permission is "" it means that there is none and anyone can execute this command
            if(!(info.perm().equals("")) && !(p.hasPermission(info.perm()))) {
                severe(p, getPermissionMessage());
                return true;
            }

            onCommand(p, args);
            return true;
        }

        public List<String> tabComplete(CommandSender sender, String label, String[] args) {
            List<String> completions = onTabComplete((Player) sender, args);
            //If returned null it won't show a list of online players like it would by default
            if(completions == null) return new ArrayList<String>();
            return completions;
        }

    }

     
    And this one:
    Code (Java):

    package me.legendxp.ezcommand;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface CommandInfo {

        String name();
        String desc();
        String perm() default ""; //If not specified anyone can use this command
        String[] usage() default {}; //A description of what arguments this command takes
        String[] aliases() default {}; //Different names that players can run this command from

    }

     
    And this one:
    Code (Java):

    package me.legendxp.ezcommand;

    import org.bukkit.Bukkit;
    import org.bukkit.craftbukkit.v1_13_R2.CraftServer;

    public class CommandRegistry {

        @SafeVarargs
        public static void register(EzCommand... cmds) {
            for(EzCommand cmd : cmds) {
                ((CraftServer) Bukkit.getServer()).getCommandMap().register(cmd.getName(), cmd);
            }
        }  

    }

     
    Now you can extend the EzCommand class to make your own command
    (This is actually real code that I am using in a plugin of mine)
    Code (Java):

    package me.legendxp.kitpvp.cmds;

    import java.util.List;

    import org.bukkit.entity.Player;

    import me.legendxp.ezcommand.CommandInfo;
    import me.legendxp.ezcommand.EzCommand;
    import me.legendxp.kitpvp.ArenaManager;

    @CommandInfo(name = "CreateArena", desc = "Create an arena", perm = "kitpvp.commands.createarena", usage = {"<Name>"})
    public class CreateArena extends EzCommand {

        public void onCommand(Player p, String[] args) {
            if(args.length == 0) {
                severe(p, "You must specify a name for the arena");
                return;
            }
           
            if(ArenaManager.getInstance().getArena(args[0]) != null) {
                severe(p, "An arena with that name already exists");
                return;
            }
           
            ArenaManager.getInstance().createArena(args[0]);
            info(p, "Created arena '" + args[0] + "'");
        }
       
        public List<String> onTabComplete(Player p, String[] args) {
            return null;
        }

    }

     

    How can I use this to make sub-commands?
    (Again this is a real snipped of code that I'm actually using in a plugin of mine)
    Code (Java):

    package me.legendxp.kitpvp;

    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;

    import org.bukkit.entity.Player;

    import me.legendxp.ezcommand.CommandInfo;
    import me.legendxp.ezcommand.EzCommand;
    import me.legendxp.kitpvp.cmds.AddSpawn;
    import me.legendxp.kitpvp.cmds.CreateArena;
    import me.legendxp.kitpvp.cmds.Leave;
    import me.legendxp.kitpvp.cmds.RemoveArena;
    import me.legendxp.kitpvp.cmds.RemoveSpawn;
    import net.md_5.bungee.api.ChatColor;

    @CommandInfo(name = "KitPvP", desc = "All KitPvP commands", aliases = {"pvp", "kp"})
    public class CommandManager extends EzCommand {

        private List<EzCommand> cmds = new ArrayList<EzCommand>();

        protected CommandManager() {
            cmds.add(new AddSpawn());
            cmds.add(new CreateArena());
            cmds.add(new Leave());
            cmds.add(new RemoveArena());
            cmds.add(new RemoveSpawn());
        }

        public void onCommand(Player p, String[] args) {
            if(args.length == 0) {
                for(EzCommand cmd : cmds) {
                    String name = ChatColor.GRAY + cmd.getName();
                    String usage = ChatColor.GRAY + cmd.getUsage();
                    String description = ChatColor.GRAY + cmd.getDescription();
                    info(p, KitPvP.getMain() + "/PvP " + name + " " + usage + KitPvP.getMain() + " - " + description);
                }
                return;
            }

            EzCommand wanted = null;

            for(EzCommand cmd : cmds) {
                if(cmd.getName().equalsIgnoreCase(args[0])) {
                    wanted = cmd;
                    break;
                }
            }

            if(wanted == null) {
                msg(p, "Could not find sub-command '" + args[0] + "'");
                return;
            }

            List<String> newArgs = new ArrayList<String>();
            Collections.addAll(newArgs, args);
            newArgs.remove(0);
            wanted.onCommand(p, newArgs.toArray(new String[newArgs.size()]));
        }

        public List<String> onTabComplete(Player p, String[] args) {
            if(args.length == 1) {
                List<String> suggestions = new ArrayList<String>();
                for(EzCommand cmd : cmds) suggestions.add(cmd.getName());
                return suggestions;
            }
            return null;
        }

    }


     

    How to register
    In your onEnable() all you need to do is:
    Code (Java):

    package me.legendxp.ezcommand;

    import org.bukkit.plugin.java.JavaPlugin;

    public class Main extends JavaPlugin {

        public void onEnable() {
            CommandRegistry.register(new Info());
        }

    }
     

    This is the end
    Please tell me your honest opinion is the comment section. Also vote if this was useful or not. I really hope this helped you!

    For the future
    I definitely want to add more features. Let me know in the comments how I can improve this. Things I plan on adding are allowing console to use commands and to make an API so that you don't need to write all of this.
     
    #1 zLegendXP, Oct 9, 2018
    Last edited: Nov 25, 2018
    • Like Like x 5
  2. Wow. I think I will try this but the old way is good too :p
     
  3. Having just "new Dog()" may be seen as redundant and confusing to programmers not used to this setup, perhaps have a command registry of some sort?

    Also how do you do multiple aliases?
     
    • Agree Agree x 1
  4. Benz56

    Junior Mod Supporter

    The main drawback is that this isn’t version independent. To change that you’d have to add a lot of bloat/per version stuff or reflection as well as caching.

    If you know which version the plugin will be used on this is a good alternative.
     
  5. I would've personally used a command listener rather than forcing it into the command map tbh
     
  6. MiniDigger

    Supporter

    That would remove compatibilities with many plugins tho. Injecting it into the command map is definitely the nicer way.
    In 1.13 it might be an even better option to register them with brigadier tho.
     
  7. I really don't get what people have against writing a few lines to plugin.yml
     
    • Agree Agree x 1
  8. Perhaps they wish to have configurable commands :unsure:? This is the only reason I can think of where this is useful.
     
  9. There's this really useful feature in Bukkit, the commands.yml file, which already allows you to remap commands and inject default arguments (i.e. having a /survival command that runs /server survival - which is a horrible example, I know)
     
  10. why do you register it in the constructor? have the constructor serve one purpose; mapping the variables. register the command through some manager or in its own method. this can also be said for events.

    also, that whole static thing you got going on there for getting the pl instance, just pass the class around through DI
     
  11. Updated this resource:

    Added aliases, command registry, and better example.
     
    • Like Like x 2
  12. Updated this resource:

    Added sub-commands, better permission integration, tab completion, command usage, and even better examples. :)
     

Share This Page