Difference between revisions of "Manual:Scripting"

From Mudlet
Jump to navigation Jump to search
Line 290: Line 290:
 
This little snippet will have your commands be executed right after, depending on the context you run it in, all triggers, aliases or scripts are completed:
 
This little snippet will have your commands be executed right after, depending on the context you run it in, all triggers, aliases or scripts are completed:
  
<lua>tempTimer(0, [[mycode]])</lua>
+
<syntaxhighlight lang="lua">tempTimer(0, [[mycode]])</syntaxhighlight>
  
 
== How to delete the previous and current lines ==
 
== How to delete the previous and current lines ==
Line 296: Line 296:
 
This little snippet comes from [http://forums.mudlet.org/viewtopic.php?f=9&t=2411&p=10685#p10685 Iocun]:
 
This little snippet comes from [http://forums.mudlet.org/viewtopic.php?f=9&t=2411&p=10685#p10685 Iocun]:
  
<lua>
+
<syntaxhighlight lang="lua">
 
moveCursor(0,getLineCount()-1)
 
moveCursor(0,getLineCount()-1)
 
deleteLine()
 
deleteLine()
 
moveCursor(0,getLineCount())
 
moveCursor(0,getLineCount())
 
deleteLine()
 
deleteLine()
</lua>
+
</syntaxhighlight>
  
 
=db: Mudlet's database frontend=
 
=db: Mudlet's database frontend=

Revision as of 05:41, 29 June 2017

Scripting with Mudlet

Lua tables can basically be considered multidimensional arrays and dictionaries at the same time. If we have the table matches, matches[2] is the first element, matches[n+1] the n-th element.

 a = "Tom"
 matches[2] = "Betty"
 b = matches[2]
 c = a .. b and e will equal "TomBetty"

To output a table you can use a convenience function - display(mytable), which is built into Mudlet.

Lua interface functions to Mudlet - or how do I access triggers, timers etc. from Lua scripts

How to get data from regex capture groups? Regular expression capture groups (e.g. "(\d+)" ) are passed on to Lua scripts as a Lua table matches. To make use of this information inside Lua scripts, you need to specify the number of the capture group within the regex.

Example: You have (\d+) weapons and they are (?:(\b\w+\W+)+)

This regex contains 3 capture groups, but only the 2 green colored ones contain data as the red capture group is a non-capturing group. Consequently, your Lua script gets passed only 2 instead of 3 capture groups and matches[4] is undefined.

In your Lua script you may write following program in order to print the number and status of your weapons on the screen:

You have (\d+) weapons and they are (?:(\b\w+\W+)+)

 number_of_weapons = matches[2]
 status_of_weapons = matches[3]
 notice = number_of_weapons .. status_of_weapons
 echo( notice )
 send( "put weapons in backpack" )

 -- the following 2 lines color the first capture
 -- group red and the second group blue
 -- see below for details

 selectCaptureGroup( 2 )
 setFgColor( 255,0,0 )

 selectCaptureGroup( 3 )
 setFgColor( 0,0,255 )

The best way is to use selectCaptureGroup( number ) to select the proper capture group and then perform your actions on it e.g. replace(), highlight etc. Note: Both selectCaptureGroup() and matches[n] start with group 1 (which is the whole match. The defined capture groups start with 2).

How to select all occurrences of "Tom" and highlight them?

You add a function like this to a script containing you main function definitions. Note that script items differ from all other "scripts" in triggers, timers, actions etc. because they require you to put your code in proper functions that can be called by your other trigger-scripts, timer-scripts etc. via normal function calls. Trigger-scripts, timer-scripts etc. cannot contain any function definitions because they are automatically generated functions themselves because this makes usage a lot easier.

To come back to our question how to select all occurrences of "Tom" and highlight them:

  function setBgColorAll( word, r, g, b )
    i = 0
    word_count = 1
    while i > -1 do
        i = selectString(word, word_count)
        if i == -1 then
             return
        end
        word_count = word_count +1
        setBgColor( r, g, b )
    end
  end

Then you simply define a substring matching trigger on the word "Tom" and in the trigger script you call above function:

setBgColorAll("Tom", 255,50,50)

Sending commands to the MUD or printing information messages on the screen

To print information messages on the session screen you can use the echo( message ) function, or insertText( text ). Currently, it only takes one string as argument.

To send a command to the MUD, you can use the send( command ) function. In Alias scripts the command that is being sent to the MUD is contained in the variable command that you can change in the context of Alias scripts. Alias take regular expressions, as well. As a result, you can use following regex and script to talk like Yoda: Perl regex:

say (\w+).*(\w*).*(.*)

script:

send( "say " .. matches[4] .." " .. matches[2] .." ".. matches[3] )

Note: The variable "command" contains what was entered in the command line or issued via the expandAlias( ) function. If you use expandAlias( command ) inside an alias script the command would be doubled. You have to use send( ) inside an alias script to prevent recursion. This will send the data directly and bypass the alias expansion.

Changing text from the MUD or reformatting text (highlight, make bold etc.)

When sending commands to the MUD - from now on referred to as output stream - alias scripts find the command that was issued by the user stored in the variable "command".

By manipulating the value, the command can easily be changed before it is being sent to the MUD.

However, things get much more complicated with the data received from the MUD, from now on referred to as input stream. Before triggers can be run on the MUD data, Mudlet has to strip all format codes from the text and store it in data structures associated with the text. Consequently, the text that is being passed on to the trigger processing unit is a small subset of the data received from the MUD. If you want to edit, replace, delete or reformat text from within your trigger scripts you have to keep this in mind if you don’t want to lose all text format information such as colors etc.

As the text is linked with data structures containing the format of the text, the cursor position inside the line is important if data is being changed. You select a word or a sequence of characters from the line and then issue commands to do actions on the selected data.

Replacing the word "Tom" with "Betty" in the line: Jim, Tom and Lucy are learning a new spell. This could be done with following script:

selectString("Tom",1)
replace("Betty")

Things get more complicated if there are two or more occurrences of "Tom" in the line e.g. Jim and Tom like magic. Jim, Tom and Lucy are learning a new spell.

The above example code would select the first occurrence of "Tom" in this line and ignore the second. If you want to work on the the second occurrence of "Tom" you have to specify the occurrence number in the call to select().

selectString( "Tom", 2 )
replace( "Betty" )

This code would change the second "Tom" and leave the first "Tom" alone. The function call

replaceAll( "Betty" )

will replace all occurrences of "Tom" with "Betty" in the line if "Tom" has been selected before. replaceAll() is a convenience function defined in LuaGlobal.lua.

Colorization example: You want to change to color of the words "ugly monster" to red on a white background.

You add a new trigger and define the regex: ugly monster In the script you write:

selectString("ugly monster", 1 )
setFgColor(255,0,0)
setBgColor(255,255,255)
resetFormat()

Another means to select text is to select a range of characters by specifying cursor positions. If we have following line: Jim and Tom like magic. Jim, Tom and Lucy are learning a new spell.

selectSection( 28, 3 )

This example would select the second Tom. The first argument to selectSection is the cursor position within the line and the second argument is the length of the selection.

selectCaptureGroup( number )

This function selects the captureGroup number if you use Perl regular expressions containing capture groups. The first capture group starts with index 1.

Deleting Text - Gagging

deleteLine()

This function deletes the current line - or any line where the cursor is currently placed. You can use repeated calls to this function to effectively erase the entire text buffer. If you want to delete or gag certain words only, you can select the text that you want to delete and then replace it with an empty string e.g:

If you get this line form the MUD: "Mary and Tom walk to the diner."

selectString( "Tom", 1 )
replace( "" )

Then the output will be changed to: "Mary and walk to the diner."

Cursor Movement and Cursor Placement

Mudlet allows you to insert text arbitrarily within the buffer - in the previous line, in the previous ten lines, or even the first line that you've ever saw. These are the tools to help you with the job:

moveCursor( windowName, x, y ) This will move the user cursor of window windowName to the absolute (x/y) coordinates in the text.

moveCursor( "main", 20, 3950 ) will move the cursor on the 20th character from the left on line number 3950. To determine the current cursor position you can use getLineNumber() and getColumnNumber() as well as getLastLineNumber() to get the number of the last line in the text buffer. moveCursorEnd("main") will move the cursor of the main display to end of the buffer. This is always a new line of size 1 containing the character \n.

number_of_last_line_in_text = getLineCount() returns the number of the last line of the text in the console buffer. This number will change as soon as a new \n is printed either by the user or when a new line arrives from the MUD. All lines from the MUD are terminated with \n which is called line feed or the new line character. This control character ends the current line and move the cursor to the beginning of the next line, thus creating a new, empty line below the line that contains the \n.

line_number_of_the_current_cursor_position = getLineNumber()

column_number_of_the_current_cursor_position = getColumnNumber()

luaTable_containing_textlines = getLines( absolute_line_number_from, absolute_line_number_to ) this will return a Lua table containing all lines between the absolute positions from and to.

Note Note: This function uses absolute line numbers, not relative ones like in moveCursor().

moveCursor() returns true or false depending on whether the move was possible and done.

Inserting text in the middle of the line

Here's an example where we'll append the text (one) right after the word exit in the line of You see a single exit leading west.

Inserting text.png

The pattern we used was an exact match for You see a single exit leading west. and the code is:

if moveCursor(string.find(line, "exit")+#("exit")-1, getLineNumber()) then
  insertText("(one)")
end

What happened here:

  • we triggered on the desired line using our pattern above
  • we used moveCursor() to place the cursor in the desired position within the line. moveCursor() takes both the line number and the position within the line to place our virtual cursor on, so
    • we used string.find() to find the position of the word "exit" within the line
    • but string.find() returns us the position of the first character of the word we want, while we want to have the text be right after it - so we find out how long our word is with #("exit"), add that to the number returned by string.find() which gets us the first character + the length of the word - which isn't quite at the end of the word, but the word plus the space (because of string.find() and the first character) - so we substract 1 to move our cursor right at the end of the word
    • we then use getLineNumber() to get the current line number, so our cursor is placed on the current line, at our desired position
  • lastly we finally insert the text with insertText(), which puts the text exactly where our virtual cursor is

Done! You can also use cinsertText() for coloured inserts, and other functions mentioned above and in the API manual to help you do what you'd like.

User defined dockable windows

You may want to use dock windows to display information you gathered in your scripts, or you may want to use them as chat windows etc. Adding a user defined window:

openUserWindow( string window_name )

echoUserWindow( string window_name, string text )

setWindowSize( int x, int y )

clearWindow( string window_name )

Please note that these have been depricated in favour of embedded miniconsoles, as those integrate more seamlessly into the UI. User Windows still work fine, however, as we don't break scripts (backwards compatibility) in Mudlet.

Dynamic Triggers

triggerID = tempTrigger( substring pattern, code ) creates a fast substring matching trigger

triggerID = tempRegexTrigger( regex, code ) creates a regular expression matching trigger

triggerID = tempBeginOfLineTrigger( begin of line pattern, code ) creates a fast begin of line substring trigger

Scripting howtos

How to convert a string to value?

Say you'd like to capture a number from a trigger, but the capture always ends up being a "string" (or, just text on which you can't do any maths on) even if it's a number. To convert it to a number, you'd want to use the tonumber() function:

myage = tonumber(matches[2])

How to highlight my current target?

You can put the following script into your targetting alias:

if id then killTrigger(id) end
id = tempTrigger(target, [[selectString("]] .. target .. [[", 1) fg("gold") deselect() resetFormat()]])

Where target is your target variable. Note that you have to use the full name, capitalized. If you’d like the script to auto-capitalize for you, you can use this version:

target = target:title()
if id then killTrigger(id) end
id = tempTrigger(target, [[selectString("]] .. target .. [[", 1) fg("gold") deselect() resetFormat()]])

How to format an echo to a miniConsole?

One of the ways you can create multi-line displays in a miniConsole with variable information is with string.format and the use of [[ ]] brackets for text, which allow for multiple line in text:

local WindowWidth, WindowHeight = getMainWindowSize();
createMiniConsole("sys",WindowWidth-650,0,650,300)

local name, age, sex = "Bob", 34, "male"

cecho("sys", string.format([[

  /---------\
      %s
   %dyrs
   sex - %s 
  \---------/
]], name, age, sex))

How to play a sound when I receive communication while afk?

Play sound when afk trigger.png

For this to work, place the line you'd like to trigger on in the first pattern box and select the appropriate pattern type. Then add return not hasFocus() with the Lua function as the second pattern, enable the AND trigger, and set the line delta to zero. Then just enable play sound, choose your sound and you're set!

How can I make a key that toggles between commands?

To make a key that toggles between two actions, for example - sleep/wake, you can use this as the script:

if not sleeping then
  send("sleep")
  sleeping = true
else
  send("wake")
  sleeping = false
end

How can I make a trigger that goes off only if the next line is not a particular one?

Here's how you can set it up - replace line1 and line2 with appropriate lines.

Trigger multiline not another example.png

Here's the Mudlet XML you can download for this!

Advanced scripting tips

Do stuff after all triggers/aliases/scripts are run

This little snippet will have your commands be executed right after, depending on the context you run it in, all triggers, aliases or scripts are completed:

tempTimer(0, [[mycode]])

How to delete the previous and current lines

This little snippet comes from Iocun:

moveCursor(0,getLineCount()-1)
deleteLine()
moveCursor(0,getLineCount())
deleteLine()

db: Mudlet's database frontend

The DB package is meant to provide easier access to a database, so you don’t have to know SQL or use the luasql module to set and get at your data. However, it does require that the luasql module be compiled and included in Mudlet to function - and this all is available since Mudlet 1.0.6.

Creating a Database

Before you can store anything in a database, you need to create one. You may have as many independent databases as you wish, with each having as many unique tables-- what we will call sheets in this package, so as to avoid confusion with Lua tables - think spreadsheets.

To create a database, you use the db:create() function, passing it the name of your database and a Lua table containing its schema configuration. A schema is a mold for your database - it defines what goes where. Using the spreadsheet example, ths would mean that you’d define what goes into each column. A simple example:

<lua> db:create("people", {friends={"name", "city", "notes"}, enemies={"name", "city", "notes"}}) </lua>

This will create a database which contains two sheets: one named friends, the other named enemies. Each has three columns, name, city and notes-- and the datatype of each are strings, though the types are very flexible and can be changed basically whenever you would like. It’ll be stored in a file named Database_people.db in your Mudlet config directory on the hard drive should you want to share it.

It’s okay to run this function repeatedly, or to place it at the top-level of a script so that it gets run each time the script is saved: the DB package will not wipe out or clear the existing data in this case. Note that adding new columns to an existing database currently doesn't work in Mudlet 2.1 - see db:create() on how to deal with this.

A note on column or field names: you may not create a field which begins with an underscore. This is strictly reserved to the db package for special use.

Adding Data

To add data to your database, you must first obtain a reference (variable) for it. You do that with the db:get_database function, such as:

<lua>

 local mydb = db:get_database("people")

</lua>

The database object contains certain convenience functions (discussed later, but all are preceded with an underscore), but also a reference to every sheet that currently exists within the database. You then use the db:add() function to add data to the specified sheet.

<lua>

 db:add(mydb.friends, {name="Ixokai", city="Magnagora"})

</lua>

If you would like to add multiple rows at once to the same table, you can do that by just passing in multiple tables:

<lua>

 db:add(mydb.friends,
   {name="Ixokai", city="Magnagora"},
   {name="Vadi", city="New Celest"},
   {name="Heiko", city="Hallifax", notes="The Boss"}
 )

</lua>

Notice that by default, all columns of every table are considered optional-- if you don’t include it in the add, then it will be set to its default value (which is nil by default)

For those familiar with databases: with the DB package, you don’t have to worry about committing or rolling back any changes, it will commit after each action automatically. If you would like more control then this, see Transactions below.

You also cannot control what is the primary key of any sheets managed with DB, nor do you have to create one. Each row will get a unique integer ID that automatically increments, and this field can be accessed as "_row_id".

Querying

Putting data in isn’t any fun if you can’t get it out. If you want every row from the sheet, you can do:

<lua>

 db:fetch(mydb.friends)

</lua>

But rarely is that actually useful; usually you want to get only select data. For example, you only want to get people from the city of Magnagora. To do that you need to specify what criteria the system should use to determine what to return to you. It looks like this:

<lua>

 db:fetch(mydb.friends, db:eq(mydb.friends.city, "Magnagora"))

</lua>

So the basic command is - db:fetch(_sheet_, _what to filter by_)

The following filter operations are defined:

<lua>

 db:eq(field, value[, case_insensitive]) -- Defaults to case insensitive, pass true as the last arg to
             reverse this behavior.
 db:not_eq(field, value[, case_insensitive) -- Not Equal To
 db:lt(field, value) -- Less Than
 db:lte(field, value) -- Less Than or  Equal to.
 db:gt(field, value) -- Greater Than
 db:gte(field, value) -- Greater Than or Equal To
 db:is_nil(field) -- If the column is nil
 db:is_not_nil(field) -- If the column is not nil
 db:like(field, pattern) -- A simple matching pattern. An underscore matches any single character,
         and a percent(%) matches zero or more characters. Case insensitive.
 db:not_like(field, pattern) -- As above, except it'll give you everything but what you ask for.
 db:between(field, lower_bound, upper_bound) -- Tests if the field is between the given bounds (numbers only).
 db:not_between(field, lower_bound, upper_bound) -- As above, only... not.
 db:in_(field, table) -- Tests if the field is in the values of the table. NOTE the trailing underscore!
 db:not_in(field, table) -- Tests if the field is NOT in the values of the table *

</lua>

The db:in_ operator takes a little more explanation. Given a table, it tests if any of the values in the table are in the sheet. For example:

<lua>

 db:in_(mydb.friends.city, {"Magnagora", "New Celest"})

</lua>

It tests if city == "Magnagora" OR city == "New Celest", but with a more concise syntax for longer lists of items.

There are also two logical operators:

<lua>

 db:AND(operation1, ..., operationN)
 db:OR(operation1, operation2)

</lua>

You may pass multiple operations to db:fetch in a table array, and they will be joined together with an AND by default. For example:

<lua>

 db:fetch(mydb.friends,
   {db:eq(mydb.friends.city, "Magnagora"), db:like(mydb.friends.name, "X%")}
 )

</lua>

This will return every record in the sheet which is in the city of Magnagora, and has a name that starts with an X. Again note that in LIKE patterns, a percent is zero or more characters — this is the same effect as "X.*" in pcre patterns. Similarly, an underscore matches any single characters and so is the same as a dot in pcre.

Passing multiple expressions in an array to db:fetch is just a convenience, as its exactly the same as:

<lua>

 db:fetch(mydb.friends, db:AND(db:eq(mydb.friends.city, "Magnagora"), db:like(mydb.friends.name, "I%")))

</lua>

The db:OR operation only takes two arguments, and will check to see if either of the two is true. You can nest these logical operators as deeply as you need to.

You can also just pass in a string directly to db:fetch, but you have to be very careful as this will be passed straight to the SQL layer. If you don’t know any SQL then you want to avoid this… for example, in SQL there’s a very big difference between double and single quotes. If you don’t know that, then stick to the db functions. But an example is:

<lua>

 db:fetch(mydb.friends, "city == 'Magnagora'")

</lua>

Now, the return value of db:fetch() is always a table array that contains a table dictionary representing the full contents of all matching rows in the sheet. These are standard Lua tables, and you can perform all normal Lua operations on them. For example, to find out how many total items are contained in your results, you can simply do #results. If a request from the friends sheet were to return one row that you stored in the results variable, it would look like this if passed into the display() function:

<lua>

 table {
 1: table {
   'name': 'Bob',
   'city': 'Magnagora',
   'notes': 'Trademaster of Tailoring'
 }

} </lua>

And if you were to echo(#results), it would show 1.

The order of the returned rows from db:fetch is generally the same as the order in which you entered them into the database, but no actual guarantees are made to this. If you care about the order then you can pass one or two optional parameters after the query to db:fetch() to control this.

The first table is an array of fields that indicate the column names to sort by; the second is a flag to switch from the default ascending(smallest to largest) sort, to a descending(largest to smallest) sort. For example:

<lua>

 db:fetch(mydb.friends, db:eq(mydb.friends.city, "Magnagora"), {mydb.friends.city})

</lua>

This will return all your friends in Magnagora, sorted by their name, from smallest to largest. To reverse this, you would simply do:

<lua>

 db:fetch(mydb.friends, db:eq(mydb.friends.city, "Magnagora"), {mydb.friends.city}, true)

</lua>

Including more then one field in the array will indicate that in the case that two rows have the same value, the second field should be used to sort them.

If you would like to return ALL rows from a sheet, but still sort them, you can do that by passing nil into the query portion. For example:

<lua>

 db:fetch(mydb.friends, nil, {mydb.friends.city, mydb.friends.name})

</lua>

This will return every friend you have, sorted first by city and then their name.

Indexes and Types

The sheets we’ve defined thus far are very simple, but you can take more control over the process if you need to. For example, you may assign default values and types to columns, and the DB package will attempt to coerce them as appropriate. To do that, you change your db:create() call as:

<lua> db:create("people", {

 friends={"name", "city", "notes"},
 enemies={
   name="",
   city="",
   notes="",
   enemied="",
   kills=0
 }

}) </lua>

This is almost the same as the original definition, but we’ve defined that our first four fields are strings with a default value of blank, and the new kills field which is an integer that starts off at 0. The only way to set a datatype is to set a default value at this time.

Please note, beneath the DB package is SQLite, and SQLite is very data-type neutral. It doesn’t really care very much if you break those rules and put an integer in a string field or vice-versa, but the DB package will — to a limited degree — attempt to convert as appropriate, especially for the operations that work on numbers.

You may also create both standard and unique indexes. A unique index enforces that a certain criteria can only happen once in the sheet. Now, before you go and make indexes, pause and consider. There is no right or wrong answer when it comes to what to index: it depends on what kind of queries you do regularly. If in doubt, don’t make an index. Indexes will speed up reading data from the sheet, but they will slow down writing data.

To add an index, pass either the _index or _unique keys in the table definition. An example:

<lua> db:create("people", {

 friends={"name", "city", "notes"},
 enemies={
   name="",
   city="",
   notes="",
   enemied="",
   kills=0,
   _index = { "city" },
   _unique = { "name" }
 }

}) </lua>

Now, bear in mind: _index = { "name", "city"} creates two indexes in this sheet. One on the city field, one on the name field. But, _index = { {"name", "city"} } creates one index: on the combination of the two. Compound indexes help speed up queries which frequently scan two fields together, but don’t help if you scan one or the other.

The admonition against making indexes willy-nilly holds double for compound indexes: do it only if you really need to!

Uniqueness

As was specified, the _unique key can be used to create a unique index. This will make it so a table can only have one record which fulfills that index. If you use an index such as _unique = { "name" } then names must be unique in the sheet. You can specify more than one key to be unique; in that case they will be checked in an OR condition.

Now, if you use db:add() to insert a record which would violate the unique constraint, a hard error will be thrown which will stop your script. Sometimes that level of protection is too much, and in that case you can specify how the db layer handles violations.

There are three possible ways in which the layer can handle such violations; the default is to FAIL and error out.

You can also specify that the db layer should IGNORE any commands that would cause the unique constraint to be violated, or the new data should REPLACE the existing row.

For example:

<lua> db:add(mydb.enemies, {name="Bob", city="Sacramento"}) db:add(mydb.enemies, {name="Bob", city="San Francisco"}) </lua>

With the name field being declared to be unique, these two commands can’t succeed normally. The first db:add() will create a record with the name of Bob, and the second would cause the uniqueness of the name field to be violated. With the default behavior (FAIL), the second db:add() call will raise an error and halt the script.

If you want the IGNORE behavior, the second command will not cause any errors and it will simply fail silently. Bob’s city will still be Sacramento.

With the REPLACE behavior, the second command will cause its data to completely overwrite and replace the first record. Bob’s city will now be San Francisco.

A word of caution with REPLACE, given:

<lua> db:add(mydb.enemies, {name="Bob", city="Sacramento", notes="This is something."}) db:add(mydb.enemies, {name="Bob", city="San Francisco"}) </lua>

With the REPLACE behavior, the second record will overwrite the first-- but the second record does not have the notes field set. So Bob will now not have any notes. It doesn’t -just- replace existing fields with new ones, it replaces the entire record.

To specify which behavior the db layer should use, add a _violations key to the table definition:

<lua> db:create("people", {

 friends={"name", "city", "notes"},
 enemies={
   name="",
   city="",
   notes="",
   enemied="",
   kills=0,
   _index = { "city" },
   _unique = { "name" },
   _violations = "IGNORE"
 }

}) </lua>

Note that the _violations behavior is sheet-specific.

Timestamps

In addition to strings and floats, the db module also has basic support for timestamps. In the database itself this is recorded as an integer (seconds since 1970) called an epoch, but you can easily convert them to strings for display, or even time tables to use with Lua’s built-in time support.

The most common use for the Timestamp type is where you want the database to automatically record the current time whenever you insert a new row into a sheet. The following example illustrates that:

<lua> local mydb = db:create("combat_log",

 {
   kills = {
     name = "",
     area = "",
     killed = db:Timestamp("CURRENT_TIMESTAMP"),
     _index = { {"name", "killed"} }
   }
 }

)

-- observe that we don't provide the killed field here - DB automatically fills it in for us. db:add(mydb.kills, {name="Drow", area="Undervault"})

results = db:fetch(mydb.kills) display(results) </lua>

The result of that final display would show you this on a newly created sheet:

<lua> table {

 1: table {
   '_row_id': 1
   'area': 'Undervault'
   'name': 'Drow'
   'killed': table {
     '_timestamp': 1264317670
   }
 }

} </lua>

As you can see from this output, the killed fields contains a timestamp-- and that timestamp is stored as an epoch value in the GMT timezone. For your convenience, the db.Timestamp type offers three functions to get the value of the timestamp in easy formats. They are as_string, as_number and as_table, and are called on the timestamp value itself.

The as_number function returns the epoch number, and the as_table function returns a time table. The as_string function returns a string representation of the timestamp, with a default format of "%m-%d-%Y %H:%M:%S". You can override this format to anything you would like. Details of what you can do with epoch values, time tables, and what format codes you can use are specified in the Lua manual at: http://www.lua.org/pil/22.1.html for the Lua date/time functions.

A quick example of the usage of these functions is:

<lua> results = db:fetch(mydb.kills) for _, row in ipairs(results) do

 echo("You killed " .. row.name .. " at: " .. (row.killed and row.killed:as_string() or "no time") .."\n")

end </lua>

Deleting

The db:delete function is used to delete rows from the sheet. It takes two arguments, the first being the sheet you are deleting and the second a query string built using the same functions used to build db:fetch() queries.

For example, to delete all your enemies in the city of Magnagora, you would do:

<lua> db:delete(mydb.enemies, db:eq(mydb.enemies.city, "Magnagora")) </lua>

Be careful in writing these! You may inadvertantly wipe out huge chunks of your sheets if you don’t have the query parameters set just to what you need them to be. Its advised that you first run a db:fetch() with those parameters to test out the results they return.

As a convenience, you may also pass in a result table that was previously retrieved via db:fetch and it will delete only that record from the table. For example, the following will get all of the enemies in Magnagora, and then delete the first one:

<lua> results = db:fetch(mydb.enemies, db:eq(mydb.enemies.city, "Magnagora")) db:delete(mydb.enemies, db:eq(mydb.enemies._row_id, results[1]._row_id)) </lua>

That is equivalent to:

<lua> db:delete(mydb.enemies, results[1]) </lua>

You can even pass a number directly to db:delete if you know what _row_id you want to purge.

A final note of caution: if you want to delete all the records in a sheet, you can do so by only passing in the table reference. To try to protect you from doing this inadvertently, you must also pass true as the query after:

<lua> db:delete(mydb.enemies, true) </lua>

Updating

If you make a change to a table that you have received via db:fetch(), you can save those changes back to the database by doing:

<lua> db:update(mydb.enemies, results[1]) </lua>

A more powerful (and somewhat dangerous, be careful!) function to make changes to the database is db:set, which is capable of making sweeping changes to a column in every row of a sheet. Beware, if you have previously obtained a table from db:fetch, that table will NOT represent this change.

The db:set() function takes two arguments: the field to change, the value to set it to, and the db:fetch() like query to indicate which rows should be affected. If you pass true as the last argument, ALL rows will be changed.

To clear out the notes of all of our friends in Magnagora, we could do:

<lua> db:set(mydb.friends.notes, "", db:eq(mydb.friends.notes, "Magnagora")) </lua>

Be careful in writing these!

Transactions

As was specified earlier, by default the db module commits everything immediately whenever you make a change. For power-users, if you would like to control transactions yourself, the following functions are provided on your database instance:

<lua> local mydb = db:get_database("my_database") mydb._begin() mydb._commit() mydb._rollback() mydb._end() </lua>

Once you issue a mydb._begin() command, autocommit mode will be turned off and stay off until you do a mydb._end(). Thus, if you want to always use transactions explicitly, just put a mydb._begin() right after your db:create() and that database will always be in manual commit mode.

Viewing and editing the database contents

A good tool to view and edit the database contents in raw form is SQlite Sorcerer (free and available for Linux, Windows, Mac).

Debugging raw queries

If you'd like to see the queries that db: is running for you, you can enable debug mode with db.debug_sql = true.

GUI Scripting in Mudlet

Mudlet has extremely powerful GUI capabilities built into it, and with the inclusion of the Geyser and Vyzors layout managers in Mudlet's Lua API it is quicker and easier to get a basic UI created.

Geyser

Geyser is an object oriented framework for creating, updating and organizing GUI elements within Mudlet - it allows you to make your UI easier on Mudlet, and makes it easier for the UI to be compatible with different screen sizes.

Geyser's manual is quite extensive - follow here to read up on it.

UI template

Akaya has created a UI template based on Geyser that can serve as a quick start for customizing the Mudlet interface to your character:

Geyser UI template.png

To get started with it, see the forum thread about Geyser UI Template.

Vyzor

Vyzor is a GUI framework that provides an object-oriented interface for Mudlet's Labels and Qt's Stylesheets. It provides Frames (Vyzor's container object) mapped to Mudlet's borders, which update dynamically or maintain a size specified by the user. Users creates Frames, and can define them using values relative to parent Frames; in this way, you can have widgets that properly map to, say, the right side of the main console. All Frames can be filled with Components, which map directly to Stylesheet Properties.

Vyzors homepage is available here; a guide to using it can be found here.

Simple Window Manager

Simple Window Manager is a light-weight script designed to provide the GUI object placement and sizing functionality seen in Geyser and Vyzor without any of the extras. Any GUI object, once created normally, can be added to the SWM along with a corner to base its positioning info off of, the distance from that corner, and the size of the object in any combination of pixels and percentages of the screen. SWM does all the behind the scenes work to keep all objects it has been given to manage in their correct places and sizes as the screen changes size. All other interactions with GUI objects are handled using standard functions built into Mudlet.

Simple Window Managers manual, and download, can be found here.

Useful Resources

There are several useful resources you can refer to when creating your GUI.

Wiki Resources
Excellent for getting an initial feel of how to use the Geyser layout manager.
The GUI section of the Lua API manual
External Resources
The Geyser Layout Manager subforum on Mudlet's forums.
A pinned thread on Mudlet's forums for showing what your GUI looks like. Useful for ideas and to see what is possible.
A forum thread which follows the evolution of the UI provided by God Wars II

Resetting your UI

While scripting your UI, you can reset it completely and have your scripts be loaded from scratch with the resetProfile() function.