Lua script extensions
MUSHclient has some scripting extensions which are only available to Lua scripting. This page describes them.




Lua "sandbox"

To help block out dangerous functions, for example:


os.execute "del mushclient.exe"


... MUSHclient has a 'preliminary script' box in its Global Preferences -> Lua section.

This has code that disables some 'dangerous' functions (like 'os') by setting them to nil.

If you are not planning to run untrusted scripts (eg. plugins) then you can edit that code and comment-out any parts you feel comfortable with having available to your scripts.

The code in this box is executed every time the Lua script engine is instantiated, in other words for every world, and every plugin.

There are suggestions in the default script for how you might modify it to block certain plugins (or worlds) but not others, from having access to dangerous commands. You can do this by using GetWorldID and GetPluginID to find the unique indentifier of the current world or current plugin.

The default behaviour of the sandbox is to disable the following libraries:


  • package.loadlib (so DLLs cannot be loaded, which may have malicious code)

  • io (so files cannot be created or opened)

  • os (some functions) (so operating system calls, such as "execute", cannot be made)

  • The package library is modified so that the "require" statement will not load DLLs.


You can modify the sandbox code to remove all restrictions, add more, or fine-tune them to your requirements. For example, if you wanted to use os.date and os.time (and others), but not os.execute, os.remove or os.rename, you would replace:


os = nil -- no operating system calls


by:


os.execute = nil  -- no executing OS commands
os.remove = nil   -- no removing files
os.rename = nil   -- no renaming files





print function

To make it easier to use Lua examples in MUSHclient, the function "print" is defined to effectively call the world "Note" function. However, unlike Note, print adds a space between each argument (like the Lua "print" does).

eg.


Note "hello, world"
print "hello, world"    --> does the same thing

Note (1, 2, 3) --> 123
print (1, 2, 3) --> 1 2 3





world functions available from global scope

Although the normal MUSHclient script functions are defined in the "world" table, MUSHclient adds a metatable to the _G table and uses the __index entry to make the script functions available at global scope.

Put another way, you can either write:


world.Note "hello, world"  --> calls Note function in world table
Note "hello, world"        --> does the same thing


Both methods call the world.Note script function.

If you wish to change the behaviour of an inbuilt script function you must replace the world.XXX version (eg. change world.Note) rather than simply replacing the global version.




Constants

To make it easier to write scripts, MUSHclient's Lua interface has various built-in tables of constants.


Trigger flags for AddTrigger

These are in the 'trigger_flag' table.


print (trigger_flag.OmitFromOutput) --> 4


All the available keys and values are:


  Enabled  -->  1
  ExpandVariables  -->  512
  IgnoreCase  -->  16
  KeepEvaluating  -->  8
  LowercaseWildcard  -->  2048
  OmitFromLog  -->  2
  OmitFromOutput  -->  4
  RegularExpression  -->  32
  Replace  -->  1024
  Temporary  -->  16384


Trigger flags for AddAlias

These are in the 'alias_flag' table.


print (alias_flag.AliasQueue) --> 4096


All the available keys and values are:


  AliasMenu  -->  8192
  AliasQueue  -->  4096
  AliasSpeedWalk  -->  2048
  Enabled  -->  1
  ExpandVariables  -->  512
  IgnoreAliasCase  -->  32
  OmitFromLogFile  -->  64
  RegularExpression  -->  128
  Replace  -->  1024
  Temporary  -->  16384


Trigger flags for AddTimer

These are in the 'timer_flag' table.


print (timer_flag.OneShot) --> 4


All the available keys and values are:


  ActiveWhenClosed  -->  32
  AtTime  -->  2
  Enabled  -->  1
  OneShot  -->  4
  Replace  -->  1024
  Temporary  -->  16384
  TimerNote  -->  16
  TimerSpeedWalk  -->  8


Custom colour flags for AddTrigger

These are in the 'custom_colour' table.


print (custom_colour.NoChange) --> -1


All the available keys and values are:


  NoChange  -->  -1
  Custom1  -->  0
  Custom2  -->  1
  Custom3  -->  2
  Custom4  -->  3
  Custom5  -->  4
  Custom6  -->  5
  Custom7  -->  6
  Custom8  -->  7
  Custom9  -->  8
  Custom10  -->  9
  Custom11  -->  10
  Custom12  -->  11
  Custom13  -->  12
  Custom14  -->  13
  Custom15  -->  14
  Custom16  -->  15
  CustomOther  -->  16



Error codes - map error names to numbers

These are in the 'error_code' table.

You can use these to check individual error codes.

eg.


if AddTimer (blah blah blah) == 
     error_code.eTimerAlreadyExists  then
  error ("That timer already exists")
end



print (error_code.eBadRegularExpression) --> 30021


