Command Mapping and Priority
When multiple plugins register the same command name, or when a server administrator needs to change which command name a plugin responds to, RocketMod's command mapping and priority system resolves the conflict. Command mapping allows administrators to rename commands through configuration without modifying plugin code. Command priority determines which plugin's implementation wins when two plugins register the same command name.
This article covers both systems in detail: how to configure command mappings in Rocket.config.xml, how priority values determine command resolution, and how plugin authors can design their commands to coexist with other plugins on the same server. The 57 Studios™ team has deployed multiple plugins that share command names on purpose (e.g., a core plugin providing a base command and optional addons extending it), and the patterns here reflect real coexistence experience.

Prerequisites
- A working RocketMod plugin with at least one command. See Creating Your First RocketMod Plugin.
- Understanding of
IRocketCommandproperties. See Defining Commands with IRocketCommand. - Understanding of attribute-based commands. See Attribute-Based Commands.
- Access to the server's
Rocket.config.xmlfile.
The command resolution problem
RocketMod allows every plugin to register commands independently. There is no centralized command registry that prevents duplicates. When two plugins register a command with the same name, RocketMod needs to decide which one to execute when a player types that command.
Consider this scenario:
- Plugin A (EconomyCore) registers
/balance. - Plugin B (EconomyPlus) also registers
/balance. - A player types
/balance.
Which plugin's balance command runs? The answer depends on command priority and load order.
CommandPriority enum
Each command class can declare a priority through the CommandPriority enum. The priority tells RocketMod which command implementation should take precedence when multiple commands share the same name.
csharp
public enum CommandPriority
{
Lowest = 0,
Low = 1,
Normal = 2,
High = 3,
Highest = 4
}Priority values
| Value | Enum member | When to use |
|---|---|---|
0 | Lowest | Fallback commands that should only run if no other plugin handles this command |
1 | Low | Generic implementations that can be overridden by more specific plugins |
2 | Normal | Default priority. Most commands use this |
3 | High | Plugin-specific implementations that should override generic ones |
4 | Highest | Override commands that must take precedence over all others |
Declaring priority in IRocketCommand
IRocketCommand does not have a Priority property by default. To declare a priority, implement the IRocketCommandPriority interface alongside IRocketCommand:
csharp
using Rocket.API;
using Rocket.API.Commands;
public class BalanceCommand : IRocketCommand, IRocketCommandPriority
{
// IRocketCommand properties
public string Name => "balance";
public string Help => "Check your balance.";
public string Syntax => "/balance";
public List<string> Aliases => new List<string> { "bal", "money" };
public string[] Permissions => new string[] { };
public AllowedCaller AllowedCaller => AllowedCaller.Both;
public void Execute(IRocketPlayer caller, string[] command)
{
// Command logic
}
// IRocketCommandPriority implementation
public CommandPriority Priority => CommandPriority.Normal;
}Default priority
If a command class does not implement IRocketCommandPriority, RocketMod assigns it CommandPriority.Normal by default.
Declaring priority in attribute commands
When using [RocketCommand] attribute, implement IRocketCommandPriority the same way. The attribute does not have a priority parameter.
csharp
[RocketCommand("balance", "Check your balance.")]
public class BalanceCommand : IRocketCommand, IRocketCommandPriority
{
public CommandPriority Priority => CommandPriority.High;
public void Execute(IRocketPlayer caller, string[] command)
{
// Command logic
}
}How priority resolves conflicts
When a player types a command name that is registered by multiple plugins, RocketMod uses this resolution order:
- Compare the
CommandPriorityof all registered implementations. - The implementation with the highest priority wins.
- If two implementations have the same priority, the plugin that was loaded FIRST wins.
Tie-breaking by load order
When priorities are equal, load order is the tiebreaker. Since RocketMod loads plugins in file-system order (alphabetical by DLL filename), the plugin whose DLL filename comes first alphabetically wins the tie.
This means 01_EconomyCore.dll wins over 02_EconomyPlus.dll if both register /balance at Normal priority, because 01_EconomyCore is loaded first. Server administrators can control this by renaming DLL files, but relying on alphabetical ordering is fragile.
Example resolution scenarios
| Plugin A | Plugin B | Winner |
|---|---|---|
Priority Normal (2) | Priority Normal (2) | First-loaded plugin |
Priority Low (1) | Priority High (3) | Plugin B |
Priority Highest (4) | Priority Normal (2) | Plugin A |
Priority Lowest (0) | Priority Low (1) | Plugin B |
Priority Normal (2), loaded first | Priority Normal (2), loaded second | Plugin A |
CommandMapping in config.xml
CommandMapping is a RocketMod configuration feature in Rocket.config.xml that allows server administrators to remap command names at the framework level. It does not require any plugin code changes.
Location in config.xml
The CommandMapping section lives inside the <RocketConfiguration> element:
xml
<?xml version="1.0" encoding="utf-8"?>
<RocketConfiguration>
<CommandMappings>
<CommandMapping>
<OldCommand>balance</OldCommand>
<NewCommand>money</NewCommand>
</CommandMapping>
<CommandMapping>
<OldCommand>heal</OldCommand>
<NewCommand>healplayer</NewCommand>
</CommandMapping>
<CommandMapping>
<OldCommand>tpto</OldCommand>
<NewCommand>teleport</NewCommand>
</CommandMapping>
</CommandMappings>
</RocketConfiguration>How mapping works
Each <CommandMapping> maps an OldCommand to a NewCommand. When a player types OldCommand, RocketMod transparently redirects the execution to NewCommand. The mapping applies at the command manager level, before any plugin code runs.
- Only the
OldCommandname is what players type. - The
NewCommandname must match a registered command'sNameproperty exactly (case-insensitive). - If no command is registered with the
NewCommandname, the mapping does nothing. - Mappings apply globally to all plugins — any command name on the server can be remapped.
Use cases for command mapping
Resolving conflicts between plugins:
If Plugin A registers /heal and Plugin B also registers /heal, and both need to coexist, the administrator can remap one of them:
xml
<CommandMapping>
<OldCommand>heal</OldCommand>
<NewCommand>heal_b</NewCommand>
</CommandMapping>Now Plugin A's /heal still works under that name, and Plugin B's original /heal is accessible via /heal_b. If Plugin B's command has aliases, those aliases follow the remapped name.
Creating server-wide aliases:
If the server community is used to a specific command name from a previous plugin, the administrator can create a mapping so the old name works with the new plugin:
xml
<CommandMapping>
<OldCommand>money</OldCommand>
<NewCommand>balance</NewCommand>
</CommandMapping>Players who type /money get the current /balance command from whatever plugin provides it.
Hiding commands:
By mapping a command to an unused name, the original command effectively disappears from normal usage. This is useful when retiring a command without removing the plugin:
xml
<CommandMapping>
<OldCommand>oldcommand</OldCommand>
<NewCommand>__deprecated_oldcommand</NewCommand>
</CommandMapping>Players who type /oldcommand see nothing, and the command only works if they know the hidden name.
Mapping and aliases
Command mappings interact with aliases in a specific way:
- If the
OldCommandmatches a command's primaryName, the mapping redirects the primary name to theNewCommand. - If the
OldCommandmatches a command's alias, the mapping redirects that specific alias only. The primary name and other aliases are unaffected. - After mapping, the
NewCommandis the effective name. Players must use the mapped name, not the original.
Mapping limitations
- No chaining: you cannot map one command to another, and then that command to a third. The mapping is a single-level redirect.
- No parameter transformation: mapping only changes the command name. Arguments pass through unchanged.
- No per-player mapping: mappings apply to all players and the console. There is no way to apply different mappings for different users.
- No runtime changes: mappings are read at startup from
Rocket.config.xml. Changing mappings on a running server requires a restart or reload.
Command priority in attribute commands
Attribute-based commands can declare priority the same way as interface-based commands — by implementing IRocketCommandPriority:
csharp
[RocketCommand("heal", "Heals a player.",
Syntax = "/heal <player>",
AllowedCaller = AllowedCaller.Player)]
[RocketCommandAlias("hp")]
[RocketCommandPermission("myplugin.heal")]
public class HealCommand : IRocketCommand, IRocketCommandPriority
{
public CommandPriority Priority => CommandPriority.High;
public void Execute(IRocketPlayer caller, string[] command)
{
// Command logic
}
}The priority value is read at plugin load time, along with the rest of the command metadata.
Design patterns for command coexistence
The base-override pattern
A common pattern in the 57 Studios™ plugin suite is a base plugin that registers a command at Low priority and an optional addon plugin that registers the same command at High priority. If the addon is present, it overrides the base command. If the addon is absent, the base command works on its own.
Base plugin:
csharp
public class BaseCommand : IRocketCommand, IRocketCommandPriority
{
public string Name => "welcome";
// ... other properties
public CommandPriority Priority => CommandPriority.Low;
public void Execute(IRocketPlayer caller, string[] command)
{
UnturnedChat.Say(caller, "Welcome to the server!", Color.green);
}
}Addon plugin:
csharp
public class EnhancedWelcomeCommand : IRocketCommand, IRocketCommandPriority
{
public string Name => "welcome";
// ... other properties
public CommandPriority Priority => CommandPriority.High;
public void Execute(IRocketPlayer caller, string[] command)
{
UnturnedChat.Say(caller,
"Welcome to the server! Check out our Discord at discord.gg/...",
Color.green);
}
}When both plugins are installed, the addon's High priority wins and players see the enhanced message. When only the base plugin is installed, the base command runs.
The priority escalation pattern
If you have a command that must never be overridden by another plugin (e.g., a core moderation command), declare it at Highest priority:
csharp
public class CoreKickCommand : IRocketCommand, IRocketCommandPriority
{
public string Name => "kick";
// ...
public CommandPriority Priority => CommandPriority.Highest;
}Use Highest sparingly. If every plugin uses Highest, the priority system becomes meaningless and the first-loaded plugin wins for all commands.
The fallback pattern
A plugin can register many commands at Lowest priority to serve as defaults. If another plugin registers a more specialized version of the same command at a higher priority, the specialized version is used. If no specialized version exists, the fallback activates.
This is useful for framework plugins that provide default implementations of common commands:
csharp
public class DefaultHealCommand : IRocketCommand, IRocketCommandPriority
{
public string Name => "heal";
// ...
public CommandPriority Priority => CommandPriority.Lowest;
public void Execute(IRocketPlayer caller, string[] command)
{
// Basic heal logic — works but has no features
UnturnedPlayer target = U.Instance.Players.FindPlayer(command[0]);
if (target != null)
target.Heal(100);
}
}A specialized plugin can then register a higher-priority version with additional features (logging, cooldown management, permission checks for specific item amounts).
Debugging command conflicts
Listing all registered commands
Use the in-game command /rocket commands or call the API:
csharp
var allCommands = R.Commands.GetCommands();
foreach (var cmd in allCommands)
{
Rocket.Core.Logging.Logger.Log(
$"Command: /{cmd.Name} (Plugin: {cmd.Plugin.Name}, Priority: {cmd.Priority})");
}This shows every registered command, its owning plugin, and its priority. If the command you expect is not listed, it was not registered (or another plugin's command with the same name and higher priority is overriding it in the listing).
Testing command resolution
To verify which plugin's command runs when a name is typed, create a diagnostic plugin:
csharp
public class CommandDebugCommand : IRocketCommand
{
public string Name => "cmdtest";
public string Help => "Debug command resolution.";
public string Syntax => "/cmdtest <commandname>";
public List<string> Aliases => new List<string>();
public string[] Permissions => new string[] { };
public AllowedCaller AllowedCaller => AllowedCaller.Both;
public void Execute(IRocketPlayer caller, string[] command)
{
if (command.Length < 1)
{
UnturnedChat.Say(caller, "Specify a command name to test.", Color.red);
return;
}
string cmdName = command[0];
var allCommands = R.Commands.GetCommands();
bool found = false;
foreach (var cmd in allCommands)
{
if (cmd.Name.Equals(cmdName, StringComparison.OrdinalIgnoreCase))
{
UnturnedChat.Say(caller,
$"Command /{cmdName}: Plugin={cmd.Plugin.Name}, Priority={cmd.Priority}",
Color.yellow);
found = true;
}
}
if (!found)
{
UnturnedChat.Say(caller,
$"No plugin registers /{cmdName}.", Color.red);
}
}
}Checking the Rocket.log
When a command name collision occurs at registration time (not at execution time), RocketMod logs a warning:
[Managed] Command '/heal' already registered by 'PluginA'. PluginB's registration will be used when priority is higher.If PluginB has a higher priority, the warning still appears, but PluginB's implementation wins at execution time.
Common problems and solutions
Problem: Command does nothing when typed
The name is registered but the handler is from a different plugin than expected. Check:
- Is the command name registered by multiple plugins?
- Does the expected plugin have a lower priority than another plugin?
- Is there a
CommandMappingredirecting the name?
Use the /cmdtest diagnostic command above to see which plugin is handling the command.
Problem: Command works after server restart but not after reload
RocketMod's command registry is rebuilt on every plugin load and unload. If Plugin A reloads, its command registrations are removed and re-added. During the brief window between unload and load, the command may not exist. If Plugin B is also registered with the same name and has the same priority, Plugin A's reload could temporarily promote Plugin B's command to the active slot.
To prevent this, give your plugin's commands a distinct priority level from other plugins' commands, or use CommandMapping to assign unique names.
Problem: Command mapping does not take effect
Verify:
- The
CommandMappingsection is inside<RocketConfiguration>, not outside it. - The
OldCommandis the name players type. - The
NewCommandexactly matches a registered command'sName(case-insensitive). - The XML is well-formed — missing closing tags or stray characters silently break the mapping.
- The server has been restarted since the mapping was added (mappings are not hot-reloaded).
Problem: Another plugin's command takes priority over mine
Check whether the other plugin implements IRocketCommandPriority with a higher value. If both plugins are at Normal priority, the first-loaded plugin wins. To guarantee your command wins without changing the other plugin's code:
- Increase your command's priority to
HighorHighest. - Or use
CommandMappingto give your command a different name. - Or ask the server administrator to rename the other plugin's DLL file to load after yours (not recommended as a long-term solution).
Complete configuration example
The following shows a complete Rocket.config.xml with command mappings and related settings:
xml
<?xml version="1.0" encoding="utf-8"?>
<RocketConfiguration>
<CommandMappings>
<!-- Resolve conflict: two plugins register /heal -->
<CommandMapping>
<OldCommand>heal</OldCommand>
<NewCommand>heal_economy</NewCommand>
</CommandMapping>
<!-- Server-wide alias: community used /money from old plugin -->
<CommandMapping>
<OldCommand>money</OldCommand>
<NewCommand>balance</NewCommand>
</CommandMapping>
<!-- Rename for clarity -->
<CommandMapping>
<OldCommand>tp</OldCommand>
<NewCommand>teleport</NewCommand>
</CommandMapping>
</CommandMappings>
<RCON_Enabled>false</RCON_Enabled>
<RCON_Port>27115</RCON_Port>
<LanguageCode>en</LanguageCode>
</RocketConfiguration>Priority and command mapping interaction
When a player types a command, the resolution flow is:
- RocketMod's command manager receives the input.
- It checks
CommandMappingsto see if the typed name maps to a different command. - If mapped, it looks up the mapped name instead.
- It finds all implementations of the (mapped) command name.
- It selects the implementation with the highest
CommandPriority. - If there is a tie, it selects the implementation from the plugin that was loaded first.
- It executes the selected implementation's
Executemethod.
This means command mapping happens BEFORE priority resolution. A mapped OldCommand is transparently redirected to NewCommand before any plugin code runs. The priority system then resolves among the implementations of NewCommand.
Best practices for plugin authors
Always declare an explicit priority
Even if your command uses Normal priority, implement IRocketCommandPriority and return CommandPriority.Normal explicitly. This signals to other plugin authors and server administrators that you have considered priority and chosen a deliberate value.
Use High priority sparingly
Commands at High or Highest are override commands by design. If your command is not specifically intended to override another plugin's command, use Normal or Low. The override intent should be documented in the plugin's README.
Document command names in the plugin manifest
List every command name your plugin registers, along with its priority and whether it is intended to coexist with or override other plugins' commands. This helps server administrators diagnose conflicts before they become production problems.
Support CommandMapping in design
Do not hardcode assumptions about your command's registered name. If your plugin internally references its own command (e.g., executing it programmatically), use the command object reference rather than a hardcoded string:
csharp
// WRONG — hardcoded name breaks if mapped
R.Commands.Execute(caller, "heal", new string[] { "Notch" });
// CORRECT — use the registered command reference
var healCommand = R.Commands.GetCommands()
.FirstOrDefault(c => c.Name == "heal" && c.Plugin == this);
if (healCommand != null)
{
// Execute via the command manager's internal methods
healCommand.Execute(caller, new string[] { "Notch" });
}Priority anti-patterns
Some priority patterns cause maintenance problems in production. Avoid these.
The arms race pattern
When Plugin A sets Highest and Plugin B also sets Highest to override Plugin A, the result is that load order becomes the sole decider — the priority system is effectively disabled. Both plugins intended to guarantee their command wins, but neither does. The server restart ordering becomes unpredictable.
Solution: Use High instead of Highest, and communicate with the other plugin author about intended priority. If both plugins genuinely need to coexist with the same command name, use CommandMapping to give each a distinct name.
The lowest-for-everything pattern
Setting every command to Lowest because "it's safer" makes the plugin's commands always lose in conflicts. This is appropriate for fallback commands but not for commands that provide core functionality. If your plugin is the primary provider of a feature, use Normal priority.
The silent override pattern
A plugin that overrides another plugin's command at Highest priority without documenting this behavior creates confusion when the original command stops working. Server administrators see the command registered, but it does what the override plugin wants, not what the original plugin's documentation says.
Solution: Document every overridden command in the plugin README, including the original command name, the overriding plugin, and the reason for the override.
Command versioning strategy
When your plugin evolves and commands change names or behavior, a versioning strategy prevents breaking existing server configurations.
Deprecating a command name
- In version N, add the new command name and register the old name as an alias.
- In version N+1, log a warning when the alias is used.
- In version N+2, remove the alias.
csharp
// Version N — both names work
[RocketCommand("heal", "Heals a player.")]
[RocketCommandAlias("healplayer")]
public class HealCommand : IRocketCommand { }
// Version N+1 — warn on old name (requires tracking how it was called)
// Not natively supported by RocketMod — use a wrapper pattern
// Version N+2 — only the new name
[RocketCommand("heal", "Heals a player.")]
public class HealCommand : IRocketCommand
{
// The old /healplayer alias is removed
}Tracking which name was used
RocketMod does not tell Execute which name the player typed. To track alias usage, you can use a wrapper approach:
csharp
public class HealCommand : IRocketCommand
{
public string Name => "heal";
public string Help => "Heals a player.";
public List<string> Aliases => new List<string> { "healplayer" };
// ...
public void Execute(IRocketPlayer caller, string[] command)
{
// Log the alias usage (if available via stack trace or custom tracking)
Rocket.Core.Logging.Logger.Log(
$"[HealCommand] Executed by {caller.DisplayName}");
}
}Mapping old names forward
If you cannot change the plugin code but need to rename a command, use CommandMapping in config.xml:
xml
<CommandMapping>
<OldCommand>oldheal</OldCommand>
<NewCommand>heal</NewCommand>
</CommandMapping>This redirect persists across plugin updates as long as the new command name does not change. It is the safest approach for server administrators who want to keep a stable player-facing command name while the underlying plugin evolves.
Cross-server command compatibility
On server networks where players move between servers, command names should be consistent to avoid player confusion.
Standardizing command names across a network
Define a shared CommandMapping configuration that is deployed to every server in the network. This ensures that even if different servers run different plugin sets, the player-facing command names are identical.
Example shared mappings for a network running EconomyCore on one server and EconomyLite on another:
xml
<!-- Deployed to ALL servers -->
<CommandMappings>
<CommandMapping>
<OldCommand>bal</OldCommand>
<NewCommand>balance</NewCommand>
</CommandMapping>
<CommandMapping>
<OldCommand>shop</OldCommand>
<NewCommand>store</NewCommand>
</CommandMapping>
</CommandMappings>| Server | Plugin | Registered command | Mapped name |
|---|---|---|---|
| Server A | EconomyCore | /balance | /bal → /balance |
| Server B | EconomyLite | /money | /bal → /money |
Players on both servers use /bal and get the correct server-specific command.
Mapping compatibility between RocketMod and OpenMod
If your network runs a mix of RocketMod and OpenMod servers, CommandMapping is RocketMod-only. OpenMod has its own command routing system. Maintain a separate mapping configuration for each framework and keep them in sync manually.
Frequently asked questions
Can I change a command's priority at runtime?
No. CommandPriority is read from the class at plugin load time and cached for the lifetime of the plugin. Changing the return value of the Priority property has no effect after the plugin has loaded.
How do I see which priority a command is running at?
Use the /rocket commands console command or call R.Commands.GetCommands() from a diagnostic plugin. The output includes each command's priority value and the plugin it belongs to.
Can two commands with the same name but different aliases coexist?
Yes, if the commands are registered by different plugins with different priorities. The higher-priority command's aliases are active; the lower-priority command's aliases only resolve if they do not conflict with the higher-priority command's primary name or aliases.
Does CommandMapping affect command aliases?
Mapping the OldCommand to a NewCommand redirects only the OldCommand string. If a command has aliases, those aliases are unaffected by the mapping unless they are individually mapped. If the mapped NewCommand has aliases of its own, those aliases are available normally.
Is priority the same as load order?
No. Priority is a declared value on the command class. Load order is the order in which RocketMod discovers and loads plugin DLLs. Priority is checked FIRST. Load order is only the tiebreaker when two commands have the same priority.
What happens if I do not implement IRocketCommandPriority?
Your command gets CommandPriority.Normal by default. This is the same as explicitly returning Normal from IRocketCommandPriority.Priority. The difference is that explicit declaration makes your intent visible to other developers who inspect your code.
Can I use CommandMapping to disable a command?
Indirectly, yes. Map the command to a name that does not exist as a registered command. Players who type the old name get no response. This effectively disables the command from the user's perspective while the plugin remains loaded.
Document history
| Version | Date | Author | Notes |
|---|---|---|---|
| 1.0 | 2026-07-27 | 57 Studios | Initial publication. Full command mapping and priority reference. |
