Commands
The examples use the GameChat engine and Player source from The chat engine.
Commands from the builder
CreateCommand starts a command, and Build registers it. Parameters are numbered in the order you
add them:
using BSGameLib.Chat;
var chat = new GameChat();
chat.CreateCommand("roll")
.WithKeywords("roll", "r")
.WithRequiredParameter<int>("sides", "Number of sides")
.WithHelpText("Rolls a die.")
.WithAction(ctx =>
{
var sides = ctx.GetParameterByName<int>("sides").Value;
Console.WriteLine($"{ctx.SourceTo<Player>().Name} rolled {Random.Shared.Next(1, sides + 1)}");
})
.Build();
WithKeywordssets what is typed. Without it, the command's lowercase name is its only keyword.WithRequiredParameter<T>is a parameter that must be given.WithParameter<T>is one that may be left out.- A value is converted to
Twith the current culture. A value that won't convert, such asabcfor anint, is reported as a wrong parameter, and the command doesn't run. - A parameter that was left out has no value:
IsSetis false, andValueisdefault,0for anintandnullfor anint?. CheckIsSetto tell a left-out value from a typed0. A parameter can be read as its nullable form,GetParameterByName<int?>, whether it was declared asintorint?:
using BSGameLib.Chat;
var chat = new GameChat();
chat.CreateCommand("pay")
.WithRequiredParameter<string>("player", "Who to pay")
.WithParameter<int>("amount", "How much, default 1")
.WithAction(ctx =>
{
var amount = ctx.GetParameterByName<int>("amount");
var count = amount.IsSet ? amount.Value : 1;
Console.WriteLine($"Paying {ctx.GetParameterByName<string>("player").Value} {count}");
})
.Build();
Commands from attributes
A command class implements ICommand, takes its keywords in [Command], and takes each parameter
as a property with [RequiredParameter] or [OptionalParameter]. The first argument of a parameter
attribute is its position, and the second is its name, which defaults to the property's name:
using BSGameLib.Chat;
using BSGameLib.Chat.Attributes;
[Command("give", "g", HelpText = "Gives an item.")]
public class GiveCommand : ICommand
{
[RequiredParameter(1, "item", HelpText = "What to give")]
public string Item { get; set; } = "";
[OptionalParameter(2, "amount", HelpText = "How many", Suggestions = "items")]
public int? Amount { get; set; }
public void Execute(ICommandContext context) =>
Console.WriteLine($"{context.SourceTo<Player>().Name} gives {Item} x{Amount ?? 1}");
}
- An optional parameter of a value type must be nullable, as
int?is. Otherwise registration throws anArgumentException. - The properties are filled in before
Executeruns. A new command object is made each time the command is typed, so the arguments of one use never reach the next. - On the server, the command's constructor parameters come from dependency injection. On its own, the factory provider creates the command.
Suggestionsnames the values the game knows for a parameter, such asplayers. A client uses it to complete a line; see Describing commands to players.
Taking the rest of the line
A parameter with CapturesRemainder = true takes everything after it, spaces included. Only the
last parameter should set it:
using BSGameLib.Chat;
using BSGameLib.Chat.Attributes;
[Command("say", "s", HelpText = "Says something to everyone.")]
public class SayCommand : ICommand
{
[RequiredParameter(1, "message", CapturesRemainder = true)]
public string Message { get; set; } = "";
public void Execute(ICommandContext context) => Console.WriteLine($"Say: {Message}");
}
With that command, /s hello big world gives Message the value hello big world.
Command groups
A group is a keyword followed by one of several sub-commands. Each sub-command has [CommandGroup]
with the group's name and keywords, and its own [Command]. The sub-command with
IsDefaultCommand = true runs when the group is typed with nothing after it. A default can't take
parameters.
using BSGameLib.Chat;
using BSGameLib.Chat.Attributes;
[CommandGroup("party", "p", IsDefaultCommand = true)]
[Command("list", "l")]
public class PartyList : ICommand
{
public void Execute(ICommandContext context) => Console.WriteLine("Party: Ann, Ben");
}
[CommandGroup("party", "p")]
[Command("say", "s")]
public class PartySay : ICommand
{
[RequiredParameter(1, "message", CapturesRemainder = true)]
public string Message { get; set; } = "";
public void Execute(ICommandContext context) => Console.WriteLine($"To the party: {Message}");
}
With these, /p and /p l list the party, and /p s hello there says something to it. The
group's name, party, is not typed unless you list it as a keyword.
The builder makes a group the same way. WithCommand adds a command built with CreateCommand, and
WithDefaultAction names the one that runs when the group is typed alone. As with the attribute, that
command can't take parameters:
using BSGameLib.Chat;
var chat = new GameChat();
var list = chat.CreateCommand("list")
.WithKeywords("l")
.WithAction(ctx => Console.WriteLine("Party: Ann, Ben"))
.Build();
chat.CreateCommandGroup("party")
.WithKeywords("p")
.WithCommand(list)
.WithDefaultAction("list")
.Build();
Registering attribute commands
On its own, register each class with RegisterCommand<TCommand>():
using BSGameLib.Chat;
var chat = new GameChat();
chat.RegisterCommand<GiveCommand>();
A set of classes can be given through an ICommandProvider, which returns their
types. The engine reads the providers when its Initialize() runs. Calling Initialize() again does
nothing. Registering a class both ways also throws. Names and keywords must be unique: a clash throws
an ArgumentException when the command is registered.