All the available keys and values are:


  eAliasAlreadyExists  -->  30011
  eAliasCannotBeEmpty  -->  30012
  eAliasNotFound  -->  30010
  eAlreadyTransferringFile  -->  30052
  eArrayAlreadyExists  -->  30055
  eArrayDoesNotExist  -->  30056
  eArrayNotEvenNumberOfValues  -->  30057
  eBadDelimiter  -->  30059
  eBadMapItem  -->  30023
  eBadParameter  -->  30046
  eBadRegularExpression  -->  30021
  eCannotCreateChatSocket  -->  30042
  eCannotImport  -->  30062
  eCannotLookupDomainName  -->  30043
  eCannotPlaySound  -->  30004
  eChatAlreadyConnected  -->  30049
  eChatAlreadyListening  -->  30047
  eChatIDNotFound  -->  30048
  eChatPersonNotFound  -->  30045
  eClipboardEmpty  -->  30050
  eCommandNotEmpty  -->  30020
  eCommandsNestedTooDeeply  -->  30041
  eCouldNotOpenFile  -->  30013
  eErrorCallingPluginRoutine  -->  30040
  eFileNotFound  -->  30051
  eImportedWithDuplicates  -->  30058
  eInvalidObjectLabel  -->  30008
  eItemInUse  --> 30063
  eKeyDoesNotExist  -->  30061
  eLogFileAlreadyOpen  -->  30015
  eLogFileBadWrite  -->  30016
  eLogFileNotOpen  -->  30014
  eNoChatConnections  -->  30044
  eNoMapItems  -->  30024
  eNoNameSpecified  -->  30003
  eNoSuchCommand  -->  30054
  eNoSuchPlugin  -->  30034
  eNoSuchRoutine  -->  30036
  eNotAPlugin  -->  30035
  eNotTransferringFile  -->  30053
  eOK  -->  0
  eOptionOutOfRange  -->  30026
  ePluginCannotGetOption  -->  30033
  ePluginCannotSetOption  -->  30032
  ePluginCouldNotSaveState  -->  30037
  ePluginDisabled  -->  30039
  ePluginDoesNotSaveState  -->  30037
  ePluginFileNotFound  -->  30030
  eProblemsLoadingPlugin  -->  30031
  eScriptNameNotLocated  -->  30009
  eSetReplacingExistingValue  -->  30060
  eSpellCheckNotActive  -->  30064
  eTimeInvalid  -->  30022
  eTimerAlreadyExists  -->  30018
  eTimerNotFound  -->  30017
  eTriggerAlreadyExists  -->  30006
  eTriggerCannotBeEmpty  -->  30007
  eTriggerLabelNotSpecified  -->  30029
  eTriggerNotFound  -->  30005
  eTriggerSendToInvalid  -->  30028
  eTriggerSequenceOutOfRange  -->  30027
  eUnknownOption  -->  30025
  eVariableNotFound  -->  30019
  eWorldClosed  -->  30002
  eWorldOpen  -->  30001


Error codes - map error codes to descriptions

These are in the 'error_desc' table.

You can use these to give meaningful error messages.

eg.


status = AddTimer ("a", 0, 0, 0, 0, 0)
if status ~= error_code.eOK  then
  error (error_desc [status]) -->  Time given to AddTimer is invalid
end


All the available keys and values are:


 0  -->  No error
  30001  -->  The world is already open
  30002  -->  The world is closed, this action cannot be performed
  30003  -->  No name has been specified where one is required
  30004  -->  The sound file could not be played
  30005  -->  The specified trigger name does not exist
  30006  -->  Attempt to add a trigger that already exists
  30007  -->  The trigger "match" string cannot be empty
  30008  -->  The name of this object is invalid
  30009  -->  Script name is not in the script file
  30010  -->  The specified alias name does not exist
  30011  -->  Attempt to add a alias that already exists
  30012  -->  The alias "match" string cannot be empty
  30013  -->  Unable to open requested file
  30014  -->  Log file was not open
  30015  -->  Log file was already open
  30016  -->  Bad write to log file
  30017  -->  The specified timer name does not exist
  30018  -->  Attempt to add a timer that already exists
  30019  -->  Attempt to delete a variable that does not exist
  30020  -->  Attempt to use SetCommand with a non-empty command window
  30021  -->  Bad regular expression syntax
  30022  -->  Time given to AddTimer is invalid
  30023  -->  Direction given to AddToMapper is invalid
  30024  -->  No items in mapper
  30025  -->  Option name not found
  30026  -->  New value for option is out of range
  30027  -->  Trigger sequence value invalid
  30028  -->  Where to send trigger text to is invalid
  30029  -->  Trigger label not specified/invalid for 'send to variable'
  30030  -->  File name specified for plugin not found
  30031  -->  There was a parsing or other problem loading the plugin
  30032  -->  Plugin is not allowed to set this option
  30033  -->  Plugin is not allowed to get this option
  30034  -->  Requested plugin is not installed
  30035  -->  Only a plugin can do this
  30036  -->  Plugin does not support that subroutine (subroutine not in script)
  30037  -->  Plugin could not save state (eg. no state directory)
  30039  -->  Plugin is currently disabled
  30040  -->  Could not call plugin routine
  30041  -->  Calls to "Execute" nested too deeply
  30042  -->  Unable to create socket for chat connection
  30043  -->  Unable to do DNS (domain name) lookup for chat connection
  30044  -->  No chat connections open
  30045  -->  Requested chat person not connected
  30046  -->  General problem with a parameter to a script call
  30047  -->  Already listening for incoming chats
  30048  -->  Chat session with that ID not found
  30049  -->  Already connected to that server/port
  30050  -->  Cannot get (text from the) clipboard
  30051  -->  Cannot open the specified file
  30052  -->  Already transferring a file
  30053  -->  Not transferring a file
  30054  -->  There is not a command of that name
  30055  -->  That array already exists
  30056  -->  That array does not exist
  30057  -->  Values to be imported into array are not in pairs
  30058  -->  Import succeeded, however some values were overwritten
  30059  -->  Import/export delimiter must be a single character, other than backslash
  30060  -->  Array element set, existing value overwritten
  30061  -->  Array key does not exist
  30062  -->  Cannot import because cannot find unused temporary character
  30063  -->  Cannot delete trigger/alias/timer because it is executing a script
  30064  -->  Spell checker is not active


