Files
hytale-server/docs/04-command-system.md

11 KiB

Command System

The Hytale command system allows plugins to register custom commands that can be executed by players and the console.

Overview

Commands are defined by extending AbstractCommand and registered through the plugin's CommandRegistry. The system supports:

  • Required and optional arguments
  • Subcommands
  • Command variants (alternative syntax)
  • Automatic permission generation
  • Tab completion
  • Flag arguments

Creating a Basic Command

public class HelloCommand extends AbstractCommand {
    
    public HelloCommand() {
        super("hello", "Says hello to a player");
        
        // Add command aliases
        addAliases("hi", "greet");
        
        // Require permission (auto-generated if not specified)
        requirePermission("myplugin.command.hello");
        
        // Add a required argument
        withRequiredArg("player", "The player to greet");
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        String playerName = args.getString("player");
        context.sendMessage("Hello, " + playerName + "!");
    }
}

Registering Commands

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

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

Command Arguments

Required Arguments

Required arguments must be provided by the user:

public MyCommand() {
    super("mycommand", "Description");
    
    // Single required argument
    withRequiredArg("target", "The target player");
    
    // Multiple required arguments
    withRequiredArg("x", "X coordinate");
    withRequiredArg("y", "Y coordinate");
    withRequiredArg("z", "Z coordinate");
}

@Override
public void execute(CommandContext context, Arguments args) {
    String target = args.getString("target");
    int x = args.getInt("x");
    int y = args.getInt("y");
    int z = args.getInt("z");
}

Optional Arguments

Optional arguments have default values:

public MyCommand() {
    super("mycommand", "Description");
    withRequiredArg("player", "Target player");
    withOptionalArg("message", "Optional message");
}

@Override
public void execute(CommandContext context, Arguments args) {
    String player = args.getString("player");
    String message = args.getStringOrDefault("message", "Default message");
}

Default Arguments

Arguments with default values that are filled in if not provided:

public MyCommand() {
    super("mycommand", "Description");
    withDefaultArg("count", "Number of items", "1");
}

Flag Arguments

Boolean flags that can be toggled:

public MyCommand() {
    super("mycommand", "Description");
    withFlagArg("silent", "Execute silently");
    withFlagArg("force", "Force execution");
}

@Override
public void execute(CommandContext context, Arguments args) {
    boolean silent = args.hasFlag("silent");
    boolean force = args.hasFlag("force");
}

Usage: /mycommand --silent --force

List Arguments

Arguments that accept multiple values:

public MyCommand() {
    super("mycommand", "Description");
    withListRequiredArg("players", "List of players");
}

@Override
public void execute(CommandContext context, Arguments args) {
    List<String> players = args.getStringList("players");
}

Usage: /mycommand player1 player2 player3

Subcommands

Create hierarchical command structures:

public class AdminCommand extends AbstractCommand {
    
    public AdminCommand() {
        super("admin", "Administration commands");
        
        // Add subcommands
        addSubCommand(new AdminKickCommand());
        addSubCommand(new AdminBanCommand());
        addSubCommand(new AdminMuteCommand());
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        // Called when no subcommand is specified
        context.sendMessage("Usage: /admin <kick|ban|mute>");
    }
}

public class AdminKickCommand extends AbstractCommand {
    
    public AdminKickCommand() {
        super("kick", "Kick a player");
        withRequiredArg("player", "Player to kick");
        withOptionalArg("reason", "Kick reason");
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        String player = args.getString("player");
        String reason = args.getStringOrDefault("reason", "No reason specified");
        // Kick logic
    }
}

Usage: /admin kick PlayerName "Breaking rules"

Command Variants

Define alternative syntax for the same command:

public class TeleportCommand extends AbstractCommand {
    
    public TeleportCommand() {
        super("teleport", "Teleport to a location");
        addAliases("tp");
        
        // Variant 1: teleport to player
        withRequiredArg("target", "Target player");
        
        // Variant 2: teleport to coordinates
        addUsageVariant(new TeleportCoordsVariant());
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        String target = args.getString("target");
        // Teleport to player
    }
}

public class TeleportCoordsVariant extends AbstractCommand {
    
    public TeleportCoordsVariant() {
        super("teleport", "Teleport to coordinates");
        withRequiredArg("x", "X coordinate");
        withRequiredArg("y", "Y coordinate");
        withRequiredArg("z", "Z coordinate");
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        double x = args.getDouble("x");
        double y = args.getDouble("y");
        double z = args.getDouble("z");
        // Teleport to coordinates
    }
}

