Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to verify your details, confirm your email, resolve issues, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ MUSHclient ➜ Tips and tricks ➜ Introduction to MUSHclient scripting

Introduction to MUSHclient scripting

This subject is now closed.     Refresh page


Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Fri 28 Oct 2005 12:20 AM (UTC)

Amended on Tue 26 Nov 2013 02:47 AM (UTC) by Nick Gammon

Message

This page can be quickly reached at http://mushclient.com/scripting

An introductory tutorial video on scripting is at http://vimeo.com/80333485.


Why use scripting?

Scripting is optional, but if you choose to do it you can extend the power of the client by doing such things as:

  • Conditional tests (eg. warn if HP low)
  • Arithmetic (count things you kill)
  • Fancy displays (different colours)
  • Stats rollers
  • Automated attacks
  • Auto healer
  • Health bar
  • Level timer
  • Wilderness walker
  • Random socials
  • Lots more ...

Setting up for scripting

The first thing you need to do for each MUSHclient "world" is to enable scripting in the world configuration dialog box. This does a number of things:

  • Indicates you wish to use scripting
  • Declares which script language you are going to use
  • Initializes the appropriate scripting "engine"
  • Declares which script file (to hold your scripts), if any, you want to use
  • Defines a "scripting prefix" for typing script commands into the main command window
  • Optionally lets you specify script handlers for "events" such as when you connect to a MUD

Example:

Setting up scripting - 19K

The important things to configure here are:

  1. Language - I have chosen Lua, however you can choose from (at present):
    • VBscript (a form of Visual Basic)
    • JScript (a form of JavaScript)
    • PerlScript (Perl scripting language)
    • Python
    • Tcl
    • Lua (recommended)
    • PhpScript
  2. Enable scripting - this tells MUSHclient to "turn scripting on".
  3. Script file - this selects the name of the file you are storing scripts in. This is optional and can be omitted initially. A script file is simply a text file you create with a text editor, and save with the appropriate suffix (eg. .lua for Lua files, .vbs for VBscript files).
  4. Script prefix - this is a character string that you will use for entering script commands into the main command window. The default is "/", which the examples below will assume you are using.

Which language?

If you already are familiar with a language, such as VBscript or JScript then it probably makes sense to choose that one. If not, I recommend choosing Lua, for the following reasons:
  • Lua is easy to learn, yet very powerful
  • The Lua script DLL is shipped with MUSHclient - you can be sure the other MUSHclient users (assuming they have a reasonably recent version) will also have Lua.
  • The scripting support for Lua lets you do some more powerful things (like named wildcards in triggers) very easily
  • Lua's "table" structure, and inline functions are very powerful and let you write some very complex scripts
  • Lua is cross-platform, so if you ever want to use MUSHclient on Unix (under the Wine program) you can still use your Lua scripts

The documentation for Lua is at Lua Documentation.


Where do I put my scripts?

Script commands can go in one or more of 4 main places:

  • Type into command window
  • The "Immediate" scripting window
  • Directly into triggers, timers, aliases ("Send to script")
  • Your script file

You can use any or all of the above. Typically the command and intermediate windows are used for quick tests of script commands or short scripts.

Also you can put scripts into plugins, in which case they can also go directly into triggers, aliases and timers (send to script), or into the script part of the plugin.

Let's look at each one in turn. I will demonstrate a "hello, world" script for each one. These examples will use Lua, although they would look very similar in other languages.


Command window

Command window script - 3K

Note that in some languages, such as Lua, script function names and keywords are case-sensitive, so you must use 'Note' rather than 'note'.

If you have images disabled, the graphic shows:

    /Note "Hello, world!"
in the Command window.

In this example the script command is simply typed into the MUD "command" window, prefixed by the scripting prefix, in this case a "/" symbol. This is useful for testing commands to see how they work, eg.

Command window - ColourNote test - 3K

If you have images disabled, the graphic shows:

    /ColourNote ("white", "green", "how does ColourNote work?")
in the Command window.

The command window is most suitable for single-line commands, although you can do multiple lines by pressing Ctrl+Enter to insert linebreaks into the command window. Multiple-line commands are more easily entered in the Immediate window, described below.


Immediate window

Immediate window script - 4K

If you have images disabled, the graphic shows:

    Note "Hello, world!"
in the Immediate window (dialog box).

You access the Immediate window from the Game -> Immediate menu (or press Ctrl+I). This opens a dialog box, as shown above, that lets you type in multi-line script commands, and then press "Run" to execute the script. The contents of the window are remembered for the current MUSHclient session, when you click on the Close button.

For ease of editing you can click on the "Edit..." button to open a larger, resizable, dialog box that has a monospaced font.

