hytale-commands
Hytale Custom Commands
Complete guide for creating custom server commands with arguments, permissions, tab completion, and execution handling.
When to use this skill
Use this skill when:
- Creating new slash commands for players or admins
- Adding command arguments (required, optional, flags)
- Setting up command permissions
- Creating command collections/groups
- Implementing async commands for long-running operations
- Adding tab completion for arguments
Command Architecture Overview
Hytale uses a command system based on abstract command classes with typed arguments. Commands are registered through the plugin's CommandRegistry and managed by the CommandManager singleton.
Command Class Hierarchy
AbstractCommand
├── CommandBase # Base for simple commands
├── AbstractAsyncCommand # For async execution
├── AbstractPlayerCommand # Requires player sender
├── AbstractWorldCommand # Requires world context
├── AbstractTargetPlayerCommand # Target another player
└── AbstractCommandCollection # Group of subcommands
Command Flow
Player Input -> CommandManager -> Parse Arguments -> Check Permissions -> Execute
Basic Command Implementation
Simple Command
package com.example.myplugin.commands;
import com.hypixel.hytale.server.core.command.CommandBase;
import com.hypixel.hytale.server.core.command.CommandContext;
public class HelloCommand extends CommandBase {
public HelloCommand() {
super("hello", "Says hello to the world");
}
@Override
protected void execute(CommandContext ctx) {
ctx.sendSuccess("Hello, World!");
}
}
Registration in Plugin
@Override
protected void setup() {
getCommandRegistry().registerCommand(new HelloCommand());
getCommandRegistry().registerCommand(new SpawnCommand());
getCommandRegistry().registerCommand(new TeleportCommand());
}
Command Arguments
Argument Types
| ArgType | Description | Example Value |
|---|---|---|
STRING |
Text string | "hello" |
INTEGER |
Whole number | 42 |
FLOAT |
Decimal number | 3.14 |
BOOLEAN |
True/false | true |
PLAYER_REF |
Online player | PlayerName |
WORLD |
World name | world_overworld |
ITEM_ID |
Item identifier | hytale:sword |
BLOCK_ID |
Block identifier | hytale:stone |
ENTITY_TYPE_ID |
Entity type | hytale:zombie |
RELATIVE_INT_POSITION |
Block position | ~10 ~0 ~-5 |
RELATIVE_POSITION |
Precise position | ~10.5 ~0 ~-5.5 |
DIRECTION |
Direction vector | north, up |
DURATION |
Time duration | 10s, 5m, 1h |
JSON |
JSON object | {"key":"value"} |
GREEDY_STRING |
Rest of input | "hello world" |
Argument Kinds
// Required argument - must be provided
RequiredArg<String> nameArg = new RequiredArg<>("name", ArgType.STRING);
// Optional argument - can be omitted
OptionalArg<Integer> countArg = new OptionalArg<>("count", ArgType.INTEGER);
// Default argument - uses default if omitted
DefaultArg<Integer> amountArg = new DefaultArg<>("amount", ArgType.INTEGER, 1);
// Flag argument - boolean switch
FlagArg silentFlag = new FlagArg("silent", "s");
Command with Arguments
public class GiveCommand extends CommandBase {
private static final RequiredArg<PlayerRef> TARGET =
new RequiredArg<>("target", ArgType.PLAYER_REF);
private static final RequiredArg<ItemId> ITEM =
new RequiredArg<>("item", ArgType.ITEM_ID);
private static final DefaultArg<Integer> AMOUNT =
new DefaultArg<>("amount", ArgType.INTEGER, 1);
private static final FlagArg SILENT =
new FlagArg("silent", "s");
public GiveCommand() {
super("give", "Give items to a player");
addArg(TARGET);
addArg(ITEM);
addArg(AMOUNT);
addArg(SILENT);
}
@Override
protected void execute(CommandContext ctx) {
Player target = ctx.get(TARGET).resolve();
ItemId item = ctx.get(ITEM);
int amount = ctx.get(AMOUNT);
boolean silent = ctx.has(SILENT);
// Give the item
target.getInventory().addItem(item, amount);
if (!silent) {
ctx.sendSuccess("Gave " + amount + "x " + item + " to " + target.getName());
}
}
}
Specialized Command Classes
Player-Only Command
Automatically checks that sender is a player. The execute method receives 5 parameters:
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import javax.annotation.Nonnull;
public class FlyCommand extends AbstractPlayerCommand {
public FlyCommand() {
super("fly", "Toggle flight mode");
}
@Override
protected void execute(
@Nonnull CommandContext context,
@Nonnull Store<EntityStore> store,
@Nonnull Ref<EntityStore> ref,
@Nonnull PlayerRef playerRef,
@Nonnull World world
) {
// Access player data through PlayerRef
String username = playerRef.getUsername();
// Execute on world thread for world modifications
world.execute(() -> {
context.sendSuccess("Flight toggled for " + username);
});
}
}
World Context Command
Requires a world context:
public class TimeCommand extends AbstractWorldCommand {
private static final RequiredArg<Integer> TIME =
new RequiredArg<>("time", ArgType.INTEGER);
public TimeCommand() {
super("time", "Set world time");
addArg(TIME);
}
@Override
protected void execute(CommandContext ctx, World world) {
int time = ctx.get(TIME);
world.setTime(time);
ctx.sendSuccess("Set time to " + time + " in " + world.getName());
}
}
Target Player Command
For commands that target another player:
public class HealCommand extends AbstractTargetPlayerCommand {
public HealCommand() {
super("heal", "Heal a player to full health");
}
@Override
protected void execute(CommandContext ctx, Player target) {
target.setHealth(target.getMaxHealth());
ctx.sendSuccess("Healed " + target.getName());
}
}
Async Command
For long-running operations:
public class BackupCommand extends AbstractAsyncCommand {
public BackupCommand() {
super("backup", "Create world backup");
}
@Override
protected CompletableFuture<Void> executeAsync(CommandContext ctx) {
return CompletableFuture.runAsync(() -> {
ctx.sendMessage("Starting backup...");
// Perform backup operation
performBackup();
ctx.sendSuccess("Backup complete!");
});
}
}
Command Collections (Subcommands)
Group related commands together:
public class AdminCommands extends AbstractCommandCollection {
public AdminCommands() {
super("admin", "Admin commands");
// Register subcommands
addSubCommand(new BanSubCommand());
addSubCommand(new KickSubCommand());
addSubCommand(new MuteSubCommand());
}
// Subcommand implementation
private class BanSubCommand extends CommandBase {
private static final RequiredArg<PlayerRef> TARGET =
new RequiredArg<>("target", ArgType.PLAYER_REF);
private static final OptionalArg<String> REASON =
new OptionalArg<>("reason", ArgType.GREEDY_STRING);
public BanSubCommand() {
super("ban", "Ban a player");
addArg(TARGET);
addArg(REASON);
}
@Override
protected void execute(CommandContext ctx) {
Player target = ctx.get(TARGET).resolve();
String reason = ctx.getOrDefault(REASON, "No reason provided");
// Ban logic
ctx.sendSuccess("Banned " + target.getName() + ": " + reason);
}
}
}
Usage: /admin ban PlayerName Being naughty
Permissions
Auto-Generated Permissions
Commands automatically get permissions based on plugin identity:
{plugin.group}.{plugin.name}.command.{commandName}
Example: com.example.myplugin.command.give
Custom Permissions
public class SecretCommand extends CommandBase {
public SecretCommand() {
super("secret", "A secret command");
// Override default permission
setPermission("admin.secret.access");
}
@Override
protected void execute(CommandContext ctx) {
ctx.sendSuccess("You found the secret!");
}
}
Permission Checks in Execution
@Override
protected void execute(CommandContext ctx) {
if (!ctx.hasPermission("special.feature")) {
ctx.sendError("You don't have permission for this feature!");
return;
}
// Execute feature
}
Command Context
The CommandContext provides access to sender info and utilities:
@Override
protected void execute(CommandContext ctx) {
// Get sender info
CommandSender sender = ctx.getSender();
boolean isPlayer = ctx.isPlayer();
boolean isConsole = ctx.isConsole();
// Get player if sender is player
Optional<Player> player = ctx.getPlayerSender();
// Get world context
Optional<World> world = ctx.getWorld();
// Send messages
ctx.sendMessage("Plain message");
ctx.sendSuccess("Success message"); // Green
ctx.sendError("Error message"); // Red
ctx.sendWarning("Warning message"); // Yellow
// Get argument values
String name = ctx.get(NAME_ARG);
int count = ctx.getOrDefault(COUNT_ARG, 10);
boolean hasFlag = ctx.has(SOME_FLAG);
// Check permissions
boolean canUse = ctx.hasPermission("some.permission");
}
Tab Completion
Arguments provide automatic tab completion. Custom completion:
public class CustomArg extends RequiredArg<String> {
public CustomArg() {
super("mode", ArgType.STRING);
}
@Override
public List<String> getSuggestions(CommandContext ctx, String partial) {
return List.of("easy", "medium", "hard")
.stream()
.filter(s -> s.startsWith(partial.toLowerCase()))
.toList();
}
}
Complete Example Plugin
package com.example.admintools;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import javax.annotation.Nonnull;
public class AdminToolsPlugin extends JavaPlugin {
public AdminToolsPlugin(@Nonnull JavaPluginInit init) {
super(init);
}
@Override
protected void setup() {
// Register individual commands
getCommandRegistry().registerCommand(new HealCommand());
getCommandRegistry().registerCommand(new FlyCommand());
getCommandRegistry().registerCommand(new TeleportCommand());
// Register command collection
getCommandRegistry().registerCommand(new AdminCommands());
getLogger().atInfo().log("AdminTools commands registered!");
}
}
Best Practices
Argument Validation
@Override
protected void execute(CommandContext ctx) {
int amount = ctx.get(AMOUNT);
// Validate ranges
if (amount < 1 || amount > 64) {
ctx.sendError("Amount must be between 1 and 64");
return;
}
// Continue execution
}
Error Handling
@Override
protected void execute(CommandContext ctx) {
try {
Player target = ctx.get(TARGET).resolve();
if (target == null) {
ctx.sendError("Player not found!");
return;
}
// Execute command
} catch (Exception e) {
ctx.sendError("An error occurred: " + e.getMessage());
getLogger().atSevere().withCause(e).log("Command error");
}
}
Feedback Messages
@Override
protected void execute(CommandContext ctx) {
// Always provide feedback
ctx.sendMessage("Processing...");
// Do work
// Report result
if (success) {
ctx.sendSuccess("Operation completed!");
} else {
ctx.sendError("Operation failed: " + reason);
}
}
Troubleshooting
Command Not Found
- Verify command is registered in
setup() - Check command name doesn't conflict with existing commands
- Ensure plugin is loading correctly
Permission Denied
- Check player has the auto-generated permission
- Verify custom permission is granted
- Check permission node spelling
Arguments Not Parsing
- Verify argument order matches usage
- Check ArgType matches expected input
- Ensure required arguments are provided
Tab Completion Not Working
- Verify argument has suggestions defined
- Check completion returns non-empty list
- Ensure partial matching is implemented
Detailed References
For comprehensive documentation:
references/argument-types.md- Complete argument type reference with all ArgTypes, parsing, validationreferences/command-patterns.md- Advanced patterns: cooldowns, confirmations, pagination, wizards