Skip to content

Command System and Registration

Unturned's command system is built on the abstract Command class and the Commander static registry. Commander.init() registers approximately 60 built-in commands in a sorted list. Each command has a _command name string, an _info description, and a _help detailed usage string — all loaded from localization .dat files. Commands execute with a CSteamID executorID parameter that identifies the caller for permission checks. The system supports runtime registration/deregistration for mods and a Unity event execution path with permission callbacks.

This article covers the Command base class, the Commander sorted-list registration pattern, permission checks (server-only, admin-only, owner-only), the help system localization, the Unity event permission model, and cataloging of all built-in command categories.

Source code location: Unturned/Command/Command.cs, Unturned/Command/Commander.cs, Unturned/Command/Command*.cs

Command Base Class

csharp
public class Command : IComparable<Command>
{
    protected Local localization;
    protected string _command;
    public string command => _command;
    protected string _info;
    public string info => _info;
    protected string _help;
    public string help => _help;

    protected virtual void execute(CSteamID executorID, string parameter) { }

    public virtual bool check(CSteamID executorID, string method, string parameter)
    {
        if (method.ToLower() == command.ToLower())
        {
            execute(executorID, parameter);
            return true;
        }
        return false;
    }

    public int CompareTo(Command other)
    {
        return command.CompareTo(other.command);
    }
}

check() performs case-insensitive name matching. When matched, it calls the virtual execute() method. The CompareTo implementation alphabetizes by command name for the sorted list insertion.

Subclasses override execute() to implement their logic. The executorID parameter is:

  • CSteamID.Nil — executed from the server console.
  • Valid Steam ID — executed via in-game chat by a player.

This duality allows commands to check who called them and behave differently — for example, CommandHelp outputs to CommandWindow for console calls and to ChatManager.say() for in-game calls.

Constructor Pattern

Every command subclass follows the same constructor pattern:

csharp
public CommandGameMode(Local newLocalization)
{
    localization = newLocalization;
    _command = localization.format("GameModeCommandText");
    _info = localization.format("GameModeInfoText");
    _help = localization.format("GameModeHelpText");
}

Three localization keys are always used:

  • {Name}CommandText — the command name (invocation string).
  • {Name}InfoText — the short description shown in the help listing.
  • {Name}HelpText — the detailed usage help shown on help {command}.

Commander Registry

Commander is a static class managing the command list.

Registration

csharp
public static void register(Command command)
{
    int insert = commands.BinarySearch(command);
    if (insert < 0)
        insert = ~insert;
    commands.Insert(insert, command);
}

register() uses BinarySearch to find the insertion index, maintaining alphabetical order. This allows O(log n) lookup potential, though the current execute() iterates linearly — the sorted list is primarily for the help display to show commands alphabetically.

deregister() removes a command by reference:

csharp
public static void deregister(Command command)
{
    commands.Remove(command);
}

This is the extension point for mods that want to replace or remove built-in commands.

Execution

csharp
public static bool execute(CSteamID executorID, string command)
{
    try
    {
        string method = command;
        string parameter = "";
        int split = command.IndexOf(' ');
        if (split != -1)
        {
            method = command.Substring(0, split);
            parameter = command.Substring(split + 1, command.Length - split - 1);
        }
        for (int index = 0; index < commands.Count; index++)
        {
            if (commands[index].check(executorID, method, parameter))
            {
                return true;
            }
        }
    }
    catch (Exception e)
    {
        UnturnedLog.exception(e, "Caught exception while executing command string \"{0}\"", command);
    }
    return false;
}
  1. Splits the input string into method (first token) and parameter (everything after the first space).
  2. Iterates through all registered commands, calling check().
  3. Returns true on first match, or false if no command matched.
  4. All exceptions are caught and logged to prevent a crashing command from bringing down the server.

Unity Event Execution

csharp
public static void execute_UnityEvent(string command, ServerTextChatMessenger messenger)
{
    if (Dedicator.IsDedicatedServer && !Provider.configData.UnityEvents.Allow_Server_Commands)
    {
        UnturnedLog.info("Blocking UnityEvent command \"{0}\" because Allow_Server_Commands is off");
        return;
    }
    bool shouldAllow = true;
    onCheckUnityEventPermissions?.Invoke(messenger, command, ref shouldAllow);
    if (shouldAllow)
    {
        execute(CSteamID.Nil, command);
    }
}