Colour names - map colour picker names to RGB values

These are in the 'colour_names' table.

You can use these to look up colour names (eg. "red") and find the corresponding RGB value.


Extended colour codes - map colour selector numbers to RGB values

These are in the 'extended_colours' table.

You can use these to see what the RGB equivalent is for the 256 extended colours (keyed by 0 to 255).




Bit manipulation library


The Lua language does not contain native support for bit-wise manipulation of numbers (and, or, exclusive or etc.)

MUSHclient has a few simple extensions that permit that.

They are in the library (table) "bit". The following operations are supported:


bit.shr - shift right

This takes two arguments. Both are converted to unsigned 'long long' (64-bit unsigned integers). The first argument is shifted right the number of bits in the second argument.

eg.


print (bit.shr (1024, 6)) --> 16


bit.ashr - arithmetic shift right

This takes two arguments. The first is converted to signed 'long long' (64-bit integer), the second to unsigned 'long long' (64-bit unsigned integer). The first argument is shifted right the number of bits in the second argument.

Use this version for shifting signed numbers right, as it preserves the sign. (The other version, bit.shr, will shift the sign bit into the number part).

eg.


print (bit.ashr (-1024, 6)) --> -16


bit.shl - shift left

This takes two arguments. The first is converted to signed 'long long' (64-bit integer), the second to unsigned 'long long' (64-bit unsigned integer). The first argument is shifted left the number of bits in the second argument.

You can use this for signed or unsigned numbers, as the sign bit will still be preserved.

eg.


print (bit.shl (4, 6)) --> 256


bit.band - bitwise "and"

This takes one or more arguments. All are converted to signed 'long long' (64-bit integers). The result is all arguments "and-ed" together bitwise.
eg.


print (bit.band (15, 7, 3)) --> 3


bit.bor - bitwise "or"

This takes one or more arguments. All are converted to signed 'long long' (64-bit integers). The result is all arguments "or-ed" together bitwise.
eg.


print (bit.bor (1, 2, 8)) --> 11


bit.xor - bitwise "exclusive or"

This takes one or more arguments. All are converted to signed 'long long' (64-bit integers). The result is all arguments "exclusive or-ed" together bitwise.
eg.


print (bit.xor (15, 1)) --> 14


bit.neg - bitwise "negate" (ones complement)

This takes one argument. It is converted to a signed 'long long' (64-bit integer). The result is the ones-complement of the number (zero bits become one, one bits become zero).
eg.


print (bit.neg (1)) --> -2
print (bit.band (14, bit.neg (8)))  --> 6  (clear 8-bit)


bit.mod - bitwise "modulus" (remainder after integer divide)

This takes two arguments. Both are converted to signed 'long long' (64-bit integers). The result is modulus - the remainder after doing an integer divide of the first argument by the second argument.
eg.


print (bit.mod (17, 4))  --> 1


bit.tonumber (s, base) - convert a string into a number

This takes a string, and converts it into a number. Unlike the standard Lua tonumber function this function will handle up to a 52-bit number (the default Lua number conversion will only go to 32-bit numbers).

eg.


print (bit.tonumber ("A7C5AC471", 16)) -->  45035996273


The base is optional and defaults to 10. The base can be in the range 2 to 36. Fractional numbers are not supported, nor are numbers with exponents (eg. 10.24e15). For such numbers use the standard Lua "tonumber" function.

Because of limitations in the size of a floating point number, the maximum string value that can be converted is a 52 bit number, ie: hex FFFFFFFFFFFFF (decimal 4503599627370495).

Leading whitespace is skipped. After that, there can be an optional + or - sign.

bit.tostring (n, base) - convert a number into a string

This takes a number, and converts it into a string to the given base, in uppercase. The base is optional and defaults to 10. The base can be in the range 2 to 36. Fractional parts are discarded, as the number is first converted to a 64-bit number internally. Negative numbers are OK, and will be converted with a leading "-" sign.

