This tutorial goes deep into the new advanced trigger features provided in zMUD version 6.20 and later. Before reading this document, you should read the "Introduction to Triggers" section of the help file included with zMUD so that you understand basic trigger operations.
- Trigger States
- Trigger Types
- Sequential Patterns
- Skip Lines
- Wait
- Loop Pattern
- Loop Lines
- Resetting Triggers
- Loop Expression
- Duration
- Within Lines
- Regular Expressions
- ReParse triggers
- Manual States
- Accessing patterns from previous states
- MXP Triggers
- Summary
In version 6.20, the concept of multiple trigger states and conditions was introduced. This feature allows you to chain together multiple events in powerful ways. Some of this functionality could be emulated in previous versions (such as multi-line triggers), but much of the power is brand new.
Trigger states allow you to group multiple triggers into a single trigger. At first, this might seem similar to grouping triggers into class folders. However, there are several important differences:
The first point mentioned above is the most important: Only one state per trigger is active at any time. Unlike classes, where all triggers within the class are normally active. This causes trigger states to be fast and efficient.
Each trigger state has its own properties. Normal options like "case sensitive" can be set for each trigger state individually. Simply select the active trigger state on the main tab of the trigger editor, then switch to the Option tab to change options.
The new State tab in the trigger editor allows you to see all of the trigger states for that trigger at once. You can drag/drop states to put them in different order, insert new states, or delete existing states. You can also modify the pattern and condition for each trigger state.
In past versions of zMUD, there have only been 4 trigger types:
In version 6.20 and later, several new trigger types have been added that work within trigger states:
Any of these new types, along with the 4 original types, may be freely mixed as multiple states within a single trigger.
Reading the above descriptions probably doesn't help in understanding how they are used. So here is an example of each new trigger type.
In each of the following examples, the command line text needed to create the full trigger will be given. The new #CONDITION command is used to add additional trigger state conditions to an existing trigger (or the last trigger defined, by default). You could also use the trigger editor to enter these examples.
First, let's look at a simple example of a trigger with two states, *both* of which are Pattern states. The first pattern will be "Zugg", the second pattern "Hello":
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue}
The #COND command adds a new state to the previously defined trigger. So now, what happens if the MUD sends the text "Hello"? Nothing! Remember, only one state is active at a time, and since we just defined this trigger, the first state is active, so it's looking for the text "Zugg". This trigger isn't going to do anything until it receives the "Zugg" text first. Once the MUD sends "Zugg", then the first trigger state fires, and colors that text bright red. Then it advances to the next state within the trigger, which is the Hello state. Now the trigger is waiting for the text "Hello". If the MUD sends "Zugg" again, it is ignored. Only when the MUD sends "Hello" does the trigger fire, coloring the text bright blue, then advancing to the next state.
Since we are at the last state, the trigger wraps around and the first state becomes enabled again. You can set an option to have the entire trigger disabled after it successfully matches all states, but by default, the first state is just reactivated.
So, what did we just create? A trigger that first waits for the text "Zugg", then waits for the text "Hello". It doesn't matter how much text the MUD sends between these two patterns. In the old version of zMUD, we could emulate this behavior with the following code:
#TRIGGER {Zugg} {#T- FirstTrigger;#CW high,red;#T+ SecondTrigger} "FirstTrigger" #TRIGGER {Hello} {#T- SecondTrigger;#CW high,blue;#T+ FirstTrigger} "SecondTrigger"
The above example uses two triggers in different trigger classes. When the first trigger fires, it disables itself and activates the second trigger. When the second trigger fires, it disables itself and activates the first trigger. But this is more complicated than using the new trigger states since it requires 2 separate triggers and 2 separate class folders, cluttering up your script settings.
OK, we will start this example with a normal Pattern trigger. Then follow it with a Skip state. The trigger will remain dormant until the initial pattern is received. When this initial pattern is received, the trigger moves to the next state, which is the Skip step. The Skip step will ignore the next 5 lines from the MUD, then fire on the next line that matches the second pattern.
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {Skip|Param=5}
Notice the Options field, where we specify a string list of options, giving the name of the trigger type, followed by the setting for the Trigger Parameter (Param) option. The Param option is used to give numeric parameters to the new trigger states. In this case, Param is the number of lines we want to skip.
Now, with this trigger entered and active, we get a line from the MUD that says: "Hello". Nothing happens yet. The trigger is still waiting for the initial pattern "Zugg" to be received from the MUD. OK, so now the MUD sends the line "Zugg". The initial trigger fires, and colors this text bright red. The trigger advances to the next state, so now the Skip pattern is activated. The parameter we gave to skip was 5, so the next 5 lines are ignored. If the MUD sends "Zugg", nothing happens...remember, the original pattern is no longer active...the Skip pattern is active, and we are waiting for 5 lines...that was the first line.
Next, the MUD sends "Hello". Again, it is ignored because that was only the second line. The MUD now sends line 3, 4, and 5. Doesn't matter what text are on these lines, since the trigger is skipping them. OK, now we have gotten 5 lines from the MUD and ignored them. So now, as soon as the MUD sends the "Hello" pattern, the Skip state will fire and color the text bright blue. If the MUD sends different text, like "Zugg" again, it is ignored. The trigger is waiting for the "Hello" text. Once the MUD finally sends "Hello", it is colored bright blue, and the trigger advances to the next state.
Since we were already at the last state, the trigger wraps around back to the first state. So now the original "Zugg" state is active again.
There is no easy way to emulate this behavior in previous versions of zMUD, without a very complex script that could count lines from the MUD. It would have looked something like this:
#TRIGGER {Zugg} {#T- FirstTrigger;#CW high,red;#VAR LineCount 0;#T+ SecondTrigger} "FirstTrigger" #TRIGGER {(%*)} {#ADD LineCount 1;#IF (@LineCount >= 5) {#T- SecondTrigger;#T+ ThirdTrigger}} "SecondTrigger" #TRIGGER {Hello} {#T- ThirdTrigger;#CW high,blue;#T+ FirstTrigger} "ThirdTrigger"
What a mess! Not to mention the fact that the (%*) pattern used to count lines received from the MUD is slow because of the wildcard. Hopefully you can start to see the power of the new trigger states.
For the "Wait" trigger type, we will look at two examples. First, an example similar to the last one:
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {Wait|Param=5000}
In this case, Param is the number of milliseconds the trigger should wait before starting to match. So the MUD must first send the text "Zugg" to fire the first pattern. Now we are on the second pattern and waiting for 5000 ms, or 5 seconds. Any text received from the MUD during this 5 seconds is ignored. Once the 5 seconds is up, the trigger waits to receive the "Hello" text. When it does, it is colored in bright blue, and we advance to the next trigger state, which wraps back to the first state.
The above example could be emulated in previous versions like this:
#TRIGGER {Zugg} {#T- FirstTrigger;#CW high,red;#ALARM +5 {#TEMP {Hello} {#T+ FirstTrigger;#CW high,blue}}} "FirstTrigger"
Messy. Also notice that the old versions could only wait for a certain number of seconds, instead of milliseconds. The #WAIT command might have been used, but it has lots of side effects.
OK, for the second example, what happens when there is no text pattern for the Wait state? Here is the example:
#TRIGGER {Zugg} {#CW high,red} #COND {} {#BEEP} {Wait|Param=5000}
Well, first we wait for the text "Zugg" from the MUD to arrive. Then the trigger colors the text in bright red, and advances to the Wait part of the trigger. The trigger waits for 5 seconds. When it's done, it notices that the pattern is empty, which causes the trigger to fire immediately, sounding the #BEEP.
In previous versions of zMUD, you would emulate this with:
#TRIGGER {Zugg} {#CW high,red;#ALARM +5 {#BEEP}}
or
#TRIGGER {Zugg} {#CW high,red;#WAIT 5000;#BEEP}
Of course, the #WAIT command had side effects and could pause the reception of text from the MUD or other trigger processing. The new Wait trigger state does not have these side effects. So, if you want to issue a series of commands to the MUD with a delay between them, you could do this:
#ONINPUT {Go} {First Command} #COND {} {Second Command} {Wait|Param=2000} #COND {} {Third Command} {Wait|Param=2000} #COND {} {Fourth Command} {Wait|Param=2000}
Now, this starts with a command input trigger, which fires when you enter the matching text on the command line. So, when you enter the command "Go", the text "First Command" is sent to the MUD, followed by a 2 second delay, then the "Second Command" text, then another 2 second delay, then the "Third Command" text, and so on.
You could do this in previous versions with the #WAIT command, along with its side effects.
The "Loop Pattern" type fires a trigger a specific number of times when the pattern matches the text from the MUD. Here's an example:
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {LoopPat|Param=5}
Once again, the trigger first waits for the text "Zugg" to come from the MUD. Then it colors the text in bright red and moves to the next state. In this case the second state of the trigger is looking for the text "Hello" from the MUD. Any other text from the MUD is ignored. When the MUD sends "Hello", it will be colored in bright blue. But only for the next 5 times the MUD sends "Hello". When the fifth "Hello" is received from the MUD, the text is colored blue, but then the trigger state is done, and it increments to the next state, wrapping around to the first. Now "Hello" will be ignored again until "Zugg" is received from the MUD to fire the trigger again.
The "Loop Lines" type will fire the trigger whenever the pattern is matched during the next N lines. Once N lines have been received, the trigger state is automatically incremented. For example:
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {LoopLines|Param=5}
Once the "Zugg" text is received from the MUD, the trigger will then look at the next 5 lines from the MUD. If any of those lines contains the "Hello" text, it will get colored bright blue. Once the 5 lines have been received, it increments to the next trigger state, and wraps back to the beginning.
So, what happens if the LoopLines pattern is empty? Well, then it will match any text on the next 5 lines. So, for example:
#TRIGGER {Inventory} {#VAR Inv ""} #COND {} {#ADDITEM Inv %line} {LoopLines|Param=5}
This trigger would wait until the MUD sent the word "Inventory". It would then clear out the @Inv variable and then take the next 5 lines from the MUD, putting each line into the @Inv variable as a new item in a string list.
What if you wanted to end the previous trigger before the 5 lines were up? For example, what if you wanted to collect the inventory until a blank line was received? Well, you can use the new #STATE command to set the current state of any trigger. Setting a trigger state back to zero resets the trigger. So, you could do the following:
#TRIGGER InvTrig {Inventory} {#VAR Inv "";#TEMP {^$} {#STATE InvTrig 0}} #COND {} {#ADDITEM Inv %line} {LoopLines|Param=99}
OK, so there are a couple of new items to explain in the previous example. First, notice that we have given the trigger a name of "InvTrig". This is the ID of the trigger and can be used by many other commands to control the trigger. For example, you can use the trigger ID in a #T- command to disable a specific trigger. In this case, we use the InvTrig name in the #STATE command to reset the trigger back to state 0. This is done with a Temporary trigger that waits until a blank line is received and then resets the state. The Param=99 gives a limit on the number of lines we will add to the inventory.
Remember that only one trigger state is active at a time. So, in order to watch the MUD for a blank line, we needed to create a second trigger. While the first trigger is adding lines to the @Inv variable, the second temporary trigger is waiting for a blank line to reset the first one. The #TEMP command is a good trick for creating a trigger that you just want to fire once. Notice that we didn't have to use any trigger classes in this example.
The "Loop Expression" state will fire the trigger for each line that is received while the Expression given in the Pattern field is true. Normal Expression triggers only execute when the expression is true when any variable is set. The Loop Expression type is a way to continue performing an action while an expression is true. However, keep in mind that with the Loop Expression type, the expression is only tested when a new line is received from the MUD.
Consider this difference:
#TRIGGER (@a=1) {It matched!}
As soon as the variable @a becomes 1 (for example, enter "A=1" on the command line), the trigger will fire. It doesn't need any text from the MUD to execute. Now try a new Loop Expression trigger:
#TRIGGER (@a=1) {Hello!} "" "LoopExp"
Enter "A=1" on the command line. Notice this trigger doesn't fire yet. But now, for each line received from the MUD, it will fire. Set A=0 and now it will stop firing.
The Param for LoopExp gives the number of times the trigger will fire. So, for example:
#TRIGGER {Zugg} {#CW high,red} #COND {@a=1} {#BEEP} {LoopExp|Param=2}
This will wait for the "Zugg" text from the MUD. Then, if @a=1 it will beep for the next 2 lines. If @a isn't equal to 1, the trigger will reset back to the first state.
The "Duration" state will fire the trigger for each line that is received which matches the pattern within the given number of milliseconds. After the given number of milliseconds have expired, it will automatically increment to the next trigger state. Note that unlike the Wait type, a line of text must be received from the MUD in order to test the clock. The trigger will not increment to the next state unless a line is received from the MUD. For example:
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {Dur|Param=5000}
First the trigger waits to receive "Zugg" from the MUD. Then, for the next 5 seconds, any line that contains the word "Hello" is colored in bright blue. If the pattern is empty, than any line from the MUD received in the time interval will cause the trigger to fire.
The "Within Lines" type will fire the trigger if the pattern is received within the next N lines. For example:
#TRIGGER {Zugg} {#CW high,red} #COND {Hello} {#CW high,blue} {Within|Param=5}
This will wait for the "Zugg" text from the MUD. Then, if "Hello" is received within the next 5 lines of text, it will be colored bright blue. If 5 lines are received without seeing the "Hello" text, the trigger resets (it does not advance to the next state).
This is one way to handle a multi-line trigger. For example:
#TRIGGER {Pattern1} {} #COND {Pattern2} {command} {Within|Param=1}
This would only execute the "command" if the Pattern2 was on the line immediately following Pattern1. In previous versions of zMUD you could emulate this with:
#TRIGGER {Pattern1$Pattern2} {command}
Seems simpler, but this old multi-line trigger is much slower to process than the new Within syntax. Also, the Within syntax allows you to match more than just two lines. For example:
#TRIGGER {Pattern1} {} #COND {Pattern2} {} {Within|Param=1} #COND {Pattern3} {command} {Within|Param=1}
(added in zMUD v6.25)
Instead of using the normal zMUD trigger pattern matching engine, you can also specify normal unix regular expressions. Mark the trigger condition with the RegEx type. Then the Pattern field is interpreted as a regular expression. The following regular expression patterns are supported:
Note that unlike normal zMUD triggers, there is no way to "capture" text from the MUD using regular expression triggers. They simply define a pattern to match. In order to capture text from the MUD, use the #CONDITION command to follow the regular expression trigger with a normal zMUD pattern that can capture the text and mark the condition with the ReParse type (see below). This will cause zMUD to reprocess the line that matched the regular expression so that you can capture text from it.
(added in zMUD v6.25)
Sometimes you might want to match a trigger on the same line that has already been tested. For example, if you match a line using a regular expression, you then want to parse the line with normal zMUD trigger patterns to extract text. Or, you might want to split up multiple tests of the same line into different states.
When a state has a type of ReParse, the same line that matched the previous trigger state is tested again against the Pattern of the trigger state. If the pattern matches, then the commands for the state are executed. However, unlike other states, zMUD increments to the next trigger state in the list, no matter whether the ReParse state matched or not.
For example:
#TRIGGER {(%w) tells you} {} #CONDITION {Zugg} {#ADD NumZugg 1} "reparse" #CONDITION {Darker} {#ADD NumDark 1} "reparse"
This trigger waits until a line of the format "somebody tells you" is received from the MUD. When this line is received, the first state matches, and since there are no commands, it just increments to the next state. The next state is a ReParse state, so it checks the same "somebody tells you" line and if the word Zugg is anywhere in the line, it increments the NumZugg variable. Then, regardless of whether this state matched, zMUD goes to the next state. The next state is another ReParse state which now checks the same "somebody tells you" line for the word Darker. If the word Darker appears anywhere in the line, the NumDark variable is incremented. Then, regardless of whether Darker appeared in the line, the trigger state is incremented, looping back to the beginning of the trigger, where is waits for another line from the MUD.
You could create this example without using trigger states with the old trigger:
#TRIGGER {(%w) tells you} {#IF (%trigger =~ "Zugg") {#ADD NumZugg 1};#IF (trigger =~ "Darker") {#ADD NumDark 1}}
but the new ReParse trigger state is easier and more flexible than this.
You can also use these new trigger states to implement a formal "State Machine". If you set the state to "Manual", then the trigger will only execute and advance to the next state when you use the #SET command in a script. Or you can use the #STATE command to set the next state position for the trigger. Combinations of #SET and #STATE can be used to create all sorts of text parsing possibilities.
Also, the #SET command can be used to fire a particular trigger state before it would normally be executed. Then, when the trigger gets to that state normally, it will detect that the state has already matched, and skip it.
In normal triggers, the %1..%99 variables are set to the text that matches any patterns placed within parenthesis. For example:
#TRIGGER {(%d)Hp} {#VAR hp %1}
can be used to save your hp into a variable called @hp.
But %1..%99 only refer to the current pattern of the current state. To access patterns from previous trigger states, you can use the new %t1..%t99 syntax. These variables match all of the patterns saved by any trigger state so far. So, for example:
#TRIGGER {Name: (%w)} {} #COND {Level: (%d)} {char.name=%t1;char.level=%t2} {Within|Param=1}
would save the name and level of your character, but only if the line with the "Level:" information came right after the line with the "Name:" information. In previous versions of zMUD, you'd have to store the Name information into a temporary variable and wait till you got the Level info. Or, in this case, you'd have to use the old style of multi-line triggers.
The %t1..%t99 variables are stored in the order that the states are executed. So, if you are playing with the #STATE command to manually change state values, you can change the order in which these variables are filled.
The MXP Trigger type is a special pattern trigger that fires when the given MXP tag is received from the MUD. You can also list specific arguments for the tag, and the trigger will only fire when those arguments are received. The #MXPTRIG command can be used to quickly set a MXP trigger. For example:
#MXPTRIG {color red} {hello}
will fire whenever the MXP tag <COLOR red> is received. Note that for color values, the trigger will also match if the MUD specifies a numeric color value. So, the previous trigger would also fire on <COLOR #FF0000>.
By default, MXP triggers actually fire when the closing MXP tag is received. So, in the above example, the trigger actually fires when the </COLOR> closing tag is received. The %0 parameter is filled with the text sent by the MUD between the opening and closing tag. So, if the MUD sent:
<COLOR red>Hello World</COLOR>
The trigger would fire and %0 would contain "Hello World".
If you modify the trigger options and turn off the "Newline" option and turn on the "Prompt" option, then the MXP trigger will fire on the initial tag instead of the closing tag. In this special case, you can also modify the behavior of the MXP tag before it is parsed by zMUD. NOTE: You can only modify the arguments of OPEN MXP tags. SECURE tags cannot be modified.
To modify the arguments of an OPEN tag, the %mxp database variable is filled with the argument data for the tag sent by the MUD. You can change fields in the %mxp variable to change the tag arguments.
For example, the <COLOR> tag has two arguments: "FORE" for the foreground color, and "BACK" for the background color. If you set up a Prompt MXP trigger:
#MXPTRIG {color} {} "" "Prompt|NoCr"
then if the MUD sends the tag <COLOR Fore=Red Back=Blue> then your trigger will fire and %mxp.fore will be "red" and %mxp.back will be "blue". If the MUD just sent <COLOR red> then %mxp.fore="red" and %mxp.back="".
Since the <COLOR> tag is an OPEN MXP tag, you can change it's arguments by modifying the %mxp fields. For example, let's say that you hate the color red and want to change all red text to blue. You would use a Prompt MXP trigger like this:
#MXPTRIG {color red} {%mxp.fore="blue"} "" "Prompt|NoCr"
Whenever the MUD sends <COLOR red>, your trigger will fire. It will then change %mxp.fore from "red" to "blue". This changes the tag to <COLOR blue> and causes the following text to be blue instead of red.
Using Prompt MXP triggers, you can remap the MXP colors sent by the MUD, or change the fonts set by the MUD. Note that you can only change the arguments of a tag, not the tag itself. There is no way to remove a tag. Also, you should avoid using the #SUB or #CW commands in an MXP trigger...the operation is undefined.
The addition of multiple states to each trigger, and the addition of new trigger types provides great new power in zMUD scripting. Very complex scripts can now be reduced to much simpler trigger states. And operations not possible in previous versions can now be attempted.