Unity events (from asset dialogs, triggers, or NPC interactions) can execute commands. The permission model:

  1. Server config UnityEvents.Allow_Server_Commands must be enabled.
  2. The onCheckUnityEventPermissions event allows external permission handlers to veto.
  3. The messenger and command string are logged for auditability.

Initialization

Commander.init() creates the commands list and registers all built-in commands:

csharp
public static void init()
{
    commands = new List<Command>();
    Local emptyPlaceholder = new Local();

    register(new CommandModules(Localization.read("/Server/ServerCommandModules.dat")));
    register(new CommandReload(Localization.read("/Server/ServerCommandReload.dat")));
    register(new CommandHelp(Localization.read("/Server/ServerCommandHelp.dat")));
    register(new CommandName(Localization.read("/Server/ServerCommandName.dat")));
    register(new CommandPort(Localization.read("/Server/ServerCommandPort.dat")));
    register(new CommandPassword(Localization.read("/Server/ServerCommandPassword.dat")));
    register(new CommandMaxPlayers(Localization.read("/Server/ServerCommandMaxPlayers.dat")));
    // ... ~60 registrations total
}

Commands are fully registered in init() before the server starts accepting connections. The emptyPlaceholder is used for debug-only or localization-free commands.

Server Configuration Commands

CommandDescriptionFile
CommandModulesList/load server modulesServerCommandModules.dat
CommandReloadReload server configurationServerCommandReload.dat
CommandHelpDisplay command helpServerCommandHelp.dat
CommandNameSet server nameServerCommandName.dat
CommandPortSet server portServerCommandPort.dat
CommandPasswordSet server passwordServerCommandPassword.dat
CommandMaxPlayersSet max player countServerCommandMaxPlayers.dat
CommandQueueSet queue sizeServerCommandQueue.dat
CommandMapSet/change current mapServerCommandMap.dat
CommandPvEToggle PvE modeServerCommandPvE.dat
CommandWhitelistedToggle whitelistServerCommandWhitelisted.dat
CommandCheatsToggle cheat modeServerCommandCheats.dat
CommandHideAdminsHide admin status from playersServerCommandHideAdmins.dat
CommandEffectUIToggle effect UIServerCommandEffectUI.dat
CommandSyncToggle server sync settingsServerCommandSync.dat
CommandFilterManage chat filterServerCommandFilter.dat
CommandVotifyToggle votingServerCommandVotify.dat
CommandModeSet game mode categoryServerCommandMode.dat
CommandGameModeSet game mode (deprecated)ServerCommandGameMode.dat
CommandGoldToggle gold modeServerCommandGold.dat
CommandCameraSet camera mode (first/third)ServerCommandCamera.dat
CommandCycleSet day/night cycle speedServerCommandCycle.dat
CommandTimeSet current time of dayServerCommandTime.dat
CommandDaySet to daytimeServerCommandDay.dat
CommandNightSet to nighttimeServerCommandNight.dat
CommandWeatherOverride weatherServerCommandWeather.dat
CommandAirdropTrigger an airdropServerCommandAirdrop.dat

Player Management Commands

CommandDescriptionFile
CommandKickKick a playerServerCommandKick.dat
CommandBanBan a playerServerCommandBan.dat
CommandUnbanUnban a playerServerCommandUnban.dat
CommandBansList active bansServerCommandBans.dat
CommandAdminGrant admin to a playerServerCommandAdmin.dat
CommandUnadminRevoke admin from a playerServerCommandUnadmin.dat
CommandAdminsList adminsServerCommandAdmins.dat
CommandOwnerSet server ownerServerCommandOwner.dat
CommandPermitWhitelist a playerServerCommandPermit.dat
CommandUnpermitRemove from whitelistServerCommandUnpermit.dat
CommandPermitsList whitelisted playersServerCommandPermits.dat
CommandSpyToggle admin spy modeServerCommandSpy.dat

Player Interaction Commands

