Commands
Status: Implemented for command trees with typed arguments and permissions, the simple one-callback command, running commands from code, and reading arguments. Suggestions, redirects (
/execute-style forks), custom argument types, client-side commands and sending mod commands’ argument types to clients are Planned.
Function table: cmd. Guide: A command with arguments and permissions.
Commands are registered inside the event server.command_register (server, MAIN), which fires when the server starts and after every /reload.
Command trees
cl_cmd_node cl_cmd_literal(cl_str name);
cl_cmd_node cl_cmd_argument(cl_str name, cl_str type); // see the types below
cl_result cl_cmd_then(cl_cmd_node parent, cl_cmd_node child);
cl_result cl_cmd_executes(cl_cmd_node node, cl_cmd_fn fn, void* user);
cl_result cl_cmd_requires_level(cl_cmd_node node, int32_t level); // 0 everyone, 2-4 operators
cl_result cl_cmd_requires_permission(cl_cmd_node node, cl_str permission); // a name = operators (level 2) without a permissions plugin
cl_result cl_cmd_register_tree(cl_cmd_node root); // takes the whole tree
typedef int32_t (*cl_cmd_fn)(cl_cmd_ctx ctx, void* user); // the result value (> 0 success, like vanilla); < 0 failure
A node lives until cl_cmd_register_tree takes its root. A requirement on a node also applies to everything below it; sources that don’t pass don’t see the node (vanilla clients only get the commands they may use).
Argument types
| Type | Reads | Getter |
|---|---|---|
"word" |
one word | cl_cmd_arg_str |
"string" |
one word or a "quoted phrase" |
cl_cmd_arg_str |
"greedy" |
the rest of the line | cl_cmd_arg_str |
"integer" |
a 32-bit integer | cl_cmd_arg_i32 |
"double" |
a number | cl_cmd_arg_f64 |
"bool" |
true / false |
cl_cmd_arg_bool |
"player", "players" |
names, UUIDs and selectors (@p, @a[distance=..5]), players only |
cl_cmd_arg_entities |
"entity", "entities" |
names, UUIDs and selectors, any entity | cl_cmd_arg_entities |
brigadier: and minecraft: prefixes are accepted ("brigadier:integer"). Entity arguments resolve through the server’s own selector parser from the source’s position; failures use vanilla’s messages. Selectors with spaces inside [...] stay one argument.
The command context
| C | Lua | C++ (clight::CmdCtx) |
|---|---|---|
cl_cmd_arg_str(ctx, CL_STR("name"), buf, cap, &n), C cl_easy_arg_str |
ctx:arg("name") (a string, number, boolean, entity or list) |
c.arg_str("name") → Expected<std::string> |
cl_cmd_arg_i32, cl_cmd_arg_f64, cl_cmd_arg_bool, C cl_easy_arg_int(ctx, "n", fallback) |
ctx:arg("n") |
c.arg_i32("n"), arg_f64, arg_bool |
cl_cmd_arg_entities(ctx, name, out, cap, &n), C cl_easy_arg_entity |
ctx:arg("who") (one entity for player / entity), ctx:entities("who") (always a list) |
c.arg_entities("who") → Expected<std::vector<Entity>> |
cl_cmd_ctx_entities(ctx, CL_STR("@a"), out, cap, &n) |
c.entities("@a") |
|
cl_cmd_ctx_player(ctx) |
ctx:player() (nil for the console) |
c.player() |
cl_cmd_ctx_level(ctx) |
c.level() |
|
cl_cmd_ctx_permission_level(ctx) |
ctx:permission_level() |
c.permission_level() |
cl_cmd_ctx_args(ctx, buf, cap, &n) (the raw text after the name) |
ctx:args() |
c.args() |
cl_cmd_send_success(ctx, text, broadcast_to_admins) |
ctx:success("Done", false) |
c.reply("Done"), c.send_success("Done", broadcast) |
cl_cmd_send_failure(ctx, text) |
ctx:failure("No") |
c.send_failure("No") |
cl_cmd_ctx_entities resolves any selector text, also outside an argument. Lua also has ctx:source_name() and ctx:is_player().
Bindings for building
| C | C++ | Lua |
|---|---|---|
cl_cmd_literal(CL_STR("pay")) |
clight::cmd::literal("pay") |
cl.cmd.literal("pay") |
cl_cmd_argument(CL_STR("who"), CL_STR("player")) |
clight::cmd::argument("who", "player") |
cl.cmd.argument("who", "player") (type defaults to "string") |
cl_cmd_then(a, b) |
a.then(b) (chainable) |
a:then_(b) (then is a Lua keyword) |
cl_cmd_executes(n, fn, u) |
n.executes([](clight::CmdCtx c) { ... }) (capturing lambdas; void counts as 1) |
n:executes(function(ctx) ... end) |
cl_cmd_requires_level(n, 2) |
n.requires_level(2) |
n:requires_permission(2) |
cl_cmd_requires_permission(n, CL_STR("my_mod.admin")) |
n.requires_permission("my_mod.admin") |
n:requires_permission("my_mod.admin") |
cl_cmd_register_tree(root) |
root.register_command() |
cl.cmd.register(root) |
Example: /pay <who> <amount>
cl.on("server.command_register", function()
cl.cmd.register(
cl.cmd.literal("pay"):then_(
cl.cmd.argument("who", "player"):then_(
cl.cmd.argument("amount", "integer"):executes(function(ctx)
local from, to, n = ctx:player(), ctx:arg("who"), ctx:arg("amount")
if not from then ctx:failure("Players only.") return 0 end
if from:take("minecraft:emerald", n) < n then ctx:failure("Not enough emeralds.") return 0 end
to:give("minecraft:emerald", n)
ctx:success(("Paid %d emeralds to %s"):format(n, to.name))
return 1
end))))
end)
static int32_t pay(cl_cmd_ctx c, void* u) {
(void)u;
cl_entity to = cl_easy_arg_entity(c, "who");
int32_t n = cl_easy_arg_int(c, "amount", 0);
cl_entity from = cl_cmd_ctx_player(c);
if (CL_IS_NULL(from) || cl_player_take(from, CL_STR("minecraft:emerald"), n) < n) return 0;
cl_easy_give(to, "minecraft:emerald", n);
return 1;
}
static void on_commands(void* payload, void* user) {
cl_cmd_node root = cl_cmd_literal(CL_STR("pay"));
cl_cmd_node who = cl_cmd_argument(CL_STR("who"), CL_STR("player"));
cl_cmd_node amount = cl_cmd_argument(CL_STR("amount"), CL_STR("integer"));
(void)payload; (void)user;
cl_cmd_executes(amount, pay, NULL);
cl_cmd_then(who, amount);
cl_cmd_then(root, who);
cl_cmd_register_tree(root);
}
/* in mod_main: CL_ON(CL_EV_SERVER_COMMAND_REGISTER, on_commands) */
using namespace clight::cmd;
clight::on<clight::ev::ServerCommandRegister>([](cl_ev_command_register&) {
literal("pay").then(argument("who", "player").then(argument("amount", "integer").executes([](clight::CmdCtx c) {
auto to = c.arg_entities("who");
int32_t n = c.arg_i32("amount").value_or(0);
clight::Entity from = c.player();
if (!from || !to || to->empty() || from.take("minecraft:emerald", n) < n) return 0;
(*to)[0].give("minecraft:emerald", n);
return 1;
}))).register_command();
}).detach();
The simple form
typedef struct cl_cmd_desc {
uint32_t struct_size;
cl_str name; // "rubyc"
int32_t permission_level; // 0 = everyone
cl_cmd_fn execute;
void* user;
cl_str usage; // shown on errors: "/rubyc"
} cl_cmd_desc;
cl_result cl_cmd_register(const cl_cmd_desc* desc);
/name followed by anything; read the rest with cl_cmd_ctx_args. C++: clight::cmd::add("name", [](clight::CmdCtx c) { ... }, permission_level, usage).
Running commands
cl_result cl_cmd_run(cl_str command, int32_t* out); // as the server console (level 4), server TICK
Lua cl.cmd.run("weather clear"), C++ clight::cmd::run("weather clear").