I will divide this doc into several chapters, so you can quickly find the things you are interested in. In this document, when some- thing is between brackets (<...>), it means that it is required to fill in the piece between the brackets in your command with something that makes sense. If something is between braces ([...]) it means that that part is optional, and can be left out.
I wrote this doc, to help people who have absolutely no experience in programming LPC 3.0, or with the 3.0 mud itself. I hope that you will be enlightened in the use of 3.0 and that you can make simple 3.0 objects after you have read this doc. Some people think this is the reference guide for LPC 3.0. Let me assure you that it isn't, and that such a document would be hard to write. LPC 3.0 is a language on the move, and I have learned that the best way to learn it is to look in the sources. However, not everyone will be able or willing to do that, so I hope that reading this doc will get you on the coding track a bit sooner. If you want to know the bottom of everything you are using, I suggest you take a look in all the files you include/inherit.
It could very well be that this document is not very clear to the layman. The problem is that I have been programming in LPC for a long time now, and to me everything seems very clear, so I might not even notice that I skip important explanations that ought to be given. If you find such a spot in this document, or notice an erroneous remark or erroneous code, do not refrain from mailing me, your help is greatly appreciated.
There are a few commands that really are interesting in 3.0, of which the most important ones are probably:
Well, those were by far not all commands, but these are the most basic ones. If you want to know more, use "allcmd" to find out your commands, and then ask "help" on one of them.
The paths under 3.0 have changed radically. I will point out what the interesting directories are.
These were the most interesting directories. You probably cannot write in most of them. This rule does not apply to all people. Lords can write in every directory in their domain, arches and keepers can write everywhere they please. Note that you can allow other wizzes to read and/or write in your own directories by doing "sanction [R][W] <name>", or by simply putting the stuff you want them to examine in your open directory.
So much for the introductory part, let's get down to the serious business:
Some fear it, some think it is impossible, others adore it. I don't know. For those of you who know LPC 2.4.5 (or 2.4.6), it hasn't changed that much. Ofcourse there are lots of new functions defined by LPC 3.0, but they are implemented because they make your life easier. All that is written below is made with the intention to enable you to make simple objects in LPC 3.0. If you want to make advanced objects, ask someone who knows about it. You'd better try to make some simple objects at first, and then try to improve them.
If you look for the first time at official 3.0 code, you will notice one difference with 2.4.5: the functions and their arguments have types, e.g. "object *query_team_members(int flag)".
It is possible to use types in your code, however you don't have to. If you use types, you must use it for all functions, and you must make sure that functions are put in the right order, i.e. if function a() calls in its body function b(), function b() must be declared above function a(). This declaration can be done in two ways: you can put the entire funtion b() above a(), or you can implement the function b() somewhere below a() and put the prototype of b() (e.g.: "int b(int arg);") above a(). One good reason to use types is that the compiler will be able to detect errors in your code more easy, and maybe even directly at the compilation and not wait until the bugous function is called.
LPC 3.0 knows these types:
Several of these types can be checked within your code with the functions intp(), stringp(), objectp(), mappingp(), which can be useful if you want to enable people to call your functions with e.g. objects or arrays of objects.
By adding certain keywords to the basic types you can modify the behaviour of your objects, functions and variables. They work differently depending on if they are applied to functions or variables.
Applied to functions:
Varargs and nomask can be combined with any of the other modifiers and with each other...
Applied to global variables:
Arrays have become very powerful in LPC3.0. There are many new functions that support the use of arrays. Let me first explain what an array looks like in LPC: ({ a, b, c }) is an array with 3 elements, a, b and c. these elements can be of any type: integer, string, object or even array. Arrays can simply be added like this: ({ "foo" }) + ({ "bar" }) gives ({ "foo", "bar" }). Arrays are allocated on the fly, i.e. you don't have to allocate or deallocate anything, the gamedriver does that for you.
Here are a few array functions that are very handy:
And then you would do:
Et voila! An alphabetically sorted array.
Mappings? Yes. Mappings are arrays that are addressed not with a number as index, but with a string. Mappings were designed to make certain function a little bit faster. Since mappings use more memory than normal arrays, you are asked to use mappings as little as possible, preferably not at all. This is how a mapping looks: ([ "foo":"bar", "jacuzi":"zukini" ]). The words before the colon are the indices. Let's call the previous mapping M. In that case M["foo"] would be "bar" and M["jacuzi"] would give "zukini". The part after the colon is of the type mixed, so it can be anything, even arrays or other mappings.
As Commander wrote in his NEW_V3 doc, all objects will inherit something. The file that is being inherited is called /std/xxxx. The first function you define in your object, in which you will probably set the name, the long description and the short description, is called "create_xxxx", where xxxx is the name of the object you inherited. The function "create_xxxx" will be called the first time the object is loaded or cloned.
Every half hour, the mud will send a reset signal to all its objects. Then, the function "reset_xxxx" will be called. So, if you want to refresh your guards periodically in a room, so it will not stay unguarded too long, the "reset_room" function is a good place to put a check in.
The last interesting function is init(). This function will be called every time a living creature comes 'in sight' of an object. Eg. if the object is picked up, if a monster enters the same room, if an object is taken out of a bag, its init() will be called. Some objects require that you put a line "::init();" in the init() function. Typically, you will add commands to a player in the init(). Note that it is not possible to use the function init() in a monster. Instead, you should use init_living(). The '::' in front of a function means that you try to call the old version of init(), the one that existed before you decided to redefine it.
Suppose you want to make a lead ball and a big cardboard box. To create these things, you set their name, and make up a long description. This is not enough, however. If you only do that, people will be able to carry about seven of each, whereas in real life two lead balls, because of the weight, or only one big cardboard box, because of its size, could be carried by a person.
To make objects more like in real life, objects have properties that their creator can set. Many standard properties have been invented, and are used to determine lots of things. They can be found in /sys/stdproperties.h, which you probably will want to include in your object. The defined names have a standard form, take for example OBJ_I_WEIGHT. The first part OBJ means that it is an object property. The second part, I, means that the property wants a value of the integer type. Other type-descriptors are S, O and V, and combinations like AI, AS, etc. for arrays of a type. S means string, O means object and V means 'value by function call'-string. An M means mixed type = anything :)
Properties can be added by doing "add_prop(<property>, <value>);" in the object. If the property already existed in the object, the old value will be replaced by the new value. You can ask the value of a certain property by calling "query_prop(<property>);" in an object. If a property was not set, query_prop() will return 0.
Some examples of interesting properties to set:
#include "/sys/stdproperties.h"
...
add_prop(OBJ_I_WEIGHT, 10000); /* Set weight to 10 Kg */
add_prop(OBJ_M_NO_DROP, "@@my_drop"); /* VBFC (explained below) */
add_prop(OBJ_I_INVIS", 1); /* Make invisible */
...
int
my_drop()
{
if (this_player()->query_wiz_level() > 0)
return 0; /* Wizards are allowed to drop this */
else
return 1; /* Players are not */
}
As you can see, the weight is set to 10000 grams, and invisibility is set to 1, meaning on. The NO_DROP property is used cunningly to detect whether someone wants to drop this object. If someone tries to drop our object, query_prop(OBJ_M_NO_DROP) is done, which causes a call to our function my_drop(). There the wizardlevel of the person is checked, and 1 (meaning NO_DROP is on) is returned if it was a player who attempted to drop it.
There are a few basic objects that I will explain here. Ofcourse that will not be all objects, but the most interesting ones. I hope that the examples have some didactic value.
One thing: if you code an object, please, please, please, pretty please code with indentation like /doc/man/general/code_standards describes. There even is a command to help you: "indent <filename>". You don't have to use exactly that way of indenting, but use one alike. Other peoples code is unreadable as it is, not properly indented code is really not readable. Not for you, and not for the person that is trying to figure your code out.
This is probably the most interesting object, since it is the one you will create most often. This is what the world is made of. Let's make a simple room.
inherit "/std/room.c";
#include "/sys/stdproperties.h"
void
create_room()
{
set_short("Entrance"); /* This will show up when the player is in */
/* "brief" mode. */
/* Set the long description of the room */
set_long(break_string(
"There is something very peculiar with this room... It is "
+ "cube-shaped, yet it has no corners! You wonder what sorceror "
+ "would make up such a mind-boggling kind of room... Luckily "
+ "you can escape to the south.\n",70));
add_exit("/d/Standard/start/church", "south", 0, 1); /* add exit */
/* Make the corner examinable */
add_item(({ "corner", "round corner", "edge" }), break_string(
"You desperately search for a corner, but you cannot locate one. "
+ "How is this possible? You are starting to get a headache.\n", 70));
add_prop(ROOM_I_INSIDE, 1); /* This is an indoors room */
}
That was the room... As you can see, I used the break_string("...",70) technique to make sure that the string is cut into lines of length < 70 characters. The same technique is used with add_item. Note that it is possible to give either one string, or an array of strings as first argument to add_item. The given description will be shown every time a player wants to look at the identifier string. The add_exit function has four arguments: The filename of the room that is connected to this room, the command that will get you there and the third argument is an optional VBFC function (explained below). If that function returns 1, the exit cannot be taken. If the function returns 0, the exit can be taken. The last argument indicates how tiring it is to walk that direction. 1 is the default value.
Let's say you want to make a light-bulb. This is typically an generic object. This is what the code would look like:
/* A light bulb */
inherit "/std/object";
#include "/sys/stdproperties.h" /* We want to use standard properties */
void
create_object()
{
set_name("bulb"); /* The id of this object */
set_pname("bulbs"); /* The plural id */
set_short("lightbulb"); /* The short description */
set_pshort("lightbulbs"); /* Plural short descr. */
/* Long description */
set_long("This lightbulb is now more like a dark bulb.\n");
add_prop(OBJ_I_WEIGHT,75); /* Set weight property to 0.075 Kg */
add_prop(OBJ_I_VOLUME,200); /* Set volume property to 0.200 Ltr */
}
That was all! You could leave out the add_prop(...) part, but then the light bulb would weigh 1 Kg and measure 1 Ltr. This goes for all properties: if you don't set them specifically, they will take a standard value. You can also leave out the set_pname and set_pshort part, but then the parser will try to make its own plural, which you might not like. Eg. when I cloned another "test version of the pipe of the Shires", I suddenly carried "two test versions of the pipes of the Shireses"...
Suppose you want the players to be able to light the lightbulb. Then you would have written the lightbulb something like this:
/* A little bit more interesting light bulb */
#include "/sys/macros.h" /* Some useful macros */
#include "/sys/stdproperties.h" /* We want to use standard properties */
inherit "/std/object";
int lighted; /* Global variable to indicate if the bulb is lighted */
void
create_object()
{
set_name("bulb"); /* The id of this object */
set_pname("bulbs"); /* The plural id */
set_short("@@my_short"); /* The short description */
set_pshort("@@my_pshort"); /* Plural short descr. */
set_long("@@my_long"); /* Long description */
add_prop(OBJ_I_WEIGHT,75); /* Set weight property to 0.075 Kg */
add_prop(OBJ_I_VOLUME,200); /* Set volume property to 0.200 Ltr */
lighted = 0;
}
void
init()
{
add_action("do_light", "light"); /* Add the command 'light' */
}
string
my_short()
{
if (lighted)
return "lightbulb (bright)";
return "lightbulb (dim)";
}
string
my_pshort()
{
if (lighted)
return "lightbulbs (bright)";
return "lightbulbs (dim)";
}
string
my_long()
{
if (lighted)
return "The lightbulb is currently lit.\n";
return "The lightbulb is more like a dark bulb. Perhaps you can light "
+ "it.\n";
}
int
do_light(string str)
{
if (str != "bulb") /* Did the player type 'light bulb'? */
{
notify_fail("Light what?"); /* Set message if everything fails */
return 0; /* 0 means we didn't recognise it */
}
if (lighted)
{
notify_fail("The bulb is already lighted.");
return 0; /* 0 means we didn't recognise it */
}
lighted = 1;
write("You light your lightbulb.\n"); /* Message to the player */
say(QCTNAME(this_player()) + " lights "
+ this_player()->query_possessive() + " lightbulb.\n");
return 1; /* We recognised the command */
}
As you can see, I use for the short, plural short and long description weird strings, like "@@my_short". This is called Value By Function Call, and is explained in another part of this document. For the moment it is enough to know that they return the value of the mentioned function.
To add a command to a player I use the add_action() function in the init(). Each time a player "comes near" the object, the command "light" is added. If he "leaves" the vicinity of the object, the command is gone again. Should the player type "light torch", the function do_light() is called with whatever was typed behind the command "light" as argument. Functions that are called by add_action() have to return 0 if they do not recognise the command, or 1 if they handled the command. This allows multiple objects to define the command "light" in our case. The moment some function returns 1, the quest for "light"-commands stops. If no function returns 1, the player will get to see "What?". That is, if no notify_fail() was set. If some string was set, then the player does not get to see "What?", but the set string.
If the player typed "light bulb" and the bulb was not lighted, then we can turn the bulb on. First we give a message to the player that she succeeded, with write(). Write() goes to the player that gave the command, this_player() to be exact. Then we give a message to everyone in the same room as the player, but not to the player herself. For this you can use say(). Now a typical 3.0 thing happens: the macro QCTNAME(). This stands for Query- CapitalizeTheName, and makes sure that people who know this_player() get to see her name, but that people who do not know her get to see something like "The cute happy hobbit" instead. There is a good document that describes the met-nonmet system exellently, I believe it is called /doc/man/general/meet_people.
Because we want to say "his lightbulb" for men and "her lightbulb" for women we ask the possessive article of the player with query_possessive().
After all of this we return 1, because we have recognised and completed executing the command "light". Perhaps it would be a good exercise to enable the players to "darken" the lightbulb as well.
This is a more tricky object, because it does not only have its own volume and weight, but can also hold a specific volume and weight. Let's say you want to make a nightstand:
/* A nightstand */
inherit "/std/container"
#include "/sys/stdproperties.h"
void
create_container()
{
set_name("nightstand");
set_short("crummy old nightstand");
set_adj(({"crummy","old"}); /* extra adjectives, the player */
/* can now do "exa old nightstand" */
set_long("The crummy old nightstand will probably not last long.\n");
add_prop(CONT_I_WEIGHT, 20000); /* It weighs 20 Kg */
add_prop(CONT_I_MAX_WEIGHT, 27000); /* It can contain up to 7 Kg */
add_prop(CONT_I_VOLUME, 14000); /* It measures 14 Ltr */
add_prop(CONT_I_MAX_VOLUME, 17000); /* It can contain 3 Ltr */
add_prop(CONT_I_RIGID, 1); /* It is a rigid object */
}
As you notice, the MAX_WEIGHT and MAX_VOLUME are the WEIGHT and the VOLUME plus what they can contain. The nightstand cannot change shape, so it is defined RIGID. A bag would typically not be rigid, unless it has been washed with too much paste ;-).
These babies can do a lot. I myself am not quite aware of all their capabili- ties, but there sure are a lot. I will only show how to make a really simple talking monster. Let's make an ugly nazgul by the name of Dschik.
/* Dschik, the ugly nazgul */
inherit "/std/monster";
#include "/sys/stdproperties.h"
#include "/sys/ss_types.h"
void
create_monster()
{
set_name("dschik");
set_race_name("nazgul");
set_adj("ugly");
set_long("Nazguls are ugly, but this one is one of the uglier types.\n");
/* STR DEX CON INT WIS DIS */
set_stats(({ 15, 13, 19, 3, 3, 70 })); /* Set his stats */
set_hp(1000); /* Heal him fully */
set_chat_time(10); /* Set the time between speaking */
add_chat("Go away!"); /* Add some lines to say (randomly) */
add_chat("Who are you?");
add_chat("Go hither, foul creature!");
set_cchat_time(4); /* Set some combat chat lines */
add_cchat("This is that one, fatal mistake!");
add_cchat("Prepare to die!");
set_act_time(7); /* Set some random actions */
add_act("growl");
add_act("grin");
}
This really is the most basic monster you can possible make. There are much more intersting features of monsters, such as making them do sequences of actions, letting them react to the outside world etc., but I won't discuss them here. Notice that I don't set the WEIGHT and VOLUME properties, so they will be set to 70 Kg and 70 Ltr.
This monster is not very tough. Novices begin with their stats between 8 and 18. This is what the stats mean:
Ofcourse you don't want your monsters to run around unprotected against the violent players. Therefore you wish to give them good armours. On the other hand: too many good armours will devaluate all armours. So, be reluctant in making good armours. I think the general rule is this: don't make very good armours, unless you make a very strong monster. Good armours should be tough to obtain. Here is an example of a blue platemail:
/* A blue platemail */
inherit "/std/armour";
#include "/sys/wa_types.h" /* Weapon and armour types */
#include "/sys/formulas.h" /* Some handy conversion formulas */
#include "/sys/stdproperties.h" /* Standard properties */
void
create_armour()
{
set_name("platemail");
set_short("blue platemail");
set_long("The blue platemail is heavy and doesn't look magical.\n");
set_default_armour(
19, /* Armour class */
A_BODY, /* Armour type */
0, /* Armour/weapon modifier list, it can be use to modify */
/* the armour class towards different damage types */
0); /* Object that defines wear and remove */
add_prop(OBJ_I_WEIGHT, 11000); /* 11 Kg. Well, it is a platemail... */
add_prop(OBJ_I_VOLUME, 1380); /* An iron platemail (see table) */
add_prop(OBJ_I_VALUE, F_VALUE_ARMOUR(19) + random(200) - 100);
/* Standard formula to calculate value with given ac */
}
I think this platemail is selfexplanatory... It is a body armour, when worn is provides a protection of ac 19, then the value is set to the standard formula for an armour of ac class 19. The value is randomized a bit, so the players will not be able to judge an armour by the price it has. With this construction, it will be max 100 coins more or 100 coins less than it should be.
With these objects you can arm your monsters. The same goes here, as with the armours: don't make too strong weapons, for it will cause inflation. If you have a good reason to make a strong weapon, then don't. There are probably hundreds of other people with similar ideas, and they all have the same conviction that you have. So, stick to bad/mediocre/fairly good weapons. Don't forget: there are more ways to make a good weapon other than making its weapon class high; you could make for example an aluminum sword, which would be very light.
/* A copper katana */
inherit "/std/weapon";
#include "/sys/wa_types.h" /* Weapon and armour types */
#include "/sys/formulas.h" /* Some handy conversion formulas */
#include "/sys/stdproperties.h" /* Standard properties */
void
create_weapon()
{
set_name("katana");
set_short("copper katana");
set_long("The copper katana looks like it can slice things easily.\n");
set_default_weapon(
19, /* Weapon hit (see /sys/wa_types for max.) */
33, /* Penetration (see same file for max) */
W_SWORD, /* Weapon type */
W_SLASH | W_IMPALE, /* Damage type */
W_NONE, /* A one hand weapon, free to choose which hand */
0); /* The object defining the (un)wield functions */
add_prop(OBJ_I_WEIGHT, 5000); /* 5.0 Kg */
add_prop(OBJ_I_VOLUME, 560); /* 0.56 Ltr */
add_prop(OBJ_I_VALUE,F_VALUE_WEAPON(19) + random(130) - 65);
/* Standard formula to calculate value with given hit */
}
This is one of the neatest new possibilities of 3.0. Instead of filling in what a function needs, you fill in "@@my_func@@", where my_func() returns the desired value. This means that my_func() can return a value that is dependant of the status when the function is called. You only need to give the latter "@@" if you want to imbed the string in another string, like:
Let's give a simple example; in the previous weapon, I change the set_long line to this:
And I add this function to the code:
string
dependant_long()
{
/* Check if the environment of the weapon is human */
if (environment(this_object())->query_race() == "human")
return "The copper katana has a firm grip in human hands\n";
else
return "The copper katana looks like a standard weapon.\n";
}
Every time someone examines the object, the function dependant_long() will be called. This function then checks if its carrier (if any) is of the human race. If so, another long than usual is returned. Virtually all the standard set_... functions support the usage of VBFC. It is a very powerful feature, which will often come in handy.
Very important is the difference between these two lines:
If you can explain the difference, you have understood VBFC truly. Let me give it a try: (1) sets the long description to whatever dependant_long() returns on the moment that set_long() is done. This will always be "The copper katana looks like a standard weapon.\n". The long description will not change after the set_long(), and even if dependant_long() changes, it will not affect the long description. (2) however, sets the true long description to the string "@@dependant_long", which will make the internal function of an object that really returns the long description evaluate the function dependant_long() each time it is called. This way the long description will be different under different circumstances.
Sometimes VBFC contructions do not work, e.g. sometimes if you use
The functions write() and tell_object() sends their messages exactly as they where given, the weird string you get. The function say() on the other hand handles VBFC nicely.
The whole VBFC effect is destroyed by using process_string, though. To get back at our previous examples:
It is very easy to use VBFC too much. As a rule of thumb one could use that if the outcome of a function is can be evaluated immediately, one should use a function. When the evaluation has to take place somewhere in the future, VBFC can be used.
Note that VBFC is not implemented in the driver, so if you want your functions also to be able to use VBFC, you will have to make sure that they can. Let's say you want to make a function that turns on a remote-control. You would make the code something like this:
void
set_remote(string arg)
{
global_remote = arg;
}
string
query_remote()
{
return check_call(global_remote);
}
The check_call() function handles the VBFC part, so you don't have to worry about that yourself.
There are dozens of skills, and you can find all of them in the file /sys/ss_types.h. This is how you would use skills in your code:
#include "/sys/ss_types.h"
/* This function returns 1 if the player can climb good enough */
int
try_climb(object pl)
{
if (pl->query_skill(SS_CLIMB) > 10)
return 1;
else
return 0;
}
It is not mandatory, but use the skills that players can have as often as you can. Make object values dependant of the value of SS_TRADING, or make a dog more aggressive when he encounters someone with SS_ANIMAL_HANDLING low. If the skills are used often the players will be more interested in raising them. Another way to use skills is to use tasks. This is more complicated, but gives you more freedom. To explain tasks adequately would take a lot of words, but luckily you can do "man tasks" and read all about them in great detail.
If you are new at coding in LPC, you are bound to make errors in your code. If your are not new at it, you will still make errors. They just slip into the code. Luckily the gamedriver offers you a way to debug your programs.
When you have edited an object, say "bulb.c", you want to see your result. To do this, you do "clone bulb". If all goes well, you will find that you are carrying a bulb on you. Check it, to see if it works fine. I.e. can you examine it, can you drop it, can you get it, etc. etc.
If you made a mistake in the code, you will get a message like "Error in line 23: syntax error". Beside that, an error message will be written to a log file. Which log file, depends on the directory that "bulb.c" is in. If it is in your directory, you can do "errlog" to see your error logfile. If the file is in a domain directory, you can do "errlog Domain" to check that errorlog. The last few errors on the bottom are probably the ones that made the error occur. In some muds the errors of the gamedriver are also logged to a file. Those errors tell usually more about the error you made, but also give a lot of other errors that came with it. So, more searchwork for you. On Genesis you can examine that file with the command "tail /lplog".
All errors are of the form:
If you look at the errorlog, usually a number of errors are given. It is wise to solve the highest new one first, because all the lower errors might exist due to it. For example: if you forget one bracket (")"), the gamedriver will claim not to know any variables in other functions below. Also the line number can best be interpreted in a wide sense: "the error must be around that line number". It is very well possible that a forgotten semicolon (";") generates an error a few lines lower.
Let's have a few error messages, and see what causes them:
You can do "man <subject>" if you want to, "more /doc/man/general/<subject>" is also possible.
The following table is of course utterly silly, because nobody is really going to use it. You could use it to get an indication of the weight differences, though. It is not obliged to make things look extremely real, but to get a feeling, here are the actual values for the Rho of several kinds of metal, where Weight = Volume x Rho.
| Metal | Rho | Alloy | Rho |
|---|---|---|---|
| Aluminum
Chrome Gold Lead Magnesium Copper Platinum Tin Iron Silver Zinc | 2.7
7.1 19.3 11.3 1.7 8.9 21.4 7.3 7.9 10.5 6.9 | Bronze
Cast iron Brass Steel Stainless steel | 8.9
7.3 8.5 6.9 7.8 |
Example: If you think your gold bar measures about 1.5 ltr, it will weight about 1.5*19.3 = 29 Kg.