Crashcourse - Part 05

[previous part] [Table of contents] [next part]

At last it's time to start the real struggle. In this lesson we will take a closer look at functionfiles and hooks.

PS! If you copy these files into your own directory, remember to change all pathnames to point at the new directory.

Let's start with some information about hooks. For some background, read the man page 'hook'. It will also give you the basics of hooks. But, here are the most important aspects. First of all, hooks are interrupting functions. They are set to be activated if certain events occur. For instance, you have a hook called '__reset'. This hook will wait until the specified object resets and will then be activated. There is a multitude of different hooks for all sorts of events. To get a listing of most of them, do an 'ls /doc/hooks'. Hooks ALWAYS start with the two chars '__' There are two main types of hooks, blocking hooks and non-blocking hooks. Non-blocking are also called notification hooks.
Blocking hooks always start with the letter b (for blocking). Examples of blocking hooks can be __bdrink, __bmoveplayer and __bwield. These hooks may prevent the event from occuring. Let's take '__bwield' as an example. This is a hook used in weapons and is called every time the weapon is attempted wielded. Since this is a blocking hook, the hook is called before the wield actually occurs, so that we may prevent the weapon from being wielded. This is not possible with non-blocking hooks since they are called after the event has occured. When you create a blocking hook, the return value of the functions is very important. If you want the hook to block the event from occuring you must return a TRUE value(non-zero) from the function. If you however want the event to occur, a FALSE value(zero) must be returned. Return values from a non-blocking hook are ignored by the mudlib.

There are two ways of setting up a hook. You may use standard array notation '({ })' or you may use function pointers '(: :)'. Although function pointers are faster, they DO NOT send through the standard arguments for the specified hook (read man pages for details about standard arguments). Let's use __moveplayer as an example to illustrate this. This hook is called in all living objects when they are moved.
We use a functionfile called FUNC and a functionname called 'monster_move'. If you take a look at the man page for __move_player you will see that the standard arguments are the direction, the old environment and the new environment.

add_hook("__move_player",(:call_other,FUNC,"monster_move":));

This call will NOT send the arguments through, so the function will not know about the old and new environment and of the direction. And note the call for call_other inside the function pointer. It is used to call the monster_move function. The structure of the function will be like this:

monster_move() { /* FUNCTION */ }

add_hook("__move_player",({FUNC,"monster_move"}));

This call on the other hand will send through the standard arguments making us able to examine the arguments later in the function. The structure of the function will be like this:

monster_move(direction, oldenv, newenv) { /* FUNCTION */ }

It is important to note that all hooks are not available to all types of objects. If you are in doubt if a certain hook does exist in a certain object, check the manual page for that hook. There is a part called 'AVAILABLE TO' which tells you which objects may use that hook. But, it does not give a full listing of all objects only the type of object furthest up in the inheritance hierarchy. For instance, the __reset hook is available to I_OBJECT. This also means that the __reset hook is available to all objects that inherit I_OBJECT. And all cloneable types of objects do just that(I_ITEM/I_WEAPON/I_ARMOUR/etc).

It is very important that you take care when using function pointers. If the owner of the functionpointer (the hook, trigger or whatever) is destroyed, the function pointer will become invalid. To illustrate: If you create a function which adds the monster_move() hook described earlier using function pointers, and the object owning the hook, in this case FUNC, is destroyed, every time the monster/player moves, it will receive the nasty message: 'There is a bug here. Please contact...'. So be very careful when using functionpointers anywhere.

Now, let's jump right into an example of a room using hooks:

FILENAME: path.c

This room has a couple of hooks. And these hooks call different functions when the events occur. The only problem with that is that replaceprograms doesn't like any other functions but create. No worry. That's what functionfiles are for. At the top of the path file, we have defined a functionfile using the compiler directive '#define'. The format for this directive is :

'#define <text> [argument]'

