[LIB] ReflectCommand 1.1.2 - Dynamic Commands For The Lazy!

Discussion in 'Resources' started by Icyene, Nov 7, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    Icyene

    ReflectCommand is a small, lightweight library I designed to dynamically register commands, providing awe-inspiring power at a mere 182 lines (coincidentally exactly the same length as my other library, ErrorLogger).

    Don't like having to register commands within your plugin.yml? Want to register commands at runtime? Are you very lazy and don't want to have to do instanceof checks for CommandSender? If so, this library is for you!

    Registering a command is as simple as:

    Code:Java
    1.  
    2. ReflectCommand commandRegistrator = new ReflectCommand(YourPluginInstance);
    3. commandRegistrator.register(CommandClass.class);
    4.  


    And in CommandClass, you might have something like:

    Code:Java
    1.  
    2. @ReflectCommand.Command(name = "test", alias = "atest", sender = ReflectCommand.Sender.EVERYONE)
    3. public static boolean derp(ConsoleCommandSender sender) {
    4. System.out.println("TEST!");
    5. return true;
    6. }
    7.  


    ReflectCommand.Sender is an enum containing PLAYER, CONSOLE, and EVERYONE. They are simply arrays containing classes, and so sender could also = {SomeExtendingClassOfConsoleCommandSender.class, AnotherExtendingClass}. Obviously, the ConsoleCommandSender argument would also need to change.

    A complete command overload would look like this:

    Code:Java
    1.  
    2. @ReflectCommand.Command(name = "test", alias = "atest", sender = ReflectCommand.Sender.PLAYER, usage = "/derp lalalalala")
    3. public static boolean derp(ConsoleCommandSender sender, String... args) {
    4. System.out.println("Your arguments: " + args);
    5. return true;
    6. }
    7.  
    8. @ReflectCommand.Command(name = "test", alias = {"mytest", "yourtest", "bukkitstest"), sender = ReflectCommand.Sender.CONSOLE, usage = "/test melaikpai")
    9. public static boolean derp(ConsoleCommandSender sender, String... args) {
    10. System.out.println("Your arguments: " + args);
    11. return true;
    12. }
    13.  


    That basically provides a full guide to the @Command annotation. alias is a String[], so to add multiple aliases you'd just do ("alias1", "alias2"). There is also permission and permissionMessage. I think both are self explanatory.

    Also note that ReflectCommand has 0 (ZERO) sanity checks. It can and WILL crash if you do erratic things to it. It also abuses reflection to save a switch case, so do not rename the command HashMaps in it or it will crash.

    Also keep in mind that the methods must be STATIC and PUBLIC to be accessed. ReflectCommand will read them regardless, and WILL crash if it tries to handle with a non-static or private/protected field. Once again, if you want, add .isStatic and .isAccessible checks. Its up to you: the code is pretty readable and easy to modify.

    Enjoy, and if you have any questions ask!

    Coming Soon:
    • Type guessing, so your methods don't have to take a clumsy String[].
     
  2. Offline

    ELCHILEN0

    Dang you beat me to this. I was just going to post mine on here :p
     
  3. Offline

    Icyene

    Ninja'd :p
     
  4. Offline

    Uniclaw

    Wow, awesome :D :eek: !
     
  5. Offline

    Icyene

    blackwolf12333

    CommandMethod, while being a quite handy library, is over 1K lines long, vs ReflectCommand which, while definitely not as clean or user friendly, is only 182. They both use different methods to handle custom commands: I reflect them into Bukkit's SimplePluginManager.commandMap.

    And up until this point I was not aware CommandMethod even existed :)
     
  6. That's true:p
     
  7. Offline

    hawkfalcon

    [​IMG]
     
    Icyene likes this.
  8. Offline

    Icyene

    ReflectCommand 1.1.2 released

    Mainly a bug fix for the previous version, fixes permissions being ignored.
     
  9. Offline

    iTidez

    Hello,

    Let me start off with what a useful lib this is to me. I am now implementing it in every new plugin I make!

    I am getting one problem however, I am making a simple command that broadcasts a message to the server when the command is run and it runs just fine but none of the code in the static boolean is executed. Perhaps im doing something wrong but it appears to be like the example you provided. I am including a copy of the class file below:

    Code:
    package me.itidez.plugins.derpessentials;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import net.milkbowl.vault.chat.Chat;
    import org.bukkit.Bukkit;
    import org.bukkit.ChatColor;
    import org.bukkit.command.ConsoleCommandSender;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.RegisteredServiceProvider;
     
    /**
    *
    * @author tjs238
    */
    public class CommandHi {
        private static Chat chat;
     
        @CommandManager.Command(name = "hi", permission = "derpstaff.staff")
        public static boolean hi(ConsoleCommandSender sender, String... args) {
            sender.sendMessage("test");
            Player p = (Player)sender;
         
                setupChat();
                if(chat == null) {
                    p.sendMessage(ChatColor.RED+"Error: Vault not found");
                }
                String prefix = chat.getPlayerPrefix(p).replaceAll("(?i)&([a-z0-9])", "§$1");
                if (prefix.equalsIgnoreCase("null") || prefix.isEmpty()) {
                    prefix = chat.getGroupPrefix(p.getWorld(), chat.getPrimaryGroup(p)).replaceAll("(?i)&([a-z0-9])", "§$1");
                    if (prefix.equalsIgnoreCase("null") || prefix.isEmpty()) {
                        prefix = "";
                    }
                }
                String t = "&e[G]&f"+prefix+p.getDisplayName()+"&7:&8 WELCOME &8TO "+randColor()+"D"+randColor()+"E"+randColor()+"R"+randColor()+"P "+randColor()+"C"+randColor()+"R"+randColor()+"A"+randColor()+"F"+randColor()+"T";
                Bukkit.broadcastMessage(t);
                return true;
        }
     
        /*public static String replaceColors (String message) {
            return message.replaceAll("(?i)&([a-z0-9])", "\u00A7$1");
        }*/
     
        public static String randColor() {
            List<String> colors = new ArrayList<String>();
            colors.add("§0"); //Black
            colors.add("§1"); //Dark Blue
            colors.add("§2"); //Dark Green
            colors.add("§3"); //Dark Aqua
            colors.add("§4"); //Dark Red
            colors.add("§5"); //Purple
            colors.add("§6"); //Orange
            colors.add("§7"); //Grey
            colors.add("§8"); //Dark Grey
            colors.add("§9"); //Indigo
            colors.add("§a"); //Bright Green
            colors.add("§b"); //Aqua
            colors.add("§c"); //Red
            colors.add("§d"); //Pink
            colors.add("§e"); //Yellow
            colors.add("§f"); //White
            int start = 0;
            int end = 16;
            Random rand = new Random();
            int randm = showRandomInteger(start, end, rand);
            return colors.get(randm);
        }
     
        private static Integer showRandomInteger(int aStart, int aEnd, Random aRandom) {
        if ( aStart > aEnd ) {
          throw new IllegalArgumentException("Start cannot exceed End.");
        }
        //get the range, casting to long to avoid overflow problems
        long range = (long)aEnd - (long)aStart + 1;
        // compute a fraction of the range, 0 <= frac < range
        long fraction = (long)(range * aRandom.nextDouble());
        int randomNumber =  (int)(fraction + aStart);
        return randomNumber;
      }
     
        private static boolean setupChat()
        {
            RegisteredServiceProvider<Chat> chatProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.chat.Chat.class);
            if (chatProvider != null) {
                chat = chatProvider.getProvider();
            }
     
            return (chat != null);
        }
    }
    Thank you for any help you can provide!
     
  10. Offline

    Icyene

    iTidez

    That actually may be because of my new varargs implementation. Instead of using String... args, try String[] args. Should essentially be the same thing. If that doesn't work, make sure you are registering the command :)

    Hope that helps!
     
  11. Offline

    iTidez

    Icyene

    Switched to String[] args and made sure the command was registered and it still does not work.

    If it helps you, the command acts as if it was run correctly, no /hi showing up on player console and in the actual console it shows:
    16:39:39
    CONSOLE:
    [INFO] iTidez issued server command: /hi

    I dont know what else to tell you, there are no error logs or anything seemingly out of place, only that little I have been able to provide.
     
  12. Offline

    Icyene

    Well, that explains it. You're not passing any arguments to the /hi command. Do something like '/hi bla bla bla' and it will work. You registered a command expecting arguments after it, and did not pass any arguments. To relieve the complication of users having to do things like 'if (args[0] != null)', ReflectCommand just ignores the command invocation. I should probably make it return the usage instead... Hmm... I will probably have time to do that in a few days :)

    In short: Remove the String[] completely if your command doesn't take any arguments.
     
  13. Offline

    iTidez

    Icyene

    Well good news is now I have an error log for you ^_^

    Code:
    16:59:10
     CONSOLE: 
    [INFO] iTidez issued server command: /hi
    16:59:10
     CONSOLE: 
    [SEVERE] java.lang.IllegalArgumentException: argument type mismatch
    16:59:10
     CONSOLE: 
    [SEVERE] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    16:59:10
     CONSOLE: 
    [SEVERE] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    16:59:10
     CONSOLE: 
    [SEVERE] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    16:59:10
     CONSOLE: 
    [SEVERE] at java.lang.reflect.Method.invoke(Method.java:601)
    16:59:10
     CONSOLE: 
    [SEVERE] at me.itidez.plugins.derpessentials.CommandManager$1.onCommand(CommandManager.java:54)
    16:59:10
     CONSOLE: 
    [SEVERE] at me.itidez.plugins.derpessentials.CommandManager$DynamicCommand.execute(CommandManager.java:171)
    16:59:10
     CONSOLE: 
    [SEVERE] at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:186)
    16:59:10
     CONSOLE: 
    [SEVERE] at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:502)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.NetServerHandler.chat(NetServerHandler.java:903)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.NetServerHandler.handleCommand(NetServerHandler.java:985)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.NetServerHandler.a(NetServerHandler.java:858)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.Packet3Chat.handle(Packet3Chat.java:44)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.NetworkManager.b(NetworkManager.java:290)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.NetServerHandler.d(NetServerHandler.java:113)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.ServerConnection.b(SourceFile:39)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.DedicatedServerConnection.b(SourceFile:30)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.MinecraftServer.r(MinecraftServer.java:595)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.DedicatedServer.r(DedicatedServer.java:222)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:493)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:426)
    16:59:10
     CONSOLE: 
    [SEVERE] at net.minecraft.server.ThreadServerApplication.run(SourceFile:856)
    
     
  14. Offline

    Icyene

    iTidez

    Can you pastie.org it please? Bukkit inserted alot of formatting tags and I can't understand one thing :-P
     
  15. Offline

    iTidez

    Icyene

    Of course, I noticed that the second I posted it and changed it over, if you still cant read it you can here: http://pastie.org/5476061

    EDIT: I looked through your code and the error starts here: if (varargs.length >= m.getParameterTypes().length)
    returnValue = m.invoke(null, trim(varargs, m.getParameterTypes().length));
     
  16. Offline

    Icyene

    iTidez

    Ah, I see the problem. By default, a command is only available to a player. You added a listening method for a ConsoleCommandSender. Either add sender = Sender.EVERYONE, or change ConsoleCommandSender to Player.
     
  17. Offline

    iTidez

    Icyene

    That's what I was thinking however after reviewing the code you provided in the original post, I thought the argument: ConsoleCommandSender was required as it was in both examples.

    Thank you for clearing it up, when I get home I will make sure to try it out. In the meantime perhaps you could add that into the post, i'm sure there are other people that were a little confused by it ^_^.
     
    Icyene likes this.
  18. Offline

    brord

    Great job man!
    Just one thing, the example:
    Code:
    @ReflectCommand.Command(name = "test", alias = {"mytest", "yourtest", "bukkitstest"), sender = ReflectCommand.Sender.CONSOLE, usage = "/test melaikpai")
        public static boolean derp(ConsoleCommandSender sender, String... args) {
        System.out.println("Your arguments: " + args);
        return true;
    }
    is wrong. Youre using a ")" instead of a "}" @ {"mytest", "yourtest", "bukkitstest")

    Thanks for making plugins a whole lot more fun!

    Edit:
    Okay so i gave this a shot. It was really easy to set up :)
    But whenever i tried the example, and even simplified it, it gave the same error as iTidez (Argument type mismatch)
    Code:
    @ReflectCommand.Command(name = "test", alias = "atest", sender = ReflectCommand.Sender.EVERYONE, usage = "/test lalalalala")
            public static boolean test(Player sender, String[] args) {
            sender.sendMessage("Your arguments: " + args);
            return true;
        }
    /test got registered but did nothing, as expected, and /test nshagasdgh gave the error stated above
     
  19. Offline

    Icyene

    Thank you for your kind words! Derp. I wrote the example in the Bukkit edit window :p
    If you use Sender.EVERYONE, your command must take in a CommandSender, not a Player. After I finish this heap of work I have, I'll clean up the examples :)
     
  20. Offline

    brord

    Okay, tried that, but still nothing.
    I am probably doing something wrong, but i cant find it.
    Code:
    import net.castegaming.plugins.FPSCaste.FPSCaste;
    import net.castegaming.plugins.FPSCaste.command.ReflectCommand.Sender;
    import net.castegaming.plugins.FPSCaste.config.ArenaConfig;
     
    import org.bukkit.Bukkit;
    import org.bukkit.command.CommandSender;
    import org.bukkit.command.ConsoleCommandSender;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.plugin.java.JavaPlugin;
     
    @SuppressWarnings("unused")
    public class LobbyCommands {
       
        FPSCaste plugin;
       
        LobbyCommands(FPSCaste plugin){
            this.plugin = plugin;
        }
       
        @ReflectCommand.Command(name = "test", alias = "atest", sender = Sender.EVERYONE, usage = "/test lalalalala")
        public static boolean test(CommandSender sender, String[] args) {
            sender.sendMessage("Your arguments: " + args);
            return true;
        }
        @ReflectCommand.Command(name = "setwarp", sender = Sender.PLAYER, usage = "/setwarp [warpname]", permission = "FPSCaste.setwarp")
        public static boolean setwarp(Player sender, String[] args) {
            JavaPlugin FPSCaste = (JavaPlugin) Bukkit.getPluginManager().getPlugin("FPSCaste");
            ArenaConfig arena = new ArenaConfig(FPSCaste);
            arena.setWarp(args[0], sender.getLocation());
            return true;
        }   
    }
    the /test gives nothing if it has 0 args, and an error with more then 0.
    The /setwarp does nothing for console (as it is supposed to do), but an error for args > 0 if player
     
  21. Offline

    Icyene

    brord

    I can't help much if I know what 'error' you are referring to :)
     
  22. Offline

    brord

    Damnit >_< stop beeing asleep when im awake :)

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 29, 2016
  23. Offline

    iTidez

    brord

    I see your error.

    In your test command:
    Code:
    @ReflectCommand.Command(name = "test", alias = "atest", sender = Sender.EVERYONE, usage = "/test lalalalala")
        public static boolean test(CommandSender sender, String[] args) {
            sender.sendMessage("Your arguments: " + args);
            return true;
        }
    You use CommandSender when it should be ConsoleCommandSender I believe. Correct me if I am wrong but try it out to see if it works for you!

    You will also want to put an if statement to check for args in your command anyway, I don't know if the command class passes empty arrays for it but it would never hurt to stop any NPE's. Below is an edited method for you:
    Code:
    @ReflectCommand.Command(name = "test", alias="atest",sender=Sender.EVERYONE, usage="/test lalalalala")
    public static boolean test(ConsoleCommandSender sender, String[] args) {
        if(args.length == 0 || args == null) {
            sender.sendMessage("No args detected");
            return true;
        }
        sender.sendMessage("Your arguments: " + args);
        return true;
    }
    Once again, I am not 100% its correct but it would never hurt to try it out ;)
     
  24. Offline

    brord

    "If you use Sender.EVERYONE, your command must take in a CommandSender, not a Player."
    Is what Icyene said.
    Tried it anyways. But no. it doesnt work.
    The error links to this part:
    Code:
    returnValue = m.invoke(null, trim(varargs, m.getParameterTypes().length));
    and
    Code:
    return owner.onCommand(sender, this, label, args);
    Error log:
    Show Spoiler

    Code:java
    1.  
    2. 2012-12-07 16:57:46 [SEVERE] java.lang.IllegalArgumentException: argument type mismatch
    3. 2012-12-07 16:57:46 [SEVERE]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    4. 2012-12-07 16:57:46 [SEVERE]at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    5. 2012-12-07 16:57:46 [SEVERE]at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    6. 2012-12-07 16:57:46 [SEVERE]at java.lang.reflect.Method.invoke(Unknown Source)
    7. 2012-12-07 16:57:46 [SEVERE]at net.castegaming.plugins.FPSCaste.command.ReflectCommand$1.onCommand(ReflectCommand.java:41)
    8. 2012-12-07 16:57:46 [SEVERE]at net.castegaming.plugins.FPSCaste.command.ReflectCommand$DynamicCommand.execute(ReflectCommand.java:166)
    9. 2012-12-07 16:57:46 [SEVERE]at org.bukkit.command.SimpleCommandMap.dispatch(SimpleCommandMap.java:186)
    10. 2012-12-07 16:57:46 [SEVERE]at org.bukkit.craftbukkit.CraftServer.dispatchCommand(CraftServer.java:502)
    11. 2012-12-07 16:57:46 [SEVERE]at org.bukkit.craftbukkit.CraftServer.dispatchServerCommand(CraftServer.java:494)
    12. 2012-12-07 16:57:46 [SEVERE]at net.minecraft.server.DedicatedServer.al(DedicatedServer.java:258)
    13. 2012-12-07 16:57:46 [SEVERE]at net.minecraft.server.DedicatedServer.r(DedicatedServer.java:223)
    14. 2012-12-07 16:57:46 [SEVERE]at net.minecraft.server.MinecraftServer.q(MinecraftServer.java:493)
    15. 2012-12-07 16:57:46 [SEVERE]at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:426)
    16. 2012-12-07 16:57:46 [SEVERE]at net.minecraft.server.ThreadServerApplication.run(SourceFile:856)
    17.  



    Current Code:
    Show Spoiler

    Code:java
    1.  
    2. package net.castegaming.plugins.FPSCaste.command;
    3.  
    4. import net.castegaming.plugins.FPSCaste.FPSCaste;
    5. import net.castegaming.plugins.FPSCaste.command.ReflectCommand.Sender;
    6. import net.castegaming.plugins.FPSCaste.config.ArenaConfig;
    7.  
    8. import org.bukkit.command.ConsoleCommandSender;
    9. import org.bukkit.entity.Player;
    10.  
    11. public class LobbyCommands {
    12.  
    13. @ReflectCommand.Command(name = "test", alias="atest",sender=Sender.EVERYONE, usage="/test lalalalala")
    14. public static boolean test(ConsoleCommandSender sender, String[] args) {
    15. if(args.length == 0 || args == null) {
    16. sender.sendMessage("No args detected");
    17. return true;
    18. }
    19. sender.sendMessage("Your arguments: " + args);
    20. return true;
    21. }
    22. @ReflectCommand.Command(name = "setwarp", sender = Sender.PLAYER, usage = "/setwarp [warpname]", permission = "FPSCaste.setwarp")
    23. public static boolean setwarp(Player sender, String[] args) {
    24. ArenaConfig arena = new ArenaConfig(FPSCaste.plugin);
    25. arena.setWarp(args[0], sender.getLocation());
    26. return true;
    27. }
    28. }
    29.  

     
  25. Offline

    Icyene

    brord

    Ah, it would appear there is a problem with my argument concatenation. Its reading the different arguments as SEPARATE Strings, rather than a String ARRAY. It trims out any excess arguments in the case that you give, say, 5 arguments but the method only takes 4. It makes commands a bit more readable, but unacceptable when said methods are MEANT to take variable amount of arguments. For now, use as many String arguments as needed; at the moment I have 3 essays to complete; once I do, I shall update this to concatenate multiple Strings into String[]'s. Though that could cause problems with methods trying to take multiple Strings. Maybe a varargs = true flag? Hmm...
     
  26. Offline

    brord

    So instead of
    Code:
    (Player sender, String[] args)
    String arg1 = args[0];
    String arg2 = args[1];
    String arg3 = args[2];
    
    I need to do

    Code:
    (Player sender, String arg1, String arg2, String arg3)
    
    Because that case, you should remove your example above, which uses "String... arg"

    Good luck with your essays :)
     
  27. Is this going to be updated? I'd greatly appreciate it!
     
Thread Status:
Not open for further replies.

Share This Page