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.
Architecture
Section titled “Architecture”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)Creating Commands
Section titled “Creating Commands”Simple Command
Section titled “Simple Command”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 Command
Section titled “Register Command”Register commands in your plugin’s setup() method:
@Overrideprotected void setup() { getCommandRegistry().registerCommand(new HelloCommand());}Command Arguments
Section titled “Command Arguments”Arguments are registered in the constructor using fluent builder methods.
Required Arguments
Section titled “Required Arguments”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
Section titled “Optional Arguments”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
Section titled “Default Arguments”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); }}Flag Arguments
Section titled “Flag Arguments”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); }}SubCommands
Section titled “SubCommands”Command Collection
Section titled “Command Collection”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
Command Sender
Section titled “Command Sender”CommandSender Interface
Section titled “CommandSender Interface”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);Check Sender Type
Section titled “Check Sender Type”The Player component implements CommandSender. Console commands use ConsoleSender:
@Override@Nonnullprotected 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);}ConsoleSender
Section titled “ConsoleSender”The console sender always has all permissions:
// Singleton instance - always has all permissionsConsoleSender console = ConsoleSender.INSTANCE;console.sendMessage(Message.raw("Console message"));
// hasPermission always returns true for ConsoleSenderconsole.hasPermission("any.permission"); // truePermissions
Section titled “Permissions”Automatic Permission Generation
Section titled “Automatic Permission Generation”Commands auto-generate permission nodes from the plugin’s base permission:
Plugin group: com.examplePlugin name: MyPluginCommand: mycommand
Generated permission: com.example.myplugin.command.mycommandFor subcommands, permissions chain: com.example.myplugin.command.admin.kick
Custom Permission
Section titled “Custom Permission”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); }}Disable Permission Check
Section titled “Disable Permission Check”@Overrideprotected boolean canGeneratePermission() { return false; // Anyone can use this command}Manual Permission Check
Section titled “Manual Permission Check”@Override@Nonnullprotected 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);}Require Specific Permission
Section titled “Require Specific Permission”public class SecureCommand extends AbstractCommand {
public SecureCommand() { super("secure", "A secure command"); requirePermission("my.custom.permission"); // Explicit permission }}Command Aliases
Section titled “Command Aliases”public class TeleportCommand extends AbstractCommand {
public TeleportCommand() { super("teleport", "Teleport to location");
// Add multiple aliases at once addAliases("tp", "warp", "goto"); }}Confirmation Required
Section titled “Confirmation Required”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
Running Commands Programmatically
Section titled “Running Commands Programmatically”// Single command - returns CompletableFuture<Void>CommandManager.get().handleCommand(sender, "give Player stone 64");
// With PlayerRefCommandManager.get().handleCommand(playerRef, "teleport 0 64 0");
// Multiple commands in sequenceDeque<String> commands = new ArrayDeque<>();commands.add("command1");commands.add("command2");CommandManager.get().handleCommands(sender, commands);
// From consoleCommandManager.get().handleCommand(ConsoleSender.INSTANCE, "stop");Messages
Section titled “Messages”Raw Message
Section titled “Raw Message”context.sender().sendMessage(Message.raw("Hello World"));Translation Message
Section titled “Translation Message”// Uses translation key with parameter substitutioncontext.sender().sendMessage( Message.translation("server.commands.give.success") .param("player", playerName) .param("amount", amount));Argument Types
Section titled “Argument Types”| Type | Description |
|---|---|
StringArgumentType | Text string |
IntArgumentType | Integer with optional min/max via ranged(min, max) |
DoubleArgumentType | Double with optional min/max |
BooleanArgumentType | true/false |
ListArgumentType<T> | List of values (e.g., multiple players) |
Built-in Commands
Section titled “Built-in Commands”| Command | Description |
|---|---|
help | List available commands |
stop | Stop the server |
kick <player> | Kick a player |
who | List online players |
gamemode <mode> | Change game mode |
give <player> <item> [--amount] | Give items |
tp <x> <y> <z> | Teleport |
entity | Entity management subcommands |
chunk | Chunk management subcommands |
worldgen | World generation commands |
Base Command Classes
Section titled “Base Command Classes”| Class | Description |
|---|---|
AbstractCommand | Base class for all commands |
AbstractAsyncCommand | For async operations with executeAsync() |
AbstractCommandCollection | Groups subcommands together |
AbstractPlayerCommand | Requires player sender |
AbstractWorldCommand | Commands operating on a world |
AbstractTargetPlayerCommand | Commands targeting another player |
AbstractTargetEntityCommand | Commands targeting entities |
Best Practices
Section titled “Best Practices”- Use typed arguments - Store
RequiredArg/OptionalArgas fields for type safety - Return CompletableFuture - Commands run asynchronously on worker threads
- Use descriptive help - Good descriptions help users understand commands
- Validate input - Check argument values before use
- Handle errors gracefully - Send clear error messages via
Message - Use permissions - Protect sensitive commands with proper permission nodes
- Use subcommands - Group related functionality under command collections
- Use AbstractAsyncCommand - For commands that perform I/O or long operations