Command Context

The CommandContext provides information about the command execution:

@Override
public void execute(CommandContext context, Arguments args) {
    // Get the command sender
    CommandSender sender = context.getSender();
    
    // Check if sender is a player
    if (sender instanceof Player player) {
        // Player-specific logic
        World world = player.getWorld();
    }
    
    // Send messages
    context.sendMessage("Success!");
    context.sendError("Something went wrong!");
    
    // Check permissions
    if (sender.hasPermission("myplugin.admin")) {
        // Admin logic
    }
}

Permissions

Automatic Permission Generation

Commands automatically receive permissions based on the plugin's base permission:

{plugin.basePermission}.command.{commandName}

For example, if your plugin is com.example:MyPlugin and command is spawn:

  • Permission: com.example.myplugin.command.spawn

Custom Permissions

Override the automatic permission:

public MyCommand() {
    super("mycommand", "Description");
    requirePermission("custom.permission.name");
}

Permission Checks in Execution

@Override
public void execute(CommandContext context, Arguments args) {
    CommandSender sender = context.getSender();
    
    if (!sender.hasPermission("myplugin.admin")) {
        context.sendError("You don't have permission!");
        return;
    }
    
    // Continue execution
}

Command Sender Types

Commands can be executed by different sender types:

Sender Type Description
Player In-game player
Console Server console
CommandBlock Command block (if applicable)
@Override
public void execute(CommandContext context, Arguments args) {
    CommandSender sender = context.getSender();
    
    if (sender instanceof Player player) {
        // Player-specific logic
    } else if (sender instanceof Console) {
        // Console-specific logic
    } else {
        context.sendError("This command can only be run by players!");
    }
}

Complete Example

public class GameModeCommand extends AbstractCommand {
    
    public GameModeCommand() {
        super("gamemode", "Change your game mode");
        addAliases("gm");
        
        // Required: game mode
        withRequiredArg("mode", "Game mode (survival, creative, adventure, spectator)");
        
        // Optional: target player (admin only)
        withOptionalArg("player", "Target player (requires admin permission)");
        
        requirePermission("myplugin.command.gamemode");
    }
    
    @Override
    public void execute(CommandContext context, Arguments args) {
        CommandSender sender = context.getSender();
        String modeStr = args.getString("mode");
        
        // Parse game mode
        GameMode mode = parseGameMode(modeStr);
        if (mode == null) {
            context.sendError("Invalid game mode: " + modeStr);
            return;
        }
        
        // Determine target player
        Player target;
        if (args.has("player")) {
            // Check admin permission for targeting others
            if (!sender.hasPermission("myplugin.command.gamemode.others")) {
                context.sendError("You don't have permission to change others' game mode!");
                return;
            }
            
            String playerName = args.getString("player");
            target = findPlayer(playerName);
            
            if (target == null) {
                context.sendError("Player not found: " + playerName);
                return;
            }
        } else {
            // Self - must be a player
            if (!(sender instanceof Player)) {
                context.sendError("Console must specify a target player!");
                return;
            }
            target = (Player) sender;
        }
        
        // Apply game mode
        target.setGameMode(mode);
        context.sendMessage("Set " + target.getName() + "'s game mode to " + mode.name());
    }
    
    private GameMode parseGameMode(String str) {
        return switch (str.toLowerCase()) {
            case "survival", "s", "0" -> GameMode.SURVIVAL;
            case "creative", "c", "1" -> GameMode.CREATIVE;
            case "adventure", "a", "2" -> GameMode.ADVENTURE;
            case "spectator", "sp", "3" -> GameMode.SPECTATOR;
            default -> null;
        };
    }
    
    private Player findPlayer(String name) {
        return Universe.get().getPlayer(name);
    }
}

Best Practices

  1. Provide clear descriptions - Help users understand what the command does
  2. Use meaningful aliases - Common abbreviations improve usability
  3. Validate input - Check argument values before using them
  4. Handle errors gracefully - Provide helpful error messages
  5. Check permissions appropriately - Use automatic permissions or explicit checks
  6. Support tab completion - Implement tab completion for better UX
  7. Document usage - Include usage examples in the description
  8. Use subcommands for complex functionality - Organize related commands together
  9. Consider console execution - Check if the command can run from console