Previous Index Next


12 - Interfaces

Table of Contents


12.1 Introduction to interfaces

Axmud interacts with the world using triggers, aliases, macros, timers and hooks. These are collectively called interfaces.

Interfaces have a stimulus and a response. In other words, when something happens (the stimulus), Axmud takes some form of action (the response).

The Axmud Guide discusses interfaces in detail. In this Section we'll discuss how scripts can interact with their own interfaces.

12.2 WAITTRIG, WAITALIAS, WAITMACRO, WAITTIMER and WAITHOOK

In Section 4 we introduced the WAITTRIG statement, which creates a temporary trigger. The trigger waits for the world to send a line matching a certain pattern, in this case a line containing the word opens.

    WAITTRIG "opens"

When the trigger notices a matching line, it takes some kind of action. We say that the trigger fires. Until then, the Axbasic script is paused. (In the previous Section we discussed pauses and mentioned that Axbasic scripts with pauses must be run as a task.)

When the temporary trigger created by WAITTRIG fires, it deletes itself and the script resumes execution. (When the script stops running, Axmud will automatically delete any triggers it created.)

In Section 4 we also discussed using a timeout. In this example, if the trigger doesn't fire within 60 seconds, the Axbasic script resumes running (and the trigger is deleted, never having fired).

    WAITTRIG "opens", 60

If you want to, you can use the WAITALIAS, WAITMACRO, WAITTIMER and WAITHOOK statements. They all behave in exactly the same way as WAITTRIG.

    WAITMACRO "f1"
    WAITMACRO "f1", 60

In practice, there are usually better ways of doing things. In the case of WAITTIMER, it would be a lot simpler to use a SLEEP statement.

12.3 ADDTRIG

ADDTRIG also creates a trigger, but the script doesn't wait around for a response; it just creates the trigger and then executes the next statement. As a result, the trigger doesn't interact with the script directly.

ADDTRIG expects three arguments - a stimulus, a response and (optionally) a trigger name. If you don't specify a name, Axmud will choose a name for you.

This example creates a trigger that waits for a line containing the word treasure, and then sends the world command get treasure in response.

    ADDTRIG "treasure", "get treasure"

An ADDTRIG statement behaves just like the client command ;addtrigger, except for one important detail - when the Axbasic script stops running, the trigger is deleted automatically.

Of course, if you don't want the trigger to be deleted automatically, you can create a permanent trigger. If you were typing a client command, you would type:

    ;addtrigger -s treasure -p <get treasure>

But in an Axbasic script, you would use a CLIENT statement:

    CLIENT "addtrigger -s treasure -p <get treasure>"

If you're not sure how to compose a client command in the correct format, you can consult the Axmud Guide or just type

    ;help addtrigger

(Note that ;addtrigger is expecting a response that contains no spaces. Because our get treasure response contains spaces, we need to enclose it within diamond brackets.)

12.4 ADDALIAS, ADDMACRO, ADDTIMER and ADDHOOK

ADDALIAS, ADDMACRO and so on behave in the same was as ADDTRIG, and use the same three arguments - a stimulus, a response and an optional name.

    ADDALIAS "^gt$", "get treasure"
    ADDMACRO "f1", "kill orc"
    ADDTIMER 60, "score"
    ADDHOOK "lose_focus", "sleep"

In each case, the interface is deleted automatically when the script stops running.

Be very careful that you don't create an alias like this, with the same word in both the stimulus and response:

    ADDALIAS "treasure", "get treasure"

This alias fires when you type treasure, and sends a world command get treasure. Which makes the alias fire again, sending another world command get treasure, and before you know it you'll have an infinite loop.

The safest way to use ADDALIAS is to add a ^ character at the beginning, and a $ character at the end, so that the alias only fires when you type that word (any nothing else):

    ADDALIAS "^treasure$", "get treasure"

(See the Axmud Guide for more information about special characters in patterns.)

12.5 DELTRIG, DELALIAS, DELMACRO, DELTIMER and DELHOOK

The opposite of ADDTRIG is DELTRIG.

To delete a trigger, you need to know its name. If you don't know the name, then you need to modify your script to specify one.

    ADDTRIG "treasure", "get treasure", "mytrigger"
    SLEEP 60
    DELTRIG "mytrigger"

DELALIAS, DELMACRO, DELTIMER and DELHOOK behave in exactly the same way. In fact, these five statements are the equivalent of the client commands ;deletetrigger, ;deletealias and so on.

