Files
Documentation/content/core-concepts/commands/subcommands.en.md
2026-01-20 20:33:59 +01:00

5.8 KiB

title, type, weight
title type weight
Subcommands docs 3

Subcommands allow you to create hierarchical command structures, organizing related functionality under a single parent command.

Creating Subcommands

Basic Structure

public class AdminCommand extends AbstractCommand {

    public AdminCommand() {
        super("admin", "Administration commands");

        // Add subcommands
        addSubCommand(new KickSubCommand());
        addSubCommand(new BanSubCommand());
        addSubCommand(new MuteSubCommand());
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext context) {
        // This runs when no subcommand is specified
        context.sender().sendMessage(Message.raw("Usage: /admin <kick|ban|mute>"));
        return null;
    }
}

Subcommand Implementation

public class KickSubCommand extends AbstractCommand {

    private final RequiredArg<PlayerRef> playerArg;
    private final OptionalArg<String> reasonArg;

    public KickSubCommand() {
        super("kick", "Kick a player from the server");

        playerArg = withRequiredArg("player", "Player to kick", ArgTypes.PLAYER_REF);
        reasonArg = withOptionalArg("reason", "Kick reason", ArgTypes.STRING);
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext context) {
        PlayerRef target = context.get(playerArg);
        String reason = context.get(reasonArg);

        // Check if player is online via ECS reference
        Ref<EntityStore> ref = target.getReference();
        if (ref != null && ref.isValid()) {
            // Disconnect via PacketHandler
            String kickReason = reason != null ? reason : "Kicked by admin";
            target.getPacketHandler().disconnect(kickReason);
            context.sendMessage(Message.raw("Kicked " + target.getUsername()));
        }

        return null;
    }
}

Usage: /admin kick player123 --reason "Breaking rules"

Command Collections

For commands that only contain subcommands (no direct execution), extend AbstractCommandCollection:

public class ManageCommand extends AbstractCommandCollection {

    public ManageCommand() {
        super("manage", "Management commands");

        addSubCommand(new ManageUsersCommand());
        addSubCommand(new ManageWorldsCommand());
        addSubCommand(new ManagePluginsCommand());
    }
}

With AbstractCommandCollection, running /manage without a subcommand will show available subcommands automatically.

Nested Subcommands

Subcommands can have their own subcommands:

public class ManageUsersCommand extends AbstractCommand {

    public ManageUsersCommand() {
        super("users", "User management");

        addSubCommand(new ListUsersCommand());   // /manage users list
        addSubCommand(new AddUserCommand());     // /manage users add
        addSubCommand(new RemoveUserCommand());  // /manage users remove
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext context) {
        // Show usage for /manage users
        return null;
    }
}

Subcommand Aliases

Subcommands can have aliases just like regular commands:

public class TeleportCommand extends AbstractCommand {

    public TeleportCommand() {
        super("teleport", "Teleport commands");
        addAliases("tp");

        addSubCommand(new TeleportHereCommand());
        addSubCommand(new TeleportAllCommand());
    }
}

public class TeleportHereCommand extends AbstractCommand {

    public TeleportHereCommand() {
        super("here", "Teleport player to you");
        addAliases("h", "tome");
    }

    // ...
}

Now players can use:

  • /teleport here player1
  • /tp here player1
  • /tp h player1
  • /tp tome player1

Command Variants

Variants allow the same command to accept different argument patterns:

public class TpCommand extends AbstractCommand {

    private final RequiredArg<PlayerRef> targetArg;

    public TpCommand() {
        super("tp", "Teleport command");

        // Main variant: /tp <player>
        targetArg = withRequiredArg("target", "Player to teleport to", ArgTypes.PLAYER_REF);

        // Add variant: /tp <player> <destination>
        addUsageVariant(new TpToPlayerVariant());

        // Add variant: /tp <x> <y> <z>
        addUsageVariant(new TpToPositionVariant());
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext context) {
        // Teleport sender to target player
        PlayerRef target = context.get(targetArg);
        // ...
        return null;
    }
}

Variant Implementation

public class TpToPlayerVariant extends AbstractCommand {

    private final RequiredArg<PlayerRef> playerArg;
    private final RequiredArg<PlayerRef> destinationArg;

    public TpToPlayerVariant() {
        // No name for variants - use description only
        super("Teleport one player to another");

        playerArg = withRequiredArg("player", "Player to teleport", ArgTypes.PLAYER_REF);
        destinationArg = withRequiredArg("destination", "Destination player", ArgTypes.PLAYER_REF);
    }

    @Override
    protected CompletableFuture<Void> execute(CommandContext context) {
        PlayerRef player = context.get(playerArg);
        PlayerRef destination = context.get(destinationArg);
        // Teleport player to destination
        return null;
    }
}

{{< callout type="info" >}} Variants are distinguished by the number of required parameters. Each variant must have a different number of required arguments. {{< /callout >}}

Permission Inheritance

Subcommand permissions are automatically built from the parent:

/admin -> myplugin.command.admin
/admin kick -> myplugin.command.admin.kick
/admin ban -> myplugin.command.admin.ban

You can also set custom permissions:

public KickSubCommand() {
    super("kick", "Kick a player");
    requirePermission("myplugin.admin.kick");
}