Difference between revisions of "Manual:Advanced Lua"

From Mudlet
Jump to navigation Jump to search
Line 96: Line 96:
 
print(stripped)
 
print(stripped)
 
</lua>
 
</lua>
 +
 +
== 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 [https://www.lua.org/pil/9.1.html description] and a [http://lua-users.org/wiki/CoroutinesTutorial 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:
 +
 +
<lua>
 +
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
 +
</lua>
 +
 +
Make a ''^ritual$'' alias - which seems big, but that's just because there's a lot of explanation inside it:
 +
 +
<lua>
 +
-- coroutines didn't work before 3.2, so don't do anything if this alias
 +
-- is used in an older Mudlet - because it could crash
 +
if mudlet.supportscoroutines then return end
 +
 +
-- 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
 +
</lua>
 +
 +
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?
  
 
[[Category:Mudlet Manual]]
 
[[Category:Mudlet Manual]]

Revision as of 11:44, 4 June 2017

Advanced Lua

Using 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.

How to use multilinematches[n][m]

multilinematches[n][m] is the compliment of matches[n] when matching multi-line triggers. multilinematches[n][m] stores its matches by lines, inside each line are the relevant matches to it. TThe following example can be tested on the MUD 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:

<lua> showMultimatches() </lua>

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

-------------------------------------------------------
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
-------------------------------------------------------

</lua>

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: <lua> 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 }

} </lua> 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 MUD = "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:

<lua> myGold = myGold + tonumber( matches[2] ) mySilver = mySilver + tonumber( matches[3] ) </lua>

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:

<lua> myGold = myGold + tonumber( multimatches[1][2] ) mySilver = mySilver + tonumber( multimatches[1][3] ) </lua>

What makes multiline triggers really shine is the ability to react to MUD 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.

Using 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.

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

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:

<lua> 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 </lua>

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

<lua> -- coroutines didn't work before 3.2, so don't do anything if this alias -- is used in an older Mudlet - because it could crash if mudlet.supportscoroutines then return end

-- 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 </lua>

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?