In this article I’ll explain how to create multi-line triggers in Mudlet, to capture, munge, and summarize output beyond just one line of text.

What is Mudlet?

Mudlet is a multi-platform MUD (telnet-based, textual, multi-player on-line game) client, with support for aliases, triggers, and able to be scripted using lua.

What is a trigger?

The MUD sends text detailing what is happening.

Oftentimes, one might want some action to be performed automatically by the client (Mudlet, in our case) upon receiving some text, matching a special pattern.

This is done using a so-called “trigger”.

In Mudlet, a trigger can have multiple triggering criteria (in AND or OR), and the “type” of triggering criteria can vary - from “exact match” through “perl regex” all the way to “prompt”.

This post isn’t about basic triggers, and presumes you can create a simple one in Mudlet.

A multi-line trigger

As an example, I’d like to capture the text the MUD sends upon “identifying” an object, which shows its statistics.

In the MUD I’m playing, that text looks like this:

You recite a scroll of identify which dissolves.
You feel informed:
Object 'some dried fruit', Item type: FOOD
Item is: NO FLAGS
Weight: 0.68, Effective Weight: 0.68,  Value: 8, Level: 1
Fillingness: 2

The “triggering line” always exactly matches You feel informed:. After it, the MUD sends a variable amount of lines of text, which is always ended by a blank line, or a prompt. I’d want to capture the part in-between those lines, and store the object’s identification somewhere.

Create a trigger, call it Test IDENT (or if you call it something else, remember how you called it!) and make its triggering line You feel informed:, of type exact match. The trigger will match the beginning of that “chunk” of text.

You can verify it works by adding something like this to the trigger’s lua code window, using cecho to add text at the end of the matched line, to verify that the trigger indeed matched it:

cecho(" <purple>MATCHED!<reset>")

Reciting another scroll on the object should show you something like:

You recite a scroll of identify which dissolves.
You feel informed: MATCHED!
Object 'some dried fruit', Item type: FOOD
Item is: NO FLAGS
Weight: 0.68, Effective Weight: 0.68,  Value: 8, Level: 1
Fillingness: 2

The MATCHED! should appear at the end of the You feel informed: line, and it should be coloured purple.

Now, let’s turn that into a multi-line trigger.

First, on the top right hand side of the trigger editing window, insert 1 in the “fire length (extra lines)” input.

This will cause the trigger to not only “fire” (and execute the lua code) for the line which matched You feel informed: but also for 1 line after that.

Reciting another scroll on the object should now show you something like:

You recite a scroll of identify which dissolves.
You feel informed: MATCHED!
Object 'some dried fruit', Item type: FOOD MATCHED!
Item is: NO FLAGS
Weight: 0.68, Effective Weight: 0.68,  Value: 8, Level: 1
Fillingness: 2

That is, the lua code got executed both for the line which matched the triggering phrase, as well as the 1 line after it. This feels like progress!

If you know that the “chunk” of text you’d like to capture always has the same amount of lines of text after the triggering phrase, you can tweak the “fire length” input to match the amount of lines you expect it to fire for, and the lua code will get executed for all those lines.

In our case we don’t know the number of lines in advance, and will want to capture them until a blank line (or a prompt) is found.

The body of the lua function can be changed to contain:

local tname = 'Test IDENT'      -- The name of the trigger. IMPORTANT!
local firelen = 1               -- How many more lines to fire this trigger for
MLChunks = MLChunks or {}       -- A global, for all multi-line triggers
-- MLChunks[tname] is a global, and contains "state data" for THIS trigger. Contains:
-- - "text", default to empty string, which accumulates the text we want to
--   capture and "store", or do something with
-- - "lines", defaults to 0, and "counts" how many total lines of text we have
--   found so far:
MLChunks[tname] = MLChunks[tname] or { text = '', lines = 0 }
if line == "You feel informed:" then
  -- This is the first line that starts the multi-line trigger.
  -- We don't want to capture this line.
  -- We just "highlight" that the capturing has started:
  cecho(" <purple>STARTS!<reset>")
elseif line == "" or isPrompt() then
  -- Once we find a completely empty line (or maybe a prompt) that's it, we
  -- have captured everything we wanted to, and can "use" it.
  cecho("<purple>END! CAPTURED <white>" .. MLChunks[tname]['lines'] .. " <purple>lines!<reset>\n\n")
  -- Note the text captured so far is in MLChunks[tname]['text']
  -- You can display it, for example:
  -- display(MLChunks[tname]['text'])
  -- Or maybe add it to a database, etc.
  -- Once done with it, reset the state of things for this trigger:
  MLChunks[tname] = nil
  -- And ensure it won't fire again, until the next time:
  firelen = 0