The Immediate window is handy for testing larger script snippets, where you might want to test a loop or function definition. If you are planning a lengthy script for a trigger, alias or timer, it could be helpful to test your ideas out first in the Immediate window.

The Immediate window and command window share the same "script space" so that variables or functions declared in one are accessible in the other one (and also in the script file, and "send to script").

An example of what you might try in the Immediate window is:

Immediate window - bigger example - 16K

If you have images disabled, the graphic shows:

    
    colours = { "red", "green", "blue", "magenta", "cyan" }
    for i = 1, 5 do
      ColourNote ("white", colours [i], "Test number " .. i)
    end
    
in the Immediate window and also in the "Edit Immediate expression" window, accessed by clicking on the Edit button.

This example shows a multi-line script, and also shows the "Edit" dialog box being opened for easier editing. I have resized it a bit to make the screen dump smaller, but it shows the general idea. Again you can see that by using Note (or ColourNote or similar) you can display information in the main MUSHclient world window.


In triggers, aliases, timers (send to script)

The third method is to script directly in a trigger, alias or timer. This is known as the "send to script" method, because the text in the trigger/timer/alias is not sent to the MUD but to the script engine.

This method is an easy way of scripting your triggers/timers/aliases because it does not require the use of a separate script file.

Let's start of with our "hello, world" example ...

Send to script example - 25K

If you have images disabled, the graphic shows:

    Note "Hello, world!"
in the Send box of a newly created alias. Also the word "test" is in the Alias box. The "Send To" combo-box has "Script" selected. Every other entry is the default.

The example above shows that the script command is now in the "send" box of the alias, and the "send to" field is set to "script". Thus the command is sent to the script engine. So when I type the word "test" in the command box, the alias matches it, and sends the script command to the script engine, thus "hello, world" appears in the output window.

In the case of "send to script" the script commands are not executed (and thus checked for syntax) until the alias matches, so any syntax errors are only detected then. Thus you need to make sure you check your aliases, triggers and timers after writing scripts for them.

Trigger send to script example - 28K

If you have images disabled, the graphic shows:

    
    if %1 < 100 then
      ColourNote ("white", "red", "Warning! - health is low")
    end -- if
    
in the Send box of a newly created trigger. Also the text "<*/*hp */*m */*mv */*xp> *" is in the Trigger box. The "Send To" combo-box has "Script" selected. Every other entry is the default.

The more elaborate example above shows a trigger designed to test for low health. It uses an "if" test on wildcard 1 (%1 in the script) to see if your health is below 100, and if so, to do a red warning in the output window.

The asterisks in the trigger match text are to match the variable information (ie. hp, mana, movement points, xp).

In the "send" box is the script, however since it is too big to easily edit there, I have clicked on the "..." button which opens up a larger, monospaced font, editing window.

Once again, "send to script" is chosen, so the script commands are executed whenever the trigger matches.

In order to test triggers, it is handy to "force through" something you know will match. Rather than waiting for it to occur in the heat of battle, you can use the "Debug simulated world input" dialog box to pretend that the relevant line has arrived. Press Ctrl+Shift+F12 to open the dialog box. If your trigger matches on specific colours or special text (such as bold) then you can use the "specials" dropdown menu and "insert special" button to insert the desired ANSI codes into your dialog window.

Debug simulated world input - 6K

If you have images disabled, the graphic shows:

    
    <50/1000hp 100/100m 110/110mv 2000/31581675xp>
    
in the "Debug simulated world input" dialog box. There is a blank line before and after this text (entered by pressing Ctrl+Enter).

Above is an example of how I tested the trigger described earlier. Press Ctrl+Enter to enter multiple lines.


In your script file

The final approach to scripting is to put your scripts into a script file (or in the case of plugins, the script area of the plugin). This has the advantages of:

  • Centralising your scripts into a single place, where they can be easily inspected.
  • Share one script routine between multiple triggers, aliases, and timers.
  • Have "helper" scripts that are shared between other scripts (ie. subroutines)
  • Handle special events like:
    • On world open
    • On world close
    • On getting the focus
    • On losing the focus
    • On connecting to the MUD
    • On disconnecting from the MUD
    • On world save
    • Other things, inside a plugin
  • Various MXP-related events, such as a variable being set
  • You can also initialise variables when the script loads, by placing scripts outside of any function.

To put your script in a script file, first you have to create the file (an empty file if necessary), and save it using the appropriate filename suffix for your chosen language (eg. myscript.lua). Then choose that file as your "script file" in the scripting configuration tab, as shown earlier up. Once you have done that you can edit the file using MUSHclient's text editor by using Game -> Edit Script File menu item (or pressing Shift+Ctrl+H), or simply edit it in an external editor of your own choosing.

