Triggers
A trigger reacts to a line the server sends. Two decisions define one: how it matches (the whole line exactly, a prefix, a substring, or a regex) and what it does (a string to send, or a Lua function to run).
A string action is a canned response:
rune.trigger.contains("You are hungry", "eat bread")A function runs your own logic when the line matches: make decisions, track state, send commands, call any API.
rune.trigger.regex("^Your health is (\\d+)%\\.$", function(m) if tonumber(m[1]) < 30 then rune.send("quaff heal") endend)A function can also shape the line itself: return a string to rewrite it
(this is how you highlight) or false to gag it.
Use a string when a match should send a fixed command and nothing else. Use a function whenever you need logic, state, or control over the line; only a function can do any of those.
Creating
Section titled “Creating”rune.trigger.exact(line, action, opts?) -- whole line matches exactlyrune.trigger.starts(prefix, action, opts?) -- line starts with prefixrune.trigger.contains(text, action, opts?) -- line contains textrune.trigger.regex(pattern, action, opts?) -- Go regexp, with capturesMatching runs against the clean line (ANSI stripped), so patterns don’t
fight color codes. Pass raw = true to match the raw line instead. Regex
patterns are validated at registration: a bad pattern raises immediately
instead of failing silently at match time.
Actions
Section titled “Actions”A string is sent as a command (; chaining and aliases apply). For
regex triggers, %1, %2, and so on are substituted from captures:
rune.trigger.regex("^(\\w+) gives you a (.+)\\.$", "thank %1")A function is called with (matches, ctx). matches is the array of
regex captures (empty for the literal modes). ctx carries line, name,
group, type, and matches; ctx.line is a line object with :raw()
and :clean(). The return value controls the output:
| Return | Effect |
|---|---|
nil |
Line passes through unchanged |
| a string | Rewrites the line; this is how you highlight |
false |
Gags the line |
A string action never touches the line; it only sends. Rewriting and
gagging are function-return features, plus the gag option.
Rewrites chain: later triggers match against and receive the rewritten line, so a highlighter and a tagger compose:
rune.trigger.contains("dragon", function(m, ctx) return rune.style.red(ctx.line:clean())end)rune.trigger.contains("dragon", function(m, ctx) return "!! " .. ctx.line:raw()end)-- output: "!! <red line>"Options
Section titled “Options”Triggers take the common options group,
priority and once, plus a name to manage
one by. A pattern cannot serve as the name, since several triggers can
match the same line: that is what makes a highlighter and a tagger compose.
For the same reason rune.trigger.exact does not name a trigger the way
rune.alias.exact names an alias. Trigger-specific options:
| Option | Effect |
|---|---|
gag |
Hides matching lines (no action required). |
raw |
Matches against the raw line, ANSI codes included. |
on |
"output" (default) for complete lines, or "prompt" for prompt text. See Prompt triggers. |
span |
Collects a multi-line output message before firing. See Multi-line triggers. |
Examples
Section titled “Examples”Pure gag, no action needed:
rune.trigger.contains("The shopkeeper hums", nil, { gag = true })Capture and act:
rune.trigger.regex("^(\\w+) tells you: follow me$", function(m) rune.send("follow " .. m[1])end)Mirror to a pane (gag from the output pane, keep it in the named pane):
rune.trigger.regex("^\\[Auction\\] (.+)$", function(m, ctx) rune.pane.write("auctions", ctx.line:raw()) return falseend)Prompt triggers
Section titled “Prompt triggers”Most triggers run after a complete line arrives. Some MUDs send login and
command prompts without a newline, so waiting for a complete line would miss
them. Rune shows the partial line at the end of each network batch; use
on = "prompt" to match it:
rune.trigger.contains("Username:", "Ragnar", { on = "prompt", once = true })A trigger observes one of two streams:
| Setting | Text it observes |
|---|---|
on = "output" |
Complete server lines. This is the default. |
on = "prompt" |
Partial lines and prompts confirmed by Telnet GA/EOR. |
Before the server finishes the text, Rune cannot know whether it is a prompt
or the beginning of an ordinary line. A prompt trigger may therefore run again
as the partial line grows. If CR/LF eventually completes the line, that
complete line runs through output triggers; if a GA/EOR prompt boundary
confirms it as a prompt, it stays with the prompt triggers. Make repeated
prompt actions harmless, or use once = true
for one-shot work such as login automation.
Prompt triggers can rewrite or gag the displayed prompt just like output
triggers. They cannot use span, because spans collect complete output lines.
Submitting input finishes the partial line before the input is processed. A command sent by Lua finishes it only after the connection accepts the send; failed sends and protocol traffic such as GMCP leave it open.
Multi-line triggers
Section titled “Multi-line triggers”Plenty of server output spans lines: chat and tells that wrap, score
sheets, who lists, quest logs, mail. A plain trigger only ever sees
one line at a time; a span collects the block and fires the action
once, with everything.
For wrapped messages, the end of the block is usually a pattern on the final line — many LPMuds leave the channel’s ANSI color reset on the last wrapped line (log raw output to find your server’s marker):
rune.trigger.regex("^(\\w+) tells you: (.+)$", function(m, ctx) rune.pane.write("chat", "[Tell] " .. m[1] .. ": " .. ctx.text)end, { span = { to = "\\x1b\\[0?m\\s*$", raw = true, max = 8 } })span.to is the regex for the line that ends the message, inclusive.
span.raw matches it against the raw line, since stripping removes
escape codes. span.max (default 8) is the safety cap when the
terminator never shows.
For fixed-shape blocks, skip to and use max alone — the span
collects exactly that many lines:
-- A score block that is always 4 linesrune.trigger.starts("You have scored", function(m, ctx) parse_score(ctx.lines)end, { span = { max = 4 } })In the action, ctx.text is the joined text and ctx.lines has the
individual lines. The collected lines have already been displayed by
the time the action runs, so return values do nothing; gag = true
is the exception and hides every collected line as it arrives:
-- Silence a multi-line broadcast entirelyrune.trigger.starts("The town crier bellows:", nil, { gag = true, span = { to = "\\x1b\\[0?m\\s*$", raw = true } })Full semantics: rune.trigger reference.
Testing
Section titled “Testing”Test output triggers without a server with /test <line>. It runs one
complete line through the output triggers and shows the result. Multi-line spans
collect across /test calls, one line per call. /test does not run prompt
triggers.
Managing
Section titled “Managing”Every constructor returns a handle:
local h = rune.trigger.contains("hungry", "eat bread", { name = "auto-eat" })h:disable() h:enable() h:remove()By name: rune.trigger.disable/enable/remove(name). The full list is in
the API reference. In the client,
/triggers shows every trigger with its state, mode, flags, group, and the
file:line that registered it.
Gotchas
Section titled “Gotchas”- Patterns are Go regexp (RE2), not Lua patterns:
\\d,\\w, and\\swork, backreferences and lookaround do not — see rune.regex for the syntax notes. spanrequireson = "output". Partial-line observations leave a span open; a GA/EOR-confirmed prompt or a finished partial line closes it. See the full span semantics.- A trigger that errors three times in a row is quarantined.
Related: rune.trigger reference, Aliases, Hooks & Events, Panes