title |
---|
Player Commands |
SampSharp provides an easy-to-use commands system. A command can be created by registering a class to the CommandManager
service or by decorating a method with the Command
attribute.
A method can be decorated with the Command
attribute to tell the command manager to load it.
- The method must either be static or a member of a subclass of
BasePlayer
. - The method may be of any protection level in a class of any accessibility level.
- The method must be marked with the
Command
attribute. - The method's return type must be either
void
orbool
. - If the method is static, the first parameter must be (of a subtype) of type
BasePlayer
.
A static method marked as a command must accept a BasePlayer
or subclass of BasePlayer
as first parameter. Subsequent parameters indicate the parameters of the command itself.
class AnyClass
{
[Command("helloworld")]
private static void HelloWorldCommand(BasePlayer sender, int times)
{
for(var i = 0; i < times; i++)
sender.SendClientMessage("Hello, world!");
}
}
All parameters of a method marked as a command within a subtype of the BasePlayer
type indicate the parameters of the command itself.
class MyPlayer : BasePlayer
{
[Command("helloworld")]
private void HelloWorldCommand(int times)
{
for(var i = 0; i < times; i++)
SendClientMessage("Hello, world!");
}
}
You can specify different types of names to commands. Each of these types have a different purpose.
You can give a command multiple names simply by listing them all within the Command
attribute. The following command is available via /admin vehicle [model]
, /admin veh [model]
and /admin v [model]
.
[CommandGroup("admin")]
class AnyClass
{
[Command("vehicle", "veh", "v")]
private static void SpawnVehicleCommand(BasePlayer sender, VehicleModelType model) { }
}
If a command has multiple names, you can specify the display name which will, for example, be used in the usage message.
[Command("vehicle", "veh", "v", DisplayName = "v")]
If a command is in a group, but you also want to give it a short command, you can specify a shortcut. The following command is available via /admin vehicle [model]
, /admin veh [model]
and /v [model]
(the shortcut).
[CommandGroup("admin")]
class AnyClass
{
[Command("vehicle", "veh", Shortcut = "v", DisplayName = "v")]
private static void SpawnVehicleCommand(BasePlayer sender, VehicleModelType model) { }
}
The framework supports a number of parameter types by default: int
, float
, string
, BasePlayer
, a subclass of BasePlayer
or any type of enumeration. The command text is automatically parsed for these types.
class AnyClass
{
[Command("hello")]
private static void SayHelloCommand(BasePlayer sender, BasePlayer player, int money)
{
player.SendClientMessage("{0} says hello and has given you ${1}!", player, money)
player.Money += money;
}
[Command("like")]
private static void LikeCommand(BasePlayer sender, VehicleModelType model)
{
BasePlayer.SendClientMessageToAll("{0} would like to have a {1}!", sender, model);
}
}
The behavior of string
parameters is a special case. If a non-last parameter of type string
is parsed, only a single word (separated by a space) is taken. If a last parameter of type string
is parsed, the whole remainder of the command text is taken.
class AnyClass
{
[Command("repeat")]
private void RepeatCommand(string word, int times)
{
// `/repeat hello 2` prints `hello` twice.
for(var i = 0; i < times; i++)
sender.SendClientMessage(word);
}
[Command("like")]
private static void LikeCommand(BasePlayer sender, string message)
{
// `/like big guns` prints `John Doe likes big guns!`.
BasePlayer.SendClientMessageToAll("{0} likes {1}!", sender, message);
}
}
If you'd like a parameter to be parsed in a non-default way you can explicitly specify the parser.
class AnyClass
{
[Command("repeat")]
private void RepeatCommand(BasePlaye sender, [Parameter(typeof(WordType))]string word)
{
// `/repeat hello world` still only prints `hello` twice. `world` is discarded.
for(var i = 0; i < 2; i++)
sender.SendClientMessage(word);
}
}
It is also possible to write your own parsers.
class CustomType : ICommandParameterType
{
/// <summary>
/// Gets the value for the occurance of this parameter type at the start of the commandText. The processed text will be
/// removed from the commandText.
/// </summary>
/// <param name="commandText">The command text.</param>
/// <param name="output">The output.</param>
/// <returns>true if parsed successfully; false otherwise.</returns>
public bool Parse(ref string commandText, out object output)
{
output = null;
// Can't parse without intput.
if (string.IsNullOrWhiteSpace(commandText))
return false;
// Get the first word.
var word = commandText.TrimStart().Split(' ').First();
// Set the output (color) based on the input.
switch (word.ToLower())
{
case "red":
output = Color.Red;
break;
case "green":
output = Color.Green;
break;
case "blue":
output = Color.Blue;
break;
}
// Remove the word from the input and trim the start.
if (output != null)
{
commandText = commandText.Substring(word.Length).TrimStart();
return true;
}
return false;
}
}
class AnyClass
{
[Command("repeat")]
private static void RepeatCommand(BasePlayer sender, [Parameter(typeof(CustomType))]Color color)
{
// `/repeat red` prints `Hello!` in red.
sender.SendClientMessage(color, "Hello!");
}
}
The command manager accepts two return types: void
and bool
. If the void
return type is used the command manager assumes that the execution has successfully concluded. If a bool
return type is used the command manager assume the same if true
is returned. If false
is returned, the default SERVER: Unknown command
message is displayed.
class AnyClass
{
[Command("useless")]
private static bool RepeatCommand(BasePlayer sender)
{
// Always prints `SERVER: Unknown command`
return false;
}
}
In order to keep commands organized, you can group them. This is an optional feature and can be used if desired.
In order to assign a command group to a class, attach a CommandGroup
attribute to the class.
[CommandGroup("admin")]
class AdminCommandsClass
{
[Command("spawn")]
private static void SpawnCommand(BasePlayer sender)
{
// Command: `/admin spawn`
}
[Command("restart"))]
private static void RestartCommand(BasePlayer sender)
{
// Command: `/admin restart`
}
}
Command groups can also be nested or assigned synonyms.
[CommandGroup("admin")]
class AdminCommandsClass
{
[CommandGroup("vehicle", "veh")]
class AdminVehicleCommandsClass
{
[Command("spawn")]
private static void SpawnCommand(BasePlayer sender)
{
// Command: `/admin vehicle spawn` or `/admin veh spawn`
}
[Command("delete")]
private static void DeleteCommand(BasePlayer sender, int vehicleid)
{
// Command: `/admin vehicle delete` or `/admin veh delete`
}
}
}
Command groups can be assigned to specific methods in various ways.
class AnyClass
{
[Command("alpha bravo")]
private static void BravoACommand(BasePlayer sender)
{
// Command: `/alpha bravo`
}
[CommandGroup("alpha")]
[Command("bravo")]
private static void BravoBCommand(BasePlayer sender)
{
// Command: `/alpha bravo`
}
}
It is possible to indicate that a specific permission is required to execute a command. By default, there are two permission checkers: AdminChecker
and SilentAdminChecker
.
If a permission checker does not specify a 'permission denied'-message, the command manager will look for an alternative command. If a message has been specified, the message will be displayed when the player attempts to invoke the command with insufficient permissions.
Permission checks can also be manually written.
public class RichPermissionChecker : IPermissionChecker
{
/// <summary>
/// Gets the message displayed when the player is denied permission.
/// </summary>
public string Message
{
get { return "You need at least $1000 to run this command."; }
}
/// <summary>
/// Checks the permission for the specified player.
/// </summary>
/// <param name="player">The player.</param>
/// <returns>true if allowed; false if denied.</returns>
public bool Check(BasePlayer player)
{
return player.Money > 1000;
}
}
class AnyClass
{
[Command("amirich", PermissionChecker = typeof(RichPermissionChecker))]
private static void RichCommand(BasePlayer sender)
{
sender.SendClientMessage("You are rich!");
}
[Command("admin", PermissionChecker = typeof(SilentAdminChecker))]
private static void AdminCommand(BasePlayer sender)
{
// If a player tries to invoke this command, but isn't an admin,
// the command manager will look for a different overload of this command.
sender.SendClientMessage("Admin stuff...");
}
}
Permission checks can also be assigned to command group to indicate that all member of the group require a certain permission.
[CommandGroup("admin", PermissionChecker = typeof(SilentAdminChecker))]
class AdminCommandsClass
{
[Command("spawn")]
private static void SpawnCommand(BasePlayer sender)
{
// Command: `admin spawn`
// Spawn a car for this admin...
}
[Command("restart")]
private static void RestartCommand(BasePlayer sender)
{
// Command: `admin restart`
// Restart the server...
}
}
You can make tailing parameters optional by specifying a default value.
class AnyClass
{
[Command("repeat")]
private static void RepeatCommand(BasePlayer sender, string word, int times = 1)
{
// Command: `/repeat [word] <times>`.
// `/repeat hello` prints `hello` once.
// `/repeat hello 2` prints `hello` twice.
for(var i = 0; i < times; i++)
sender.SendClientMessage(word);
}
}
You can specify different overloads of a single command with different arguments.
Be aware! If one overload ends, for example, with a string and the other with a number, eg. /test [message]
and /test [number]
, then a call with command text /test 1337
might be handled by /test [message]
!
class AnyClass
{
[Command("transfer")]
private static void TransferCommand(BasePlayer sender, int amount)
{
sender.SendClientMessage("You entered amount: {0}", amount);
}
[Command("transfer")]
private static void TransferCommand(BasePlayer sender, string word, amount)
{
sender.SendClientMessage("You entered amount: {0} and word: {1}", amount, word);
}
}
By default the case of a command is ignored. Both /help
and /HeLP
will be handled by a command marked [Command("help")]
. If you do not want the case to be ignored, set IgnoreCase to false.
class AnyClass
{
[Command("HELP", IgnoreCase = false)]
private static void HelpCommand(BasePlayer sender, int amount)
{
sender.SendClientMessage("YOU ENTERED HELP IN UPPER CASE");
}
}
By default, the command manager automatically constructs a usage message if the user failed to enter a properly formatted command. You can override the default usage message by specifying the UsageMessage
value in the Command
attribute.
class AnyClass
{
[Command("help", UsageMessage = "Usage: /help [lots] [of] [arguments] [here(STRING)]")]
private static void HelpCommand(BasePlayer sender, int lots, int of, int arguments, string here)
{
//
}
}
The command manager has a lots of pre-defined behaviors. You can change it's behavior by overriding the CommandManager
and the DefaultCommand
:
public class MyCommandManager : CommandsManager
{
public MyCommandManager(BaseMode gameMode) : base(gameMode)
{
}
protected override ICommand CreateCommand(CommandPath[] commandPaths, string displayName, bool ignoreCase,
IPermissionChecker[] permissionCheckers, MethodInfo method, string usageMessage)
{
// Create an instance of your own command type.
return new MyCommand(commandPaths, displayName, ignoreCase, permissionCheckers, method, usageMessage);
}
}
public class MyCommand : DefaultCommand
{
public MyCommand(CommandPath[] names, string displayName, bool ignoreCase,
IPermissionChecker[] permissionCheckers, MethodInfo method, string usageMessage)
: base(names, displayName, ignoreCase, permissionCheckers, method, usageMessage)
{
}
protected override ICommandParameterType GetParameterType(ParameterInfo parameter, int index, int count)
{
// Override GetParameterType to use your own automatical detection of parameter types.
// This way, you can avoid having to attach `ParameterType` attributes to all parameters of a custom type.
// use default parameter type detection.
var type = base.GetParameterType(parameter, index, count);
if (type != null)
return type;
// if no parameter type was found check if it's of any type we recognize.
if (parameter.ParameterType == typeof (bool))
{
// TODO: detected this type to be of type `bool`.
// TODO: Return an implementation of ICommandParameterType which processes booleans.
}
// Unrecognized type. Return null.
return null;
}
protected override bool SendPermissionDeniedMessage(IPermissionChecker permissionChecker, BasePlayer player)
{
// Override SendPermissionDeniedMessage to send permission denied messages in the way you prefer.
if (permissionChecker == null) throw new ArgumentNullException(nameof(permissionChecker));
if (player == null) throw new ArgumentNullException(nameof(player));
if (permissionChecker.Message == null)
return false;
// Send permission denied message in red instead of white.
player.SendClientMessage(Color.Red, permissionChecker.Message);
return true;
}
}
[Controller]
public class MyCommandController : CommandController
{
public override void RegisterServices(BaseMode gameMode, GameModeServiceContainer serviceContainer)
{
// Register our own commands manager service instead of the default.
CommandsManager = new MyCommandsManager(gameMode);
serviceContainer.AddService(CommandsManager);
// Register commands in game mode.
CommandsManager.RegisterCommands(gameMode.GetType());
}
}
A command can manually be defined by creating a class which implements ICommand
. The commands can be registered to the CommandManager
using the CommandManager.Register
method.
class CustomCommand : ICommand
{
/// <summary>
/// Determines whether this instance can be invoked by the specified player.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="commandText">The command text.</param>
/// <returns>A value indicating whether this instance can be invoked.</returns>
public CommandCallableResponse CanInvoke(BasePlayer player, string commandText)
{
// If the player is an admin and the command text equals "/admin" allow the player to invoke this command.
return player.IsAdmin && commandText == "/admin"
? CommandCallableResponse.True
: CommandCallableResponse.False;
}
/// <summary>
/// Invokes this command.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="commandText">The command text.</param>
/// <returns>true on success; false otherwise.</returns>
public bool Invoke(BasePlayer player, string commandText)
{
// Send a friendly message to the caller.
player.SendClientMessage("You are an admin!");
return true;
}
}
class GameMode : BaseMode
{
protected override void OnInitialized(EventArgs args)
{
// Get the instance of the command manager.
var commandManager = Services.GetService<ICommandsManager>();
// Register an instance of CustomCommand to the command manager.
commandManager.Register(new CustomCommand());
}
}