eg.


print (bit.tostring (45035996273, 16)) -->  A7C5AC471





Compression and decompression

MUSHclient offers access to the zLib compression and decompression through the Lua scripting interface. These routines are 8-bit "clean", which means you can compress or decompress any data, including imbedded nul characters.


utils.compress (s [, method] )

Compresses string s and returns the compressed form. Note that it may contain nulls (bytes with a zero value).

The optional argument 'method' indicates the level of compression you want.


  • 0 = no compression
  • 1 = best speed
    ....
  • 9 = best compression


The default, if omitted, is 6.

eg.


comp = utils.compress ("this is a fine thing")


For short strings the compressed data may be longer than the uncompressed data because of a 12-byte "compression information" header that is prepended to the compressed data. For longer text (such as the section above about the Lua sandbox), the compression ratio is about 50%.


utils.decompress (s)

Decompresses string s and returns the decompressed form. Raises an error if decompression cannot be done (eg. bad compressed data).

These two functions should be complementary, so that this should always be true:


x = "some string" -- for any (string) data whatsoever 
y = utils.decompress (utils.compress (x)) --> y should be same as x





Hashing, base-64 encoding and decoding


utils.hash (s)

Returns a 40-character hex string which is the hash of the string 's'. The string 's' may contain the null byte (ie. hex 00). Otherwise, this is the same behaviour as the world.Hash function.

eg.


print (utils.hash ("Nick Gammon")) --> fe09b07227a4e006213ac005831d55b20508a568
print (Hash ("Nick Gammon")) --> fe09b07227a4e006213ac005831d55b20508a568



utils.sha256 (s)

This returns a 256-bit SHA hash (Secure Hash Algorithm) of the string s, which may contain binary zeroes. Unlike the utils.hash function this returns the result as a straight 32-byte (256-bit) field (that is, not converted to printable hex). If you want it in readable form you must then convert it yourself (eg. with utils.tohex).

eg.


print (utils.tohex (utils.sha256 ("nick gammon")))
--> result: B3223193E1C89CB1E42E2BE2DF34874320F43E149DC315A381B08B7BC52849AD


This is a more secure hash than the standard utils.hash algorithm, which returns a 160-bit hash.


utils.md5 (s)

This returns a 128-bit MD5 hash of the string s, which may contain binary zeroes. Unlike the utils.hash function this returns the result as a straight 16-byte (128-bit) field (that is, not converted to printable hex). If you want it in readable form you must then convert it yourself (eg. with utils.tohex).

eg.


print (utils.tohex (utils.md5 ("nick gammon")))
--> result: 9A380FD967D936AC99ED73B4A038CE8C



You can write a small Lua program to do the same thing that the md5sum program does (in Linux, Cygwin etc.):


f = io.open ("docs/RegularExpressions.txt", "rb")
if f then
  print (utils.tohex (utils.md5 (f:read ("*a"))))
  f:close () 
end -- if

--> result: 3764E22E2AC5BA67997C42C288253101


Compare this to the output from md5sum using Cygwin:


$ md5sum RegularExpressions.txt
3764e22e2ac5ba67997c42c288253101 *RegularExpressions.txt


The hash is the same, apart from not being in lower case, which you can change with the string.lower function if you want.


utils.base64encode (s [, linebreaks] )

Encodes the string 's' in base64 encoding (suitable for emails etc.). If 'linebreaks' is true, there will be a carriage return/linefeed every 76 characters. The string 's' may contain the null byte (ie. hex 00). Otherwise, this is the same behaviour as the world.Base64Encode function.

eg.


print (utils.base64encode ("Nick Gammon")) --> TmljayBHYW1tb24=


The output string will be 4/3 times as large as the input string, plus some possible padding to make up the result to a multiple of 4 (the padding character is "="). Also, if you request linebreaks there will be a further 2 byte for every 76 bytes output (that is, every 57 bytes of input). The default is to not have linebreaks.


utils.base64decode (s)

Decodes the string 's' from base64 encoding to plain text. The decoded string may contain the null byte (ie. hex 00). Otherwise, this is the same behaviour as the world.Base64Decode function. Bytes that are invalid are skipped (eg. spaces, newlines, other junk).

eg.


print (utils.base64decode ("TmljayBHYW1tb24=")) --> Nick Gammon


If the source string is not a multiple of 4 bytes then the last few bytes of the decoded string will be lost (because decoding is done in batches of 4 input bytes to 3 output bytes).




Converting strings to/from hex form


utils.tohex (s)

This converts the string s to hexadecimal (printable) form. The string may contain binary zeroes. Use string.lower to make a lower-case version if that is what you prefer.

eg.


print (utils.tohex ("Nick Gammon")) --> 4E69636B2047616D6D6F6E



utils.fromhex (s)


This converts the supplied hexadecimal string s back to a normal string. The converted string may contain binary zeroes.

