Crashcourse - Part 06

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

It's time to start coding some commands. This can be a very interesting aspect of coding for a MUD, because you can set who can use the commands and are not restricted to only a single monster or a group of monsters.

We'll start off by expanding our definitionsfile a bit further. So far we have not added arguments for the functions we use in the 'defs.h' file. So, it's about time we do it. To do this we write a definition including the paranthesis, which will tell the compiler that we're going to use the arguments. Inside the paranthesis we write a variablename of our own choice. The name is not important since the only place it will be used is in that define and that will only be used by the compiler. It is, however, important that the name is not used in the define name, because the precompiler replaces all occurences of the variablename with the variable. Let's take an example using this_player() (TP for short):

This one is fine:

#define Q_NAME(x) x->query_name()  // = TP->query_name()

But, this one is not:

#define Q_NAME(q) q->query_name()  // = TP->TPuery_name()

As you can see it replaces both occurences of q.

A quick passthrough of the functions in the defs.h file:

environment()           - Returns the environment of the argument.

capitalize()            - Will capitalize the first letter of the argument
                          and return the capitalized string.

query_name()            - Will return the name of the object capitalized.
                          If the object is a living object and it is
                          invisible, the function will return 'Someone'.

query_real_name()       - Will return the REAL name of a living
                          object (Not 'Someone' even if invisable).
                          Not capitalized.

query_pronoun()         - Will return 'he', 'she' or 'it', depending on
                          the gender of the object.

query_objective()       - Will return 'him', 'her' or 'it', depending on
                          the gender of the object.

query_possessive()      - Will return 'his', 'her' or 'its', depending on
                          the gender of the object.

query_attack()          - Will return the attacker (object), if any.

NOT_FAIL()              - Will set notify_fail and return.

The other ones should be pretty obvious. There's a bunch of defines to query different information about livings (strength, dexterity, etc). There's also two for reducing a living's spell points and hit points. They can also be used the other way by using a negative value. For these two functions we're using two arguments, but if you take a look at it, it's not very different from the other ones. We'll discuss the BUSY functions later in this course.

If you don't know the properties we're querying here, use the lookupp command to see what they do.

!Note. I have chosen to use these characters in front of defines:

Q_  - A query of some sort.
R_  - A remove of some sort.
S_  - A set of some sort.

And some other special cases. This should make it easier to see what they do inside the coding.

The first thing we do when we start creating commands, is make ourselves a couple of directories.

/doc/crashcourse/course06/
    course.06
    wizard.c
    /com
        CMD_ACCESS.c
        hello.c
        meteor.c
        icicle.c
        cast.c
    /help
        hello
        meteor
        icicle
        cast

A command directory (usually called com) and a help directory (usually called help). The help directory, along with the help functions we will go through, may be a bit pointless when making commands for monsters, since it's not very likely that they will ask for help on the commands. ;-)
But, when making the commands available to players, help is desirable so we will include this.

The help dir does not need any explanation, it is just a dir with a lot of textfiles which will be displayed when the player asks for help on one of the commands. The helpfiles are usually of a standard format. Take a look at the examples to get an idea about how it should look.

The com dir is where all the action lays. First of all, we have to create ourselves a 'CMD_ACCESS.c' file. It must be called exactly this and must recide in the com dir. The mudlib will call a function in this file every time someone tries to access a command in that dir. This is a functionfile, so it inherits I_DAEMON. The function called by the mudlib is called 'query_access()' and sends the player/monster object as an argument. Inside this function we may check out the player/monster and see if he/she/it will be allowed to execute the command he/she/it requested. The returnvalues are important here, since a returnvalue of 0 will deny the player/monster access and a returnvalue of 1 will approve it. As you can see, for this example, we have made the commands available to EVERYONE, by return 1. There is also an example of checking for access, if you should want to make your own commands only accessible by you and your monsters.

FILENAME: com/hello.c and help/hello

Now, it's time to start working on the actual command. First of all, commands should inherit I_COMMAND. Commands do not have a create() function so they should not be replace programmed. They should, however, have three important functions:

main()          - The actual command.
short_help()    - A short help line.
help()          - A filename pointing at the long help text.

And this one also if the filename does not correspond to the command. We will use it in all commands for convieniency.

query_action()  - Returns the command name, or names if several.

! IMPORTANT! If you take a look at the functions, they have been typecasted. This means that the variable type they return is defined just in front of the functionname. This means that only that type of variable should be returned by the specific function.
Typecasting of functions is not really neccessary, since the Icewind mudlib has not been compiled for strict typecasting. It is however, a nice habit to try to use it as often as possible. And you never know if a new version will be compiled with a more strict typechecking. ;-)

