Skip to content

Defining Commands with IRocketCommand

Commands are how players and administrators interact with RocketMod plugins. Every command in RocketMod is a class that implements the IRocketCommand interface. RocketMod's command manager automatically discovers these classes in the plugin's assembly and registers them when the plugin loads. No manual registration step is needed.

This article covers the IRocketCommand interface in detail — every property, every method, every option for controlling who can run a command, what arguments it accepts, how it responds to errors, and how it interacts with RocketMod's command infrastructure. The 57 Studios™ team has written dozens of RocketMod commands across our plugin suite, and the patterns documented here are drawn from production code that handles real player traffic.

Server console showing a RocketMod command being executed

Prerequisites

The IRocketCommand interface

The interface defines six properties and one method. Every command must implement all of them.

csharp
public interface IRocketCommand
{
    string Name { get; }
    string Help { get; }
    string Syntax { get; }
    List<string> Aliases { get; }
    string[] Permissions { get; }
    AllowedCaller AllowedCaller { get; }
    void Execute(IRocketPlayer caller, string[] command);
}

Property reference

PropertyRequiredTypePurpose
NameYesstringThe primary command keyword — what players type after /
HelpYesstringShort description shown in /help listings
SyntaxYesstringUsage format shown in /help <command>
AliasesYesList<string>Alternative keywords that also trigger this command
PermissionsYesstring[]Permission strings required to run the command
AllowedCallerYesAllowedCallerRestricts who can execute the command

The AllowedCaller enum

The AllowedCaller enum controls whether a command can be used by players in-game, from the server console, or both. RocketMod defines four values in the enum:

ValueBehaviorUse case
PlayerCommand only executes when invoked by a connected player from in-game chatPlayer-facing commands: teleport, heal, give
ConsoleCommand only executes from the server consoleAdministrative commands: shutdown, ban, plugin management
BothCommand executes from both in-game chat and server consoleCommands safe in both contexts: status, help, time
ServerCommand executes from the server process itself, outside of player or console contextInternal automation and cross-plugin communication

The Server value is useful when a command needs to be invoked programmatically by another plugin or by the server's internal systems without requiring a player or console actor. When AllowedCaller is set to Server, the command does not appear in /help listings and cannot be typed by a player or administrator. It is registered solely for programmatic invocation through R.Commands.Execute().

csharp
public AllowedCaller AllowedCaller => AllowedCaller.Server;

Choosing the right AllowedCaller

The rule of thumb is to restrict as much as possible. If a command modifies player inventory or teleports a player, it should only work for Player callers, because the console has no meaningful player context. If a command shuts down the server, it should only work from Console so a malicious player cannot trigger it. Only use Both when the command is purely informational and does not depend on player context.

Command typeRecommended AllowedCaller
Teleport, heal, give, killPlayer
Kick, ban, shutdownConsole
Help, status, ping, onlineBoth
Internal programmatic triggerServer

AllowedCaller and the caller parameter

The Execute method receives an IRocketPlayer parameter. The concrete implementation differs based on who called the command:

  • When called by a player: caller is an UnturnedPlayer instance with access to .SteamId, .DisplayName, .CharacterName, .IsAdmin, and player-specific methods like .Heal(), .Teleport(), .GiveItem().
  • When called by the console: caller is a ConsolePlayer instance. It has .DisplayName (returns "Console") and a limited set of properties. Calling player-specific methods throws NotSupportedException.
  • When called by Server context: caller is a ServerActor instance with minimal properties. It represents the server process itself and has no player or console context.

Always check the AllowedCaller configuration, but also guard against invalid assumptions in Execute:

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    // Safe type check for player-specific operations
    if (caller is UnturnedPlayer player)
    {
        player.Heal(100);
        UnturnedChat.Say(caller, "You have been healed.", Color.green);
    }
    else if (caller is ConsolePlayer)
    {
        UnturnedChat.Say(caller, "This command can only be used by players.", Color.red);
    }
}

The Name property

The Name property defines the primary keyword that triggers the command. Players type / followed by this name to invoke the command.

csharp
public string Name => "heal";

Naming conventions

  • Use lowercase letters only. RocketMod performs a case-insensitive comparison, so /Heal, /HEAL, and /heal all work. However, using mixed case in the property value is confusing when the command appears in /help listings.
  • Use single words where possible. Multi-word names like giveitem are harder to remember than single-word aliases.
  • Avoid special characters. RocketMod's command parser splits on spaces, so a name with a space never matches the full string.
  • Avoid names that conflict with Unturned's built-in commands. Unturned's /give, /teleport, and /v are reserved. If your plugin registers a command with the same name as an Unturned built-in, RocketMod's command manager overrides the built-in behavior, which may confuse server administrators.

