Commands
The server has a complete Brigadier-compatible command engine (a port of com.mojang.brigadier and vanilla’s net.minecraft.commands): the full vanilla command tree, suggestions, and the Commands packet the client uses for tab completion and syntax colouring. The commands page browses the vanilla tree.
Plugin commands are nodes of that same tree. They show up in the client’s command tree with typed arguments, tab-complete on the server, and run through the same dispatcher as vanilla commands. You build a tree with Command.Literal and Command.Argument and register it through plugin.Commands.
A first command
Section titled “A first command”[PluginInfo("HomeCommands", Version = "1.0.0")]public sealed class HomeCommandsPlugin : Plugin{ private readonly Dictionary<Uuid, Location> _homes = [];
protected override void OnEnable() { Permissions.Register("homes.use", PermissionDefault.True, "Use /sethome and /home");
Commands.Register(Command.Literal("sethome").Permission("homes.use") .Executes(ctx => SetHome(ctx)), description: "Remember this place as your home");
Commands.Register(Command.Literal("home").Permission("homes.use") .Executes(ctx => GoHome(ctx.RequirePlayer())) .Then(Command.Literal("of").OpLevel(2) .Then(Command.Argument("player", Arg.Player()) .Executes(ctx => GoHome(ctx.GetPlayer("player"))))), aliases: ["h"], description: "Teleport to your home"); }
private int SetHome(CommandContext ctx) { var player = ctx.RequirePlayer(); _homes[player.Uuid] = player.Location; ctx.Reply("Home set"); return 1; }
private int GoHome(Player player) { if (!_homes.TryGetValue(player.Uuid, out var home)) throw new CommandFailure($"{player.Name} has no home. Use /sethome"); player.Teleport(home); return 1; }}Commands.Register(root, aliases, description)returns aRegisteredCommand. Dispose it to unregister; unloading the plugin does that for you.- The root must be a literal. Each alias runs the same tree, and the client shows it too.
- A name that the server or another plugin already owns throws
InvalidOperationException. A server operator can let a plugin take over a vanilla root withcommands.overridein the server configuration; the groups aretp(withteleport) andmsg(withtellandw). Commands.Register(name, handler, description)is sugar for anameliteral followed by a greedyargsargument. Use it for quick commands that parse their own text:
[PluginInfo("FlatHello", Version = "1.0.0")]public sealed class FlatHelloPlugin : Plugin{ protected override void OnEnable() { Commands.Register("hello", ctx => { ctx.Reply($"Hello, {(ctx.Args.Length > 0 ? ctx.Args[0] : "world")}!"); ctx.Result = ctx.Args.Length; // the command's numeric result }, "Says hello"); }}To see every command a player types, including vanilla ones, subscribe to PlayerCommandEvent. Its Command field is the line without the slash, and cancelling it suppresses the command.
The context
Section titled “The context”An executor gets a CommandContext:
| Member | Meaning |
|---|---|
Source |
who runs it: a CommandSource |
Sender, Player, RequirePlayer() |
Sender is the executing entity (Entity.Null for the console); Player is the executing player, or null; RequirePlayer() returns the player and throws CommandFailure for the console |
Name, Input, RawArgs, Args |
the root word, the whole line, the text after the root, and the words of it |
Has(name) |
whether an optional argument was given |
GetInt, GetLong, GetFloat, GetDouble, GetBool, GetString |
typed values of the argument with that name |
GetPlayer, GetPlayers, GetEntities |
selectors, resolved by the engine (@a, @e[...]) |
GetBlockPos, GetVec3, GetBlockState |
positions (relative coordinates resolved against the source) and block states |
GetText |
the textual form of any argument, for types without a typed getter (items, components, NBT, uuids) |
Reply(string), Reply(Component) |
answer whoever ran the command |
Result |
the command result, the number /execute store sees (default 1) |
Detach() |
a copy of what a deferred reply needs, valid after the executor returns |
CommandSource has Entity, Player, IsConsole, PermissionLevel, Position, World, and feedback: Success(message, broadcastToOps) and Failure(message). Feedback is sent without aborting the command.
An executor returns an int, or is an Action<CommandContext> whose result is Result. 1 means success.
Argument types
Section titled “Argument types”Each Arg factory creates the argument type the client shows: Arg.Int(min, max), Arg.Long, Arg.Float, Arg.Double, Arg.Bool(), Arg.Word(), Arg.String() (one phrase), Arg.GreedyString() (the rest of the line), Arg.Player(), Arg.Players(), Arg.Entity(), Arg.Entities(), Arg.Vec3(), Arg.BlockPos(), Arg.BlockState(), Arg.Item(), Arg.Component(), Arg.Uuid(), Arg.Time(min), Arg.Dimension(), Arg.GameMode(), Arg.Resource("registry") and many more, down to the vanilla particle, loot and feature arguments. The full list, with the client-side properties, is on command argument types.
There is no enum factory yet. Use Arg.Word() with a Suggests list and check the value in the executor.
Suggestions
Section titled “Suggestions”Tab completion is computed on the server. A provider can return plain words:
Command.Argument("name", Arg.Word()) .Suggests(ctx => HomeNames(ctx.Player)) // Func<CommandContext, IEnumerable<string>>or fill a builder, with tooltips and an integer form:
Command.Argument("count", Arg.Int(1, 64)) .Suggests((ctx, builder) => builder.Suggest("8", "a stack of eight")) // SuggestionProviderThe engine filters the candidates by what the player typed, the way vanilla does.
Requirements and permissions
Section titled “Requirements and permissions”A node can require the sender to have a permission node, a vanilla operator level, or any condition:
.Permission("homes.use"): the sender needs the node (see permissions). A player without it does not see the node in the client’s tree..OpLevel(2): a vanilla operator level, 0 to 4..Requires(source => source.PermissionLevel >= 2): any condition on theCommandSource.
The console always passes a Permission check.
Failures
Section titled “Failures”Throw CommandFailure to fail the command with a red message. The result is 0, and nothing is written to the log. Any other exception shows the sender a generic error and logs the stack trace. ctx.Source.Failure(message) shows a red message without ending the command.
throw new CommandFailure("You have no home called 'base'");Ownership and reload
Section titled “Ownership and reload”Everything you register is removed when the plugin unloads. Clients get their trees refreshed once per tick, not once per change. The CommandsRegistered event fires after the tree has changed (see events).
What is not there yet
Section titled “What is not there yet”- Running a command line from a plugin. No API runs a command as a player or the console yet. Put the shared code in a method and call that instead.
- Async suggestions in the plugin worker.
SuggestsAsyncexists, but plugins run in the plugin worker, which does not start those providers yet: the client gets no suggestions from them. UseSuggestsuntil that changes. - Typed getters for
Uuidand enums. UseGetTextand parse it, andArg.Word()with a suggestion list.