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

416 lines
11 KiB
Markdown

# 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
```java
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:
```java
@Override
protected void setup() {
getCommandRegistry().registerCommand(new HelloCommand());
getCommandRegistry().registerCommand(new TeleportCommand());
}
```
## Command Arguments
### Required Arguments
Required arguments must be provided by the user:
```java
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:
```java
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:
```java
public MyCommand() {
super("mycommand", "Description");
withDefaultArg("count", "Number of items", "1");
}
```
### Flag Arguments
Boolean flags that can be toggled:
```java
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:
```java
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:
```java
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:
```java
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:
```java
@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:
```java
public MyCommand() {
super("mycommand", "Description");
requirePermission("custom.permission.name");
}
```
### Permission Checks in Execution
```java
@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) |
```java
@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
```java
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