Name collision between plugins

If two plugins register a command with the same Name, RocketMod uses a priority system to determine which plugin's implementation runs. By default, the first-loaded plugin wins. Command priority is covered in detail in Command Mapping and Priority.

The Help property

The Help property provides a one-line description of the command. It appears when a player types /help or /help <commandname>.

csharp
public string Help => "Heals a player to full health.";

Guidelines for Help text:

  • Keep it under 80 characters. The /help output formats commands in columns, and long help text wraps awkwardly.
  • Start with a capital letter and end without a period. RocketMod's help formatter adds punctuation styling.
  • Describe what the command DOES, not what it is. "Heals a player to full health" is better than "This command is for healing."
  • Do not repeat the command name. "Heals a player" not "Heal heals a player."

The Syntax property

The Syntax property shows the expected argument format. It appears when a player types /help <commandname> or when the command is used with missing or invalid arguments.

csharp
public string Syntax => "/heal <player>";

Syntax notation

The 57 Studios™ team follows a standard notation for syntax strings:

  • Required arguments: <argument> — angle brackets, no brackets inside.
  • Optional arguments: [argument] — square brackets.
  • Literal text: plain text like the command name.
  • Choices: <player|console> — pipe separated.
  • Repeatable: [args...] — ellipsis suffix.

Examples:

/teleport <player> [destination]
/give <player> <item> <amount>
/kick <player> [reason...]
/announce <message>

The syntax string should match what the Execute method expects in its command string array. If the command expects exactly two arguments, the syntax should show two required parameters.

The Aliases property

Aliases are alternative names that trigger the same command. If a player types an alias instead of the primary name, RocketMod redirects to the same Execute method.

csharp
private readonly List<string> _aliases = new List<string> { "health", "hp", "revive" };

public List<string> Aliases => _aliases;

Alias guidelines

  • Provide memorable short forms. If the primary name is teleport, aliases of tp and tpo are helpful.
  • Do not create aliases that overlap with other commands on the server. Check with the server administrator if you are unsure which commands are already registered.
  • Three to five aliases is a reasonable maximum. Too many aliases clutter the command namespace and increase the chance of collision.

Aliases and permissions

Aliases share the same permission requirements as the primary command. If myplugin.heal is required, it is required whether the user types /heal, /health, or /revive. There is no way to give an alias different permissions within the same command class — if you need different permissions for different names, create separate command classes.

The Permissions property

The Permissions property lists the permission strings required to run the command. If the array is empty, anyone can use the command. If it has one or more entries, the caller must have at least one of the listed permissions.

csharp
public string[] Permissions => new string[] { "myplugin.heal" };

Empty permissions = no restriction

csharp
public string[] Permissions => new string[] { };

Commands with empty permissions are visible in /help and executable by any player or console operator. Use this for informational commands like /help, /ping, /online.

Permission naming

The 57 Studios™ convention is to use <pluginname>.<commandname> — all lowercase, dot-separated. This convention prevents naming collisions and makes permissions readable in audits.

csharp
public string[] Permissions => new string[] { "myplugin.heal" };

Permissions are checked in permissions.xml and assigned through groups. The permission system is covered in detail in Command Permissions and Cooldowns.

The Execute method

The Execute method is the command's body — the code that runs when a player or console operator invokes the command.

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    // command[0] is the first argument after the command name
    // If the user typed "/heal Notch", command = ["Notch"]
    if (command.Length < 1)
    {
        UnturnedChat.Say(caller, "Usage: /heal <player>", Color.red);
        return;
    }

    UnturnedPlayer target = U.Instance.Players.FindPlayer(command[0]);
    if (target == null)
    {
        UnturnedChat.Say(caller, "Player not found.", Color.red);
        return;
    }

    target.Heal(100);
    UnturnedChat.Say(caller, $"Healed {target.DisplayName}.", Color.green);
}

The command parameter

The command parameter is a string[] containing the arguments after the command name, split by spaces. If a user types /give Notch 5 10, the array is ["Notch", "5", "10"].

Inputcommand array
/heal[] — empty array, no arguments
/heal Notch["Notch"]
/give Notch 5 10["Notch", "5", "10"]
/say Hello world!["Hello", "world!"] — note: no concatenation