Whenever the script engine is initialised the script file is read in. If you make changes to it you must reprocess the script file. MUSHclient tries to detect when you do that and offer to reload it automatically, but you can force it to do that by using Game -> Reload Script File menu item (or pressing Shift+Ctrl+R).

When using a script file in this way, any syntax errors in your script are immediately detected, so you can fix them before trying to test the scripts. However runtime errors (such as divide by zero) will not be detected.

Once, again, let's do our "hello, world" example, using a script file...

Script file example - 21K

If you have images disabled, the graphic shows a new alias with "test" in the Alias box. It also has "test_alias" as the function name in the Script box. All other entries are the defaults.

The graphic also shows the contents of the script file (myscripts.lua) being edited. It contains:

    
    function test_alias (name, line, wildcards)
      Note "Hello, world!"
    end -- function
    

In this case we are using a script file function, so the "send to" field is still set to "world" (the default). In practice we can use the "send" box to send things to wherever we want and call the script function.

The important part is that the "script" box is filled in with the name of the script function. This is the name used in the function declaration inside the script (in this case, "test_alias").

The function declaration in the script file must follow the conventions documented for the type of thing calling it.

In the case of triggers and aliases, that is:

  • Name - the name of the trigger or alias (the contents of the "label" field)
  • Line - the entire line that matched the trigger or alias
  • Wildcards - an array of the matching wildcards (if any)

In the case of timers, it is:

  • Name - the name of the timer (the contents of the "label" field)

The purpose of passing down the name of the trigger, alias or timer, is to let the script function share the same function between different things, and let the function detect which one called it by testing the name.

Below is a more elaborate example, that shows how you can process wildcards passed down to an alias, and also pass down a name to it.

Script file elaborate example - 24K

If you have images disabled, the graphic shows a new alias with "^target (?P<target>.*?)$" in the Alias box. It has "target_alias" as the function name in the Script box. The Regular Expression checkbox is checked. It has "target_someone" as the name of the alias in the Label box. All other entries are the defaults.

The graphic also shows the contents of the script file (myscripts.lua) being edited. It contains:

    
    function target_alias (name, line, wildcards)
      Note ("Name = " .. name)
      Note ("Line = " .. line)
      for k, v in pairs (wildcards) do
        Note ("Wildcard " .. k .. " = " .. v)
      end -- for
      SetVariable ("target", wildcards.target)
    end -- function
    

The command windows show me testing it with "target nick" as the command. The Output window shows the results, which are:

    
    Name = target_someone
    Line = target nick
    Wildcard 1 = nick
    Wildcard target = nick
    Wildcard 0 = target nick
    

Interesting points here are:

  • The script function is now in a separate file - the script file, shown in the upper window. This file could be shared between different MUSHclient world files.
  • The alias has a name (a label) which is "target_someone". That is not the name of the script it calls. Names must be unique - that is, only one alias can have a particular name. The name is passed down to the script. In this case it simply displays it. The name is highlighted above in green.
  • The alias calls a script whose function name is "target_alias". Many aliases can call the same script, however only one function of that name can be in the script file. The script name is highlighted above in yellow.
  • I have used a regular expression as the match text so I could use a named wildcard. In this case the wildcard is named "target". This is passed down in the wildcards array (as wildcards.target) so it can be directly used to set a variable. This named wildcard is highlighted above in blue. Named wildcards are a powerful feature that removes the worry of "counting wildcards". You can access a named wildcard directly from the wildcards array in Lua, as shown above. In other languages use the GetAliasWildcard or GetTriggerWildcard script function.
  • Wildcard 1 and wildcard "target" are the same thing - the first wildcard.
  • Wildcard 0 is the entire text that matches the regular expression
  • The "line" argument is the entire matching line that triggered the trigger. In this case wildcard 0 and "line" are identical, however if the regular expression only matched part of the line they would not be.

We can check that the script functioned correctly and set the variable by looking at the variables configuration screen ...

Variable configuration - 6K

If you have images disabled, the graphic shows the variables configuration window, with "target" as the name of the variable, and "nick" as the contents.

Another approach would be to simply type "/Note (GetVariable ("target"))" into the command window and check that the word "nick" was echoed in the output window.


Using variables

Scripts can use two type of variables:

  • Variables supported by the script language itself
  • MUSHclient variables

Each has their pluses and minuses.

Script language variables

These are built into the script language, and are typically:

  • Numbers
  • Strings
  • Booleans (true or false)

and may also include:

  • Arrays
  • Lists
  • Dates/times
  • COM objects
  • Functions

Using the inbuilt variables is easy, you simply script them the way your language supports. The drawbacks are:

  • Variables are lost when the script engine is re-initialised, which might happen a lot during testing
  • Variables are not saved when you close your world
  • Variables are not loaded next time you open your world

