Skip to content

Commands

I-Al-Istannen edited this page Oct 18, 2016 · 2 revisions

Function

Main classes

CommandNode

This interface is the base for all commands. It defines some common methods providing base functionality:

  • getChildren
    • Returns the direct children of the command
  • getAllChildren
    • Returns all children of the command, probably recursive.
  • execute
    • Executes the command
  • tabComplete
    • Tab-completes the command
  • find
    • Find a command
  • hasPermission
    • Checks if a sender has the permission to execute it
  • getAcceptedCommandSenders
    • Gets the accepted command senders
  • acceptsCommandSender
    • Check if it accepts the passed command sender
  • isYourKeyword
    • Checks if the String is your keyword
  • getUsage
    • Returns the usage of the command
  • getDescription
    • Returns the description of the command
  • getKeyword
    • Returns the commands keyword. Used in tab completion and the default help command
  • getName
    • Returns the name of the command Because this is unpleaseant to deal with, there is a skeleton implementation called

AbstractCommandNode

It implements most of the methods above and provides some new ones.

Constructors

  • Permission, Collection<CommandSenderType> allowedSenders
  • Permission, CommandSenderType... allowedSenders
  • String permission, CommandSenderType... allowedSenders

Relevant Methods

  • executeGeneral (protected)
    • This will be called when the console executes the command or or when ALL is set as the allowed CommandSenders
  • executePlayer (protected)
    • This will be called when a player executes the command
  • executeBlock (procteted)
    • This will be called when a player executes the command

TranslatedCommandNode

This class is a CommandNode using a MessageProvider to obtain usage, description, keyword, keyword pattern and name.
It is what you will most likely extend.

Constructors

  • Permission, String keywordKey, String keywordRegExKey, String usageKey, String descriptionKey, String nameKey, MessageProvider messageProvider, CommandSenderType... acceptedSenders
  • Permission permission, String baseKey, MessageProvider messageProvider, CommandSenderType... acceptedSenders

The first one is pretty straightforward. You pass the permission, the keys in the language file to all things, the MessageProvider to get it from and the allowed CommandSenderTypes.
The second one is a bit trickier, but shorter. It also takes a permission, the MessageProvider and the CommandSenderTypes, but only one String, the baseKey. This will be used to construct the others. The format is as follows:

  • baseKey.keyword
  • baseKey.keyword.pattern
  • baseKey.usage
  • baseKey.description
  • baseKey.name

Methods

Nothing really new there. A method to get the MessagesProvider used, but that is it.

CommandTree

This is the main access point to all commands. It organizes them to a tree and provides a transparent root. This means you can add all your commands to it, and they will appear as top levelcommands (accessable with /).

Constructor

A default constructor is provided. Nothing else is needed at the moment.

Methods

This is a tad more existing. It has the following methods:

  • getAllCommands
    • Returns all registered commands
  • addTopLevelChild
    • Adds a top level child
  • attachHelp
    • Adds the default help node to a command node
  • addTopLevelChildAndRegister
    • Adds a top level child AND registers it at runtime, to make it act like a normal command, which was defined in the plugin.yml.
  • removeTopLevelChild
    • Removes a top level child
  • find
    • Finds a command based on the users query
  • findAndTabComplete
    • Finds a command and tabcompletes it
      Unless you want to make your own CommandExecutor, you won't need any of the methods except addTopLevelChild, addTopLevelChildAndRegister, attachHelp and maybe removeTopLevelChild.

DefaultCommandExecutor

This is an implementation of the CommandExecutor interface, which uses the CommandTree. It is what you will probably end up setting as the executors for your top level commands.

Constructor

  • CommandTree tree, MessageProvider language
    • The CommandTree to get the commands from and the MessageProvider to use for it's messages.
      The messages it uses are:
  • command.executor.not.found
    • If a command was not found
  • No permission: command.executor.no.permission
    • If the sender doesn't have sufficient permission
  • Error: command.executor.error.invoking
    • If an error occured invoking the command. Typically not the fault of the user
  • Send usage: command.executor.usage
    • The usage of the command. Called when the command returns SEND_USAGE

Methods

Well, there are none.

DefaultTabCompleter

Same as the CommandExecutor.

Constructor

  • CommandTree tree
    • The CommandTree to use

Methods

There are none either.

DefaultHelpCommand

This is the default help command. It can be attached by either creating it and adding it as a child or by using the CommandTree#attachHelp method.

Constructor

  • Permission permission, MessageProvider messageProvider, CommandTree commandTree

Methods

Nothing for you to worry about :)

Function

Needs to be written. Maybe some images too.

Example

A simple /me command

The command class

package me.ialistannen.bukkittoyaround;

import static com.perceivedev.perceivecore.util.TextUtils.colorize;

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

import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.permissions.Permission;

import com.perceivedev.perceivecore.command.CommandResult;
import com.perceivedev.perceivecore.command.CommandSenderType;
import com.perceivedev.perceivecore.command.TranslatedCommandNode;
import com.perceivedev.perceivecore.util.ArrayUtils;

/**
 * A simple me command
 */
public class MeCommand extends TranslatedCommandNode {

