Skip to content

Command System

Create custom server commands for your plugin. The command system provides a flexible API for defining commands with required and optional arguments, subcommands, permissions, and more.

CommandManager (Singleton)
├── System Commands (server built-ins)
└── Plugin Commands (per-plugin via CommandRegistry)
└── AbstractCommand
├── RequiredArg (positional arguments)
├── OptionalArg (--name value)
├── DefaultArg (--name value with default)
├── FlagArg (--flag boolean switches)
└── SubCommands (nested commands)

Commands extend AbstractCommand and implement the execute method which receives a CommandContext:

import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.Message;
import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
public class HelloCommand extends AbstractCommand {
public HelloCommand() {
super("hello", "Says hello to a player");
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
context.sender().sendMessage(
Message.raw("Hello, " + context.sender().getDisplayName() + "!")
);
return CompletableFuture.completedFuture(null);
}
}

Register commands in your plugin’s setup() method:

@Override
protected void setup() {
getCommandRegistry().registerCommand(new HelloCommand());
}

Arguments are registered in the constructor using fluent builder methods.

Required arguments are positional and must be provided by the user:

import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
import com.hypixel.hytale.server.core.command.system.arguments.types.StringArgumentType;
import com.hypixel.hytale.server.core.command.system.arguments.types.IntArgumentType;
public class GiveCommand extends AbstractCommand {
private final RequiredArg<String> playerArg;
private final RequiredArg<String> itemArg;
private final RequiredArg<Integer> amountArg;
public GiveCommand() {
super("give", "Give items to a player");
// Register required arguments (order matters for positional args)
playerArg = withRequiredArg("player", "Target player", StringArgumentType.INSTANCE);
itemArg = withRequiredArg("item", "Item ID", StringArgumentType.INSTANCE);
amountArg = withRequiredArg("amount", "Quantity (1-64)", IntArgumentType.ranged(1, 64));
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
String player = context.get(playerArg);
String item = context.get(itemArg);
int amount = context.get(amountArg);
context.sender().sendMessage(
Message.raw("Gave " + amount + "x " + item + " to " + player)
);
return CompletableFuture.completedFuture(null);
}
}

Optional arguments use --name value syntax:

import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
import com.hypixel.hytale.server.core.command.system.arguments.types.DoubleArgumentType;
public class TeleportCommand extends AbstractCommand {
private final RequiredArg<Double> xArg;
private final RequiredArg<Double> yArg;
private final RequiredArg<Double> zArg;
private final OptionalArg<String> worldArg;
public TeleportCommand() {
super("tp", "Teleport to coordinates");
xArg = withRequiredArg("x", "X coordinate", DoubleArgumentType.INSTANCE);
yArg = withRequiredArg("y", "Y coordinate", DoubleArgumentType.INSTANCE);
zArg = withRequiredArg("z", "Z coordinate", DoubleArgumentType.INSTANCE);
// Optional argument: --world <name>
worldArg = withOptionalArg("world", "Target world", StringArgumentType.INSTANCE);
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
double x = context.get(xArg);
double y = context.get(yArg);
double z = context.get(zArg);
String world = context.getOrDefault(worldArg, "default");
// Teleport implementation
return CompletableFuture.completedFuture(null);
}
}

Default arguments have a fallback value when not provided:

import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg;
public class SpawnCommand extends AbstractCommand {
private final DefaultArg<Integer> radiusArg;
public SpawnCommand() {
super("spawn", "Spawn entities");
// Default argument: --radius <value> (defaults to 10)
radiusArg = withDefaultArg("radius", "Spawn radius",
IntArgumentType.ranged(1, 100), 10, "10 blocks");
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
int radius = context.get(radiusArg); // Returns 10 if not specified
// Implementation
return CompletableFuture.completedFuture(null);
}
}

Flags are boolean switches that don’t take values:

import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
public class DebugCommand extends AbstractCommand {
private final FlagArg verboseFlag;
private final FlagArg allFlag;
public DebugCommand() {
super("debug", "Toggle debug mode");
// Boolean flags: --verbose or --all
verboseFlag = withFlagArg("verbose", "Enable verbose output");
allFlag = withFlagArg("all", "Show all information");
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
boolean verbose = context.has(verboseFlag);
boolean all = context.has(allFlag);
if (verbose) {
// Verbose output
}
return CompletableFuture.completedFuture(null);
}
}

Use AbstractCommandCollection for grouping related subcommands:

import com.hypixel.hytale.server.core.command.system.basecommands.AbstractCommandCollection;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand;
public class AdminCommands extends AbstractCommandCollection {
public AdminCommands() {
super("admin", "Admin commands");
addSubCommand(new KickSubCommand());
addSubCommand(new BanSubCommand());
addSubCommand(new MuteSubCommand());
}
}
class KickSubCommand extends AbstractAsyncCommand {
private final RequiredArg<String> playerArg;
public KickSubCommand() {
super("kick", "Kick a player");
playerArg = withRequiredArg("player", "Player to kick", StringArgumentType.INSTANCE);
}
@Override
@Nonnull
protected CompletableFuture<Void> executeAsync(@Nonnull CommandContext context) {
String player = context.get(playerArg);
// Kick implementation
return CompletableFuture.completedFuture(null);
}
}