CommandDescriptionFile
CommandPlayersList connected playersServerCommandPlayers.dat
CommandSayBroadcast message to allServerCommandSay.dat
CommandWelcomeSet welcome messageServerCommandWelcome.dat
CommandSlayKill and ban a playerServerCommandSlay.dat
CommandKillKill a playerServerCommandKill.dat
CommandGiveGive item to playerServerCommandGive.dat
CommandExperienceSet/give XPServerCommandExperience.dat
CommandReputationSet reputationServerCommandReputation.dat
CommandFlagSet quest flag valueServerCommandFlag.dat
CommandQuestSet quest statusServerCommandQuest.dat
CommandVehicleSpawn a vehicleServerCommandVehicle.dat
CommandAnimalSpawn an animalServerCommandAnimal.dat
CommandTeleportTeleport playerServerCommandTeleport.dat
CommandLoadoutSet default spawn loadoutServerCommandLoadout.dat

Debug and Utility Commands

CommandDescriptionFile
CommandDebugToggle debug modeServerCommandDebug.dat
CommandBindBind server IPServerCommandBind.dat
CommandLogToggle server loggingServerCommandLog.dat
CommandTimeoutSet connection timeoutServerCommandTimeout.dat
CommandChatrateSet chat rate limitServerCommandChatrate.dat
CommandSaveSave world stateServerCommandSave.dat
CommandShutdownShutdown serverServerCommandShutdown.dat
CommandGSLTSet Game Server Login TokenServerCommandGSLT.dat
CommandLogMemoryUsageLog memory statsNone (emptyPlaceholder)
CommandLogTransportConnectionsLog transport connectionsNone
CommandCopyServerCodeCopy server codeNone
CommandCopyFakeIPCopy fake IPNone
CommandDestroyDrivenVehicleDestroy driven vehicleNone
CommandExitAndDestroyDrivenVehicleExit and destroy vehicleNone
CommandEnterAndDestroyNearestVehicleEnter and destroy nearest vehicleNone
CommandRewardListList NPC rewardsNone
CommandDialogueTrigger NPC dialogueNone
CommandScheduledShutdownInfoShow scheduled shutdown timeNone
CommandSetNpcSpawnIdSet NPC spawn IDNone
CommandToggleNpcCutsceneModeToggle cutscene modeNone
CommandNpcEventTrigger NPC eventNone

Development-Only Commands

Registered only in UNITY_EDITOR || DEVELOPMENT_BUILD:

  • CommandLogAssetOrigins — log all loaded asset origins.
  • CommandSpawnAllBarricades — spawn barricades from all assets.
  • CommandSpawnAllVehicles — spawn vehicles from all assets.
  • CommandSteamClearAchievement — clear a Steam achievement.

Permission System

The command system has no explicit permission attribute on Command — each subclass implements its own permission check inside execute().

Server-only Guard

csharp
if (!Dedicator.IsDedicatedServer) return;

Commands that only make sense on dedicated servers use this guard. Examples: CommandPort, CommandPassword, CommandGameMode, CommandMode.

Server Running Guard

csharp
if (!Provider.isServer)
{
    CommandWindow.LogError(localization.format("NotRunningErrorText"));
    return;
}

Commands that require the server to be actively running. Examples: CommandKick, CommandBan, CommandSlay, CommandAdmin, CommandGive, CommandTeleport.

Admin Permission

Commands like CommandAdmin, CommandBan, CommandKick, CommandSlay, CommandGive, CommandTeleport implicitly require admin. When executed from the server console (CSteamID.Nil), they are always allowed. When executed in-game, the Steam ID resolves through PlayerTool.tryGetSteamPlayer() which checks admin status.

CommandAdmin grants admin status:

csharp
protected override void execute(CSteamID executorID, string parameter)
{
    if (!Dedicator.IsDedicatedServer) return;
    if (!Provider.isServer) { /* NotRunningError */ return; }

    CSteamID steamID;
    if (!PlayerTool.tryGetSteamID(parameter, out steamID))
    {
        CommandWindow.LogError(localization.format("NoPlayerErrorText", parameter));
        return;
    }
    SteamAdminlist.admin(steamID, executorID);
    CommandWindow.Log(localization.format("AdminText", steamID));
}

Owner Permission

CommandOwner sets the server owner. It doesn't enforce a permission gate in the command itself — the server owner is configured in the server config file and checked by other systems (e.g., SteamAdminlist).

