Skip to content

Creating Commands Using Annotations

Matthias Ngeo edited this page May 8, 2019 · 26 revisions

Building a Command

Building a command using Literal.Builder and Argument.Buildercan often be difficult to follow when building a complex command. Since 4.1.0, commands can alternatively be declared using annotations. Using the annotations improves both readability and maintainability. In addition, the project bundles several annotation processors to ensure correctness.

This page will create the tell command provided in the cookbook using annotations.


Declaring the tell command

Since all annotations have the same lexical scope as a class, it is recommended that each command is declared in a separate class.

public class TellCommand {
    
}

Creating a Literal

A Literal can be created from a @Literal annotation on either TellCommand or a method in TellCommand. A @Literal annotation on TellCommand will yield a Literal with no behaviour. Conversely, a @Literal annotation on a method will yield a Literal with the method as its behaviour.

An annotated method's must not be private or static. In addition, its signature must match either Command.run(CommandContext) or Executable.execute(DefaultableContext).

A namespace is the path to a command, namespace = {"foo", "bar"} is equivalent to /foo bar. In addition, a Literal will be implicitly created for each name in a namespace that has not been explicitly declared. A class may not contain empty or duplicate namespaces.

import com.karuslabs.commons.command.DefaultableContext;
import com.karuslabs.commons.command.annotations.Literal;

public class TellCommand {
    
    @Literal(namespace = {"tell"}, aliases = "t")
    public void tell(DefaultableContext<CommandSender> context) {
        context.getSource().sendMessage("Hello darkness my old friend");
    }
    
}

Creating an Argument

An Argument can be created from an @Argument annotation. @Argument annotations follow the same conventions as @Literal annotations mentioned in the previous section.

In addition, an @Argument annotation contains two optional type and suggestions fields. Each field must be bound to an ArgumentType and SuggestionProvider annotated with @Bind respectively. The last name in the namespace of an @Argument is used if type is empty. No SuggestionProvider is bound if suggestions is empty. More information on binding fields is provided in the next section.

import com.karuslabs.commons.command.DefaultableContext;
import com.karuslabs.commons.command.annotations.Literal;

@Argument(namespace = {"tell", "players"})
public class TellCommand {
    
    @Argument(namespace = {"tell", "players", "message"}, type = "string", suggestions = "suggestions")
    public void send(DefaultableContext<CommandSender> context) {
        CommandSender sender = context.getSource();
        List<Player> recipents = context.getArgument("players", List.class);
        String entered = context.getArgument("message", String.class);
        
        recipents.forEach(p -> p.sendMessage(sender + " says: " + entered));
    }
    
}

Binding Fields

Fields annotated with @Bind can be bound to an Argument. They must be either ArgumentTypes or SuggestionProviders. A @Bind annotation contains an optional name to which the annotated field is bound. If name is empty, the annotated field's name is used instead.

import com.karuslabs.commons.command.DefaultableContext;
import com.karuslabs.commons.command.annotations.Argument;
import com.karuslabs.commons.command.annotations.Bind;
import com.karuslabs.commons.command.types.PlayersType;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;

@Argument(namespace = {"tell", "players"})
public class TellCommand {
    
    @Bind PlayersType players = new PlayersType();
    @Bind ArgumentType<String> string = StringArgumentType.string();
    @Bind("something") SuggestionProvider<CommandSender> suggestions = (context, builder) -> builder.suggest("\"Hello World!\"").buildFuture();
    

    @Argument(namespace = {"tell", "players", "message"}, type = "string", suggestions = "something")
    public void send(DefaultableContext<CommandSender> context) {
        CommandSender sender = context.getSource();
        List<Player> recipents = context.getArgument("players", List.class);
        String entered = context.getArgument("message", String.class);
        
        recipents.forEach(p -> p.sendMessage(sender + " says: " + entered));
    }
    
}

Registering the command

A CommandNode can be created from TellCommand using Commands.from(Object, String). After which it can be registered to a Dispatcher.

Alternatively, TellCommand can be appended to a Literal via Literal.Builder.then(Object, String).

import com.karuslabs.commons.command.Commands;

CommandNode<CommandSender> tell = Commands.from(new TellCommand(), "tell");

Dispatcher dispatcher = Dispatcher.of(yourPlugin);
dispatcher.getRoot().addChild(tell);
dispatcher.update(); // Only needed if we added "tell" after YourPlugin.onLoad() and YourPlugin.onEnable() has been called.

Putting Everything Together

After finishing this guide, TellCommand should look something like this.

import com.karuslabs.commons.command.Commands;
import com.karuslabs.commons.command.DefaultableContext;
import com.karuslabs.commons.command.Dispatcher;
import com.karuslabs.commons.command.annotations.Argument;
import com.karuslabs.commons.command.annotations.Bind;
import com.karuslabs.commons.command.annotations.Literal;
import com.karuslabs.commons.command.types.PlayersType;

import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.suggestion.SuggestionProvider;


@Argument(namespace = {"tell", "players"})
public class TellCommand {
    
    @Bind PlayersType players = new PlayersType();
    @Bind ArgumentType<String> string = StringArgumentType.string();
    @Bind("something") SuggestionProvider<CommandSender> suggestions = (context, builder) -> builder.suggest("\"Hello World!\"").buildFuture();
    
    
    @Literal(namespace = {"tell"}, aliases = "t")
    public void tell(DefaultableContext<CommandSender> context) {
        context.getSource().sendMessage("Hello darkness my old friend");
    }
    
    
    @Argument(namespace = {"tell", "players", "message"}, type = "string", suggestions = "something")
    public void send(DefaultableContext<CommandSender> context) {
        CommandSender sender = context.getSource();
        List<Player> recipents = context.getArgument("players", List.class);
        String entered = context.getArgument("message", String.class);
        
        recipents.forEach(p -> p.sendMessage(sender.getName() + " says: " + entered));
    }
    
}

CommandNode<CommandSender> tell = Commands.from(new TellCommand(), "tell");

Dispatcher dispatcher = Dispatcher.of(yourPlugin);
dispatcher.getRoot().addChild(tell);
dispatcher.update(); // Only needed if we added "tell" after YourPlugin.onLoad() and YourPlugin.onEnable() has been called. 

Testing the tell command in-game will yield the following.

Partial Command Execution

Full Command Execution


Further Reference

Clone this wiki locally