MUSHclient variables

MUSHclient variables are accessed by using GetVariable and SetVariable script routines. They are automatically loaded when you open your world file, and saved with your world file when you save it (if you save it, of course).

The drawbacks of MUSHclient variables are:

  • Clumsier to access - you have to use GetVariable and SetVariable to use them
  • They are only strings - thus things like numbers and dates have to be converted to/from strings, and special structures like lists and arrays are harder to store.

You can work around the drawbacks above by scripting appropriately. For example, you can load script variables from MUSHclient variables on world open, and save them back again on world close.


Scripting functions you can call

Apart from the functions you write yourself, and those provided by the script language itself, there are many functions built into MUSHclient that you can call (about 295 of them). They are all documented in the help file that ships with MUSHclient, and also on at MUSHclient scripting functions and MUSHclient online documentation.

As an example, here is the documentation for ColourNote.

The documentation is extensively cross-referenced, for example the ColourNote documentation references other ways of doing notes (eg. not in colour) and also various ways of manipulating colours.


Handling errors

It would be a rare scripter that does not make some sort of mistake during development. The first type is syntax or compile-time errors. An example is below ...

Example script error - 11K

If you have images disabled, the graphic shows the Immediate window with this script in it:

    
    for i = 1, 5 do
      blah (i)
    end -- for
    

The scripting error dialog box has popped up, containing:

    
    [string "Immediate"]:2: attempt to call global 'blah' (a nil value)
    stack traceback:
    	[string "Immediate"]:2: in main chunk
    

In this case I have deliberately called a non-existant function "blah". It is worthwhile reading the error message, as it usually tells you a fair bit. In this case it tells me the error is on line 2 (which I can find by counting down my script), and also the nature of the error, in this case that "blah" is a "nil value". In Lua parlance, that means "blah" is not defined.

Strictly speaking, the above error is not a syntax error but a runtime error. However the general idea is the same, you will see a dialog box similar to the one above.

Recent versions of MUSHclient have an extra option in the scripting configuration dialog that lets you redirect error messages to the output window, rather than a dialog box, which can be more useful if errors occur during a battle.

Logic errors can be harder to find, that is if the script simply isn't doing the right thing. However judicious use of Note can be helpful to display values from inside your script to see what is going wrong.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #1 on Fri 28 Oct 2005 12:57 AM (UTC)

Amended on Mon 22 May 2006 10:27 PM (UTC) by Nick Gammon

Message
Checking return codes

Many script functions in MUSHclient have a "return code" which indicates how successful they were. If your script does not work properly it can be handy to see what the return code is.

For example, if you try this:


SetVariable ("***", "test")


You will find that the variable "***" has not been set, if you check the variables configuration window. But why? Let's find the return code ...


result = SetVariable ("***", "test")
print (result)

Output

30008


We can now look up that code in the MUSHclient help file, or the error codes page:


http://www.gammon.com.au/scripts/doc.php?general=errors


In Lua, we can find the exact meaning inside the script itself, like this:


result = SetVariable ("***", "test")
print (error_desc [result])

Output

The name of this object is invalid


The "error_desc" table is automatically supplied by MUSHclient for Lua scripts.




We can get a bit more elaborate by writing a "return code checker" function, which automatically aborts the script on a bad error return. Here is an example (again, in Lua):


function check (result)
  if result ~= error_code.eOK then
    error (error_desc [result] or 
           string.format ("Unknown error code: %i", result), 
           2) -- error level - whoever called this function
  end -- if
end -- function check 

check ( SetVariable ("***", "test") ) --> line 7


The function "check" tests the supplied error code to be equal to eOK (zero in other words) and if not, raises an error, supplying the appropriate error message.

Now we simply put any doubtful function calls inside the "check" call, as shown above for SetVariable. In this case we now see a dialog box with:


[string "Immediate"]:7: The name of this object is invalid
stack traceback:
	[C]: in function `error'
	[string "Immediate"]:3: in function `check'
	[string "Immediate"]:7: in main chunk


This tells us the line in error (line 7), shows us the explanation of the error, and gives a traceback, so we know which functions were called by which other ones.

You cannot use this technique for all functions, as not all functions return an error code. You need to check the documentation to see which ones it is appropriate to use with.

However for the supported functions, it is very useful to check return codes, otherwise things like AddTrigger can silently fail, which can cause problems further down in your script.

The "check" function above would be a good candidate to put into your main script file, so it could be shared between all of your triggers, aliases, timers etc.




To simplify writing a similar routine in other languages, version 3.68 of MUSHclient will have a new script routine ErrorDesc. This will convert a supplied error code to its equivalent description.

eg.

