Manual:Advanced Lua

From Mudlet
Revision as of 12:33, 3 November 2024 by Zooka (talk | contribs) (→‎Lua tables)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Advanced Lua

Lua version

Mudlet uses Lua 5.1.

Lua tables

A good overview of tables is available on Lua's wiki in the TablesTutorial. Nick Gammon has also written a nice overview on how to deal with Lua tables.

Creating Tables

Tables can be created manually, by scripting or by a return value from a function (e.g. getRoomExits())

-- create a simple table
local myTable = { value1, value2, value3 }
local exits = { "north", "south", "west" }

-- create a table with keys in uses the following format
local myTable = { key1 = value1, key2 = value2, key3 = value3 }
local stats = { hp = 1234, mana = 321, moves = 678 }

Tables can also be a mixture of the above!

Accessing Tables

There are a number of different ways to access tables and it depends on how they are created.

Here are a few examples from the tables created above.

  print(exits[1])     -- will display north, tables start indexing at position 1
  print(stats["hp"])  -- will display 1234
  print(stats.hp)     -- will display 1234, a shorthand method
  print(stats[1])     -- will return nothing (or nil), there is no key called 1

Assigning Values to Table Elements

Now you know how to access table elements, addressing and assigning values to them works in the same way as normal variables.

  
stats["hp"] = 2000
-- or -- stats.hp = 2000
exits[1] = "down"

-- create a new entry in the stats table with the key called gold and assign it the value of 35
stats["gold"] = 35

Deleting a Table Element

Assigning an element the value of nil will remove it entirely.

stats["gold"] = nil
-- the stats table now only contains hp, mana and moves

See also: Table Functions API

How to use multimatches[n][m]

multimatches[n][m] is the complement of matches[n] when matching multi-line triggers. multimatches[n][m] stores its matches by lines, inside each line are the relevant matches to it. The following example can be tested on the game batmud.bat.org:

In the case of a multiline trigger with these 2 Perl regex as conditions:

^You have (\w+) (\w+) (\w+) (\w+)

^You are (\w+).*(\w+).*

The command "score" generates the following output on batMUD:

 You have an almost non-existent ability for avoiding hits.
 You are irreproachably kind.
 You have not completed any quests.
 You are refreshed, hungry, very young and brave.
 Conquer leads the human race.
 Hp:295/295 Sp:132/132 Ep:182/181 Exp:269 >

If you add this script to the trigger:

showMultimatches()

The script, i.e. the call to the function showMultimatches() generates this output:

 -------------------------------------------------------
 The table multimatches[n][m] contains:
 -------------------------------------------------------
 regex 1 captured: (multimatches[1][1-n])
           key=1 value=You have not completed any quests
           key=2 value=not
           key=3 value=completed
           key=4 value=any
           key=5 value=quests
 regex 2 captured: (multimatches[2][1-n])
           key=1 value=You are refreshed, hungry, very young and brave
           key=2 value=refreshed
           key=3 value=young
           key=4 value=and
           key=5 value=brave
 -------------------------------------------------------

The function showMultimatches() prints out the content of the table multimatches[n][m]. You can now see what the table multimatches[][] contains in this case. The first trigger condition (=regex 1) got as the first full match "You have not completed any quests". This is stored in multimatches[1][1] as the value of key=1 in the sub-table matches[1] which, in turn, is the value of key=1 of the table multimatches[n][m].

The structure of the table multimatches:

multimatches {
                1 = {
                       matches[1] of regex 1
                       matches[2] of regex 1
                       matches[3] of regex 1
                              ...
                       matches[m] of regex 1 },
                2 = {
                       matches[1] of regex 2
                       matches[2] of regex 2
                             ...
                       matches[m] of regex 2 },
                 ...         ...
                n = {
                       matches[1] of regex n
                       matches[2] of regex n
                             ...
                       matches[m] of regex n }
}

