Wizards of Lua — Dynamic Commands¶
With dynamic commands you can register custom commands at runtime and bind them to Lua code. You can add, remove, and inspect commands via the WizardsOfLua module API.
1. Registering a Dynamic Command¶
A command definition is a Lua table with these attributes:
id(string): unique identifier and permission node (e.g.coins.add).level(number): minimum operator level (0 - 4) required for invoking this command.pattern(string): Brigadier-style syntax with literals and%-placeholders.code(string): Lua code; receives anargstable mapping placeholders to parsed values.
Example: define a new command¶
-- Register a command that adds coins to a player
WizardsOfLua.setCommand( {
id = "coins.add",
level = 2,
pattern = "coins add amount:%i to p:%player",
code = [[
local player = args.p
local c = player.extra.coins or 0
c = c + args.amount
player:putExtra({ coins = c })
print(player.name .. " now has " .. c .. " coins.")
]]
})
In‐game, invoke it like this:
Note: Calling
WizardsOfLua.setCommand(…)queues the new command for registration at the end of the current game tick. The command becomes usable on the next tick. So if your script wants to invoke it immediately, you need to yield for one tick (e.g. viasleep(1)) before calling it.Note: Wizards of Lua will not replace a vanilla command or a command registered by another mod that has the same pattern —
setCommand(...)throws an error in that case.
2. Command ID syntax¶
- Use a dot-separated sequence of lowercase words (e.g.
my.command.action). - If you integrate with a permissions plugin (LuckPerms, etc.), this ID becomes the permission node. Any permissions mod can answer it on Fabric; on NeoForge only LuckPerms can.
3. Pattern Syntax¶
A pattern is one or more tokens separated by spaces. Each token can be:
- Literal word (e.g.
home,spawn,coins) - Unnamed placeholder (e.g.
%s,%i,%d, …) - Named placeholder (e.g.
name:%s,amount:%i, …)
Naming rule: All placeholders in a single pattern must be either all unnamed or all named.
Greedy%s: If%sappears as the last token, it captures the rest of the input, including spaces.
3.1 Enum-String Placeholders¶
If you need a string argument that’s restricted to a fixed set of values (and you want built-in tab-completion), use:
- Unnamed enum-string:
- Named enum-string:
At runtime the system will:
- Suggest only
easy,normal,hardwhen you press Tab. - Reject any other value with a “Unknown value” error.
3.2 Choices from Lua¶
If the values aren't known in advance, Lua code can find them. Give the placeholder's name in
choices, or its number if the placeholders are unnamed, and let the code return a list of strings:
WizardsOfLua.setCommand({
id = "home.go",
level = 0,
pattern = "home name:%s",
choices = {
name = [[
local names = {}
for name in pairs(spell.owner.extra.homes or {}) do
names[#names + 1] = name
end
return names
]],
},
code = [[
local home = spell.owner.extra.homes[args.name]
spell.owner.pos = home
]],
})
Only a plain %s can have choices. The code is a spell of its own, like code, and works the same way:
- It's cast by the player who types or runs the command, so
spell.owneris that player. argsholds the arguments before this placeholder.- It can take a moment, even
sleep, but it's stopped after 5 seconds. - The player only sees what fits what they've typed so far, so return everything.
Like an enum-string, this takes only the listed values, in the same upper and lower case: the code
runs once more when the command runs, and any other value is rejected with an “Unknown value” error.
If the list takes a moment, code starts that much later. Without a list, because the code returned
nothing, failed or was stopped, every value is rejected.
The code runs often, up to once per tick for each player who types, so keep it short. Each run ends with a SpellFinishedEvent, like any other spell.
4. Accessing Placeholder Values¶
When your command runs, the parsed values are provided in the args table:
- Unnamed placeholders → passed by index
- Named placeholders → passed by key
-- Pattern: "coins add amount:%i to p:%player"
-- Invocation: /coins add 50 to Alice
args = { amount = 50, p = <Player Alice> }
5. Placeholder Reference¶
| Placeholder | Description | Returns | Lua Type |
|---|---|---|---|
%s |
String (single word or greedy if last) | raw text | string |
%s[o1…oN] |
Enum-string (only accepts the listed options) | one of o1…oN |
string |
%d |
Floating-point number | e.g. 3.14, -0.5 |
number |
%i |
Integer | e.g. 42, -7 |
number |
%b |
Boolean | true or false |
boolean |
%v |
3D vector (x, y, z) | a Vec3d |
Vec3 |
%entity |
Single entity (selector or name) | one Entity |
Entity |
%entities |
Multiple entities (selector or names) | list of Entity |
{ Entity, … } |
%player |
Single online player (selector or name) | one Player |
Player |
%players |
Multiple online players (selector/list) | list of Player |
{ Player, … } |
%item |
Item-stack argument | an ItemStack |
Item |
%block |
Block, with its data, as in /setblock |
e.g. stone |
Block |
%world |
World, by its dimension | e.g. the_nether |
World |
%entitytype |
Entity type | e.g. pig |
EntityType |
Options: Options are separated by
|characters, e.g.red|green|blue.
Selectors: Most selectors (@e,@a, etc.) require operator level ≥ 2 to use.
6. Defining the Lua Callback¶
The code field is a string containing your Lua logic. It is compiled and run at invocation time.
- Syntax errors only surface when the command is executed.
- Inside your code,
argsis a predefined variable that contains the placeholder values.
7. Managing Dynamic Commands¶
7.1 Listing Commands¶
local all = WizardsOfLua.listCommands()
for i, cmd in ipairs(all) do
print(i, cmd.id, "→", cmd.pattern, "(level " .. cmd.level .. ")", cmd.code)
end
7.2 Removing a Command¶
8. Best Practices¶
- Unique IDs: Avoid collisions by choosing clear, namespaced IDs.
- Permission Levels: Assign higher levels (2–4) to potentially destructive commands.
- Code Size: Aim for inline Lua snippets under ~20 lines. For complex logic, factor code into separate
.luafiles underconfig/wizards-of-lua/andrequirethem from your code block.
What’s next?¶
- Browse the Frequently Asked Questions.