Note (ErrorDesc (30008)) --> The name of this object is invalid


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #2 on Fri 28 Oct 2005 01:46 AM (UTC)

Amended on Tue 26 Nov 2013 01:07 AM (UTC) by Nick Gammon

Message
Scripting in plugins

You can also use plugins for scripting. See the plugins page for details about making one ...


http://www.gammon.com.au/mushclient/plugins/


Plugins have a number of useful features:


  • They are self-contained, so you can easily distribute a plugin to other MUSHclient users, or simply share them between different worlds of your own.

  • You can use a different script language in each one, so one plugin might use Lua, and another use VBscript.

  • Plugins automatically save and restore their variables (if told to), so they can "save their state" for next time they are run.

  • You don't clutter your main world file with triggers, aliases and timers that are only needed in a plugin.

  • Plugins each have their own "script space" so you don't need to worry about a variable or function in one plugin clashing with another one.


See the next post for various videos you can watch, including how to make a plugin.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #3 on Fri 28 Oct 2005 02:22 AM (UTC)

Amended on Tue 26 Nov 2013 01:08 AM (UTC) by Nick Gammon

Message

Useful references

Lua-specific items

Also, our own versions of the help on Lua base, coroutines, debug, io, math, os, package, string, and table functions.

Documentation from other sites

Script engine downloads from other sites

Tutorial videos


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #4 on Fri 28 Oct 2005 02:27 AM (UTC)
Message
Example scripts

To get started in scripting, peruse the forum here for examples. There are many, many of them.

Also, look at the "exampscript" files that ship with MUSHclient. There are ones for:


  • JScript

  • Lua

  • Perl

  • Python

  • VBscript


These files show examples of accessing MUSHclient functions from a script, and will also illustrate general syntax for each of those languages.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #5 on Fri 28 Oct 2005 05:57 AM (UTC)
Message
Debugging with TraceOut

Another approach to debugging your scripts, which is available in MUSHclient version 3.68 and upwards, is to use the new TraceOut function. Here is the trigger function above modified to use that instead of Note ...


function target_alias (name, line, wildcards)
  TraceOut ("Name = " .. name)
  TraceOut ("Line = " .. line)
  for k, v in pairs (wildcards) do
    TraceOut ("Wildcard " .. k .. " = " .. v)
  end -- for
  SetVariable ("target", wildcards.target)
end -- function


Now when the trigger matches there will be no output to the Output window, unless Trace is turned on, using the Game -> Trace menu item (Ctrl+Alt+T). This lets you leave debugging notes in your scripts and turn them on and off as required.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #6 on Fri 28 Oct 2005 06:55 AM (UTC)
Message
So what can you actually do in scripting?

Well, the short answer is "virtually anything". With 295 script functions (or more), MUSHclient tries to provide something for everyone. Browse the forums for ideas that people have already tried. If you want a stats roller, there are plenty to base one off.

Here are some of the general things you can do in scripts:


  • Find things out (eg. name of the world, its IP address, how long you have been connected, how many lines are in the output window).

  • Add, delete, and modify triggers, timers, aliases, variables.

  • Enable or disable individual, or groups of, triggers, timers, and aliases. You might do this to enable a trigger only under certain circumstances.

  • Find out, and change, configuration options.

  • Script an interface to the Chat system.

  • Script special things to be done when you connect to, or disconnect from, the MUD.

  • Write to the "info bar" - a special status bar reserved for scripters to display information into.

  • Add "accelerator" keys. That is, you can dynamically configure what keystrokes do what commands.

  • Simply do groups of things at once - for example an alias could set a variable, and send a command to the MUD.

  • Interface with other programs using COM (eg. send data to a spreadsheet)

  • Store data in a database

  • Keep lists of useful things, eg. mobs you have killed

  • Use a random-number generator to do things, like randomly say different things

  • Do arithmetic, eg. counting things you have killed, see how long to the next level, work out how much damage your sword does

  • Make some sort of maze or wilderness walker, that tries each exit, and remembers which ones it has been through.

  • Send things to other open worlds (eg. a "send to all worlds" script), or notify you when playing one MUD if you have an interesting message from another one.

  • Script building activities, to automate things you do frequently.



- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #7 on Sun 04 Dec 2005 08:47 PM (UTC)
Message
How to find all the internal script function names

From version 3.70 onwards of MUSHclient, you can open a "functions list" window by pressing Ctrl+Alt+Shift+L.

This shows all inbuilt script function names, you can select one and click OK to see the inbuilt help file topic for that particular function.

There is also a "filter" box which lets you see a subset of function names (eg. ones relating to triggers or aliases).

See this post for more information, and screenshots:


http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=6120

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #8 on Tue 16 May 2006 01:42 AM (UTC)

Amended on Tue 16 May 2006 01:47 AM (UTC) by Nick Gammon