    public MeCommand() {
        // We want the permission "me", the language base key is "command.me" (important for the language later)
        // We pass a Language and specify that every sender type (Console, Block, Player) can use it
        super(new Permission("me"), "command.me", BukkitToyAround.getInstance().getLanguage(), CommandSenderType.ALL);
    }

    @Override
    public List<String> tabComplete(CommandSender sender, List<String> wholeChat, int relativeIndex) {
        // tab completion makes no real sense here, so just disable it by returning an empty list everytime
        return Collections.emptyList();
    }

    // We passed CommandSenderType.ALL in the constructor, so overwrite executeGeneral.
    @Override
    protected CommandResult executeGeneral(CommandSender sender, String... args) {
        // if the user just entered "/me", send the usage again
        if(args.length < 1) {
            return CommandResult.SEND_USAGE;
        }
        // Join the array to a string
        String concat = ArrayUtils.concat(args, " ");
        // Append the player name at the front
        concat = "&8[&7" +sender.getName() + "&8]&r " + concat;
        // send it to all players
        Bukkit.broadcastMessage(colorize(concat));

        // Equivalent of returning true, just saying the command was successfully invoked.
        return CommandResult.SUCCESSFULLY_INVOKED;
    }
}

The code in the onEnable

(Assumes you have a Language already set up)

CommandTree tree = new CommandTree();
MeCommand command = new MeCommand();

// the tree, executor and completer can be used for ALL commands
CommandExecutor executor = new DefaultCommandExecutor(tree, getLanguage());
TabCompleter tabCompleter = new DefaultTabCompleter(tree);

// adding the "/me" command to the CommandTree, to make it availlable to the users
// Register it too, with the alias "meToo". This means the user can enter "/me" and "/meToo"
tree.addTopLevelChildAndRegister(command, executor, tabCompleter, this, "meToo");

// lets just add an help command. Makes no sense here, but it is an example.
tree.attachHelp(command, "me.help", getLanguage());

And the entries in the Language file

command.me.name= &6Me
command.me.keyword= me
# The pattern is a RegEx matching all possible keywords.
command.me.keyword.pattern= me|metoo
command.me.description= &7You do
command.me.usage= &c/me &6<message>

# only needed as we added the help command
command.help.name= &6Help
command.help.keyword= help
command.help.keyword.pattern= help
command.help.description= &7Helps you
command.help.usage= &c/me help

Argument Mapping

This is another cool feature. It basically allows the arguments to the execute method to be dynamic and exactly what you need. Consider a small teleport plugin. The obvious choice would be something like this:

@Override
protected CommandResult executePlayer(Player player, String... args) {
    if (args.length < 1) {
        return CommandResult.SEND_USAGE;
    }
    Player target = Bukkit.getPlayer(args[0]);

    if (target == null) {
        player.sendMessage(getMessageProvider().tr("command.teleport.target.offline", (Object) args[0]));
        return CommandResult.SUCCESSFULLY_INVOKED;
    }

    player.teleport(target);

    player.sendMessage(getMessageProvider().tr("command.teleport.teleported", (Object) target.getName()));

    return CommandResult.SUCCESSFULLY_INVOKED;
}

But we can do better! Look at this:

@ConvertedParams(targetClasses = {Player.class})
public CommandResult onTeleportRequest(Player player, Player target, String[] args, String[] wholeChat) {
    if(target == null) {
        player.sendMessage(getMessageProvider().tr("command.teleport.target.offline", (Object) wholeChat[0]));
        return CommandResult.SUCCESSFULLY_INVOKED;
    }

    player.teleport(target);
    player.sendMessage(getMessageProvider().tr("command.teleport.teleported", (Object) target.getName()));

    return CommandResult.SUCCESSFULLY_INVOKED;
}

See what has changed?

  1. We could name the method how we want, as long as it is public, the first parameter is a CommandSender and the last a String array. AND the @ConvertedParams annotation has to be present. It tells the system what to do. In this case, it will convert the player name the user entered to a Player object, passing null if not found.
  2. We no longer need to check the size of args, as the command system will do it for us.

The last parameter, wholeChat contains the whole chat the user entered (it is optional) and args only the arguments AFTER all parameters were converted. If the user entered "/tp hello", args would contain only "hello", wholeChat " hallo".

This technique uses an ArgumentMapper. There is one registered for Player by default, which is why this works. If you want others, you can implement your own too! Here is the sample implementation for Player:

// ==== PLAYER ====
ArgumentMappers.addMapper(new ArgumentMapper<Player>() {

    @Override
    public Class<Player> getTargetClass() {
        return Player.class;
    }

    @Override
    public Optional<? extends Player> map(Queue<String> strings) {
        if (strings.isEmpty()) {
            return Optional.empty();
        }
        String name = strings.poll();

        return Bukkit.getOnlinePlayers().stream()
                     .filter(player -> player.getName().equals(name) || player.getDisplayName().equals(name))
                     .findFirst();
     }
});

As you can see you only need two methods. The first returns the class that is being converted, the second converts. You can just poll strings from the queue as needed, what you remove here will NOT be added to args later and only appear in wholeChat. You can write your own, more complex wrappers to remove the need to check in every command, if it is really a valid parameter. A != check will suffice instead.