else
  -- This is a line that isn't the first, and isn't the empty line or the
  -- prompt, which should end the capture.
  -- Add the line's contents to the "text" accumulator:
  MLChunks[tname]['text'] = MLChunks[tname]['text'] .. line .. "\n"
  -- Count that we've captured and added one more line:
  MLChunks[tname]['lines'] = MLChunks[tname]['lines'] + 1
  -- And (for debugging) show that we've gotten one line:
  cecho(" <purple>LINE!<reset>")
end
-- This is "the" trick: set _this trigger's_ fire length to either "one more
-- line", by default, to have it continue to capture more output, or to 0, to
-- prevent it from triggering again on the next line:
setTriggerStayOpen(tname, firelen)

That was a mouthful. Here’s what happens reciting yet another scroll:

You recite a scroll of identify which dissolves.
You feel informed: STARTS!
Object 'some dried fruit', Item type: FOOD LINE!
Item is: NO FLAGS LINE!
Weight: 0.68, Effective Weight: 0.68,  Value: 8, Level: 1 LINE!
Fillingness: 2 LINE!
END! CAPTURED 4 lines!

The You feel informed: line starts the trigger, but isn’t captured; the other four lines are captured; then the empty line stops the trigger, until the next time that it triggers because of another match for You feel informed: rather than for the “fire length” being bumped.

Other examples

The same logic could be employed for capturing, and pretty-printing, other “lists” of things, like an inventory:

You are carrying:
a plain waterskin
a torch
a small leather bag
the quest journal

For this, create a new trigger, Test INV, and make its trigger be You are carrying:, of type exact match. Also, make its “fire length” 1!

In the lua part of the code, which tells you how many items you have in the inventory:

local tname = 'Test INV'
local firelen = 1
MLChunks = MLChunks or {}       -- A global, for all multi-line triggers
MLChunks[tname] = MLChunks[tname] or { text = '', lines = 0 }
if line == "You are carrying:" then
elseif line == "" or isPrompt() then
  cecho("<purple>You have <white>" .. MLChunks[tname]['lines'] .. " <purple>lines of inventory.<reset>\n\n")
  MLChunks[tname] = nil
  firelen = 0
else
  MLChunks[tname]['text'] = MLChunks[tname]['text'] .. line .. "\n"
  MLChunks[tname]['lines'] = MLChunks[tname]['lines'] + 1
end
setTriggerStayOpen(tname, firelen)

There, you’ve now got a count:

You are carrying:
a plain waterskin
a torch
a small leather bag
the quest journal
You have 4 lines of inventory.

More advanced uses

In a MUD I play, I capture identifications using a method similar to the above, and (thanks to Mudlet’s sqlite integration) place them in a database table, with the object’s name (captured from the identification line) as the primary key.

Excerpt on the multi-line trigger, to capture the object’s name alongside the other lines about the identification text:

-- Object 'some dried fruit', Item type: FOOD
--         ^^^^^^^^^^^^^^^^
local objname = line:match("Object '(.+)', ")
if objname ~= nil then
  MLChunks[tname]['objname'] = objname:lower()
end

… and the insertion:

-- Note this omits duplicate checking, etc.
db:create("idents", {ident = {"objname", "ident"}})
local dbi = db:get_database("idents")
db:add(dbi.ident, {objname = MLChunks[tname]['objname'], ident = MLChunks[tname]['text']})

With that (and a few more helper functions), the trigger capturing the inventory or the equipped items, or what’s in a bag, or what’s someone wearing, can be made to display statistics about those items. Here’s a fuller inventory with the identification data added:

You are carrying:
a plain waterskin      ID[L01 W 1.00 type:LIQCONTAINER worn:HOLD OBELT]
decayed slippers       ID[W 0.85 type:TREASURE AC:0]
a torch                ID[L01 W 2.00 type:LIGHT worn:HOLD]
a small leather bag    ID[L01 W 1.00 type:CONTAINER worn:HOLD OBELT]
a bark canoe           ID[L10 W20.00 type:BOAT worn:HOLD]
the quest journal      ID[L01 W 0.01 type:OTHER]

Or the “equip” command, displaying identification for all equipped items as well as a global count of which statistics are bettered by them:

You are using:
<worn as badge>      a drakling scale           ID [L01 W 0.50 type:ARMOR ...
...
<worn around neck>   a necklace                 ID [L05 W 0.40 type:TREASURE ...
...
<worn on finger>     a coral ring               ID [L15 W 0.30 type:TREASURE ...
...
Worn stats: HITPOINTS:82 AC:71 PERCEPTION:30 ...

I hope this helps you create multi-line triggers in Mudlet whenever you don’t know how many lines you’ll want to capture in advance.