Skip to content

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").