All five statements can be used to delete an interface that was created by a different script, or which you have created yourself. This is behaviour is, in general, not a good idea.

By the way, if you try to delete a trigger that doesn't exist, you'll see a system error message (but the Axbasic script will keep running).

12.6 Interfaces for profiles

Axmud interfaces are available, by default, whenever you connect to a particular world. That is to say, if you're connected to Discworld and you run the following script, the trigger it creates will not be available when you connect to Cryosphere.

    ADDTRIG "treasure", "get treasure"
    END

However, it's possible to tie interfaces to a particular character, so that they're only available when you're playing that character. You could also tie your interfaces to a particular guild or a particular race, so that they're only available when you're playing a character of that guild or race. (This is explained in detail in the Axmud Guide.)

By default, a trigger created with ADDTRIG is tied to the current world profile. It's available whenever you connect to that world, regardless of which character you're playing.

If you want to create triggers tied to a particular character profile, you can use the PROFILE statement.

    PROFILE "gandalf"

After a PROFILE statement, ADDTRIG statements will create triggers tied to that profile. This behaviour continues until you use a different PROFILE statement.

If you want to go back to creating triggers with the current world, just use PROFILE on its own.

    ! This trigger is available to characters who
    ! are members of the thief guild
    PROFILE "thief"
    ADDTRIG "orc", "backstab orc"

    ! This trigger is available to all characters
    PROFILE
    ADDTRIG "troll", "escape"

The PROFILE statement also affects DELTRIG. Ordinarily, DELTRIG deletes the trigger that's tied to the current world profile. If you have specified a different profile using a PROFILE statement, the trigger tied to that profile is deleted instead.