Help System

CommandHelp implements the help command:

csharp
protected override void execute(CSteamID executorID, string parameter)
{
    if (string.IsNullOrEmpty(parameter))
    {
        // List all commands (console only)
        if (!Dedicator.IsDedicatedServer) return;
        CommandWindow.Log(localization.format("HelpText"));
        string commands = "";
        for (int index = 0; index < Commander.commands.Count; index++)
        {
            if (string.IsNullOrEmpty(Commander.commands[index].info)) continue;
            commands += Commander.commands[index].info;
            if (index < Commander.commands.Count - 1) commands += "\n";
        }
        CommandWindow.Log(commands);
    }
    else
    {
        // Show help for specific command
        for (int index = 0; index < Commander.commands.Count; index++)
        {
            if (parameter.ToLower() == Commander.commands[index].command.ToLower())
            {
                if (executorID == CSteamID.Nil)
                {
                    CommandWindow.Log(Commander.commands[index].info);
                    CommandWindow.Log(Commander.commands[index].help);
                }
                else
                {
                    ChatManager.say(executorID, Commander.commands[index].info, ...);
                    ChatManager.say(executorID, Commander.commands[index].help, ...);
                }
                return;
            }
        }
        // Command not found
        if (executorID == CSteamID.Nil)
            CommandWindow.Log(localization.format("NoCommandErrorText", parameter));
        else
            ChatManager.say(executorID, localization.format("NoCommandErrorText", parameter), ...);
    }
}

Without parameters: lists all registered commands' _info strings (console only). With a command name: searches for the command and outputs both _info and _help via CommandWindow (console) or ChatManager.say() (in-game).

Localization

Each command loads its strings from a .dat file via Localization.read(). The file path follows the pattern /Server/ServerCommand{Name}.dat. Each .dat file contains key-value pairs:

CommandText "help"
InfoText "Displays a list of commands"
HelpText "help [commandname]"

Commands that don't need localization pass an empty Local placeholder via new Local().

Command Examples

CommandAdmin — Granting Admin

csharp
public class CommandAdmin : Command
{
    protected override void execute(CSteamID executorID, string parameter)
    {
        if (!Dedicator.IsDedicatedServer) return;
        if (!Provider.isServer) { CommandWindow.LogError("Not running"); return; }

        CSteamID steamID;
        if (!PlayerTool.tryGetSteamID(parameter, out steamID))
        {
            CommandWindow.LogError("No player: " + parameter);
            return;
        }
        SteamAdminlist.admin(steamID, executorID);
        CommandWindow.Log("Admin: " + steamID);
    }
}

CommandSlay — Kill and Ban

csharp
public class CommandSlay : Command
{
    protected override void execute(CSteamID executorID, string parameter)
    {
        if (!Dedicator.IsDedicatedServer) return;
        if (!Provider.isServer) { CommandWindow.LogError("Not running"); return; }

        string[] components = Parser.getComponentsFromSerial(parameter, '/');
        SteamPlayer player;
        if (!PlayerTool.tryGetSteamPlayer(components[0], out player))
        {
            CommandWindow.LogError("No player: " + components[0]);
            return;
        }
        uint ip = player.getIPv4AddressOrZero();
        // Ban the player
        Provider.requestBanPlayer(executorID, player.playerID.steamID, ip,
            player.playerID.GetHwids(), components.Length == 2 ? components[1] : "Slay",
            SteamBlacklist.PERMANENT);
        // Kill the player
        player.player.life.askDamage(101, Vector3.up * 101, EDeathCause.KILL, ELimb.SKULL, executorID, out kill);
        CommandWindow.Log("Slayed: " + player.playerID.playerName);
    }
}

CommandGameMode — Deprecated

csharp
public class CommandGameMode : Command
{
    protected override void execute(CSteamID executorID, string parameter)
    {
        if (!Dedicator.IsDedicatedServer) return;
        if (Provider.isServer) { CommandWindow.LogError("RunningErrorText"); return; }
        CommandWindow.Log("GameModeText: " + parameter);
    }
}

This command is essentially deprecated — the // Provider.selectedGameModeName = parameter; line is commented out.

Error Handling