Message
Completing function names

From version 3.73 onwards you can also use "function name completion" when editing inside the inbuilt notepad window.

Simply type a partial function name and press Ctrl+Space, and all function names that match that partial name will be displayed in a small menu. Choose the one you want and hit <Enter> (or <Esc> to cancel).

For example, type "do" then Ctrl+Space to see the functions DoAfter, DoAfterNote, DoAfterSpecial, DoAfterSpeedWalk and DoCommand listed, correctly capitalised.

A similar feature is available when editing in the trigger/alias/timer GUI configuration dialogs. Once you have opened the "edit" box to edit the script, there is a "Complete" button which you can press (or hit Alt+C) to achieve the same effect. If this is a new script, make sure you select "send to script" first, otherwise that button will not be displayed.


In the main command window box, the accelerator is Shift+Tab.

To summarise the different activating accelerator keys:


  • Notepad window: Ctrl+Space

  • Command window: Shift+Tab

  • Editing inside a GUI dialog box: Alt+C


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #9 on Tue 16 May 2006 03:36 AM (UTC)
Message

Here is a screenshot of function-name completion at work ... I typed "do" and pressed Shift+Tab:


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #10 on Tue 16 May 2006 04:25 AM (UTC)

Amended on Tue 16 May 2006 06:17 AM (UTC) by Nick Gammon

Message
How to process variable data

I often get asked how do do things like "count experience" or "warn if health low". This post shows some ways of going about this.

First, let's make a trigger that detects the prompt line, and shows health on the status bar, as well as calculating what percentage health we have.

I have set my prompt to be slightly more interesting than the standard SMAUG prompt, as I want to see maximum health as well as the current health. This line will do that:


prompt <&Y%h/%Hhp &C%m/%Mm &G%v/%Vmv &P%x/%Xxp&d>


An example prompt after doing this is:


<21/21hp 143/143m 110/110mv 0/10000xp>


The next step is to make a trigger, so for accuracy I'll copy that line and paste into the trigger match text:


Match: <21/21hp 143/143m 110/110mv 0/10000xp>


However obviously the numbers will change, so we make them into wildcards:


Match: <*/*hp */*m */*mv */*xp>*


The trailing wildcard allows for data from the MUD which might be appended to the prompt.

Now by simple counting we can see what wildcards do what:


  1. Current health
  2. Maximum health
  3. Current mana
  4. Maximum mana
  5. Current movement
  6. Maximum movement
  7. Current xp
  8. XP needed to level




Scripting in "send to script"

Now to do the calculations, we will set the trigger to "send to script" and refer to the wildcards directly by putting a % in front of the wildcard numbers:


percent = math.floor (%1/%2 * 100)  -- percentage of health left
SetStatus ("Health = %1 / %2 (" .. percent .. "%%)")


I used a doubled %% in the second line to actually print a % symbol.

Here is the finished trigger:


<triggers>
  <trigger
   enabled="y"
   match="&lt;*/*hp */*m */*mv */*xp&gt;*"
   send_to="12"
   sequence="100"
  >
  <send>
percent = math.floor (%1/%2 * 100)
SetStatus ("Health = %1 / %2 (" .. percent .. "%%)")
</send>
  </trigger>
</triggers>




Scripting in the script file

As an alternative to "send to script" we'll access the wildcards from a script function inside our script file.

This time we just leave the trigger as "send to world" (the default) and instead put our scripting into the external script file (myscripts.lua for example).

Now we have to use the third argument to the script function (we can call it anything, but "wildcards" is a reasonable name), and index into that to get each wildcard:


function health_trigger (name, line, wildcards)
  local health = wildcards [1] 
  local maxhealth = wildcards [2]

  local percent = math.floor (health / maxhealth * 100)
  SetStatus ("Health = " .. health  .. " / " .. maxhealth .. 
             " (" .. percent .. "%)")

end -- function health_trigger 


Since I have chosen to call the function "health_trigger" then that is the name we have to use in the trigger as the script name:


<triggers>
  <trigger
   enabled="y"
   match="&lt;*/*hp */*m */*mv */*xp&gt;*"
   script="health_trigger"
   sequence="100"
  >
  </trigger>
</triggers>




Making the match text more robust

The trigger match text shown above uses "simple wildcards" (namely the "*" character). These are probably fine in this case, but to be really correct we should make the trigger match exactly what we expect, namely digits and not just any text. For example, if we happened to get "<abc/def hp hij/klm m xxx/zzz mv lll/kkk xp>" from the MUD, the trigger would match, and probably raise a script error to do with trying to do arithmetic on string values.

So, we will edit the trigger and click on the "Convert to Regular Expression" button, which makes it look like this:


