hytale-commands
npx skills add https://github.com/mnkyarts/hytale-skills --skill hytale-commands
Agent 安装分布
Skill 文档
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