This directive will make the compiler swap all occurences of 'text' throughout the file with the 'argument'.
In the path case, we use the name FUNC to signify a pathname. This is good programming behaviour since it makes the code more readable and makes it much easier to change paths later if neccessary.

Now, let's take a closer look at the function add_hook(). Its syntax is:

add_hook(string h_name, mixed hook, mixed args)

The first argument in the name of the hook (string), the second argument is the function pointer/array while the third argument is optional. You may send your own arguments through here. Note that your arguments have presedence if you choose to use this feature. Meaning that your arguments will come first through to the function called by the hook. Read 'man add_hook' for further information.

The two hooks we use in this example is '__reset' and '__enter_inv'. The first one is called every time the object (this case the room) resets, while the other one is called every time an object enters the room.

As you can see, both calls to add_hook() add the room object(using this_object()) as an argument. Also note the slight difference between the two methods.

FILENAME: pathfuncs.c

A functionfile is not like other files. It does not need a create() function. All functionfiles MUST inherit I_DAEMON. The next three lines contain some useful defines. There are some important LPC library functions you should be aware of when programming functions.

this_player() Returns the playerobject that caused the function to be called.
this_object() Returns the object pointer of the calling object. The object where this_object() is called.
previous_object() Returns the object pointer of the object that called the current function. Shouldn't be used too much as it cannot be trusted.

If you recall the path.c file you will remember that we called the function called reset_path() from it. If you examine the function in this functionfile, you will probably understand what it does straight away. The only thing it does is send a message to the room every time the room resets. The tell_room() function sends the specified message to all living objects inside a room.
The syntax of the tell_room() function is:

tell_room( mixed ob, string str, object *exclude )

The first argument may either be the room object or the filename of the room. Using the object is faster, though. The second argument is the string to send into the room, while the third argument is an array of objects to exclude. Meaning that 'tell_room(room,"Blah.\n",({gunner}))' would send the message 'Blah.\n' too all living objects inside the room, except to the living object gunner.

The next function is a bit more complex, but shouldn't pose as much of a problem, really. If you look at both functions you can see that only the last one uses arguments. I bet you can figure out why if you take a look at the path.c file. ;-)
The arguments coming through from the hook is the object that has been moved into the room and the object from where it came. The first thing we do is to check if the object is a living object. If so, exit the function and leave it alone. The function living() returns TRUE if the argument is a living object, FALSE if not. The next functioncall is easy. It's the tell_room() function we discussed earlier. Except in this case we have inserted something inside the message. The characters '->' means that we're calling a function inside another object, meaning that we're calling the function 'query_name()' inside the object ob. This function will then return the name of the object. This name is inserted into the string. It is important to note here that when we use functioncalls inside strings like this that we must use a plus sign in front and behind the functioncall.
The next line is a dangerous functioncall. It destroys an object totally. Beware using this function so that you don't destroy something you shouldn't. ;-)
Once the object is destroyed, we clone ourselves a new object. The pizza from course #02. And when it's been cloned, we move it out into the room.

So, what this function does, is that all non-living objects that is dropped inside the room, is destroyed and a pizza appears there instead of it.

Since defines are a bit easier to use, it's time to make ourselves a definitionsfile. Let's just call it 'defs.h'. Inside it we put the three defines: TP, PR and TO. Have a look at the file to see what they represent. You should always use capital letters in definenames, to make them stand out in the code. It is, however, not neccessary. This file does not need anything else since we are going to include this file into our own files. It will not be an object of its own, it will be a part of all the files where we include it. The file is put in the root directory of the crashcourse directory. This will make it easier to access in later courses as well as this one.

! IMPORTANT ! You should always make sure that you specifically tell the mudlib what your functions are returning. You do this by giving a variable type in front of the functionname. Like this:

int 
hello_world {
   tell_room(room,"Hello everyone!\n");
   return 1;
}