eg.


print (utils.fromhex ("4E69636B2047616D6D6F6E")) --> Nick Gammon


The supplied string may contain 'space' characters (0x09 – 0x0D or 0x20) which are ignored, otherwise if it contains characters other than A-F, a-f or 0-9 this function raises an error. If the number of characters is odd then the last character is treated as the low-order nibble of the final byte. eg.


a = utils.fromhex ("ABC") --> same as utils.fromhex ("AB0C")


Note that "spaces are ignored" means that a sequence like "A B C D" is treated as the same as "ABCD" not "0A 0B 0C 0D".




utils.readdir (s) - read a disk directory into a table

You can use utils.readdir to read an entire directory on your PC into a Lua table, based on the wildcard you supply.


For example:


t, e = utils.readdir ("c:/mushclient/plugins/*.xml")

assert (t, e) -- raises error on failure



If the directory specification is matched, the result from the call is a table of directory items, keyed by the filename. If the directory specification cannot be matched, or is invalid, it returns nil followed by an error message. You can simply test for non-nil, or call "assert" to report the error.

For each file in the directory (that matches the wildcard) the following is returned:


  • size - file size in bytes
  • create_time - creation time (except for FAT filesystems, where it is omitted)
  • access_time - last access time (except for FAT filesystems, where it is omitted)
  • write_time - time written
  • archive - true if archive. Set whenever the file is changed, and cleared by the BACKUP command.
  • hidden - hidden file. Not normally seen with the DIR command
  • normal - normal file. File can be read or written to without restriction.
  • readonly - read-only. File cannot be opened for writing, and a file with the same name cannot be created.
  • directory - Subdirectory.
  • system - system file. Not normally seen with the DIR command.


By detecting suddirectories you could conceivably recurse and find the contents of subdirectories as well.




utils.split (s, delim) - split a delimited string into a table

The function utils.split is intended to do the reverse of table.concat. That is, it takes a string and generates a table of entries, delimited by single-character delimiters (such as comma or newline).

Example:


test = "the,quick,brown,dog,jumped"  
t = utils.split (test, ",")
tprint (t)
print (table.concat (t, ","))


Output:


1="the"
2="quick"
3="brown"
4="dog"
5="jumped"
the,quick,brown,dog,jumped


You pass utils.split 2 or 3 arguments:


  • The string to be split
  • The single-character delimiter
  • (optional) the maximum number of splits to do


If the 3rd argument is not supplied, or is zero, then the entire string is split. Otherwise, it will be split the number of times you specify. eg.


t = utils.split (test, ",", 2)
tprint (t)


Output:


1="the"
2="quick"
3="brown,dog,jumped"


In this case the remaining text is placed in the 3rd table item.




utils.xmlread (s) - XML parser


The function utils.xmlread uses MUSHclient's internal XML parser to parse an XML string you supply. This effectively would let you parse triggers, aliases etc. that you have copied to the clipboard as text (or created with ExportXML script routine), and see exactly what each value is set to. Or, by reading a MUSHclient world file into memory as a string, you could parse that.

The XML parser is not necessarily 100% industry-standard XML parsing, however it is the method MUSHclient uses for its own XML documents, and should be reasonably compatible with standard XML unless you use some of the more fancy XML extensions. It should certainly parse the XML output by MUSHclient itself (eg. triggers, aliases, world files, plugins) as that is the same routine it uses to read them in.

You pass to the parser a single string, which is the XML to be parsed. If the parsing is successful three results are returned:


  • The root node (all other nodes are children of this node)
  • The root document name (eg. "muclient")
  • A table of custom entities in the document, or nil if no custom entities


If the parsing fails, three results are returned:


  • nil - to indicate failure
  • The error reason
  • The line the error occurred at


You can pass the first 2 results to "assert" to quickly check if the parsing was successful.

Each node consists of a table with the following entries:


  • name - name of the node (eg. <trigger>foo</trigger> - the name is "trigger")
  • content - contents of the node (eg. <trigger>foo</trigger> - the content is "foo")
  • empty - boolean to indicate if the node is empty. (eg. <br/> is an empty node)
  • line - which line in the XML string the node occurred on (eg. line 5)
  • attributes - a table of attributes for this node, keyed by the attribute name (eg. "world_file_version"="15").

    Attribute names have to be unique so we can used a keyed lookup to find them.

    The attributes table is not present if there are no attributes defined.

  • nodes - a table of child nodes, keyed by ascending number (the order they appeared in). Each child node has the same contents as described above.

    Children are not necessarily unique (eg. there may be more than one <trigger> node in a document) so they are keyed by number, and not by node name.

    The nodes table is not present if there are no children of this node.



Example:


a, b, c = utils.xmlread ("<foo><bar x='2'/></foo>")


Output:


"line"=1
"name"=""
"nodes":
  1:
    "line"=1
    "name"="foo"
    "nodes":
      1:
        "line"=1
        "name"="bar"
        "empty"=true
        "content"=""
        "attributes":
          "x"="2"
    "content"=""