Match: ^\<(.*?)\/(.*?)hp (.*?)\/(.*?)m (.*?)\/(.*?)mv (.*?)\/(.*?)xp\>(.*?)$


Now we want each of the wildcards to really match on one or more digits, so we replace "(.*?)" by "(\d+)". The final wildcard doesn't want to be a number, and we can simply omit it and the final "$". This is what we end up with:


Match: ^\<(\d+)\/(\d+)hp (\d+)\/(\d+)m (\d+)\/(\d+)mv (\d+)\/(\d+)xp\>




Using named wildcards

The next problem is the tedium of counting wildcards. If we wanted to get maximum XP, is it wildcard 7 or 8? What if we add another wildcard? That might throw the numbers out. To solve this we can name each wildcard in the regular expression, like this:


^\<(?P<health>\d+)\/(?P<maxhealth>\d+)hp (?P<mana>\d+)\/(?P<maxmana>\d+)m (?P<move>\d+)\/(?P<maxmove>\d+)mv (?P<xp>\d+)\/(?P<xptolevel>\d+)xp\>


It may look complex but basically each wildcard is:


(?P<name>\d+)


Testing this trigger using the functions ExampleTrigger and tprint, shipped with MUSHclient in the exampscript.lua file, we see what is happening:


Matching line was: <21/21hp 143/143m 110/110mv 0/10000xp>
Wildcards ...
1="21"
2="21"
3="143"
4="143"
5="110"
6="110"
7="0"
8="10000"
"maxhealth"="21"
"move"="110"
0="<21/21hp 143/143m 110/110mv 0/10000xp>"
"maxmana"="143"
"xp"="0"
"health"="21"
"maxmove"="110"
"mana"="143"
"xptolevel"="10000"


Now in addition to the 8 numbered wildcards, the same information is also present in named wildcards.

The trigger can now refer to the wildcards by name. Let's see what it looks like with "send to script". The script text will be:


percent = math.floor (%<health>/%<maxhealth> * 100)
SetStatus ("Health = %<health> / %<maxhealth> (" .. percent .. "%%)")


Now we use %<name> to access each of the named wildcards.

The completed trigger is:


<triggers>
<trigger
enabled="y"
match="^\&lt;(?P&lt;health&gt;\d+)\/(?P&lt;maxhealth&gt;\d+)hp (?P&lt;mana&gt;\d+)\/(?P&lt;maxmana&gt;\d+)m (?P&lt;move&gt;\d+)\/(?P&lt;maxmove&gt;\d+)mv (?P&lt;xp&gt;\d+)\/(?P&lt;xptolevel&gt;\d+)xp\&gt;"
regexp="y"
send_to="12"
sequence="100"
>
<send>percent = math.floor (%&lt;health&gt;/%&lt;maxhealth&gt; * 100)
SetStatus ("Health = %&lt;health&gt; / %&lt;maxhealth&gt; (" .. percent .. "%%)")</send>
</trigger>
</triggers>


Or, if we are using a script file, the trigger will be:


<triggers>
<trigger
enabled="y"
match="^\&lt;(?P&lt;health&gt;\d+)\/(?P&lt;maxhealth&gt;\d+)hp (?P&lt;mana&gt;\d+)\/(?P&lt;maxmana&gt;\d+)m (?P&lt;move&gt;\d+)\/(?P&lt;maxmove&gt;\d+)mv (?P&lt;xp&gt;\d+)\/(?P&lt;xptolevel&gt;\d+)xp\&gt;"
regexp="y"
script="health_trigger"
sequence="100"
>
</trigger>
</triggers>


And the script file function will be:


function health_trigger (name, line, wildcards)
  local health = wildcards.health 
  local maxhealth = wildcards.maxhealth

  local percent = math.floor (health / maxhealth * 100)
  SetStatus ("Health = " .. health  .. " / " .. maxhealth .. 
             " (" .. percent .. "%)")

end -- function health_trigger 


Now we can access the wildcard information using wildcards.health and wildcards.maxhealth.

This is particularly easy in Lua. If you are using other script languages, you will need to call the script function GetTriggerWildcard to extract wildcards by name:


http://www.gammon.com.au/scripts/doc.php?function=GetTriggerWildcard

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #11 on Sun 28 Jan 2007 09:41 PM (UTC)
Message
Serializing variables

Complex scripts are likely to need to keep lots of variable information (eg. mob lists, player lists, room lists).

An important part of doing that is keeping the variables "alive" from one session of MUSHclient to another. A simple way is to simply use MUSHclient variables, which are simple string values which are saved as part of the world file.

A drawback of doing that is that you always need to save the world, in order to save the variables, which you may not want to do in case you have accidentally changed something elsewhere.

