Create custom commands for Hytale server plugins with arguments, permissions, and execution handling...
Complete guide for creating custom server commands with arguments, permissions, tab completion, and execution handling.
Use this skill when:
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.
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
Player Input -> CommandManager -> Parse Arguments -> Check Permissions -> Execute
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!");
}
}
@Override
protected void setup() {
getCommandRegistry().registerCommand(new HelloCommand());
getCommandRegistry().registerCommand(new SpawnCommand());
getCommandRegistry().registerCommand(new TeleportCommand());
}
| 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" |
// 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");
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());
}
}
}
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);
});
}
}
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());
}
}
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());
}
}
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!");
});
}
}
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
Commands automatically get permissions based on plugin identity:
{plugin.group}.{plugin.name}.command.{commandName}
Example: com.example.myplugin.command.give
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!");
}
}
@Override
protected void execute(CommandContext ctx) {
if (!ctx.hasPermission("special.feature")) {
ctx.sendError("You don't have permission for this feature!");
return;
}
// Execute feature
}
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");
}
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();
}
}
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!");
}
}
@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
}
@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");
}
}
@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);
}
}
setup()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