"content"=""


You can see from the above that the "root" node is really just an unnamed node which is the placeholder for the top level nodes (ie. the first "real" node is a child of the root node). In this case the node "foo" is the first child of the root node.




utils.msgbox ( msg, title, type, icon, default )


This lets you display a Windows message box (very similar to MsgBox in VBscript). The intention is to allow you to display (in a small dialog box), information of an urgent nature, or ask a yes/no type question.

The calling sequence is:


result = utils.msgbox ( msg, title, type, icon, default )


The only required argument is the message text itself, the others default to their first possible value. The first 4 arguments are string arguments, the last is a number.



  • msg = message to display (max 1000 characters)
  • title = title of box - if nil, defaults to "MUSHclient" (max 100 characters)
  • type = type of box (must be in lower case and exactly as shown here):


    • "ok" - The message box contains one push button: OK. This is the default.
    • "abortretryignore" - The message box contains three push buttons: Abort, Retry, and Ignore.
    • "okcancel" - The message box contains two push buttons: OK and Cancel.
    • "retrycancel" - The message box contains two push buttons: Retry and Cancel.
    • "yesno" - The message box contains two push buttons: Yes and No.
    • "yesnocancel" - The message box contains three push buttons: Yes, No, and Cancel.

  • icon = type of icon:


    • "!" - An exclamation-point icon appears in the message box. This is the default.
    • "?" - A question-mark icon appears in the message box.
    • "i" - An icon consisting of a lowercase letter i in a circle appears in the message box.
    • "." - A stop-sign icon appears in the message box.

  • default = default button (1 - 3)

    This sets the default button (the one with the focus) to be either button 1, 2 or 3. The default is the first button.


Return value = (string) yes, no, ok, retry, ignore, cancel, abort

Example:


print (utils.msgbox ("You are being paged", "Warning!", "ok", "!", 1)) --> ok
print (utils.msgbox ("You are being paged")) --> ok
print (utils.msgbox ("Go ahead?", "Question", "yesno", "?")) --> yes / no





utils.inputbox ( msg, title, default, font, fontsize )

This lets you display a Windows message box and accept a free-format reply (very similar to InputBox in VBscript). The intention is to allow you to display (in a small dialog box) a question and accept a typed response.

The calling sequence is:

result = utils.inputbox ( msg, title, default, font, fontsize )


The only required argument is the message text itself.


  • msg = message to display (max 1000 characters)
  • title = title of box - if nil, defaults to "MUSHclient" (max 100 characters)
  • default = default text - defaults to no text
  • font = font to use in response field - defaults to standard Windows font
  • fontsize = size of font to use (ignored if no font supplied)


Return value = what they typed, or nil if cancelled

Example:


print (utils.inputbox ("What is your name?", "Query", "Nick", "Courier", 9)) --> Peter


Also see below for a similar function: utils.editbox




utils.editbox ( msg, title, default, font, fontsize )

This is almost identical to utils.inputbox, except that the response field:


  • is much larger - for entering large amounts of text
  • has scroll bars - for scrolling through it


Otherwise, the arguments are the same as for utils.inputbox.




utils.choose (msg, title, tbl, default)
utils.listbox (msg, title, tbl, default)
utils.multilistbox (msg, title, tbl, defaults)

These behave very similarly so they will be described together.

These functions display a dialog box with a predetermined list of items for the user to choose from. If the user cancels the dialog box, or does not make a selection, nil is returned. Otherwise the key of the selected item is returned.


  • utils.choose - displays a dialog box with a combo-box in it (drop-down list)
  • utils.listbox - displays a dialog box with a list control in it - single selection
  • utils.multilistbox - displays a dialog box with a list control in it - multiple selections allowed


The utils.listbox function would be more suitable for longer lists, but that is probably partly personal preference.

The utils.multilistbox function allows multiple selecions, so this is useful when you want the user to be able to select multiple items.

The calling sequence is:


result = utils.choose ( msg, title, t, default )


The only required arguments are the message text and the table of choices (t).


  • msg = message to display (max 1000 characters)
  • title = title of box - if nil, defaults to "MUSHclient" (max 100 characters)
  • t - table of key/value pairs, as described below
  • default = default key - defaults to no selection (table of defaults for multilistbox)


Return value = the key of what they selected, or nil if cancelled, or nothing selected. For multilistbox the return value is a table of the selected keys, or nil if nothing selected.

The third argument is a table of key/value pairs. The value is displayed, however the corresponding key is returned. The values are automatically sorted into ascending alphabetic order.

The fourth argument is the key (string or number) which corresponds to the wanted default selection. If it does not correspond to any key in the table then no item will be selected. For multilistbox the fourth argument is a table of the keys of the wanted defaults. For no default selection just pass nil as the default.

Example:



print (utils.choose ("Your favourite", "Foods ...", { "apples", "bananas", "peaches", "cream" } ))