Commands communicate results through CommandWindow:

  • CommandWindow.Log(string) — informational (green in default console).
  • CommandWindow.LogError(string) — error (red).
  • CommandWindow.LogWarning(string) — warning (yellow).

The Commander execute loop wraps execution in try/catch:

csharp
try { /* execution */ }
catch (Exception e)
{
    UnturnedLog.exception(e, "Exception while executing command \"{0}\"", command);
}

This prevents a crashing command from bringing down the server.

Mod Integration

Mods can create custom commands:

csharp
public class MyCommand : Command
{
    protected override void execute(CSteamID executorID, string parameter)
    {
        CommandWindow.Log("My command executed with: " + parameter);
    }

    public MyCommand() : base() { }
}

// Registration
Commander.register(new MyCommand());

// Deregistration (on mod unload)
Commander.deregister(myCommandInstance);

The deregister() method removes by reference equality, so mods must keep a reference to the registered instance. Mods can also replace built-in commands by deregistering the original and registering their own.

Command Flow Walkthrough

Console Execution

When a server admin types a command in the console:

  1. CommandWindow.input.onInputText fires with the raw input string.
  2. Commander.execute(CSteamID.Nil, inputString) is called.
  3. The input is split into method and parameter at the first space.
  4. Each registered command's check() is called in registration order.
  5. The first matching command executes and returns true.
  6. The command's execute() runs, calling CommandWindow.Log() for output.
  7. If no command matches, CommandWindow.Log() shows "unknown command" (handled by the caller, not by Commander).

Chat Execution

When a player types /command in chat:

  1. ChatManager intercepts messages starting with /.
  2. Commander.execute(steamID, message.Substring(1)) is called.
  3. Same flow as console, but executorID is the player's CSteamID.
  4. The command can distinguish console vs. chat via executorID == CSteamID.Nil.
  5. Output goes to ChatManager.say() for in-game display instead of CommandWindow.Log().

Permission Resolution

Player types "/slay Bob"
  → ChatManager detects '/'
  → Commander.execute(steamID, "slay Bob")
  → CommandSlay.check("slay", "Bob")
    → CommandSlay.execute(steamID, "Bob")
      → Dedicator.IsDedicatedServer? Yes
      → Provider.isServer? Yes
      → PlayerTool.tryGetSteamPlayer("Bob")? Found
      → Provider.requestBanPlayer(...)    // Ban Bob
      → player.life.askDamage(101, ...)   // Kill Bob

Mod Command Registration Walkthrough

Mod initializes:
  var myCmd = new MyCustomCommand(localization)
  Commander.register(myCmd)
    → BinarySearch finds alphabetical insert index
    → Inserted into sorted list

Later, mod unloads:
  Commander.deregister(myCmd)
    → commands.Remove(myCmd) removes by reference

Command Localization File Format

Each command loads its strings from a .dat file in the /{Server} directory. The format is key-value pairs:

CommandText "help"
InfoText "Displays a list of commands"
HelpText "!help [commandname]"
NoCommandErrorText "Could not find command: {0}"

The {0} placeholder in error messages is replaced with the user's input when the command is not found. This localization system allows server owners to translate or customize command messages per-language.

When localization is not needed (debug commands or commands with only hardcoded strings), the constructor passes new Local() (empty localization).

Command Naming Conventions

Built-in commands follow consistent naming:

  • Lowercase: All commands are lowercase (help, ban, give).
  • No prefixes: Commands don't include / or ! — those are handled by the chat system.
  • Single word: Command names are single words with no hyphens or underscores.
  • Unique: No two commands share the same name. The linear search returns the first match.

Extending the Command System

Plugin frameworks like RocketMod and OpenMod can extend the command system by:

  1. Registering their own Command subclasses via Commander.register().
  2. Hooking Commander.execute() via prefix delegates (not directly supported — requires wrapping Commander.execute() or replacing the text input handler).
  3. Using CommandWindow.input.onInputText to intercept console input before Commander processes it.

The command system's sorted-list registration makes it predictable (alphabetical order in help), but the linear search for execution means performance is O(n) for n registered commands. For the ~60 built-in commands, this is negligible. For plugins adding hundreds of commands, this could become noticeable in the chat input path.