Skip to content

The Scripting Model

Everything you register with rune (aliases, triggers, timers, hooks, key bindings, bars, GMCP handlers, slash commands) lives in the same kind of registry and behaves the same way. This page describes that shared behavior in one place, so the guide for each one doesn’t repeat it.

If you haven’t written a trigger or an alias yet, start with Triggers and come back. This page makes more sense once you have registered a few things.

A registry name identifies one registration. It is what you pass to get, enable, disable and remove, what a re-registration replaces, and what /triggers, /binds and the other listings show.

Some creation functions assign this name automatically. For the others, set opts.name when you want to look up, manage or replace the registration by name.

Creation function Name Because
rune.bind the key, "ctrl+g" Only one binding can use a key
rune.ui.bar the bar name, "status" Only one renderer can use a bar name
rune.command.add the command, "greet" Only one handler can use a command name
rune.alias.exact the normalized phrase, "chat off" Only one expansion can use a typed phrase
rune.trigger.* opts.name Several triggers may match the same text
rune.alias.regex opts.name Several regex aliases may use the same pattern
rune.timer.* opts.name Several timers may use the same delay or interval
rune.hooks.on opts.name Several handlers may listen to the same event
rune.gmcp.on opts.name Several handlers may listen to the same package

When only one registration can use a value, that value is already its name. A binding is named by its key, a bar by its bar name, a command by its command name, and an exact alias by its phrase. When several registrations can share the same value, give each one its own name with opts.name.

Either way you manage it the same way:

rune.trigger.contains("food", "eat bread", { name = "feeder" })
rune.bind("ctrl+g", toggle_map)
rune.trigger.disable("feeder")
rune.binds.disable("ctrl+g")

A registration without a name can still be managed through the handle returned when it was created:

local h = rune.trigger.contains("food", "eat bread")
h:disable()

Registering the same name again replaces the old entry rather than adding a second one. That is what keeps /reload from stacking a duplicate trigger each time you edit a script, so name anything you expect to re-register.

Passing opts.name to one of the four automatically named functions is ignored, with a notice telling you the name to manage it by. Automatic naming also lets you reach the core’s own binds, bars and commands, which are registered without options at all.

Every creation function takes an optional opts table as its last argument. Three fields work everywhere:

Option Type Default Description
group string none Group membership for batch enable/disable/remove.
priority number 50 Execution order where multiple items can match (regex aliases, triggers, hooks). Lower runs first.
once bool false Auto-remove after the first match (aliases, triggers).

name sets the name where the registry does not set its own, as above.

Individual registries add their own options. Triggers take gag and raw, for example. Each API reference page lists its extras.

Wherever an action is expected, a string is sent as a command and a function runs your logic:

rune.alias.exact("n", "north") -- string: sent as-is
rune.trigger.contains("hungry", "eat bread") -- string: sent on match
rune.alias.exact("heal", function(args, ctx) -- function: full control
rune.send("cast heal " .. (args ~= "" and args or "self"))
end)

Regex string actions substitute captures with %1, %2, …:

rune.alias.regex("^cmd\\s+(\\w+)\\s+(.+)", "command private %1 to %2")

Function actions receive a context table as their last argument:

Field Description
ctx.name The item’s name, if set
ctx.group The item’s group, if set
ctx.type "alias", "trigger", "timer", or "hook"
ctx.line The original line (a line object for triggers)
ctx.args Text after the matched phrase (exact aliases)
ctx.matches Capture array (regex matches)
ctx:remove() Remove this item from inside its own callback

ctx:remove() is how a timer stops itself:

rune.timer.every(10, function(ctx)
if done then ctx:remove() end
end)

Give an item a name and you can reach it from anywhere, which is how you will manage things most of the time:

rune.trigger.contains("spam", nil, { gag = true, name = "spam-gag" })
rune.trigger.disable("spam-gag")

Every registry exposes the same functions, taking the item name: get, enable, disable, remove, plus list(), count(), clear(), and remove_group(group). The full contract is in the API reference.

get returns the handle, which is how you reach something you did not register yourself:

rune.bars.get("status"):disable()

Every creation function returns a handle for managing the registration directly:

local h = rune.trigger.contains("spam", nil, {gag = true, name = "spam-gag"})
h:disable() -- stop firing, stay registered
h:enable() -- resume
h:remove() -- unregister
h:name() -- "spam-gag"
h:group() -- nil (no group set)
h:action() -- the registered action

Handle methods can be chained. To register an item in a disabled state:

rune.trigger.contains("Weather:", nil, {gag = true}):disable()

h:action() gives back what you registered, which is how you extend something instead of replacing it. Calling the result runs it directly, skipping the enabled and group checks and the failure quarantine:

local scroll = assert(rune.binds.get("pgup")):action()
rune.bind("pgup", function()
scroll()
rune.echo("scrolled")
end)

Items have two independent enable switches: their own state (h:disable()) and their group’s master switch (rune.group.disable("combat")). An item fires only when both are enabled, and re-enabling a group preserves each item’s individual state. See Groups for patterns.

To recover, fix the error and re-enable the item with h:enable(), rune.trigger.enable("name"), or /reload (re-registering resets the failure count). One successful run also clears the count.

Every registration records the registering script’s file:line. It shows up in error messages and in the listing commands (/aliases, /triggers, /timers, /hooks, /binds, /bars), so you can always tell which script owns an item.

Related: API reference overview · Triggers · Aliases · Groups