Commands
Lesson 13.1
Commands like /rubies or /home set give players a direct way to use your mod. CLight builds them from small pieces, so you can describe the whole command in one expression.
A first command
cl.on("server.command_register", function()
cl.cmd.register(
cl.cmd.literal("hello"):executes(function(ctx)
ctx:success("Hello there!")
return 1
end)
)
end)
Now typing /hello in chat answers “Hello there!”.
- Commands are registered in the
server.command_registerevent. It runs when a world opens and again after/reload, and it is replayed when your script hot reloads. cl.cmd.literal("hello")is the command’s name, without the/.:executes(fn)is the code that runs. It gets a command context,ctx.- The return value is the command’s result: a positive number for success.
return 1is normal. If you return nothing, it counts as 1.
The command context
| Method | Does |
|---|---|
ctx:player() |
The player who typed it, or nil for the server console and command blocks. |
ctx:is_player() |
true if a player typed it. |
ctx:source_name() |
The player’s name, or "Server". |
ctx:success(text) |
Sends a reply. |
ctx:failure(text) |
Sends an error reply, in red. |
ctx:arg(name) |
The value of an argument (below). |
ctx:entities(name) |
The entities of a player or entity argument, always as a list (below). |
ctx:args() |
Everything typed after the command name, as one string. |
ctx:permission_level() |
The permission level of whoever typed it: 0 for normal players, 2 to 4 for operators, 4 for the console. |
Commands can come from the console, so check for a player before you use one:
cl.cmd.literal("heal"):executes(function(ctx)
local p = ctx:player()
if not p then
ctx:failure("Only players can be healed.")
return 0
end
p:set_health(20)
ctx:success("Healed.")
end)
Returning 0 or a negative number tells the game the command failed.
Subcommands
:then_(child) adds a part after the name. (It is then_ with an underscore, because then is a Lua keyword.)
cl.cmd.register(
cl.cmd.literal("home")
:then_(cl.cmd.literal("set"):executes(function(ctx)
ctx:success("Home set.")
end))
:then_(cl.cmd.literal("go"):executes(function(ctx)
ctx:success("Going home.")
end))
)
This makes /home set and /home go. Every method returns the node again, so the calls chain. /home on its own is incomplete here, because the home node has no executes; the player gets “Unknown or incomplete command”. Give it an executes too if /home alone should do something.
Arguments
cl.cmd.argument(name, type) is a part the player fills in:
cl.cmd.register(
cl.cmd.literal("rubies")
:then_(cl.cmd.argument("amount", "integer"):executes(function(ctx)
local n = ctx:arg("amount")
ctx:player():give("my_mod:ruby", n)
ctx:success("Here are " .. n .. " rubies.")
end))
)
/rubies 5 gives five. /rubies five doesn’t match an integer, so the player gets an error message instead.
| Type | Matches | ctx:arg gives |
|---|---|---|
"string" (the default) |
One word, or several words in quotes: "Ruby Hills" |
a string, without the quotes |
"word" |
One word | a string |
"integer" |
A whole number, like 5 or -2 |
an integer |
"number" |
A number, like 2.5 |
a number |
"bool" |
true or false |
a boolean |
"greedy" |
Everything up to the end of the line | a string |
"player" |
One player: a name or a selector like @p |
the player |
"players" |
Players: a name, @a, @a[distance=..10] … |
a list of players |
"entity" |
One entity: a name, a UUID or a selector like @e[type=cow,limit=1,sort=nearest] |
the entity |
"entities" |
Entities: @e[type=zombie], a name … |
a list of entities |
"greedy" must be the last part, because it takes the rest of the line:
cl.cmd.literal("shout"):then_(cl.cmd.argument("text", "greedy"):executes(function(ctx)
cl.entity.server_broadcast(ctx:source_name() .. " shouts: " .. ctx:arg("text"):upper())
end))
The brigadier names that vanilla uses work as well: "brigadier:integer", "brigadier:double", "brigadier:bool", "brigadier:greedy_string".
Players and entities as arguments
The argument types "player", "players", "entity" and "entities" understand everything vanilla commands do: a player name, a UUID, and the selectors @p, @a, @r, @s, @e with all their options in square brackets. A selector may contain spaces inside its brackets: @e[type=zombie, distance=..10] is still one argument.
cl.cmd.register(
cl.cmd.literal("rubygift")
:then_(cl.cmd.argument("target", "player")
:then_(cl.cmd.argument("amount", "integer"):executes(function(ctx)
local target = ctx:arg("target")
target:give("my_mod:ruby", ctx:arg("amount"))
ctx:success("Gave rubies to " .. target.name)
end)))
)
/rubygift Alex 3 and /rubygift @p 3 both work. If no player matches, the player gets Minecraft’s own error (“No player was found”) and your code doesn’t run. A "player" or "entity" argument also refuses a selector that finds more than one; "players" and "entities" give a list:
cl.cmd.literal("rubyrain"):then_(cl.cmd.argument("targets", "players"):executes(function(ctx)
local targets = ctx:arg("targets")
for _, p in ipairs(targets) do p:give("my_mod:ruby") end
ctx:success("Rubies for " .. #targets .. " players")
return #targets
end))
The selector is read from the place and view of whoever runs the command, so @p from a command block is the player nearest to the block.
Who may use it
:requires_permission(...) limits a node to operators. It works on any node: the command’s name or a subcommand.
cl.cmd.register(
cl.cmd.literal("ruby")
:executes(function(ctx) ctx:success("Rubies are red.") end) -- everyone
:then_(cl.cmd.literal("reset"):requires_permission("my_mod.admin") -- operators only
:executes(function(ctx) ctx:success("All rubies reset.") end))
)
Everyone can use /ruby; only operators can use /ruby reset. For everyone else the node doesn’t exist: they get “Unknown or incomplete command”, like for vanilla’s op commands. On the name node it hides the whole command.
You can pass a name like "my_mod.admin" or a vanilla permission level:
| Value | Who |
|---|---|
"my_mod.admin" (any name) |
Operators (level 2). Name your permissions after your mod; a permissions plugin can map them later. |
2 |
Operators, like /give and /gamemode |
3 |
Operators of level 3, like /ban and /op |
4 |
The server owner, like /stop |
In singleplayer you are an operator when cheats are on. ctx:permission_level() tells you the level inside the command, for example to show more details to operators.
Running vanilla commands
cl.cmd.run(command) runs any command as the server, without the /:
cl.cmd.run("weather clear")
cl.cmd.run("time set day")
It returns the command’s result number. That is a handy way to reach game features the API doesn’t cover yet.
Try it
Make a /dice command. /dice rolls a number from 1 to 6. /dice 20 rolls from 1 to 20. Reply with the result, and refuse sides below 2.
Show a solution
local function roll(ctx, sides)
if sides < 2 then
ctx:failure("A die needs at least 2 sides.")
return 0
end
local n = math.random(1, sides)
ctx:success(ctx:source_name() .. " rolled " .. n .. " (1-" .. sides .. ")")
return n
end
cl.on("server.command_register", function()
cl.cmd.register(
cl.cmd.literal("dice")
:executes(function(ctx) return roll(ctx, 6) end)
:then_(cl.cmd.argument("sides", "integer"):executes(function(ctx)
return roll(ctx, ctx:arg("sides"))
end))
)
end)
The dice node has its own executes for the plain /dice, and the argument node has another for /dice 20. Both share one helper.
Recap: register commands in server.command_register with cl.cmd.literal, cl.cmd.argument, :then_ and :executes, and answer with ctx:success or ctx:failure. "player" and "entities" arguments take names and selectors, and :requires_permission on any node keeps it for operators.