But, it is also important to note that when you do this, you must make sure that your function returns that type and nothing but that type. If you don't want your function to return anything at all, simply use the type 'void' which ain't much at all. ;-)
There is another case which should be discussed here, and that is those very few functions that returns different types from time to time. These functions are very rare, and a function that returns different types, should rather be reprogrammed. It's not very good coding practice to allow a function to return more than one type. But, if you MUST have this, use the type 'mixed' which will allow just about any type to be returned from the function.

This kind of typecasting is good coding practice and some newer versions of the driver (the mud server which lays beneath the mudlib) needs this to operate at its best.

FILENAME: forest.c

In this room we introduce a new function called add_trigger(). This function works like add_neg() except that it exists in all objects. It's first argument is the verb used to activate the trigger. The second is either a text to be written to the player or a function pointer. Please read the manual page for this function since it is very important function and it is used a lot.

FILENAME: forestfuncs.c

Returnvalues are very important in functions called by triggers and commands. The reason is simple. If the returnvalue is TRUE(1), then the mudlib will assume that the "command" was successful and stops right there. If, however, the returnvalue was FALSE(0), the mudlib would assume that the "command" was not found and it will move on looking for other occurences of that "command". If none is found, the standard message 'What?\n' is written to the player.

The first check inside a trigger function is usually the checking of the arguments. If the arguments are not what we're expecting, we would want to write out an error message. But, we also don't want the standard message 'What?' to appear. So we return 1. There's just one problem with this. What if there is another trigger/action using the same "command"? Let's take an example:

We make ourselves a kick trigger. We want this only to work when someone types 'kick '. Fine. We check the arguments and find that the player didn't type opponent, so we write a message and returns 1. The mudlib stops searching for new commands. The only problem is, that the player wanted to use the feeling 'kick', not our trigger... Whoops. Now what? If we start returning 0 in our functions the annoying 'What?' will start appearing after our errormessages. :-S Well, that's what we have the function called 'notify_fail()' for. It sets its argument to be the last error message and then returns 0. And the mudlib checks this last error message before sending out that annoying 'What?' message. Meaning that we can use this function to store our errormessage, and if there isn't any other commands found, our errormessage will be displayed. And if there is another command, it will be allowed to run and our errormessage will not be displayed.

This functionfile should be pretty easy to understand. There are only two new functions: tell_object() which just sends a message to a living object, and move_player() which moves a living object. I suggest you read the manual page for move_player().

Oh, almost forgot, we also use load_object(), which simply loads an object and returns it. We have to use load_object() when using move_player(), because it expects an object as it's second argument, not a string.

FILENAME: amulet.c

There's only one new function in this file. set_info(). This function sets a message inside an item, which can only be accessible by spells and special abilities. This message should explain what's so special about it and should be in all special items.

FILENAME: amuletfuncs.c

The only new functions in this file are: query_attack() which simply returns the object attacking the player, if any. And hit_player() which is an attack function.

hit_player(int dam, string type, object hitter, string loc)

This function will do 'dam' hitpoints damage on the target, setting the damagetype to 'type' and the hitter to 'hitter'. You may also specify the location 'loc', but this is rarely used.

FILENAME: treebranch.c
FILENAME: treefuncs.c

There are no new functions in these files.

FILENAME: ring.c

In this file we check out a blocking function(__bwear).

FILENAME: ringfuncs.c

This ring may only be worn if you are of nasty or worse alignment. We retrieve the alignment of the wearer by using the function query_alignment(). This function returns an integer value (positive or negative) stating the wearer's alignment. Read more about alignments in the 'monster' manual page. An important function we run into here, is environment(). This function will return an object pointer to the environment of the object specified as the argument.
Usually you should use 'armour->query_worn_by()' to find the wearer, but since the hook is called before the armour is worn, we have to use environment() to find the wearer.

NB! This is in fact not true anymore, but I still prefer to use it, since you really can't wear something that's not in your inventory.