ADDALIAS, ADDMACRO and so on, as well as DELALIAS, DELMACRO and so on, are all affected by a PROFILE statement in exactly the same way. (WAITTRIG isn't affected at all.)

By the way, the Showprofile$ () function returns the name of the profile currently used by the statements ADDTRIG, DELTRIG (and so on).

    PRINT Showprofile$ ()

12.7 Interfaces and main loops

So far, the interfaces we've created don't really interact with scripts in any meaningful way.

In some respects, BASIC is a poor choice of language for interacting with interfaces. In many scripting languages, a script does nothing until one of its subroutines or functions is actually called.

That's not possible in Axbasic, at least not directly. If you need your scripts to interact with interfaces, you should design them with a main loop, as discussed in the previous Section.

This is roughly how it works:

Of course, dedicated scripting languages like Lua (or Perl, for that matter) handle this kind of thing much more efficiently. For most purposes, though, Axbasic is good enough.

12.8 Interface notifications

Let's create a complete script that creates a trigger and uses a main loop to check it continuously.

The trigger is created with a SETTRIG statement.

    SETTRIG "treasure"

We use a SETTRIG statement rather than an ADDTRIG statement because, when the trigger fires, Axmud sends an interface notification to the script. The notification contains information about the fired trigger.

When the script receives an interface notification, nothing happens immediately; the notification is just stored. If the same trigger fires ten times, then ten separate notifications are stored in the order they're received.

When your script is ready, it can retrieve the first notification and take action. That notification can then be discarded. The script can continue retrieving and discarding notifications until there are none left.

12.9 Retrieving notifications

The Ifacecount () function returns the number of interface notifications the script has received (and not yet discarded).

    LET count = Ifacecount ()

If more than one notification has been received, we deal with them one at a time. The Ifacename$ () function gets the name of the trigger that generated the first notification.

    LET name$ = Ifacename$ ()

When you use SETTRIG, Axbasic chooses a suitable trigger name. You can't specify one for yourself.

If your script uses only one SETTRIG statement, then you don't need to know the trigger name that was chosen. Any notification that's received must originate from that single trigger.

However, if your script contains multiple SETTRIG statements, you'll need to keep track of them. The Iface$ () function returns the name of the trigger created by the most recent SETTRIG statement.

Ordinarily, every SETTRIG statement should be followed by an Iface$ () function.

    ! Create a trigger and store its name
    SETTRIG "treasure"
    LET treasure_trigger$ = Iface$ ()

If you know which trigger fired, you can take whatever action is required, for example:

    IF Ifacename$ () = treasure_trigger$ THEN CALL GetStuff

Once that's done, we can discard the notification using a NEXTIFACE statement.

Here is the complete script. Once per main loop, the earliest remaining notification (if any) is retrieved, a subroutine is called, and the notification is then discarded.

    OPTION NEEDTASK

    ! Create a trigger and store its name
    SETTRIG "treasure"
    LET treasure_trigger$ = Iface$ ()

    ! Main loop
    WHILE 1

        ! Retrieve the first notification...
        IF Ifacecount () > 0 THEN
            IF Ifacename$ () = treasure_trigger$ THEN

                ! The trigger fired, so send some world commands
                CALL GetStuff
                ! Discard the notification, ready for the next main loop
                NEXTIFACE

            END IF
        END IF

    LOOP

    END

    ! This subroutine is called if the treasure trigger fires
    SUB GetStuff
        SEND "get treasure"
        SEND "put treasure in sack"
    END SUB

Actually, if no notifications have been received, Ifacename$ () returns an empty string. Therefore we could simplify the main loop by removing the Ifacecount () line entirely.

    WHILE 1
        IF Ifacename$ () = treasure_trigger$ THEN
            CALL GetStuff
            NEXTIFACE
        END IF
    LOOP

12.10 Advanced notifications

An interface notification tells us the name of the trigger that fired, but also a lot more besides. There are several functions for retrieving the additional information, if you need it.

The Ifacetext$ () function returns the line that caused the trigger to fire.

    LET line$ = Ifacetext$ ()

The Ifacetime () function returns the time at which the trigger fired. The time is expressed as the number of seconds since the current session started.

The Timestamp () function also returns a time expressed in this way, so we can work out how long ago the trigger fired by subtracting one from the other.

    LET interval = Timestamp () - Ifacetime ()

Axmud gives each active trigger a unique number. This probably isn't very useful, but nevertheless you can retrieve the number using the Ifacenum () function, if you want to.

    LET number = Ifacenum ()

The Ifacetype () function returns the type of interface that fired, in other words the string "trigger".

12.11 Retrieving substrings

Triggers test a pattern against a line of text. The Ifacestrings () function returns the number of matching substrings. (See the regular expression tutorial in the Axmud Guide if you don't know what that means.)

The contents of those substrings can be retrieved using the Ifaceshift$ () and Ifacepop$ () functions. Both of those functions remove the matching substring from the notification, so you can use them several times until there are no more substrings left. Ifaceshift$ () removes the first matching substring, and Ifacepop$ () removes the last matching substring.

Another way to retrieve the substrings is with the Ifaceselect$ (). Get the first substring with Ifaceselect$ (1), the second with Ifaceselect$ (2) and so on. Unlike Ifaceshift$ () and Ifacepop$ (), nothing is removed. If the specified substring doesn't exist, the function returns an empty string.

A shortcut for retrieving the first substring is with Ifacedata$ (), which behaves exactly in the same way as Ifaceselect (1).

A special case is when you use alternatives with substrings. For example, you might create a trigger than matches any of the following lines:

    The warrior hits you with axe
    The warrior hits you with handaxe
    The warrior hits you with poleaxe

...but not this line:

    The warrior hits you with surtaxes

In that case, you might use the following regular expression:

    The warrior hits you with (hand|pole)?axe

Additionally, you might want to capture the type of weapon used, and for that, you would add substrings:

    The warrior hits you with ((hand)|(pole))?axe

When the regular expression matches a line containing "handaxe" or "poleaxe", Perl produces a list of substrings containing two items, one of them an undefined value:

    ( "hand", undefined )
    ( undefined, "pole" )

The Ifaceselect () function returns an empty string in place of the undefined value, but it might also return an empty string if the line contained just "axe" and not "handaxe" or "poleaxe".

You can use the Ifacedefined () function if you specifically want to test for undefined values. Test the first substring with Ifacedefined (1), the second with Ifacedefined (2) and so on.

The function returns 1 if the specified substring exists, and is defined. If the specified substring exists but is not defined, it returns -1. If the specified substring doesn't exist, it returns 0.

12.12 Skipping notifications

By default, these functions work on the earliest notification received. Usually, you'll retrieve the information you want, and then discard the notification, ready for the next one.

However, if you want to retrieve information from a different notification, you can use a SKIPIFACE statement.

Functions like Ifacename$ () work on the current notification, which is usually the earliest remaining one. A SKIPIFACE statement changes the current notification to the next one in the list.

    ! Get the name of the trigger that generated
    ! the earliest notification
    LET first$ = Ifacename$ ()

    ! Get the name of the trigger that generated
    ! the notification after that
    SKIPIFACE
    LET second$ = Ifacename$ ()

If only one notification remains, then SKIPIFACE will have no effect. If there are ten remaining notifications and you've already used SKIPIFACE nine times, then the next SKIPIFACE statements moves back to the beginning of the list.

The Ifacepos () function returns the number of the current notification. By default, it returns 1, representing the earliest remaining notification. If you've used SKIPIFACE once, it would return 2, representing the second notification on the list. (If no notifications have been received, or if they have all been discarded, it returns 0.)

12.13 DELIFACE

An active interface is one that's currently available. We've discussed how triggers, aliases and so on can be tied to a particular character profile. Those interfaces are only active when you're playing the right character.

A DELIFACE statement can be used to delete an active interface. As mentioned above, you shouldn't normally use Axbasic scripts to delete interfaces created by something else:

      DELIFACE "status_task_trigger_11"

You shouldn't use DELIFACE to delete triggers created with an ADDTRIG statement, either; use DELTRIG to do that. (The reason for this is a bit complicated, but we can summarise by saying that an active interface might not have the name you were expecting it to have.)

However, DELIFACE is the recommended way to delete a trigger created with a SETTRIG or a WAITTRIG statement.

If you want to be clever, you can delete the trigger that generated the current interface notification using a line like this:

      DELIFACE Ifacename$ ()

12.14 Starting a new script with SETTRIG

SETTRIG creates a trigger. When the trigger fires, the Axbasic script receives a notification, which can be retrieved when the script is ready.

However, SETTRIG has a second purpose: it can be used to run a different Axbasic script. If this is what you want, specify both the trigger stimulus and the name of the other Axbasic script:

    SETTRIG "You are dead", "prayer_script"

The new script is not run as a task. The original Axbasic script does not receive an interface notification.

12.15 SETALIAS, SETMACRO, SETTIMER and SETHOOK

We've described interface notifications in some detail, but only in relation to triggers.

Interface notifications can also be generated by aliases, macros, timers and hooks. The way this works is very similar to the way that triggers work, but there are some important differences. This section describes those differences.

SETALIAS, SETMACRO, SETTIMER and SETHOOK behave exactly like SETTRIG. You can use them to create an interface. When the interface fires, the Axbasic script receives a notification, which the script can retrieve when it's ready.

SETALIAS requies a pattern which matches a world command. SETMACRO requires a keycode string. SETTIMER requires an interval (in seconds), or a clock time. SETHOOK requires a hook event. If you're not sure what any of these mean, consult the Axmud Guide.

    SETALIAS "^kill orc$"
    SETMACRO "ctrl f1"
    SETTIMER 180
    SETHOOK "receive_text"

The same statements can be used to launch another Axbasic script.

    SETALIAS "^kill orc$", "check_equipment"
    SETMACRO "ctrl f1", "cross_bridge"
    SETTIMER 180, "drink_potion"
    SETHOOK "receive_text", "play_alarm"

Ifacetype () will return one of the strings "trigger", "alias", "macro", "timer" or "hook", as appropriate.

The return value of Ifacetext () depends on the type of interface. For triggers, it returns the line of text received from the world (possibly after being modified by various rewriter triggers). For aliases, it returns the world command. For macros, it returns the keycode string. For timers, it returns the approximate time at which the timer fired (an epoch time, in seconds; actually the time recorded by the session). For hooks, it returns the hook event.

Both SETTRIG and SETALIAS try to match a pattern against some text. If any substrings are generated, the functions described above can be used to retrieve those substrings.

SETHOOK creates a hook that generates zero, one or two additional items of data, depending on which hook event occurs. This additional data can be retrieved in exactly the same way as trigger substrings are retrieved. For example, you could use Ifacestrings () to see how many items of data were generated, or Ifacedata$ () to retrieve the first one.

A macro doesn't generate any additional data, so after a SETMACRO statement, Ifacestrings () always returns 0 and Ifacedata$ () always returns an empty string.

SETTIMER creates a timer that generates one additional of data.

Some timers have a clock time as a stimulus. This clock time can be in the form "HH:MM", which causes the timer to fire once a day. It can also be in the form "99:MM", which causes the timer to fire once an hour, at MM minutes past each hour. If the timer stimulus is a clock time, then Ifacedata () returns it (as a string). Otherwise, it returns the time at which the timer was due to fire (which will be equal to, or slightly earlier than, the time at which the timer actually fired, which is the value returned by Ifacetext () ).


Previous Index Next