8.3. Defining Triggers in the Command Line

For those more used to Perl and to command lines, it is also possible to define and alter triggers from the command line. This is done with a series of Perl function.

Triggers are defined with the $world->trigger function. There are many ways to use this function, and we will start describing them here.

The simples way to use that function is with two arguments: $world->trigger(pattern, action). This defines a trigger that will match against pattern and execute action when a match happens.

What exactly is pattern? It is a regular expression. If you know Perl, you certainly know what is a regular expression. If not, it is advised that you look for some more information on it, there are plenty of tutorials on the Internet. For those who know them, you can use the full power of Perl's regular expressions in triggers, because Perl is used for the matching.

What about the action? It can be anything that could be entered in the command line. If it is simply a command, that command will be sent to the MUD. Or you can stack several commands separating them with %; as described in Section 3.2.

Here's an example of a very simple trigger:

Example 8-1. A very simple trigger


$world->trigger('has attacked you!', 'wield sword')

Whenever a line that contains the phrase "has attacked you!" is received, the command "wield sword" will be automatically be sent. Note that in this case the received line will probably be something like "An orc has attacked you!", but the trigger matches because that line contains the pattern. This is a feature of regular expressions. If you want to match the entire line and not only part of it, you must use the ^ and $ anchors in the beginning and end, respectively.

But the real power is the fact that you can also run Perl commands in response to triggers. Just enter them as you would in the command box: prefixed by /. If the action is simple you can enter the statements there directly, if not, you can define a function in your script file and call it from the trigger.

If the action is a Perl statement, and the pattern contains bracketed expressions, you can access the matched text in the brackets with the variables $_[1], $_[2], and so on. In $_[0] you will find the whole matched line.

Let us rewrite the previous example to be more complex:

Example 8-2. A trigger with an action in Perl