As you can see the main function has the word static in front of it. This means that they cannot be called from other objects using call_other(). That way, no other objects may mess around with our commands.. ;-)

There are two new functions in this file:

present()       - Returns an object if it exist in the specified environment.
lower_case()    - Returns the complete string in lower case letters.

The present() function is widely used and should be explained further. The first argument is the name of the object you're looking for (a string), while the second argument should be the environment you are searching through. If you don't specify the second argument the search will be done in this_object(). The function will return the object you searched for if it is found. It returns, however, only the first object to match the name. So: present("drink",TP) would return the first object in this_player()'s inventory with the id 'drink'.

The returnvalue at the end finishes the command and tells the mudlib that it was executed successfully.

The rest of the file will be left up to you to figure out. It shouldn't be too hard, and if it is, ask. :-)

As a nice little exercise, how about making a goodbye command?

FILENAME: com/meteor.c and help/meteor

If you read through the file, you will probably notice what's really important when coding commands of this kind. Checks... There's a lot of stuff the player might do wrong, so we have to check and see if the command is used correctly.
The player may use just 'meteor', but only during combat. The player may use 'meteor <name>', but only if name is a living object. And, the player may certainly not use this spell in no fight rooms or against no fight monsters. And, finally we have to check to see if magical attacks are allowed (no_mattack).
If you take a look at the start of the file, you will notice a new file included. 'colours.h'. Now, what does this one do?
If you have played around with the 'colour' command on the mud, you will know what it does. It makes the spell use the colours the player has defined for attacks and hits. Using the defines

COL_HY()    - Hityou colour.
COL_YH()    - Youhit colour.

we may easily add some colours to our spells.

These defines uses two arguments, the object getting the coloured message and the message the object is supposed to receive.

As you may or may not notice, this spell may be used by both players and monsters, but it's missing two important pieces if it is to be used by players in the game.

Can you guess which ones. The first one is pretty obvious, the cost. The spell is completely free of charge, and that would make the spell pretty powerful, don't you think? ;-)
The second one is not that obvious unless you are more experienced in spell coding. There is no blocking against using this spell several times each round. The only obstacle is how many lines the mudlib manages to process during a single round. But, if you're a fast typer or uses macros/clients you can get this spell to be cast A LOT of times each round. Even the most powerful monsters would probably die within a few rounds when using this spell effectively. ;-)

We will correct these "bugs" in the next command.

FILENAME: com/icicle.c and help/icicle

In this command we use a few new functions. Mainly query_sp() which returns the player's spell points, reduce_spell_points() which reduces the player's spell points and the two busy functions. set_busy_next_round() will cause the spell to take up one round, making the player unable to do something else until next round. query_busy_next_round() just checks to see if something has been done for the next round.

As you can see, we're using a function called query_cost() to return the amount of spell points to pay. We could just as easily have put the (DAMAGE / 2) directly into the file, but keeping it inside a certain function makes it easier to alter later on.

FILENAME: com/cast.c and help/cast

Now we're getting somewhere. This command contains no less than 4 spells. It may seem a bit more complex than the rest, but you'll figure it out, I'm sure. :-)
Most of the spell has been divided into smaller functions, making the code a bit easier to figure out. The only coding that might pose as a problem is all the ifs in the main() function.

The messages for the spells may be a bit dull, but after all, it's just an example. ;-)

Let's take a look at the function called electric_storm(). The other spell functions are almost identical and are all pretty easy to figure out.
The first new function you will run into, is all_inventory() . This function returns an array containing all objects inside a specified inventory. Once we have gotten this array and stored it inside a variable (ainv), we're ready to run through it. We use a 'for'-loop to do this.
The syntax for a 'for'-loop is:

for (expression0; expression1; expression2) {
    statements;
    ...;
}

Where 'expresssion0' is evaluated once. Meaning that we can use it for initiating the variable we're using. 'expression1' will be evaluated EVERY single loop. If this expression is TRUE, the for loop will be terminated and exited. But, while the expression is FALSE, 'expression2' will be evaluated (every loop until 'expression1' is TRUE), giving us the chance to use this as an incremental or decremental expression.

Thus, arriving at the following:

for(t=0;t<sizeof(ainv);t++) { }

This will mean that first t is set to 0. When this is done, we check to see if t has reached the value of sizeof (ainv). If so, exit the loop. If not, execute the statements inside the loop. And we go back to checking 'expression1'. And this is done until that expression is TRUE.