Also, simple string variables do not lend themselves to storing nested data (like Lua tables).

Assuming that you are using the recommended script language - Lua - the ideas below show how you can easily save an entire Lua table when required, and then reload that table next time.

An important aspect to this is to store your variables in a table. In any case, all variables in Lua (except local variables) are stored in a table anyway, however if you do not specifically mention a table, they are stored in the global "environment" table.

For example:


health = 5


... is really equivalent to:


_G.health = 5  -- variable "health" in the _G table


However, serializing (saving) the _G table is not really practical, as it contains things like functions, which cannot be serialized.

Thus, I recommend you place all variables needed for scripts into their own table. For example:


mobs = mobs or {}  -- create mobs table if it doesn't exist

mobs.kobold = {}  -- make a sub-table for a kobold

mobs.kobold.hp = 22   -- kobold has 22 hp
mobs.kobold.gold = 100  -- and 100 gold


Now, to save this table to disk somehow it needs to be turned into a string. The serialize function (inside serialize.lua file that ships with MUSHclient) will do that.

For example, given the table above, if we do this:


require "serialize"
print ((serialize.save ("mobs")))


We will see displayed on the screen:


mobs = {}
  mobs.kobold = {}
    mobs.kobold.hp = 22
    mobs.kobold.gold = 100


This is now something that is a Lua script, that can be saved to disk, and used later to rebuild that table.

If our script is the "main world" script, all we really have to do is serialize into a variable, and let that variable be saved when the world is saved. For example:


require "serialize"
SetVariable ("mobs", serialize.save ("mobs"))


This could be done every time we change something or, more efficiently, as part of the "Save" script (see the scripting configuration tab).

Saving our table in a plugin

Plugins make it easy to save things, because we just need to make an OnPluginSaveState function inside the plugin.


require "serialize"

function OnPluginSaveState ()
  SetVariable ("mobs", serialize.save ("mobs"))
end --- function OnPluginSaveState


Also make sure that the line:


 save_state="y"


... appears in the plugin header part.

This ensure that, whenever the plugin saves its state, the table "mobs" is serialized and saved as part of the plugin state file.

Of course, you would change the word "mobs" to be whatever your table is. You could also serialize multiple tables easily enough, like this:


require "serialize"

function OnPluginSaveState ()
  SetVariable ("mobs", serialize.save ("mobs"))
  SetVariable ("players", serialize.save ("players"))
  SetVariable ("rooms", serialize.save ("rooms"))
end --- function OnPluginSaveState


Loading the table next time around

Once we have the table serialized into a string, we need to be able to turn it back into a Lua table next time. This is easily done by the Lua loadstring function, which takes an arbitrary string and interprets it as Lua code. For example, to process that saved mobs table, we could do this:


loadstring (GetVariable ("mobs")) ()


The final parentheses are because loadstring returns a function, which has to be called to actually execute it and create the table.

It would be preferable to check for syntax errors, so we use assert to do that:


assert (loadstring (GetVariable ("mobs"))) ()


And, the very first time we do this the variable mobs might be empty, so we substitute an empty string so we don't get an error message:


assert (loadstring (GetVariable ("mobs") or "")) ()


Finally, for that first time through, if we didn't have a mobs variable, then we also didn't get a table created, so we will make one if necessary:


assert (loadstring (GetVariable ("mobs") or "")) ()
mobs = mobs or {}  -- ensure table exists


We can put all that together in a plugin, in the OnPluginInstall function, which gets called when the plugin is installed:


-- load data from variable in state file into a Lua table

function OnPluginInstall ()
  assert (loadstring (GetVariable ("mobs") or "")) ()
  mobs = mobs or {}  -- ensure table exists
end -- function OnPluginInstall


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

Posted by Nick Gammon   Australia  (23,102 posts)  Bio   Forum Administrator
Date Reply #12 on Sat 20 Oct 2007 05:24 AM (UTC)
Message
Recent versions of the serialize script have a "save_simple" function that saves in a neater way. Compare this:


require "serialize"
print ((serialize.save ("mobs")))


--> Output

mobs = {}
  mobs.kobold = {}
    mobs.kobold.hp = 22
    mobs.kobold.gold = 100


To this:



require "serialize"
print ("mobs = " .. serialize.save_simple (mobs))

--> Output

mobs = {
  kobold = {
    hp = 22,
    gold = 100,
    },
  }


The output in the second case looks more like a regular table definition.

Also, to avoid using SetVariable you can use the "var" script (supplied with MUSHclient), so the plugin save can look like this:


require "var"
require "serialize"

function OnPluginSaveState ()
  var.mobs = "mobs = " .. serialize.save_simple (mobs)
end --- function OnPluginSaveState


- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


135,434 views.

This subject is now closed.     Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.