$world->trigger('^(.*) has attacked you!$',
                '/$world->send("cast missile $_[1]")

Now, when a line consisting of some arbitrary text followed by "has attacked you!" is received, the $world->send will be called to send some text to the World. This text consists of cast missile followed by the text that was before "has attacked you!", which is presumably the name of a mob. So if the received line is "Sauron has attacked you!", the command that will be sent is "cast missile Sauron".

If you call a sub-routine as the trigger action, the matched arguments are not automatically passed, so you need to pass them manually. Since they are in the array @_, just pass that whole array as the argument to the sub-routine. Inside the sub-routine, they will be available as the sub-routine's arguments, which incidentally means that they will be accessed in the exact same way: with $_[1], $_[2] and so on.

Here's an example of a trigger that calls a sub-routine:

Example 8-3. A trigger that calls a sub-routine


$world->trigger('^(.*) has attacked you!$',
                '/myGreatAttackSequence(@_)')

Naturally, you need to define the myGreatAttackSequence sub-routine in your script file. It will be called as the result of the trigger. The @_ array, containing the whole matched line and the matched bracketed expressions is passed to the sub-routine as argument, and its contents will be available to the sub-routine as its paramenters.

8.3.1. Advanced Features of Triggers

Now that the basic usage of triggers has been explained, let us see some more advanced features, which will require some small changes in the way the trigger function is called.

In KildClient, gags are just triggers that are marked as such, and they behave just like other triggers, with the exception that the line that matched the pattern does not get printed in the screen. It is possible to have a gag that executes an action when triggered, just like with non-gag triggers, or it can be a simple gag, which prevents the line from being printed but does nothing else.

To specify that a trigger is a gag when it is created an extra argument is passed to the trigger function. For those that know Perl, it is a reference to a hash, and this hash can contain some attributes specifying the trigger's behaviour. For those that do not understand the previous sentence, do not worry, the examples should make the usage clear.

Let's create a simple gag to omit all that is said by Joe, a very silly and annoying player:

Example 8-4. A simple gag


$world->trigger('^Joe chats',
                '/$world->echonl("Joe said something silly here.")',
                { gag => 1 })

See the difference? There is a third parameter, enclosed in curly braces {}, with the word gag, an arrow, and the number 1. What that means is that gag has the value 1, or, as usual in computer languages, is true. (False would be zero.) The trigger is a gag, and whenever a line starting with "Joe chats" is received, it will not be printed. Instead, the code specified in the action will be executed, and that will print a message that tells you that Joe said something, but does not tell what, so you do not need to worry.

That is good, but can get even better. We do not need to know that Joe said something, it would be better if we could ignore him altogether. And we can. It is not necessary to specify an action for a gag. In this case, nothing will be done, but the matched line will not be printed. So our example becomes:

Example 8-5. A gag with no action


$world->trigger('^Joe chats',
                { gag => 1 })

The action has simply been omitted, but the argument that specifies that we are dealing with a gag remains, naturally. As a matter of fact, this kind of gag (with no action) is quite common, and there is a shorter way to do that. The command below has the exact same effect as the previous one:

Example 8-6. A shortcut function to define gags


$world->gag('^Joe chats')

Sometimes you want to change the way a line is displayed, such as highlighting it or part of it, or rearranging the information so that it appears in another way. The way to do that is with a gag, and an action that outputs the modified line. Heres a simple example that changes the way a hypothetical chat channel is displayed:

Example 8-7. Rewriting a line with gags


$world->trigger('^(.*) chats, \'(.*)\'',
                '/$world->echonl("[Chat] $_[1]: $_[2]")',
                { gag => 1 })

Remember that $_[0], the first element of the array @_ contains the whole matched line, with ANSI codes stripped. If you need the original string including ANSI codes, you can find them in the variable $colorline. A useful function you should check is colorize (see colorize), which allows easy printing of colored strings.

The gag attribute only controls whether the line is displayed in the screen or not. If you have logging enabled (see Chapter 14), a the line that matched a trigger with the gag attribute will be written in the log file normally. If you want the line to be omitted from the log file, you must set also the gaglog attribute. If it is set, then the line will not be written to the log file. Note that the two attributes are independent: you can gag the line from the screen, from the log file, from both or from neither.

To make case be ignored when matching, use a very similar construct as the above, but using the ignorecase attribute:

Example 8-8. A case-insensitive trigger


$world->trigger('^.* chats ".*joe.*"', '/callAttention()',
                { ignorecase => 1})

Another attribute that can be set for triggers is keepexecuting. By default when a trigger matches, no more checking is done. So if a line would match two or more triggers, only the first would have a successful match and have its action executed. The other ones wouldn't even have a chance to try a match.

When a trigger has the keepexecuting flag set, even if it matches it does not prevent other triggers from being tried. So, if a second trigger matches, the actions of both will be executed. Note, however, that unless this second trigger also has the keepexecuting flag, trigger matching will stop.

This flag is specified just like gag. Just add the parameter { keepexecuting => 1 } to the function call. It is possible for a trigger to be both a gag and have the keepexecuting flag. In this case, include both in the last argument: { gag => 1, keepexecuting => 1 }. Note it is just one argument, with both flags separated by a comma. The order does not matter, keepexecuting could have come before gag.

If you are having trouble with triggers and want to be informed whenver a trigger matches, enable the Preferences->Debug Matches menu. When this is enabled, information about each matched trigger will be printed to stderr. (This means you must start KildClient from a terminal to see the output.)

8.3.2. Rewriter Triggers

Rewriter triggers are a special kind of trigger that allows something not possible with ordinary triggers: changing the received line so that futher triggers work on this changed line.

As mentioned above, the $colorline variable holds the entire line that matched the trigger, including ANSI color codes. Rewriter triggers can alter this variable, and then the other triggers will match on this changed line.

Rewriter triggers run before normal triggers. Also, the keepexecuting flag is not considered for them: they never stop processing of other triggers, even if keepexecuting is not set. The gag and gaglog flags are also not significant for a rewriter trigger, and they are ignore.

Let's see an example of where rewriter triggers can be useful. Suppose you do not want to see an offensive word that might appear in the MUD. A first attempt at filtering could be a trigger like this:

Example 8-9. A profanity filter, that unfortunately does not work


$world->trigger('fuck',
                '/$colorline =~ s/fuck/f***/; $world->echo($colorline)',
                { gag => 1 })

This works for simple cases, but it is most likely not what you want. Suppose you have a trigger that captures messages sent from a specific channel (such as the one for telepathic communications) and does something, such as changing the presentation of the message or sending them to another window. If someone sends a message over that channel with the f-word, the channel-capture trigger will not work because the line has already been caught the the profanity-filter trigger above. You'll see the message with the bad word filtered, but it will not be captured as a channel message.

You might think about setting the keepexecuting flag in the profanity-filter trigger, but that would not solve the problem. That trigger would match, and print in the main window the line just with the word filtered. And then the original line would match against the channel-capture trigger, thus acting upon the channel message, but with the bad word intact, because the trigger did not change the line. So you would end up seeing the message twice, but neither case in the way you want.

The solution to this problem is to use a rewriter trigger to filter the bad word before other triggers have a chance to see the line. Our profanity-filter trigger should be like this:

Example 8-10. A profanity filter using a rewriter trigger


$world->trigger('fuck',
                '/$colorline =~ s/fuck/f***/',
                { rewriter => 1 })

Note that the line is not printed. Rewriter triggers generally need not print anything, because if no other trigger matches the line, it will be printed (with the changed contents). If another trigger does, let this trigger decide what to do.

Now everything should work: when any line with the f-word is seen, this word is filtered out and the line is changed. If this was a channel message, the channel-capture trigger will see the line with the offensive word filtered and display the message in your special way without the bad word.

8.3.3. Editing Triggers

In the previous sections it was shown how to add triggers, but nothing was told about how to change them after they are defined. This and other points will be addressed in this section.

Before going into editing, let us see how to get a list of all triggers that are currently defined for the World. Just use the $world->listtrigger function without arguments. You will be presented with a list of the currently defined triggers.

There are several columns: Num is the number of the trigger. Triggers are numbered sequentially starting at zero. This number will be useful when we start editing triggers. Next comes gag, which tells whether the trigger is a gag (for the screen). Next is GLo, which means "Gag from Log", and shows whether the line is omitted (that is, not written) in the log file also (if logging is enabled, of course). After that, Ena tells whether the trigger is enabled. Triggers that are disabled are not matched. This is a nice way to stop a trigger, but keep it stored for later use. We will see how to enable and disable triggers later in this section. Next there is KeE, which reports the status of the keepexecuting flag for the trigger. After that, IgC specifies the case is being ignored. The final two columns list the pattern and action of the trigger.

The listing produced by $world->listtrigger is compact, showing all triggers but possibly truncating the pattern and/or action. If you give a trigger number as argument to listtrigger, it will display that trigger's information detailedly.

To edit a trigger, you need to know that trigger's number. (And that can be discovered with the listing functions just described.) The same function used to add triggers can also change existing ones, you just need to pass the trigger number as the first argument.

Calling $world->trigger(number, new pattern) changes the pattern of the trigger with that number. If you want to change the pattern and action, include the action as a third argument: $world->trigger(number, new pattern, new action). What if you want to change only the action? Since passing only one string argument would change the pattern, this is done in a different way, using the same hash that is used to pass attributes to the trigger. In brief, this is how you would change only the action: $world->trigger(number, { action => new action }). Notice that the action is passed as an attribute inside the curly braces. It is also possible to change the pattern this way, just use the attribute pattern.

Naturally, it is possible to attributes such as gag or keepexecuting. The syntax is the same, include the new value inside the curly braces. To clear one of those flags, use the value 0, which means false. The example below makes trigger number 2 stop being a gag, and at the same time sets it not to prevent matching of other triggers:

Example 8-11. Changing several attributes at once


$world->trigger(2,
                { gag => 0,
                  keepexecuting => 1 })

It is also possible, naturally, to change attributes such as pattern or action at the same time that gag or keepexecuting are changed. All these attributes are equal, the only difference is that since the pattern and action are used much more often, there is a shorter way of specifying them. But even when you create a trigger you can use this extended syntax. Example 8-1 could be also entered this way, with the exact same results:

Example 8-12. A very simple trigger, in another way


$world->trigger({ pattern => 'has attacked you!',
                  action => 'wield sword'})

The final attribute for triggers is enabled. It was mentioned briefly when we described how to list triggers. It can be set just like the other attributes, and like keepexecuting or gag it is binary, that is, takes the values true (represented by anything different from 0) or false (represented by 0). When the value of this attribute is true (which is the default), the trigger is matched normally. When it is zero, received lines are not matched against it. This way, disabling a trigger effectively turns if off, as if it did not exist, but the trigger is still saved, and can be turned on again when necessary.

Here's an example of disabling a trigger (number 3 in this case):

Example 8-13. Disabling a trigger, the long way


$world->trigger(3,
                { enabled => 0 })

However, there is a shorter way: the $world->distrigger function disables the trigger whose number is passed as argument. So the example above can be rewritten in a shorter way as:

Example 8-14. Disabling a trigger, the short way


$world->distrigger(3)

The corresponding function $world->enatrigger enables the specified trigger.

It is also possible to temporarily disable all triggers. Just use the menu Preferences->Disable Triggers and this will prevent triggers from running. This does not change the "enabled" status of any triggers, it just prevents all triggers from running. When you select the menu again, triggers that were enabled will run again, and those that were disabled will remain disabled.

There are times when you want to delete a trigger. This is easy to do, use the $world->deltrigger function. It takes as argument the number of the trigger you wish to delete. Be aware that once deleted it is not possible to recover the trigger (unless you create it again). Many times just disabling the trigger is a better idea. The second thing to note is that when you delete a trigger the numbers of the other triggers may change, so be careful when you try to delete several triggers in sequence.

8.3.4. Assigning Names to Triggers

It is possible to assign names to triggers. When a trigger has a name, you can enable, disable, or delete it using its name instead of its number.

To assign a name to a trigger, specify the name attribute when creating it:

Example 8-15. Creating a trigger with a name


$world->trigger('has attacked you!', 'wield sword',
                { name => 'attack' })

You can now disable this trigger with $world->distrigger('attack'). The name can also be used in the $world->enatrigger, $world->deltrigger and $world->listtrigger functions.

It is also possible to assign a name to an existing trigger. Just edit it as described in Section 8.3.3, passing the name attribute. Use this same process to change the name of a trigger.

Another feature of trigger names is that several triggers can have the same name. In this case, all these triggers will be treated as a single group. The functions above, when passed a trigger name, will act upon all triggers of the group, that is, on all triggers with that name.

8.3.5. Reordering Triggers

Triggers are tried from the first to the last, so in some cases the order of the triggers matters. It is possible to move a trigger to another position with the $world->movetrigger function.

The function takes two parameters: the first is the name or number of the trigger that you want to move. The second is the new position that the trigger will take in the list. 0 means move the the first position. If you specify a negative number or a number greater than the number of triggers, the trigger will be moved to the end of the list.

If there are several triggers with the same name, only the first one found will be moved. And when a trigger is moved, other triggers might move up or down to accomodate the change.