sizeof (ainv) will return the size of the array ainv. In the electric_storm() function it will return the number of objects inside the present room.

NOTE! As mentioned before 'expression1' (in this example 't<sizeof(ainv)') will be evaluated EVERY loop. The size of the array will certainly not change during the loop and therefore the evaluation of sizeof(ainv) is only necessary once before the loop is executed. But we will talk about tuning of code in a later course or do you have already an idea of how to improve this piece of code?

If the current object is not living or may not be fought exit this loop, not the for loop altogether. If this_player() is an interactive living(=player) and the current object is also interactive (meaning two players), skip this loop also. This is done because we don't want people to start fighting each other. It is possible to put in a check for anarchy here also, and then make it possible for players to attack eachother, but we'll skip that in this example.
We will however not exit the loop if (ainv[t] == TP) because we want the spellcaster to sustain some damage also.
Then we send out the messages and hit the player/monster. But, we check to see if the spell is hitting the spellcaster and if so, halve the damage.

NOTE! This for() loop is really a bit messier than it has to be. There is another type of loop which can be a lot cleaner called foreach(). But, since it's important to know how for() works, we will leave the discussion of foreach() to course #08.

If we take a look at the main() function, we find two new functions. The first one can be a bit complex if you're not an experienced c coder. It is the function sscanf(). This function will scan a string(hence the name), and do some operations on it. The syntax for it is:

int sscanf( string str, string fmt, mixed var1, mixed var2 ... );

But, this seems kinda hopeless... First, an example:

sscanf(pricelist,"%s cost %d",item,price);

pricelist is supposed to be a string looking like this: 'Daggers cost 50'
This call of the sscanf() function will yield the following results: The substring 'Daggers' will be put into the variable item, while the number will be put into the variable price. The original string pricelist remains unchanged.

Now, let's take a look at the 'string fmt' argument for sscanf(). As you can see it's a normal string formatted to suit sscanf(). What you do is that you replace the information you're interested in, with the format codes. Only two format codes are used here:

%s  - A string.
%d  - A number.

The information you replace the codes with, will end up in the following variables. In this case, 'item' and 'price'. The sscanf() will also return the number of %d's and %s's found.

Using the returnvalues you can check if the string has been typed in okay without assigning the values to any variables. If you place an asterix(*) in between the % and the 's' or 'd', the sscanf() will skip that information. Meaning that 'sscanf(pricelist,"%*s cost %*d")' would return 2 if pricelist was a correct string.

There is also one special case to sscanf(). If you use it like this:

    sscanf(str,"%s %s",var1,var2);

then 'var1' would contain only the first word in the string 'str', while the rest of 'str' would be put into 'var2'.
There are several different format codes that can be used in sscanf() (and the other ones in the same family, like printf). Have a look at 'man sprintf' for a full list.

The other new function is member_array(), which is a array search function. This function will search through an array and return the location of the first corresponding value. Or last if a flag is specified. The syntax for this function is:

int member_array( mixed item, mixed *arr, int flag );

where item is the item you're looking for, arr is the array to search through and flag tells the function if you're looking for the first occurence of the item or the last. The function will return -1 if the item is not found inside that array and the location if found.
Example:

item = "lightning bolt"
*arr = ({"fireball","snowball","lightning bolt","electric storm"})
flag = not set.

member_array(item,arr)

would return 2, since the array starts with 0.

Okay, now 4 commands(7 "spells") to work with, how do you use these new commands??
If you want to test them yourself, type: 'cmdpath '. If you're using the files in the original dir, 'cmdpath /doc/crashcourse/course06/com/'should work.

'comd update ' might also be needed when messing around with commands...

'cmdpath' sets up a new path for a living object, while 'comd update' updates the path. This updating must be done everytime commands are added or removed from the command paths. This is due to the mudlib keeping lists of commands in memory to speed up execution.

FILENAME: wizard.c

Wizzie has got access to all the commands we have just created. And, he's willing to use them. So, clone him and fight him. :-) As you can see, the only new command is add_cmdpath() which adds a path to a living object (it's also used in players).
The path should end in a '/' indicating that it's a path.

All the commandcalls are made inside the load_a_chat(), but we could just as easily have put them in load_chat(). The only problem with that would be that we couldn't get the name of the target since we're not in combat. We would then have had to search for enemies inside the command instead of letting the monster take care of this.

And with this, we end course #06 and look forward to the next. At least I hope you do? See ya then...


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

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