The sub-table matches[n] is the same table matches[n] you get when you have a standard non-multiline trigger. The value of the first key, i. e. matches[1], holds the first complete match of the regex. Subsequent keys hold the respective capture groups. For example: Let regex = "You have (\d+) gold and (\d+) silver" and the text from the game = "You have 5 gold and 7 silver coins in your bag." Then matches[1] contains "You have 5 gold and 7 silver", matches[2] = "5" and matches[3] = "7". In your script you could do:

myGold = myGold + tonumber( matches[2] )
mySilver = mySilver + tonumber( matches[3] )

However, if you’d like to use this script in the context of a multiline trigger, matches[] would not be defined as there are more than one regex. You need to use multimatches[n][m] in multiline triggers. Above script would look like this if above regex would be the first regex in the multiline trigger:

myGold = myGold + tonumber( multimatches[1][2] )
mySilver = mySilver + tonumber( multimatches[1][3] )

What makes multiline triggers really shine is the ability to react to game output that is spread over multiple lines and only fire the action (=run the script) if all conditions have been fulfilled in the specified amount of lines.

Regex in Lua

Lua has its own, fast and lightweight pattern matching built in - see 20.2 – Patterns. Should you need proper regex however, Mudlet has lrexlib available - which works as a drop-in replacement; replace string. with rex. - for example string.gsub to rex.gsub. See manual for documentation.

-- example: strip out trailing .0's from text using a regex
local stripped = rex.gsub("1.0.0", [[(\.0+)+$]], '')
print(stripped)

The lrexlib manual can be hard to read, so the instructions in this section should provide most of what you need.

lrexlib comes preinstalled in Mudlet and it is automatically bounded to the rex variable, as the rex_pcre version. If you want to test your lua code outside Mudlet, you have to install it yourself. The easiest installation method uses luarocks. There are a few flavors that you can specify, but in this section we will focus on the POSIX and PCRE flavors.

luarocks install --local lrexlib-POSIX
luarocks install --local lrexlib-PCRE

After installation, you can use the library:

> local rex = require("rex_posix")

If you set the flavor to rex_pcre, your code should work afterwards in Mudlet.

Keep in mind that precomputing a pattern with the new function has the advantage that objects resulted will be garbage collected and that regexes are greedy by default.

Another thing that may surprise some users is that, when escaping, you need to backslashes:

> rex.match("The answer is 42", "(\\d+)")

You can also achieve this using long string literal syntax, where you don't need the double escape:

> rex.match("The answer is 42", [[(\d+)]])

There are a few functions that you need to know: gsub, count, split, find, match.

count

The count function counts the matches:

> print(rex.count("ab", "."))
2

A single dot here has the same meaning it has in POSIX regexes.

You can also precompute the pattern, using the new function:

> print(rex.count("ab", rex.new"."))
2

Remember that regexes are greedy by default:

> print(rex.count("aaa", ".*"))
1

split

The split function splits a string according to a regex pattern:

> for a in rex.split("aba", "b") do
print(a)
end
a
a
> for a in rex.split("aba", ",") do
print(a)
end
aba

Be careful though, because while the split consumes all the characters with the same value, if they are adjacent, you will also have the borders between them resulting in an empty string:

> for a in rex.split("abba", "b") do
print(a)
end
a

a

find

The find function searches for the first match, and returns the offsets where the match starts and ends.

> print(rex.find("aaa", "b"))
nil

> print(rex.find("aba", "b"))
2	2

find also takes an additional parameter, which is the starting position:

> print(rex.find("abab", "b"))
2	2
> print(rex.find("abab", "b", 3))
4	4

A negative starting position will circle back from the end:

> print(rex.find("ababab", "b", -1))
6	6
> print(rex.find("ababab", "b", -3))
4	4

match

The match function works similarly to the find function, but returns the matches instead of the indices:

> print(rex.match("abcdef", "ab"))
ab
> print(rex.match("abcdef", ".*"))
abcdef
> print(rex.match("abcdef", "ab.."))
abcd
> print(rex.match("abcdef", ".*", 3))
cdef
> print(rex.match("abcdef", ".*", -3))
def