Usage: /admin kick PlayerName

The CommandSender interface extends IMessageReceiver and PermissionHolder:

public interface CommandSender extends IMessageReceiver, PermissionHolder {
String getDisplayName();
UUID getUuid();
}
// From IMessageReceiver:
// void sendMessage(@Nonnull Message message);
// From PermissionHolder:
// boolean hasPermission(@Nonnull String id);
// boolean hasPermission(@Nonnull String id, boolean def);

The Player component implements CommandSender. Console commands use ConsoleSender:

@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
CommandSender sender = context.sender();
if (sender instanceof Player) {
Player player = (Player) sender;
// Player-specific logic
} else if (sender instanceof ConsoleSender) {
// Console-specific logic
}
return CompletableFuture.completedFuture(null);
}

The console sender always has all permissions:

// Singleton instance - always has all permissions
ConsoleSender console = ConsoleSender.INSTANCE;
console.sendMessage(Message.raw("Console message"));
// hasPermission always returns true for ConsoleSender
console.hasPermission("any.permission"); // true

Commands auto-generate permission nodes from the plugin’s base permission:

Plugin group: com.example
Plugin name: MyPlugin
Command: mycommand
Generated permission: com.example.myplugin.command.mycommand

For subcommands, permissions chain: com.example.myplugin.command.admin.kick

Override generatePermissionNode() to customize:

public class ProtectedCommand extends AbstractCommand {
public ProtectedCommand() {
super("protected", "A protected command");
}
@Override
@Nullable
protected String generatePermissionNode() {
return "custom.permission.node";
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
// Only executed if sender has custom.permission.node
return CompletableFuture.completedFuture(null);
}
}
@Override
protected boolean canGeneratePermission() {
return false; // Anyone can use this command
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
if (!context.sender().hasPermission("special.action")) {
context.sender().sendMessage(Message.raw("You don't have permission!"));
return CompletableFuture.completedFuture(null);
}
// Continue with action
return CompletableFuture.completedFuture(null);
}
public class SecureCommand extends AbstractCommand {
public SecureCommand() {
super("secure", "A secure command");
requirePermission("my.custom.permission"); // Explicit permission
}
}
public class TeleportCommand extends AbstractCommand {
public TeleportCommand() {
super("teleport", "Teleport to location");
// Add multiple aliases at once
addAliases("tp", "warp", "goto");
}
}

For dangerous commands that require --confirm flag:

public class ResetCommand extends AbstractCommand {
public ResetCommand() {
super("reset", "Reset all data", true); // true = requires confirmation
}
@Override
@Nonnull
protected CompletableFuture<Void> execute(@Nonnull CommandContext context) {
// Only called after user adds --confirm flag
resetAllData();
return CompletableFuture.completedFuture(null);
}
}

Usage: /reset --confirm

// Single command - returns CompletableFuture<Void>
CommandManager.get().handleCommand(sender, "give Player stone 64");
// With PlayerRef
CommandManager.get().handleCommand(playerRef, "teleport 0 64 0");
// Multiple commands in sequence
Deque<String> commands = new ArrayDeque<>();
commands.add("command1");
commands.add("command2");
CommandManager.get().handleCommands(sender, commands);
// From console
CommandManager.get().handleCommand(ConsoleSender.INSTANCE, "stop");
context.sender().sendMessage(Message.raw("Hello World"));
// Uses translation key with parameter substitution
context.sender().sendMessage(
Message.translation("server.commands.give.success")
.param("player", playerName)
.param("amount", amount)
);
TypeDescription
StringArgumentTypeText string
IntArgumentTypeInteger with optional min/max via ranged(min, max)
DoubleArgumentTypeDouble with optional min/max
BooleanArgumentTypetrue/false
ListArgumentType<T>List of values (e.g., multiple players)
CommandDescription
helpList available commands
stopStop the server
kick <player>Kick a player
whoList online players
gamemode <mode>Change game mode
give <player> <item> [--amount]Give items
tp <x> <y> <z>Teleport
entityEntity management subcommands
chunkChunk management subcommands
worldgenWorld generation commands
ClassDescription
AbstractCommandBase class for all commands
AbstractAsyncCommandFor async operations with executeAsync()
AbstractCommandCollectionGroups subcommands together
AbstractPlayerCommandRequires player sender
AbstractWorldCommandCommands operating on a world
AbstractTargetPlayerCommandCommands targeting another player
AbstractTargetEntityCommandCommands targeting entities
  1. Use typed arguments - Store RequiredArg/OptionalArg as fields for type safety
  2. Return CompletableFuture - Commands run asynchronously on worker threads
  3. Use descriptive help - Good descriptions help users understand commands
  4. Validate input - Check argument values before use
  5. Handle errors gracefully - Send clear error messages via Message
  6. Use permissions - Protect sensitive commands with proper permission nodes
  7. Use subcommands - Group related functionality under command collections
  8. Use AbstractAsyncCommand - For commands that perform I/O or long operations