TinyFugue, or `tf' for short, is a very flexible MUD client for UN*X and OS/2. This flexibility, however, does have it's price, and TinyFugue is often seen as being very difficult to learn. This document is designed as a tutorial for the beginner, to help this first step; the idea is that by the end, you will be able to use TinyFugue to almost the fullest extent possible without going deeply into macro programming, and certainly have some idea of how to go about any given task and where to refer to in the help for more information.
You may notice that, for most of the document, I refer to the game to which you are connected as a `MUD'. This is not to say that TinyFugue is only useful for MUDs---far from it---but that (from a quite-possibly-biased MUSHer's viewpoint) I tend to refer to the whole genre of text-based online games as MUDs.
It is a constantly evolving document, which I hope will grow and change as time (and TinyFugue!) goes on, so if you have any comments, criticisms or contributions, please do email me and let me know. In particular, since I have not used OS/2, most of the information in this document is based around the UN*X version. As far as I am aware, the program behaves the same, but I suspect some of the files are different. If anybody knows of any differences between the two which are not documented here, please let me know and I will incorporate them. Also, I am currently using TinyFugue version 3.5 alpha 19, and so any differences in earlier or later versions which you feel ought to be mentioned would also be appreciated.
And finally, due to the nature of this document, you may see some comments <| like this |> scattered through it. These are reminders and remarks about places where I feel the document is not yet finished: it is probably safe to ignore these, but if you can answer the questions, please do get in touch! A not insignificant number of them are issues which I have no way of solving myself, so your input would be appreciated.
These are the longer-term things which I still want to put in, or which I am still wondering if I ought to put in. Any suggestions for addition to (or removal from!) this list would be appreciated, as would submissions for sections like `TinyFugue tips and tricks'. Any submissions of code will be assumed to have been placed in the public domain; if this is not the case, please state it on submission.
This document is copyright 1996 Andrew Mortimer. You are permitted to distribute it freely, either electronically or as hardcopy, either in one of the forms provided or in a different form, so long as the text of the document is left in it's entirety, including this copyright notice, and no significant changes are made to the document structure; you may not charge for the document itself, although you may charge a handling fee.
Any example code contained herein, unless stated otherwise, is free of all copyright and has been placed in the public domain by the author. If the name of an author is specified with the code, this name should be given also in any redistributions of the code.
In this first version of the document, thanks go mainly to Hawkeye for writing TinyFugue!
If TinyFugue is not already installed on your system, you will probably need to compile it first. I don't intend to go into this in any detail here, as it is well documented in the source package in the `README' file, but I think I did ought to mention that under OS/2, you will require EMX to compile it.
To run TinyFugue, simply type tf from a command prompt[1], if the TinyFugue program is on your path. If not, you will have to find out where it is installed and give the full pathname (under UN*X, this would usually be `/usr/games/tf'). As TinyFugue starts up, it reads in it's standard library file `stdlib.tf', a file called `local.tf' in the same directory as `stdlib.tf' if your system administrator has installed one, and finally your personal configuration file[2]. This file sets up various parameters for the program, which will be explained in more detail as we progress, but if TinyFugue behaves in a way which is very different to what is described here, you might wish to check if you have this file. For now, you don't need to do anything with it.
[1] I have no idea if this is valid under OS/2 as well, but it seems a reasonable guess.
When TinyFugue starts up, there are two different screens which you might see, depending on which version you have. Versions before 3.5 alpha 17 default to what is called non-visual mode, where output scrolls up the screen and you type on the bottom line (see figure 1), and versions 3.5 alpha 17 and above default to visual mode, where TinyFugue sections off the bottom few lines of the screen for you to type in, and uses most of the screen for output only. In visual mode, you will also see a status bar separating the two areas, with various information about the current state of TinyFugue. There is a command to change between these two modes, detailed in section 2.6; in this document, I will give all the examples in non-visual mode as it is easier to represent!
TinyFugue version 3.5 alpha 19 Copyright (C) 1993, 1994, 1995, 1996 Ken Keys (hawkeye@tcp.com) Type `/help copyright' for more information. Regexp package is Copyright (c) 1986 by University of Toronto. Type `/help', `/help topics', or `/help intro' for help. Type `/quit' to quit tf. \% Loading commands from /usr/lib/games/tf/tf-35a19-lib/stdlib.tf. \% Loading commands from /usr/lib/games/tf/tf-35a19-lib/local.tf. ---- No world ----
Figure 1: Sample TinyFugue startup, in non-visual mode
To round off this section, I should probably note that to quit TinyFugue, you can simply type /quit, as it says at startup in figure 1, which will close all open connections and return you to the shell prompt.
For those of you who have been using UN*X for a while, especially programs such as Emacs and Bash which use what seems to have become a `standard' set of keys, the basic editing in TinyFugue will be a breeze, although there are some slight differences and many of the more obscure functions have moved totally. For those of you who haven't got used to it, though, and for those of you who use OS/2 and have never had the chance to use UN*X (or, I suppose, never wanted to?), this section will summarise the major editing keys in TinyFugue. <| What's in `kb-os2.tf'? Does it use all the standard (home, end, etc) keys? |> But first, a quick note about the Meta key: under UN*X <| and OS/2? |>, there is a key called `Meta' or `Alt', which can be used in the same way as the control key. However, it can also be `used' by pressing the `Escape' or `Esc' key before the letter or whatever you wish to type. So if you see a key called `Meta-D', say, this can be entered either by holding down the key labelled `Alt' or `Meta' and then pressing `D', or by pressing `Esc' followed by `D'.
First of all, cursor movement. You can use the cursor keys to get around, so that won't be a problem, but there are some others which will make your life easier. The `Ctrl-A' and `Ctrl-E' keys go to the beginning and end of the line, respectively, and the `Ctrl-F' (forward) and `Ctrl-B' (backward) keys move by words[3]. You can also move about in the `history' of lines you have already typed using the `Ctrl-P' and `Ctrl-N' keys.
[3] Emacs users take note: that was with the control key, not the meta key!
Deletion of single characters can be done with the backspace key or the `Ctrl-D' key, which deletes the character under the cursor. Words can be deleted backwards (ie, normally) with `Ctrl-W', or forwards (the word under the cursor) with `Meta-D', and the line can be deleted from the cursor to the end with `Ctrl-K', or the entire line with `Ctrl-U'. And finally, if the screen gets messed up, you can redraw it[4] using the `Ctrl-L' key.
If you can't remember all that, don't worry; it's there for you to refer to later on. But you should at least have some idea how to edit things in TinyFugue now.
Before we get onto any specific commands, though, I ought to mention the way commands are processed in TinyFugue. All communication with the program is done by typing in the data entry area (ie, in the bottom window in visual mode, or on the bottom line in non-visual mode), and so TinyFugue needs some way to distinguish it's own commands from those which you wish to send on to the MUD. It does this by starting all it's commands with a slash, `/'. So, for example, to get help about TinyFugue you would type /help, but to get help about your MUD you might type help (obviously, this depends on whether your MUD supports a help command!).
There are two ways to connect to a remote host on which your MUD is running. The first is almost like the telnet command, in that it simply takes a host name and a port number and connects to the game running there. This is the easiest type to use if you wish to try out a MUD, or indeed TinyFugue, and don't want to go to all the hassle of setting up a world for it (see later). To use this method, type /connect host name port.
This method does have quite a few disadvantages for MUDs you visit often, not least of which is the fact that you have to remember the address of the MUD every time you want to log into it. Wouldn't it be nice if you could give each different game a descriptive name, and get TinyFugue to remember things like the address? And while we're at it, why not take it one step further and store the name of your character as well, and get TinyFugue to connect to that character every time you visit that game? Sound far-fetched? No? Oh, well ...
As you probably guessed, TinyFugue can indeed do this, using the concept of worlds. Worlds are defined using the /addworld command, in one of the following ways:
All of these forms take an optional argument to specify the type of the world, by putting ``-Ttype'' after /addworld above. The type field is used to work out how to log you in, and can have one of the following values:
Note that, although it is strictly speaking optional to specify a world type, it is a good idea. If you don't, TinyMud login format is assumed, but prompts etc are not necessarily handled correctly. It is also possible to specify a macro file to load when you connect to that world; see /help addworld for more details on this and on the command in general.
Having defined the world, you can connect to it using the /connect world command, in exactly the same way as above.
Once connected to a world, by either method, you can type any commands you like, which TinyFugue will then send to the MUD[6]. In order to disconnect again from the world, you can either tell the game to disconnect using a command such as QUIT on a TinyMUSH, or use the /dc command which hangs up TinyFugue's end. It is recommended that you use the first method if it is available, however[7].
<| /listworlds, /unworld, etc |>
Unlike telnet, or many similar programs, TinyFugue allows you to connect to more than one world at the same time, and to switch between them at will. The world which you are displaying and typing to at any point is known as the active or foreground world, and any others are known as background worlds. If you are in visual mode, the name of the foreground world is displayed in the status bar above the input area. When something happens in a background world, then rather than displaying it mixed it with the output from the foreground world, which would be confusing to say the least, TinyFugue displays `% Activity in world name', and lets you go back to it at your leisure. In visual mode, the text `(Active: n)' is also displayed in the status bar, where n is the number of worlds from which have output you have not yet seen.
Connecting to more than one world is easy: you simply give one /connect command for each world, when you want to open it. In order to switch between open worlds, you use the /fg name command, which makes the named world the foreground world and displays anything which has arrived from that world while it was in the background. There is also a /world name command, which combines the functions of the /connect and /fg commands: if you are currently connected to a world, it brings it to the foreground, or if you are not connected it connects you (and then brings it to the foreground).
There is one other command which is useful when you have many worlds open, and that is /listsockets, which prints a table listing all the open worlds, as well as which is the foreground world, how many lines are waiting for you, and some other information.
One of the supplemental files supplied as standard with TinyFugue is also useful here. I will go into these in more detail later, but if you type /require world-q.tf, it will record whenever something arrives in a background world, and add it to a list of active worlds. It will also set up the key Meta-W, otherwise known as Alt-W or ESC-W, to go to the next world on this list, or the previous world you visited if there are no remaining active worlds. I personally find this invaluable.
Having hopefully got TinyFugue up and running, it is time to start fine-tuning our working environment. There are a lot of variables and commands[8] in TinyFugue which change small aspects of the program's behaviour. I have listed here a few of the more important ones, together with examples; more information can be found in various places in the /help if you need it. Note that setting a variable to a given value is done using /set variable=value, so for example to get a 24 hour clock you would type /set clock=24-hour; more details of this are in section 3.1.
Having gone to all the trouble of setting it up, you don't want to have to retype everything whenever you reload TinyFugue. This section tells you how to go about saving all the configuration which you have done so far.
There are two places you can put this sort of information: in your personal configuration file (usually called `.tfrc', or `tfrc' on OS/2 with a FAT filesystem, and stored in your home directory), or in a file which is automatically generated by TinyFugue itself, and the two are not interchangeable. Most of the things which you create, as opposed to changing, can be saved automatically: this includes the worlds you have defined, as well as macro definitions, bindings, hilites, gags, triggers and hooks, more of which later.
First, the automatic ones. Worlds are saved using the command /saveworld, hilites with /savehilite, etc (see table 1); they are loaded again using the command /loadworld, etc. The filenames used depend on whether you are running UN*X or OS/2, by default. If ident is the `filename identifier' given in table 1, then the filename under UN*X is `tiny.ident', and under OS/2 it is `ident.tf'. These defaults can be changed, however, by redefining the `name macro', which is simply a macro the body of which is the name of the file[9], in your `.tfrc' file. Note that if you decide to use this method, you have to include /loadsuffix commands in your `.tfrc' file for all the types you decide to save; they will not get reloaded automatically.
Type | Command Suffix | Filename Identifier | Name macro |
World Definitions | worlds | world | /WORLDFILE |
Macro Definitions | def | macros | /MACROFILE |
Key Bindings | bind | bind | /BINDFILE |
Hilites | hilite | hilite | /HILITEFILE |
Gags | gag | gag | /GAGFILE |
Triggers | trig | trig | /TRIGFILE |
Hooks | hook | hook | /HOOKFILE |
Table 1: The different automatic save files and their associated commands
One final note about using the /saveworld command on later UN*X-based versions of TinyFugue[10]: since version 3.5 alpha 14, you will be warned whenever a password is loaded from a world-readable file. If you get this message, you will need to execute the command chmod go= filename from the shell prompt, where filename is the file in which your world definitions are saved, which will ensure nobody else will be able to access the file.
Now we come on to your personal configuration file, often referred to as simply your `.tfrc' file. You can do just about anything in here, including defining worlds, hilites, etc, although it is recommended that you use the automatically saved files instead if you can, and certainly do not define something in your `.tfrc' file which you are actually using the automatic files for! Other than these, there are a number of things you can do in this file which you cannot do anywhere else, including:
[11] This doesn't work for your own files, though, unless you've set them up specially.
TinyFugue stores many different types of text, including what you have typed, the output from each world, and everything output by TinyFugue, in what is called history buffers. You can access all of these buffers using the /recall command, although the history of things you have typed can more easily be accessed using the `Ctrl-P' and `Ctrl-N' keys to move through it.
The /recall command is very powerful; I shall only skim the surface here. It can be called as /recall buffer-switch range, where range is a plain number, in which case it means to recall that many lines, or something of the form `from-to', where from and to are line numbers in history, either or both of which may be omitted. If you leave out the from parameter, you will have to put `--' before it in order to stop it being processed as a switch, for example as /recall Legends -- -30.
The buffer-switch parameter can be any of the following:
It is also possible to include a pattern after the range, in which case only lines matching the pattern are displayed. See section 3.3 for more details on patterns; if you are using a pattern, you may prefer to give the range as `/n', which means the last n lines which match the pattern.
You can also save the output from a world in a file on disc, using the /log buffer-switch state, where buffer-switch is exactly as for the /recall command, except that you cannot leave it blank, and state is either `ON', to turn logging on, `OFF', to turn logging off, or a filename, to turn logging on and log to that file. Only one file may be open for each buffer-switch. If you switch logging on without specifying a filename, it goes to the file named by the LOGFILE macro, which defaults to `tiny.log'.
<| /relog? |>
This section introduces some of the concepts which will be useful in the next section, when we go on to defining macros and triggers.
A variable is, quite simply, a `slot' in which you store information. The sort of variables we will discuss here are what is called global variables, which is to say that setting them inside a macro is exactly the same as setting them directly[12]. The way to set a variable called var to have the value val is to type /set var=val, either directly or as part of a macro (see section 3.2). You can see what the variable is set to by typing /set var on it's own.
There is also another way to access variables which can only usually be used easily from macros[13], which is to use what is called a percent substitution. There are many different types of these, but the one that concerns us here has the format %{var}, which in a macro body would be replaced by the value of the variable named var. For more details of this, type /help substitution, and make sure you have set /more on!
I won't go into the details of writing macros here, but some of the basics are well worth knowing before we start to discuss triggers and other such things. In essence, a macro is a way of defining your own TinyFugue commands, and in fact a large proportion of the standard TinyFugue commands are actually implemented as macros. Macros can have a name, a body, which is the list of commands which it executes, a maximum number of times it can be executed, a chance of happening, a set of conditions under which it will automatically execute, and more besides, but in this section we will look only at simple macros which you have to call yourself, and which don't do anything fancy.
The simplest macro is one whose body is not a TinyFugue command at all, but just some text which is sent to the current world. For example, the following command:
/def insultmary=say Mary is really quite a silly personwould send the text `say Mary is really quite a silly person' to the game whenever you typed /insultmary, presumably resulting in output similar to `Rolo says "Mary is really quite a silly person"'.
This has quite a few drawbacks (not to mention the likelihood of getting frowned at by Mary!), but the most major of these comes if you are very grumpy, and want to be able to insult lots of different people. Of course, you could define commands called /insultfrodo, /insultmortitia, and so on, but it would be much easier and more flexible to be able to type simply /insult Rhubarb and have TinyFugue put the name in the right place for you. This is done, again, through percent substitution, but instead of the name of a variable you put the position where the text is expected to appear after the command, as a number. So in the above example, where we only wanted one word, we could define our /insult command as follows:
/def insult=say %1 is really quite a silly person
This would get boring after a while, though; it would be nice to be able to choose our insult. Given the comments above, we would be tempted to try something like
/def comment=say %1 is %2in order to allow us to type `/comment Custard the wrong colour' and have it transmit `say Custard is the wrong colour', but unfortunately this doesn't work. The reason? The `%2' would take only the second word, rather than everything that remains, and so we would only say `Custard is the'. Instead, we must replace this with
/def comment=say %1 is %{-1}where `%{-n}' stands for `all words except the first n'.
The other thing which is worth mentioning at this point is regexp substitution, which is (you guessed it!) another type of percent substitution. It is only really worthwhile when using triggers (see section 4.2), but this would seem the logical place to talk about it. If you define a macro which, at any point, uses a regular expression (this is usually in the pattern of a trigger, but can be for example in the regmatch() function), you can use parentheses to denote different parts of the regexp which can then be extracted later. So, for example, if you define a macro which triggers on the regular expression `(Flopsy|Mopsy|Cottontail) has arrived.', your macro can include `say Hello, %{P1}' in the body in order to automatically greet Flopsy, Mopsy or Cottontail whenever they arrive[14]. This explanation has been deliberately left vague, because of complications in using regexp matching; triggers are explained in much more detail in section 4.2 below.
The astute among you will have noticed that so far, I haven't mentioned how to use TinyFugue commands inside a macro. To start with, you are less likely to want to do this, but there will probably come a time when you want to do more in your macros than just output text. The solution is simple, however, as you may have already guessed: enter them just like when you type them in directly! (In other words, TinyFugue macros are always preceded by a slash, which tells TinyFugue to process the command itself rather than sending it to the MUD.) In order to enter more than one command---or to send more than one line to the MUD---you can separate them with the string `%;'[15]:
/def insult=say %1 is really quite a silly person%;/duckwould execute the TinyFugue command /duck whenever you insulted anybody.
It is possible to see the definition of a macro, using the /list name command, which displays the definition of the command name, or all (non-invisible) defined macros if name is not given. You can also give switches to /list, just like to /def, which will make it display only those macros defined using those switches. So, for example, the command /list -t would display all trigger macros. In order to change a macro which you have written, you cannot just use /def[16], but instead you have to use the /edit command. This has exactly the same syntax as /def, but can only be used when the named macro already exists. If you must use /def, though, or if you wish to simply get rid of a macro, the command is /undef name, which removes the definition of the macro name.
And finally, a note about defining commands with no name. This is perfectly possible to do, and can be quite useful when defining triggers etc to save you having to come up with unique names for them all, but as soon as you try to edit or remove it, you will run into problems. However, if you give a /list command with appropriate arguments, you will be able to find it's macro number (see figure 2), which can be used to refer to it in the commands /undefn num or /edit #num which act just like their named counterparts.
% 410: /def -p1 -P1BCgreen -mregexp -F -t'([Ff]es(ber)*(ter)*)' % 411: /def -p1 -P1Cyellow -mregexp -F -t'([Ll]ath(en)*)' % 412: /def -p1 -P1Cyellow -mregexp -F -t'([Tt](ie(mar)*|ack))' % 413: /def -p1 -P1Cyellow -mregexp -F -t'([Aa]q(uila)*)'
Figure 2: Sample output from a /list command, showing some macros without names
There are a lot of times when you want to compare some text (perhaps received from a MUD, or typed in by the user, or whatever) against a certain pattern, and act differently according to whether it matches or not. TinyFugue has three different types of pattern, which I will explain in this here. What I will not do is to give any examples of code, but all of section 4 is about matching text, and doing various things with it. The three matching styles are known as simple matching (`simple'), glob matching (`glob'), and regular expression matching (`regexp').
As the name implies, this is the simplest of the lot, and just compares the two strings character-by-character, treating uppercase and lowercase characters as being different. As a quick example, `text' would match `text', but not `Text' or `more text'.
Those of you who are used to the UN*X shell may have come across the name `glob patterns' before, referring to the way the shell does it's pattern matching, and indeed glob matching in TinyFugue is very similar to this. There are a small number of special characters, listed below, and apart from these few any character stands for itself, uppercase and lowercase being considered to be the same for these purposes. The special characters are:
Note that, unlike regular expressions, the pattern must match the entire string. So to match one word in the middle of a line, you must put an asterisk at either end of your pattern, like `*Dark Avenger*' rather than `Dark Avenger'. To match a word at the beginning of a line only, of course, you would use a pattern such as `Pusspuss *'.
Regular expressions are even more complicated than that. I will only give a summary here; the TinyFugue help about them is pretty complete, so you can look at /help regexp for more information. I have tried to put in a lot of examples, but if you can't understand it on your first read through I suggest you skip it, and come back later.
Regular expressions can be thought of as a sequence of `things', which we will refer to as atoms. The simplest atom is a single character, which (unless it's a special character, more of which later) matches itself. Atoms can be put one after the other to make the regexp, and so the pattern `abcd' would match `abcd'. Note that a regular expression matches a line of text if it matches anything within the line, as distinct from a glob pattern which must match the whole line, so the regular expression `abcd' above would also match `abcdefg' and `The slug said "abcd"'.
In addition to these, there are some other single-character atoms, which have special meanings inside a regular expression:
There are some characters you can add to the end of any atom to change the way it matches. You can only add one of these characters to each atom. They are:
And finally, there are some longer atoms which you can use. These behave exactly the same as the single-character atoms given above, but remember that if you add a *, + or ? to the end it applies to the entire atom, not just the last character. These longer atoms are:
And that is---finally---it. The TinyFugue online help (/help regexp) gives a more technical description of regular expressions, and I think a slightly more complete one, as well as a very useful comparison of glob patterns and regular expressions.
Having waded through all that, we now come on to the useful bits: how to get TinyFugue to notice what comes from the MUD, or from other places, and to react accordingly. This is done---you guessed it!---using the pattern matching methods which you have already met.
For a lot of the things mentioned below, there is a special command to do it, like the /hilite command which marks the entire line containing the text. However, all of these commands are actually macros, which are exactly equivalent to certain forms of the /def command (/hilite pattern simply does /def -ah -t"pattern", for example), and we will have to use these more complicated forms in order to get different styles of highlighting. For now, though, I will simply give in passing these equivalences, and you can refer back to them later.
There are two global variables which control the way hiliting works: matching and hiliteattr. matching can take one of the values `simple', `glob' or `regexp', and determines the default matching style used by commands such as /hilite; this defaults to `glob', and I shall assume this in all the examples which follow. hiliteattr changes the attribute (effect or colour) used by these commands, and defaults to bold (see /help hiliteattr and /help attributes for details on how to change this variable). Both of these can be overridden using the more complicated /def forms of the commands, which we will see how to do in section 4.4.
To start with, when you use TinyFugue, all the output will come up the same colour. This is usually fine, except that when there is a lot of spam, it can be quite difficult to read it all. For this reason, TinyFugue provides a mechanism whereby you can spot certain strings in the text coming from the game, and hilite the entire line which contains them. This is done, as mentioned above, using the /hilite pattern command, which is equivalent to /def -ah -t"pattern". pattern uses the matching style given in the matching variable.
For example, to hilite whenever anybody pages you[17], you would give the command
/hilite {*} pages: *(or /def -ah -t"{*} pages: *") and to hilite any line which contains `Apeman', often because it is your name, you would use
/hilite *Apeman*
Hiliting the entire line is quite limiting, though, especially if you're talking quite a lot or if you want to notice some other people's names being mentioned as well, and so there is an alternative called partial highlighting which highlights only the text matched by the pattern. The command for this is /partial pattern, which is equivalent to /def -Ph -F -t"pattern". Now, you may remember that glob matching and simple matching can only match entire lines, so we can't use them here: what's left is regular expression matching, so the pattern must be a regular expression. The highlighted part is that covered by the entire regular expression.
For example, if as above you were called Apeman, or Ape to your friends, you could give the command
/partial [Aa]pe(man)?and your name would be hilited whenever it appeared, even if whoever typed it forgot the capital letter. This is also useful for `chat channels' or whatever:
/partial ^KewlKroo>would hilite the `KewlKroo>' prefix on any line on the Kewl Kroo's chat channel, rather than the whole line like /hilite would, which makes it much less intrusive.
The major reason for using hilites, as demonstrated above, is to draw your attention to important information which you might otherwise miss. There are some things in this category, though, which simply require you to do something in return; for example, if somebody tries to kill you, or just if somebody waves and you want to wave back. This is what triggers are for.
A trigger is just like a normal macro (see section 3.2), except that rather than waiting for you to run it, it is executed automatically whenever certain text is received from the MUD. The command to do this is /trig pattern=body (or /def -tpattern=body), where pattern is the text you want to trigger on, in the style given by the matching variable, and body is just like the body of a normal macro. For example, if you are on many games at once and there is a lot of background noise, you need to know immediately if somebody starts trying to kill you. The command
/trig {*} tried to kill you!=/echo !!! Attempted murder !!!would set up a trigger to warn you, but not very helpfully; better would be the command
/trig {*} tried to kill you!=/eval /echo !!! Attempted murder !!!%; /fg ${world_name}which would also switch to the offending world[18]. It is not, unfortunately, possible to easily extract and use the name of the person using the /trig command, but it can be done with the more flexible /def command: see section 4.3.
There are, however, some other forms of the /trig command which allow you to modify it's behaviour somewhat. The first, /trigc chance pattern=body, gives the trigger a certain chance out of 100 of executing when the text which it matches arrives, and is otherwise exactly the same as the /trig command. The /trigc command is equivalent to /def -cchance -tpattern=body.
The other change you can make is to give the trigger a priority over other triggers. This usually defaults to 0, but can be set to any value below about 2147483647! When a piece of text arrives, TinyFugue searches through the macros which match it in order of this priority, and executes the first one it comes across. If there are more than one of the same priority, one is chosen at random. This allows you to exclude certain cases from matching, by defining a (possibly empty) trigger of higher priority, as in the following example which would wave back to anyone who waved, unless it was you:
/trig {*} waves=:waves
/trigp 1 Apeman waves
You can also change both these things at once, using the /trigpc priority chance pattern = body command.
Recall the trigger in section 4.2, which would print up a message and switch worlds whenever somebody tried to kill you, but could not attack them back. There is in fact a way to do this, but unfortunately it requires a regular expression pattern, and so we cannot do it with the simple /trig command (unless we set matching=regexp), but must use /def instead.
The /def command has a switch which will set the matching style for a particular trigger, the `-mstyle' switch, where the styles are the same as those for the matching variable. So, for example, the command
/def -t"^[^ ]+ waves$" -mregexp=:waveswould catch whenever anybody waves and wave back, just like the trigger
/trig {*} waves=:wavesin section 4.2 above, if rather less efficiently (note that both of these still need to be overridden for the case `yourname waves').
The use of regular expressions, in particular, has one major advantage: the ability to extract certain parts of the pattern and re-use them in your macro (see sections 3.3 and 3.2), by enclosing them in parentheses and using the `%{Pn}' substitution, where n is the number of the particular set of parentheses. So, for example, the following trigger would wave back to anyone who waved:
/def -t"^([^ ]+) waves$" -mregexp = :waves to %{P1}or, finally, we can attack somebody back when they start trying to kill us:
/def -t"^([^ ]+) tried to kill you!$" -mregexp = kill %{P1}Note that this would only try once for each of their tries, which is still not ideal, but it would give you a bit of time. See appendix ?? for a more sophisticated trigger to deal with this situation.
This sort of thing can also be done with partial highlighting, using a special form of the `-P' switch. Recall that in section 4.1, we said that the /partial command was equivalent to the /def -Ph -F -tregexp command. If, instead of `-Ph', we use `-Pnh', where n is the number of our parenthesised expression, it will hilite only that part of the regular expression[19]. For example, <| there must be a better example? |> on a TinyMUSH game, objects are referenced by dbref numbers, written as `#123', but when you look at some objects it also tells you the flags which are used by the object, as single letters, for example `#123Rh' indicates that object #123 has the ROOM and HALTED flags set on it. The following command will hilite these flags[20]:
/def -F -P1h -t"#[0-9]+([A-Za-z]+)"
[19] Notice the lack of the `-mregexp' switch in the above command; it will not do any harm to include it, but as mentioned in section 4.1, partial hiliting requires a regular expression anyway.
[20] In itself, this is not very useful, but when combined with a command to hilite the dbref in a different colour (see section 4.4) it can make displays much easier to read.
Code | Name | Meaning |
n | normal | Do not apply any other attributes to this |
g | gag | Do not display |
G | norecord | Do not record in history |
u | underline | Underline this text |
r | reverse | Display this text in reverse video |
f | flash | Make this text flash |
d | dim | Display this text dimmed |
B | Bold | Display this text brightened or made bold |
b | bell | Beep when this text is displayed |
h | hilite | Display this text using the attributes in hiliteattr |
Ccolour | Colour | Display this text coloured (see table 3) |
Table 2: The available attributes and their one-letter codes
Foreground | Background |
black | bgblack |
red | bgred |
green | bggreen |
yellow | bgyellow |
blue | bgblue |
magenta | bgmagenta |
cyan | bgcyan |
white | bgwhite |
Table 3: Colour names for use with the `C' attribute
When hiliting text, you are not stuck with just the one way to do it. Of course, if you only have a monochrome display, you will not be able to use colour, but if your terminal supports it you can still gag text (tell TinyFugue not to display it), put it in reverse video, beep when it arrives, underline it, make it flash, and more. A complete list of all available attributes is in table 2.
These attributes are entered using the `-aattr' (for hiliting) or the `-Pattr' and `-Pnattr' (for partial hiliting) switches to /def, using the forms of these commands given in section 4.1. More than one attribute may be entered by simply listing their characters one after the other, except for the normal (`n') attribute, which is rather pointless to combine with any other, and the Colour attribute `Ccolour', which must always be the last named so TinyFugue can read the name of the colour properly.
For example, the following would highlight the name `Anna' in bold red whenever it appeared as a word on it's own:
/def -F -P2BCred -t"([^A-Za-z0-9]|^A-Za-z0-9]|$)"or to make any whispers show up in reverse video and beep when they are displayed, you might use
/def -arb -t"{*} whispers*"
Although TinyFugue comes with it's own set of built-in key bindings, it is possible to define your own, and to change the existing ones, quite easily, using the /bind sequence = command command. Any legal macro body is valid for the command, so you can use key sequences to send strings to the current world, to execute any existing TinyFugue command or macro, and as a macro themselves to do complex operations, although it is usually better to write the macro seperately and then just bind a key to call that macro. To remove the binding again, just call the /unbind sequence command with the same key sequence.
The key sequence can be anything you can type, although it is strongly advised that it does not begin with a common character such as a letter, since once you type a key which starts a keybinding, nothing is displayed until what you have typed could no longer match the binding. For example, if I bound the key sequence `john' to enter `John_The_Fish' into the input buffer, then while I was typing `johan' I would have to wait until the `a' before anything was displayed! Instead, I should bind a key sequence such as `~john', or `Ctrl-X john', or even just `Ctrl-X j', which I would not want to type under any other circumstances.
The sequence parameter to the /bind command can be any string, but in order to allow you to easily include control characters in the sequence, the following conversions are applied to it:
There are two other TinyFugue commands which are very useful for binding to keys: the /input and /dokey commands. The /input string command simply inserts the string into the input buffer as if you had just typed it; for instance,
/bind ^xj=/input John_The_Fishwould make the key sequence `Ctrl-X j' be the same as typing `John_The_Fish'. The /dokey name command performs the function of the predefined editing key name; see /help dokey for a list of these and some examples. This is mostly for redefining the basic TinyFugue bindings.
In the course of a session, TinyFugue will do lots of things for you, such as connecting or disconnecting from a world, changing the current world, loading a file, etc. It can sometimes be useful to be able to execute a command whenever one of these events happen, and the mechanism by which you do this is called hooking. TinyFugue provides 30 or so different parts of it's execution which you can hook into and execute your own commands, using the /hook command.
This command has the syntax /hook hook = body, where body is a standard macro body, and hook is the name of the hook to catch, optionally followed by a pattern. This command is equivalent to /def -h"hook" = body. The name of the hook, and the format of the pattern, can be found under /help hooks, but just to whet your appetite, here are some examples:
Hooks have another use, which is perhaps not so well documented, which is to change the attributes of a lot of the messages which TinyFugue displays to you. This can be done quite simply, using the /def form and including a `-a' switch exactly as for hiliting in section 4.4. You do not need to include a body if you are doing this. For instance, to make all the `---- World world ----' messages come out bold, you would use
/def -hWORLD -aBand to hide all the `% Loading commands from file filename' messages, you could use
/def -hLOAD -ag
A lot of the switches which the /def command takes, and which we have given above, can be used together; for example, it is quite permissible to define a macro which hilites a piece of text as well as acting on it as a trigger (the macro above which notices when you get killed would seem to be a prime candidate for this). However, there are more switches to /def which we haven't mentioned above, or which we have touched on only briefly, and I would like to tell you about some of them here. As ever, though, read /help def for more information about these, and about the others which I won't mention here.
As well as `stdlib.tf', the file containing many of the standard TinyFugue commands, TinyFugue comes with a lot of other files which you can load and use. Some of these are useful, and some are just to demonstrate various aspects of coding; it is the useful ones which I have listed here. If you are interested in coding, I suggest you look through your library directory to see what else is in there. Most of the files have instructions or comments in them, to tell you what they do.
The aim of this appendix is to give some slightly more comprehensive and useful macros than the ones which are used as examples in the text. I have tried to attach a reasonable commentary to each one, but you may find you have to look at the /help for some of the commands.
<| If anybody has any reasonably simple macros which they think would go nicely in here, please feel free to send them to me and I'll put them in. |>
The following macro, still quite a simple one, was written because the TinyMUSH help text is mostly split up into 25-line chunks for those who don't have a MUSH client, but I got bored with having to keep typing more as I read through the help, and it really seemed a shame not to use TinyFugue's paging instead. It looks for lines of the format
and executes the command, so it is not limited to help.`{ 'command' for more }'
/def -T(tiny\.mush(\..*)?) -ag -mregexp -t'^{ \]+)\for more }' = %{P1}Notice that it is set to only work for worlds of type `tiny.mush' or subtypes thereof, and that it gags the line so it is not displayed.
This is an extension of the examples given in sections 4.2 and 4.3, to notice when somebody is trying to kill you and try to kill them. It is a long way from perfect---in particular, it requires a message from the game before each try, in order to prevent it totally spamming the game and leaving twenty commands in the queue when you get killed, it gives an ugly message about the att_name_retry macro not being found, and it will probably crash and burn horribly if somebody with the same name attacks you in more than one world at once, or if more than one person attacks you in the same world---but it should at least serve as a starting point, and it illustrates a few techniques of macro programming. You may have to change the message format for your server. Note, though, that due to the network transmission not being instantaneous, you should never use this macro in your own home (or wherever you get sent when you die), as there will quite probably be a command still in the queue when one of you dies, and if it is you who dies, the fight will just go on!
Since some of these are long macros, I have split them over several lines, terminating each line with a backslash. You will not be able to type it in like this, but if you put it straight into a `.tf' file, you can use /load file.tf and it will be read fine.
/def -mregexp -ah -t'^([^ ]*) tried to kill you!$' = \ /if /eval /test $${att_%{P1}_retry} =~ ""%; /then \ /attack %{P1}%; \ /endif /def attack=\ /def -msimple -t"Your murder attempt failed." att_%1_retry=kill %1%; \ /def -mglob -t"You killed %1!" att_%1_done=/att_%1_stop%; \ /def -mglob -t"%1 killed you!" att_%1_dead=/att_%1_stop%; \ /def att_%1_stop=/undef att_%1_retry%%; \ /undef att_%1_done%%; \ /undef att_%1_dead%%; \ /undef att_%1_stop%; \ /att_%1_retry
This is rather more complicated than any you may have met before, so I shall go through it step-by-step. The trigger defined by the first /def simply waits for somebody to try to kill you, and then (unless you're already in the middle of a fight) calls the /attack command with the name of the person who just attacked you. The attack command define a trigger for when you fail to kill somebody, which simply tries again. It also defines triggers for when you kill them, and when they kill you, which both call the final command which removes all the commands the /attack command has just defined. And finally, it starts the ball rolling by trying to kill them (by calling directly the trigger we defined earlier).
As an example, suppose we have two people called Alice and Bob. Alice, being really rather cunning, has installed this set of macros, but Bob has not. Unfortunately for him, Bob then tries to kill Alice, and the following happens:
<| This macro hasn't been particularly well tested. As I said before, it's far from perfect, but if anyone happens to find any major bugs in it, please do let me know, and I'll try to correct them. |>