As you can see we add a hook into the wearer of the ring. And the wearer may be a player. And when dealing with players you should be very careful as not to mess up something. Just think about a hook blocking something in a player, for instance the player's death, or the ability to heal, etc.

! BUG ! There is a design bug in this ring. Can you spot it? :-)

Usually we can use standard properties when we want to store values. These properties can hold just about any type of variable. But, when dealing with players, these properties should not be used. This is due to the fact that normal properties are stored when the player quits. Therefore, we have temporary properties. These properties are NOT stored when a player quits and are much safer to use. The downside is that they can only hold integers.
The functions for normal properties are:

add_property()      - For adding a property.
query_property()    - For retrieving the value of a property.
remove_property()   - For removing a property.

The functions for temporary properties are:

add_tmp_prop()      - For adding a tmp property.
query_tmp_prop()    - For retrieving the value of a tmp property.
remove_tmp_prop()   - For removing a tmp property.
set_tmp_prop()      - For setting a tmp property.

But, this is not the only difference between the two types of properties. Using standard properties you can check to see if a certain property is just "set" in an object: ob->add_property("a_property"). When querying this property you will get TRUE(1). But, this is not the case with the tmp props. Using tmp props you HAVE TO give the property a value to check for: ob->add_tmp_prop("a_tmp_property",1). If you don't, querying will return the value 0, but the property may still be there.
There is one more difference when adding properties.

ob->add_property("test_property",5);
ob->add_property("test_property",5);
ob->query_property("test_property"); // would yield the value 5

ob->add_tmp_prop("test_property",5);
ob->add_tmp_prop("test_property",5);
ob->query_tmp_prop("test_property"); // would yield the value 10

This is because normal properties don't actually add themselves, they just overwrite with the new value. Thus a more appropriate name could have been set_property(). Temporary properties on the other hand are added to the old value. If you want temporary properties to work the same way, you have to use set_tmp_prop() instead of add_tmp_prop().

! Note that temporary properties are only possible in living objects !

! BUG ! Haven't found it yet? It has something to do with the hooktype...

It is very important to try to keep your functionfiles clean and simple to understand. Please try to comment your coding in a clean fashion. Don't overdo it by placing a comment on every single line, but try to make a comment for every function explaining what it does. And, if some of your coding seems hard to read, please add a comment of what is going on. Identing your text is also important. Try to ident every "block" of code to make it easier to read.
These steps are not only for other people trying to figure out your code, but also for yourself. When your functions gets really long and you haven't had a look at them for a while, it can often get hard to figure out what you have done everywhere.
And, by doing this, you make the life of your dear QC Arches much easier. This may seem like not too important for you, but if the code is messy the QC Arch may use a long time to go through it, and may even reject it if it's REALLY messy. ;-)

And we have come to an end of yet another course. This time, I tried not to explain every single little part of the coding and let you think for yourself. And I will do that more and more as the courses fly by. If there's something you don't understand, ask. :-)

! BUG ! Okay, did you figure it out? Well, here it is: As you can see, we're using a blocking hook, which you may or may not recall, is called _before_ the event actually occurs. We do our stuff and add the __kill hook. The problem is, that once our blocking hook is finished, another blocking hook on the same object may kick in, stopping the ring from being worn. Now what? The player is not wearing the ring, but the ring is still counting kills! Whoopsie. The solution to this problem is obvious and quite simple. Split the function into two parts, where the first blocking stuff stays where it is. The remaining part is moved into another function which is called using the __wear hook instead of __bwear(meaning you need two hooks for this). But, this is a job for you. ;-)

NOTE: For a list of different operators, have a look at course part #AC.


WRITTEN: 01 - Oct - 1996 - Gunner
LAST UPDATE: 26 - May - 1998 - Gunner
HTML Version: 02 - Feb - 2000 - Ghorwin

[previous part] [to the top] [Table of contents] [next part]