Possible returned values would be:


  • nil - if no choice made or dialog cancelled
  • 1 - apples chosen
  • 2 - bananas chosen
  • 3 - peaches chosen
  • 4 - cream chosen


(Note that peaches would actually be shown 4th in the list as the list is sorted).

To convert from the key back to the value, simply index into your table. Eg.





t = { "apples", "bananas", "peaches", "cream" } 
result = utils.listbox ("Your favourite", "Foods ...", t)

if result then
  print ("You chose", t [result])
else
  print "Nothing chosen"
end -- if





Keys and values can be either strings or numbers. MUSHclient will distinguish between strings and numbers which are the same (eg. "10" and 10 are considered different keys).

Here is an example of using string keys, and supplying a default choice:





t = { 
    fruit = "apple", 
    vegetable = "potato", 
    spice = "pepper", 
    herb = "parsley",
    } 
result = utils.listbox ("Choose a food", "Foods ...", t, "fruit")

if result then
  print ("You chose key", result, "which is", t [result])
else
  print "Nothing chosen"
end -- if





Possible returned values would be:


  • nil - if no choice made or dialog cancelled
  • "fruit" - apple chosen
  • "vegetable" - potato chosen
  • "spice" - pepper chosen
  • "herb" - parsley chosen



The return value will be one of the following types:



  • nil - if no selection made or dialog cancelled
  • string - if an item with a string key is selected
  • number - if an item with a numeric key is selected





Here is an example of using multilistbox:




t = { 
    fruit = "apple", 
    vegetable = "potato", 
    spice = "pepper", 
    herb = "parsley",
    } 

defaults = {
    fruit = true, 
    spice = true, 
    }

result = utils.multilistbox (
    "Choose a food", 
    "Foods ...", 
    t, 
    defaults
    )




In this case if OK is pressed then the variable 'result' is a table of all of the selected keys (eg. {fruit = true, spice = true } if the defaults were taken).



rex - PCRE regular expression library

The functionality of the PCRE (Perl Compatible Regular Expression) library is available to Lua scripts.

re = rex.new (pattern, flags)

This compiles a pattern, returning a regular expression object that can be used to test regular expressions. For example:


re = rex.new ("(.+) goes (.+)")


Flags are optional. If you want to use them you can use the rex.flags () function to convert various flags into numbers.

flag_table = rex.flags ()

This returns a table of PCRE flags. You can index into this to get various compile and runtime flags.

The following are valid compile-time flags:


  • ANCHORED
  • CASELESS
  • DOLLAR_ENDONLY
  • DOTALL
  • EXTENDED
  • EXTRA
  • MULTILINE
  • NO_AUTO_CAPTURE
  • UNGREEDY
  • UTF8



The following are valid execution-time flags:


  • ANCHORED
  • NOTBOL
  • NOTEOL
  • NOTEMPTY


An example of using the flags would be:


re = rex.new ("(.+) goes (.+)", rex.flags ().CASELESS)


This would make a caseless regular expression.


start, end, substrings = re:match (string, pos, flags)

This takes a regular expression object compiled previously with rex.new, and matches it against a target string.

The "pos" argument is optional, and specifies a 1-relative starting point for the match. If omitted, the whole string is tested. You can also supply a negative number to count from the right, eg. -10 would start 10 characters from the end of the string.

The "flags" argument is optional, and specifies execution flags, as described above.

Example:


re = rex.new ("(.+) goes (.+)")
s, e = re:match ("Nick goes East")
print (s, e) --> 1 14


If you are planning to do multiple matches against the same regular expression, it is faster to compile once only (ie. do rex.new once), and test multiple times.

However for once-off tests you can combine them both into the same line:


s, e = rex.new ("(.+) goes (.+)"):match ("Nick goes East")
print (s, e) --> 1 14


The third result returned is a table of capture patterns that have been matched.

Example:


re = rex.new ("(?P<who>.+) goes (?P<where>.+)")
s, e, t = re:match ("Nick goes East")
print (s, e) --> 1 14
table.foreach (t, print) --> see below


Output from table.foreach:

1 Nick
2 East
where East
who Nick

This shows that the 2 capture patterns (the things in round brackets) have been captured in the table as index 1 and 2 (first and second pattern) and also under named indices "where" and "who" because we used named capture patterns in the regular expression.

start, end, offsets = re:exec (string, pos, flags)

This takes a regular expression object compiled previously with rex.new, and matches it against a target string. It takes the same arguments are re:match, however the table returned as the 3rd result consists of pairs of offsets, rather than the strings themselves.

For example:


re = rex.new ("(.+) goes (.+)")
s, e, t = re:exec ("Nick goes East")
print (s, e)  --> 1 14
table.foreach (t, print) --> see below


Output from table.foreach:

1 1
2 4
3 11
4 14

In this case we see that the first capture was from columns 1 to 4, and the second capture was from columns 11 to 14.

result = re:gmatch (string, fun, count, flags)