gsub

The gsub function replaces the pattern in the string, with the value in the third parameter:

> print(rex.gsub("abcdef", "[abef]+", ""))
cd	2	2

This snippet replaces all the "a", "b", "e" or "f" characters with the empty string, so only "c" and "d" remain.

The fourth parameter determines how many replacements are made:

> print(rex.gsub("abcdef", "[abef]+", "", 1))
cdef	1	1

Because the regex is greedy, the "ab" is a single match, so it gets replaced.

PCRE specific functionality

The case insensitive flag:

> rex = require("rex_pcre")
> flags = rex.flags()
> print(rex.find("ABab", "a", 0, flags.CASELESS))
1	1

Coroutines

Mudlet supports Lua's coroutines starting with 3.2.0, which opens up a whole lot of possibilities for the way you program your scripts. A pretty technical description and a tutorial is available, but for a quick explanation, think of coroutines allowing you to pause and resume running a function. If you're familiar with other clients, it is something like a #wait statement where a script will stop running, except unlike a #wait which auto-resumes the script later, you resume it when it yourself whenever you'd like.

Here's an example - add this code as a new script:

function ritual()
  send("get wood")
  -- think of coroutine.yield as yielding (giving away) control,
  -- so the function will stop here and resume on making fire 
  -- when called the next time
  coroutine.yield()
  send("make fire")
  coroutine.yield()
  send("jump around")
  coroutine.yield()
  send("sacrifice goat")
end

Make a ^ritual$ alias - which seems big, but that's just because there's a lot of explanation inside it:

-- create a coroutine that'll be running our ritual function
-- or re-use the one we're already using if there is one
ritualcoroutine = ritualcoroutine or coroutine.create(ritual)

-- run the coroutine until a coroutine.yield() and see
-- if there's any more code to run
local moretocome = coroutine.resume(ritualcoroutine)

-- if there's no more code to run - remove the coroutine,
-- so next time you call the alias - a new one gets made
if not moretocome then
  ritualcoroutine = nil
end

Now try doing the ritual command. You'll see that the send()'s are being sent one at a time, instead of all at once as they would have been without the yields. Cool, huh?

You can also install the demo as a package - paste this into Mudlet:

lua installPackage("http://wiki.mudlet.org/images/3/36/Ritual-coroutine-demo.zip")

Note that if you'll be using coroutines as part of a package you'll give to others, remember about the if mudlet.supports.coroutines then return end bit. Older Mudlets that don't support coroutines might crash, which sucks. Newer ones that do support them are completely fine, however!

Coroutines have many uses: finite state machines, running intensive tasks (yielding every once in a while so Mudlet isn't frozen), and so on.

Batching Processing with Coroutines

Say you want to process large jobs that lock up the user interface for the duration of the job, such as colouring entire maps, deleting rooms or processing large files. You can process these jobs in small amounts and then yield to the main loop where you can display a progress meter.

local function task()

  -- process a large task, like colouring all rooms in a map
  for i = 1, 100 do
    -- setRoomEnv(i, ...)                  -- perform a small part of the job, colour just one room
    echo("processed part of a task.. ")
    coroutine.yield()                      -- now yield to the progress meter function
  end
end

function batch()

  local routine = coroutine.create(task)
  
  for i = 0, 100 do
    coroutine.resume(routine, i)
    echo(i .. "% complete.\n")    
  end
end

All running lua batch() you will see;

processed part of a task.. 0% complete.
processed part of a task.. 1% complete.
processed part of a task.. 2% complete.
processed part of a task.. 3% complete.
processed part of a task.. 4% complete.
processed part of a task.. 5% complete.

...

Introspective facilities

Lua brings a helpful debug.getinfo(function) function, which gets you some information about where a function comes from: whenever it's your own, or one defined by Mudlet (in C++ or Lua). You can also use it to get more information on the Alias## / Trigger## objects you see in the error console:

If you're working on coding Mudlet itself, use this function to tell where the actual definition of a function is.