Skip to content

A command with arguments and permissions

Status: Implemented. Custom suggestions, redirects and client-side commands are Planned.

Goal: /ruby give <targets> [amount] for operators and /ruby count for everyone.

Language: Lua, with C++ and C at the end. APIs: Commands.

1. The tree

ruby
├── count                         everyone
└── give            (level 2)     operators only
    └── <targets: players>
        └── <amount: integer>

Requirements apply to a node and everything below it, and clients only see what they may use.

2. Lua

local RUBY = "my_mod:ruby"

local function give(ctx, amount)
  local targets = ctx:entities("targets")          -- always a list
  for _, p in ipairs(targets) do p:give(RUBY, amount) end
  ctx:success(("Gave %d rubies to %d players"):format(amount, #targets))
  return #targets
end

cl.on("server.command_register", function()
  local cmd = cl.cmd
  cl.cmd.register(
    cmd.literal("ruby")
      :then_(cmd.literal("count"):executes(function(ctx)
        local p = ctx:player()
        if not p then ctx:failure("Only players have pockets.") return 0 end
        ctx:success("You have " .. p:count_item(RUBY) .. " rubies.")
        return 1
      end))
      :then_(cmd.literal("give"):requires_permission(2)
        :then_(cmd.argument("targets", "players")
          :executes(function(ctx) return give(ctx, 1) end)
          :then_(cmd.argument("amount", "integer")
            :executes(function(ctx) return give(ctx, ctx:arg("amount")) end)))))
end)

targets accepts names, UUIDs and selectors (@a, @p[distance=..10]) and fails with vanilla’s message when nothing matches.

3. Feedback conventions

  • ctx:success(text) for the result line (plain, like vanilla; colour it with a text if you want).
  • ctx:failure(text) for errors (red, like vanilla).
  • Return the number of things affected (> 0 means success), like vanilla commands.

4. C++

using namespace clight::cmd;
clight::on<clight::ev::ServerCommandRegister>([](cl_ev_command_register&) {
    auto give = [](clight::CmdCtx c, int32_t n) {
        auto targets = c.arg_entities("targets");
        if (!targets) return 0;
        for (clight::Entity p : *targets) p.give("my_mod:ruby", n);
        c.reply(std::format("Gave {} rubies to {} players", n, targets->size()));
        return int(targets->size());
    };
    literal("ruby")
        .then(literal("count").executes([](clight::CmdCtx c) {
            clight::Entity p = c.player();
            if (!p) { c.send_failure("Only players have pockets."); return 0; }
            c.reply(std::format("You have {} rubies.", p.count_item("my_mod:ruby")));
            return 1;
        }))
        .then(literal("give").requires_level(2)
            .then(argument("targets", "players")
                .executes([give](clight::CmdCtx c) { return give(c, 1); })
                .then(argument("amount", "integer")
                    .executes([give](clight::CmdCtx c) { return give(c, c.arg_i32("amount").value_or(1)); }))))
        .register_command();
}).detach();

5. C

static int32_t count_cmd(cl_cmd_ctx c, void* u) {
    char msg[64];
    cl_entity p = cl_cmd_ctx_player(c);
    (void)u;
    if (CL_IS_NULL(p)) return 0;
    snprintf(msg, sizeof msg, "You have %d rubies.", cl_easy_count(p, "my_mod:ruby"));
    cl_easy_send_message(p, msg);
    return 1;
}

static void on_commands(void* payload, void* user) {
    cl_cmd_node root = cl_cmd_literal(CL_STR("ruby"));
    cl_cmd_node count = cl_cmd_literal(CL_STR("count"));
    (void)payload; (void)user;
    cl_cmd_executes(count, count_cmd, NULL);
    cl_cmd_then(root, count);
    cl_cmd_register_tree(root);
}
/* mod_main: CL_ON(CL_EV_SERVER_COMMAND_REGISTER, on_commands) */

Planned

Custom suggestions for arguments, redirects and forks (/execute-style), permission nodes with defaults, and commands that run only on the client.