The gmatch function:


  • Tries to match the regex re against string up to count times (or as many as possible if count is either not given or is not a positive number), subject to execution flags "flags".

  • Each time there is a match, fun is called as fun (m, t), where m is the matched string and t is a table of substring matches (this table contains false in the positions where the corresponding sub-pattern did not match.).

  • If fun returns a true value, then gmatch immediately returns.

  • gmatch returns the number of matches made.



re = rex.new ("(\[A-Za-z\]+)")
n = re:gmatch ("Nick goes East", function (m, t) print (m) end )
print (n) --> 3


Output from function during execution:

Nick
goes
East





utils.functionlist

This returns a table of all the internal MUSHclient function names (the same list used by the Help script function).

The intention here is that you could use this table in an internal Notepad "global replace" to fix the capitalization of function names. You could also use it go generate keywords for use in text editors such as SciTE.

Example:


table.foreachi (utils.functionlist (), print)

-->
1 Accelerator
2 AcceleratorList
3 Activate
4 ActivateClient
5 ActivateNotepad
6 AddAlias

...

296 WorldName
297 WorldPort
298 WriteLog







utils.filepicker

This invokes the Windows standard "file picker" dialog box, which lets you choose a file for opening or saving. Usage is:


filter = { txt = "Text files", ["*"] = "All files" }

filename = utils.filepicker (title, name, extension, filter, save)



  • title - title to appear on the dialog box (eg. "Name of plugin")
  • name - default name of file to be loaded/saved
  • extension - default extension to use
  • filter - table of file filters, see example above
  • save - true for a "save" dialog, false for a "load" dialog


All arguments are optional.

Returns nil if dialog dismissed, or the chosen filename if not.




m1, m2 = utils.metaphone (word)

This returns one or two metaphones (sound-alike codes) for the supplied word. This is the same behaviour as the world.Metaphone function, except that this one returns the secondary metaphone as a separate result.

eg.


print (utils.metaphone ("swordfish"))  --> SRTF XRTF





n = utils.edit_distance (word1, word2)

This returns the Levenshtein Edit Distance between the two words. This is the same behaviour as the world.EditDistance function.

eg.


print (EditDistance ("food", "fodder"))  --> 3





utils.spellcheckdialog

This invokes a GUI dialog box, intended for use with a spell checker.

It is called with two arguments, the first being the misspelt word, the second being a table of suggested replacement words.

If this dialog is cancelled, utils.spellcheckdialog return nil.

Otherwise, it returns two things (which are strings): action, replacement

The action can be one of:


  • ignore
  • ignoreall
  • add
  • change
  • changeall


The replacement is the replacement word - if the user chose "change" or "changeall" (or double-clicked a suggested word).

eg.


print (utils.spellcheckdialog ("fod", {"food", "fodder"} ))  --> change food





utils.info

This returns a table with the following things in it:


  • "current_directory" --> the current directory
  • "app_directory" --> the directory in which MUSHclient.exe resides
  • "world_files_directory" -->> the default world files directory
  • "log_files_directory" --> the default log files directory
  • "plugins_directory" --> the default plugins directory
  • "startup_directory" --> the MUSHclient startup directory


This function is primarily intended for situations where you do not have access to the world "Info" functions, such as in the spell-checker.




progress

This provides functionality for displaying a "progress" dialog box during length operations.

First you create the progress dialog with:

progress.new (description)

That returns a userdata item that you can then use in subsequent operations:


dlg:status (msg) --> set a status message (eg. "processing 'foo' ")
dlg:range (low, high) --> sets the range of operations (eg. 1 to 100)
dlg:position (pos)  --> sets where we are in the range (eg. 80)
dlg:checkcancel ()  --> returns true if the user clicked on the cancel button
dlg:setstep (amount)  --> sets the step amount (eg. 5)
dlg:step ()  --> increase the position by the step amount
dlg:close ()  --> dismiss the progress dialog


Example of use:


local dlg = progress.new ("Loading things ...")
dlg:range (0, 3)  --> 3 steps
dlg:setstep (1)  --> step 1 per step
dlg:status ("Processing foo")  --> first step
dlg:step ()
-- do something lengthy here
dlg:status ("Processing bar")  --> second step
dlg:step ()
-- do something lengthy here
dlg:status ("Processing fruit")  --> third step
dlg:step ()
-- do something lengthy here
dlg:close ()  --> all done


The dialog box is a "modal" dialog, so make sure you arrange to close it, even on a script error, or it will lock out attempts to use the GUI interface. If necessary, use "pcall" on whatever it is you do while the dialog is open.


See Also ...

Topics

DOC_lua_base Lua base functions
DOC_lua_coroutines Lua coroutine functions
DOC_lua_debug Lua debug functions
DOC_lua_io Lua io functions
DOC_lua_math Lua math functions
DOC_lua_os Lua os functions
DOC_lua_string Lua string functions
DOC_lua_tables Lua table functions
DOC_scripting Scripting
DOC_function_list Scripting functions list

(Help topic: general=lua)

DOC_contents Documentation contents page