Quotation mark handling is limited. RocketMod does NOT support quoted strings as a single argument. /say "Hello world!" passes ["\"Hello", "world!\""] with the quotes included as part of the strings. If your command needs multi-word arguments, the caller must use an alternative syntax (e.g., -m "Hello world" flags) or you must concatenate the remaining arguments manually:

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    if (command.Length < 1)
    {
        UnturnedChat.Say(caller, "Usage: /say <message>", Color.red);
        return;
    }

    // Concatenate all arguments into one message
    string message = string.Join(" ", command);
    UnturnedChat.Say(message, Color.white);
}

Return value is void

Execute returns void. There is no return code or error reporting mechanism through the interface. Errors are communicated to the caller by sending a chat message. Use Color.red for errors and Color.green for success messages to provide visual feedback.

Exception handling in Execute

Unhandled exceptions in Execute are caught by RocketMod's command manager and logged to Rocket.log. The player sees no error message unless the command explicitly sends one. Always wrap command logic in a try-catch:

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    try
    {
        // Command logic
    }
    catch (Exception ex)
    {
        Rocket.Core.Logging.Logger.LogError(
            $"[MyPlugin] Error in heal command: {ex.Message}");
        UnturnedChat.Say(caller,
            "An error occurred while executing the command.", Color.red);
    }
}

Complete command example

The following is a complete command that teleports a player to another player, with proper argument validation, caller type checking, and error handling:

csharp
using Rocket.API;
using Rocket.Unturned.Chat;
using Rocket.Unturned.Player;
using System.Collections.Generic;
using UnityEngine;

namespace MyPlugin.Commands
{
    public class TeleportToPlayerCommand : IRocketCommand
    {
        public string Name => "tpto";
        public string Help => "Teleport to another player.";
        public string Syntax => "/tpto <player>";
        public List<string> Aliases => new List<string> { "tp", "goto" };

        public string[] Permissions => new string[]
        {
            "myplugin.tpto",
            "myplugin.tp"
        };

        public AllowedCaller AllowedCaller => AllowedCaller.Player;

        public void Execute(IRocketPlayer caller, string[] command)
        {
            try
            {
                // Validate arguments
                if (command.Length < 1)
                {
                    UnturnedChat.Say(caller,
                        "Usage: /tpto <player>", Color.red);
                    return;
                }

                // Ensure the caller is a player
                if (!(caller is UnturnedPlayer player))
                {
                    UnturnedChat.Say(caller,
                        "This command can only be used by players.", Color.red);
                    return;
                }

                // Find the target player
                UnturnedPlayer target = U.Instance.Players.FindPlayer(command[0]);
                if (target == null)
                {
                    UnturnedChat.Say(caller,
                        $"Player '{command[0]}' not found.", Color.red);
                    return;
                }

                // Teleport
                player.Teleport(target.Position, target.Rotation);
                UnturnedChat.Say(caller,
                    $"Teleported to {target.DisplayName}.", Color.green);
            }
            catch (System.Exception ex)
            {
                Rocket.Core.Logging.Logger.LogError(
                    $"[MyPlugin] Teleport error: {ex.Message}");
                UnturnedChat.Say(caller,
                    "An error occurred during teleport.", Color.red);
            }
        }
    }
}

Command discovery and registration

RocketMod discovers commands through assembly scanning. When a plugin loads, RocketMod's command manager scans every type in the plugin's assembly for classes that implement IRocketCommand. Each discovered class is instantiated and registered with the command name from the Name property.

Automatic registration

csharp
// RocketMod does this for you — no manual registration needed
// When MyPlugin loads, all IRocketCommand implementations are found
// and registered automatically.

When commands are registered

Command registration happens during Load(). If the plugin's Load() method throws an exception, no commands from that plugin are registered. Commands are deregistered when the plugin unloads.

Logging command registration

RocketMod logs each discovered command at startup:

[Managed] Loaded command /heal from MyPlugin v1.0.0
[Managed] Loaded command /tpto from MyPlugin v1.0.0

If a command does not appear in the logs, check that the class is public and implements IRocketCommand with the correct namespace. The interface is in Rocket.API.

Best practices

Validate arguments early

Check command.Length at the top of Execute and return immediately with a usage message if the required arguments are missing. This keeps the rest of the method unindented and readable.

Use the Syntax property for error messages

The Syntax string is the canonical usage format. When returning a usage error, include the Syntax value so the player sees the correct format even if they typed the command wrong.

csharp
if (command.Length < 1)
{
    UnturnedChat.Say(caller,
        $"Usage: {Syntax}", Color.red);
    return;
}

Log execution for administrative commands

Commands that have side effects (kick, ban, teleport, give) should log the action for the server operator's audit trail:

csharp
Rocket.Core.Logging.Logger.Log(
    $"[MyPlugin] {caller.DisplayName} healed {target.DisplayName}.");

