Skip to content

Commands

Griffin Kubesa edited this page Nov 27, 2019 · 21 revisions

BNCore has a custom commands framework built with Bear Nation in mind.

Instead of having one giant method with a ton of if statements and switch cases, each function of the command has it's own method in the class.

Before vs After

Please note that it is still under development and may change.

Features

  • Neat, concise, easy to read
  • No editing the plugin.yml
  • Automatic command registration
  • Permission and executor checks (e.g. console only)
  • Error handling
  • Event based (cancellable by other processes)
  • Built in and extendable tab completion

Technology behind it

The main resource that the commands framework utilizes is Annotations. Annotations are descriptors or metadata on classes, methods, variables, or parameters. By themselves they do nothing, but using reflection (reading that metadata in runtime), you can utilize those annotations to make real decisions. Some examples of how the framework utilizes annotations are:

  • Command aliases (@Aliases)
  • Command arguments and default argument values (@Arg)
  • Command permissions (@Permission)
  • Command argument paths (@Path)

The framework also utilizes an abstract class that each command must extend which provides all of the magic you can use when implementing your command. For example, to throw an error, simply write: error("Your message here"); and the framework handles the rest.

Example command

Below is an example command that covers a wide range of the framework's features. Refer to the full documentation below for more info on each part of the command.

// Define at least one alias (required)
@Aliases({"examplecommand", "examplecmd"}) // Note that annotations require curly braces when defining a list
@Permission("example.command")
// Extend CustomCommand (required)
public class ExampleCommand extends CustomCommand { 
    // Class variables
    // Load shared variables here (e.g. services)

    // Constructor (required)
    public ExampleCommand(CommandEvent event) {
        super(event);
    }

    @Path
    void help() {
        reply("You didn't pass any arguments that match what we expected, so this is method is executed by default!");
    }

    @Path("hello")
    void helloWorld() {
        reply("Hello, World!");
    }

    // Inject arguments
    @Path("add {int} {int}")
    void add(@Arg int num1, @Arg int num2) {
        reply("Result: " + num1 + num2);
    }

    @Path("msg {player} {string...}")
    // Require permission: example.command.message
    @Permission("message")
    void message(@Arg Player recipient, @Arg String message) {
        recipient.sendMessage("From " + player() + ": " + message);
        reply("To " + recipient.getName() + ": " + message);
    }

    // Default arguments
    @Path("default [string]")
    void defaultArg(@Arg("Hello!") String arg) {
        // arg will have a default value if not passed
        reply("Argument: " + arg);
    }
}

All done! Nothing else is needed.

Annotations

@Aliases

Location: Class
Required: Yes

Define all aliases of the command.

Examples:

@Aliases("examplecommand")
@Aliases({"examplecommand", "examplecmd"})

@Permission

Location: Class and Method
Required: No

If the annotation exists on the class, all methods will require that permission.

If the annotation exists on the method, the permission is only required for that method.

If the annotation exists on both, both the class permission and the method permission are required. By default, the method permission extends the class permission. For example, if the class requires example.command, and the method should require example.command.dosomething, then you would write @Permission("dosomething"). The framework concatenates the two together.

Examples:

@Permission("example.command")

If you want to have a method level permission that does not fall under the class's permission namespace, use:

@Permission(value = "some.permission.here", absolute = true)

@Path

Location: Method
Required: On all executable methods

Define the argument path needed to execute the method. When the command is run, the framework checks each available path and decides which one the arguments best match. If no match is found, it will look for an empty path (nothing in the parenthesis).

Syntax

  • Literal words are treated as such
  • (one|uno) - Define argument aliases
    • These are treated as literal words. They are not passed into the method. If you want to make a decision based off which one is present, you should either use {string} or create a new path and method.
  • {type} - Define a required variable argument.
  • [type] - Define an optional variable argument.

Supported types

  • String
    • Use {string...} to capture all subsequent arguments
  • Player, OfflinePlayer
    • Default to self to default to executor
  • Date (TODO)
  • Int(eger), Double, Float, Short, Long, Byte
  • Your own! See Custom Argument Types

Paths also control tab completion if the type has an available tab completer. See Tab Completion

Examples:

@Path("example")
@Path("example2 (one|uno)")
@Path("example3 {int}")
@Path("example4 {player} [string]")

@Arg

Location: Parameter
Required: Yes

Define a method argument to be a command argument. Method arguments must match the path's arguments in order.

  • Any {} path arguments MUST have a corresponding method argument.
  • Any [] path arguments MUST have a corresponding method argument WITH a default value.
  • Nothing else is injected into the method.

Note that defaults are only respected within the framework's wiring. Calling the method directly in code doesn't respect the defaults, you will have to pass them yourself.

CustomCommand class

All commands must extend this class, and the command's constructor must call super(event);. The class mostly consists of helper methods.

Helper methods

Chat

  • getPrefix() - Returns the command's prefix, which is the class name without Command in our default format.
  • send(player, message) - Send a colored message to a player
  • send(player, message, delay) - Send a delayed (ms) message to a player
  • reply(message) - Send a message to the executor
  • newline() - Send an empty line to the executor
  • error(message) - Throw an error that is displayed to the player. Stops execution.

Executor

Calling the following performs an executor check (e.g. to make a command console only, call console())

  • player() - Checks executor is a player and returns the player object
  • console() - Checks executor is console and returns the console object
  • commandBlock() - Checks executor is a command block and returns the command block object

Arguments

Arrays and indexes are hard. There are multiple methods to return the argument at a certain index (starting at 1, not 0). It will return null if the index doesn't exist instead of throwing an exception.

  • arg(index) - Returns a string
  • arg(index, rest) - Returns a string of all the arguments at and after the index supplied.
  • intArg(index) - Returns an integer. Throws an exception if not a number.
  • doubleArg(index) - Returns a double. Throws an exception if not a number.
  • booleanArg(index) - Returns a boolean (accepts true, enable, on, yes, and 1)
  • playerArg(index) - Returns a Player or an OfflinePlayer

Tab Completion

TODO

Custom Argument Types

TODO

Clone this wiki locally