Do not block the server

Execute runs on the server's main thread. Avoid long-running operations like web requests, heavy file I/O, or database queries inside Execute. If you need to perform an async operation, spawn a background task, but be aware that IRocketPlayer is not thread-safe and should not be accessed from a background thread.

Handle the ConsolePlayer case

Even if AllowedCaller is set to Both, guard against console callers calling player-specific operations:

csharp
if (caller is ConsolePlayer)
{
    UnturnedChat.Say(caller, "This command requires a player context.", Color.red);
    return;
}

Common mistakes

Null reference on caller

If AllowedCaller is Both and a console user calls a command that accesses UnturnedPlayer-specific properties without a type check, a NullReferenceException is thrown. Always type-check caller before casting.

Arguments treated as one string

New plugin developers sometimes expect command to be a single string. It is a string[]. Access command[0] for the first argument, not the entire string.

Case-sensitive comparison on arguments

RocketMod's command matching is case-insensitive, but the arguments in the command array preserve the original casing. If you need to compare an argument to a literal, use StringComparison.OrdinalIgnoreCase:

csharp
if (command[0].Equals("all", StringComparison.OrdinalIgnoreCase))
{
    // Heal all players
}

Assuming command.Length checks pass

command.Length is zero when no arguments are supplied. Accessing command[0] without checking command.Length >= 1 throws an IndexOutOfRangeException.

Subcommand patterns

Some commands benefit from a subcommand structure where the first argument determines the operation. For example, /player might have subcommands like /player info, /player kick, /player warn.

Subcommand dispatch pattern

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    if (command.Length < 1)
    {
        UnturnedChat.Say(caller, $"Usage: {Syntax}", Color.red);
        return;
    }

    string subcommand = command[0].ToLowerInvariant();
    string[] subArgs = command.Length > 1
        ? command.Skip(1).ToArray()
        : new string[0];

    switch (subcommand)
    {
        case "info":
            HandleInfo(caller, subArgs);
            break;
        case "kick":
            HandleKick(caller, subArgs);
            break;
        case "warn":
            HandleWarn(caller, subArgs);
            break;
        default:
            UnturnedChat.Say(caller,
                $"Unknown subcommand '{command[0]}'. Use /player info, " +
                "/player kick, or /player warn.",
                Color.red);
            break;
    }
}

private void HandleInfo(IRocketPlayer caller, string[] args) { }
private void HandleKick(IRocketPlayer caller, string[] args) { }
private void HandleWarn(IRocketPlayer caller, string[] args) { }

Subcommand permission grouping

When using subcommands, check permissions per-subcommand rather than at the command level. This lets server administrators grant or deny specific operations without exposing the entire command.

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    if (command.Length < 1)
    {
        UnturnedChat.Say(caller, $"Usage: {Syntax}", Color.red);
        return;
    }

    string subcommand = command[0].ToLowerInvariant();

    // Check subcommand-specific permission
    string subPerm = $"myplugin.player.{subcommand}";
    if (!R.Permissions.HasPermission(caller, subPerm))
    {
        UnturnedChat.Say(caller,
            $"You do not have permission for the '{subcommand}' subcommand.",
            Color.red);
        return;
    }

    // Dispatch to handler
}

The corresponding permissions in permissions.xml:

xml
<Permission Cooldown="0">myplugin.player.info</Permission>
<Permission Cooldown="0">myplugin.player.kick</Permission>
<Permission Cooldown="0">myplugin.player.warn</Permission>

When subcommands beat separate command classes

Use subcommands when the operations are conceptually related and share significant infrastructure (database connections, shared validation logic, common output formatting). Use separate command classes when the operations are distinct enough to warrant their own /help entries and individual permission management.

Argument validation patterns

Proper argument validation prevents runtime errors and gives clear feedback to the command user. The following patterns cover the most common validation scenarios.

Enum argument parsing

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    if (command.Length < 1)
    {
        UnturnedChat.Say(caller, "Usage: /settime <day|night|dawn|dusk>", Color.red);
        return;
    }

    if (!Enum.TryParse(typeof(EDayMode), command[0], true, out object mode))
    {
        UnturnedChat.Say(caller,
            $"Invalid time mode '{command[0]}'. Use: day, night, dawn, or dusk.",
            Color.red);
        return;
    }

    EDayMode dayMode = (EDayMode)mode;
    // Apply the time mode
}

Numeric range validation

csharp
private int ParseAmount(string input, IRocketPlayer caller)
{
    if (!int.TryParse(input, out int amount))
    {
        UnturnedChat.Say(caller, $"'{input}' is not a valid number.", Color.red);
        return -1;
    }

    if (amount < 1 || amount > 100)
    {
        UnturnedChat.Say(caller, "Amount must be between 1 and 100.", Color.red);
        return -1;
    }

    return amount;
}

Player existence validation

csharp
private UnturnedPlayer FindTarget(string name, IRocketPlayer caller)
{
    UnturnedPlayer target = U.Instance.Players.FindPlayer(name);
    if (target == null)
    {
        UnturnedChat.Say(caller,
            $"Player '{name}' is not online.", Color.red);
        return null;
    }
    return target;
}

Combined validation in a production command

csharp
public void Execute(IRocketPlayer caller, string[] command)
{
    // Validate argument count
    if (command.Length < 2)
    {
        UnturnedChat.Say(caller, $"Usage: {Syntax}", Color.red);
        return;
    }

    // Validate target player
    UnturnedPlayer target = FindTarget(command[0], caller);
    if (target == null) return;

    // Validate numeric argument
    int amount = ParseAmount(command[1], caller);
    if (amount < 0) return;

    // All validation passed — execute
    target.GiveItem(1001, amount);
    UnturnedChat.Say(caller,
        $"Gave {amount}x item 1001 to {target.DisplayName}.", Color.green);
}

Command result reporting

RocketMod's IRocketCommand.Execute returns void, which means there is no built-in mechanism for reporting success, failure, or error codes to the caller. The convention is to communicate results through chat messages.

Result color conventions

The 57 Studios™ team follows a standardized color scheme for command feedback:

OutcomeColorHexExample
SuccessGreen#00FF00"Healed Notch."
Error / failureRed#FF0000"Player not found."
Warning / cooldownYellow#FFFF00"Please wait 30 seconds."
InformationalWhite#FFFFFF"5 players online."
System messageMagenta#FF00FFConfiguration change notification

Standardized message helper

csharp
public static class CommandResult
{
    public static void Success(IRocketPlayer target, string message)
    {
        UnturnedChat.Say(target, message, Color.green);
    }

    public static void Error(IRocketPlayer target, string message)
    {
        UnturnedChat.Say(target, message, Color.red);
    }

    public static void Warning(IRocketPlayer target, string message)
    {
        UnturnedChat.Say(target, message, Color.yellow);
    }

    public static void Info(IRocketPlayer target, string message)
    {
        UnturnedChat.Say(target, message, Color.white);
    }

    public static void AdminAlert(string message)
    {
        foreach (var player in U.Instance.Players)
        {
            if (player.IsAdmin)
            {
                UnturnedChat.Say(player, message, Color.magenta);
            }
        }
    }
}

Logging results for audit

Administrative commands should log their results for the server's audit trail. The log entry should include who ran the command, what it did, and the target:

csharp
Rocket.Core.Logging.Logger.Log(
    $"[Audit] {caller.DisplayName} ({caller.Id}) healed " +
    $"{target.DisplayName} ({target.SteamId}) for {amount} HP.");

Frequently asked questions

How do I make a command that works from both chat and console?

Set AllowedCaller to AllowedCaller.Both. Inside Execute, check whether caller is UnturnedPlayer or ConsolePlayer and branch accordingly. Do not assume player-specific methods are available.

What is the AllowedCaller.Server value used for?

AllowedCaller.Server is used for commands that are invoked programmatically by the server process itself or by cross-plugin automation systems. It is not accessible from in-game chat or the console. It is useful for scheduled tasks, internal event handlers, and plugins that need to expose an internal API without exposing a player-facing command.

Do I need to register my command class anywhere?

No. RocketMod automatically discovers all IRocketCommand implementations in the plugin's assembly. Class accessibility must be public, and the class must be in the same assembly as the plugin class.

Can one command class handle multiple command names?

The primary name is defined by Name. Alternate names go in Aliases. All names trigger the same Execute method. If you need different behavior for different names, create separate command classes.

How do I parse integer arguments?

Use int.TryParse() and return an error message if parsing fails:

csharp
if (!int.TryParse(command[1], out int amount))
{
    UnturnedChat.Say(caller, "Amount must be a number.", Color.red);
    return;
}

Can I prevent a command from appearing in /help?

Set the Permissions array to non-empty and do not assign the permission to most players. Commands with required permissions still appear in /help but show as restricted. There is no built-in way to hide a command entirely from /help in RocketMod. For truly hidden commands, you can work around this by not implementing IRocketCommand and instead parsing chat messages manually.

Document history

VersionDateAuthorNotes
1.02026-07-2757 StudiosInitial publication. Full IRocketCommand reference.