This document is an adaption of a text written by tricky@genesis in 1992.
Some things were simply outdated, other were not relevant or just wrong for the situation at outerspace.

Avatar@outerspace, Thu Oct 7 14:37:58 2004


LPC 3.0... What changed?

Index


Preface

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.


Introduction

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 appriciated.

Tricky

Remark from avatar. If you got any suggestions, improvements or found bugs in his file please mail them to the persons who are currently in charge of the webpages (www@ mud. stack.nl) or to the last editor of the relevant page. In case of this document the editor is Hennes@stack.nl


The 3.0 mud

Some commands

Here are some command from LPC 3.0 that really are interesting in 3.0

allcmd Shows all your commands.
allcmd can select subgroups, e.g. allcmd dutch shows all your commands that are added by the dutch soul.
localcmd Shows all commands added by your direct environment.
help <cmd> Shows often help on the command <cmd>.
man [-k] <subject> Searches for help on the function <fun>. If you use the -k flag, the function <fun> will serve as a keyword, all matching functions will be displayed. This command is to search for information on efuns and general subjects.
sman [-k] [-s] <fun> Searches for help on the function <fun>. If you use the -k flag, the function <fun> will serve as a keyword, all matching functions will be displayed. This is used to search the mudlib sources. If you use the -s flag, you get to see the sourcecode of the function. Note that this works fine when the searchtable is updated regularly, but that it could occur that you end up in the wrong spot in the sourcecode. No fear; you probably are near the place where the source really is.
ed <file> Edit a file with the build-in editor. This is a quite old lineeditor. You can get help on the editor with "h", or "?" in the editor.
NOTE: you don't have to do your editing in Ed, if you can ftp, try this: "ftp mud.stack.nl 3001", use the login "<yourname>" and your own mud-password.
( Then you can use "[m]put" and "[m]get" to transfer files to and from your own directory. That way, you can use all the comforts of your home editor. )
If you use tf and you use Johans's .tfrc then you can also use "/vi filename".
cp <from> <to> Copy the file <from> to the file <to>. You may even use wildcards like "*" to match files. There is a maximum number of files you may match, though. <To> may also be a path.
mv <from> <to> Move (rename) the file <from> to the file <to>. The file <to> doesn't have to be in the same directory as <from>.
rm <file> Remove (delete) the file <file>. Use wildcards if you like.
rm has an anoying safety 'feature' which prevents you from erazing more then 20 files.
sanction <flags> <who> Allow/disallow someone to read/write in your own directory or to snoop you. Try "help sanction" for more info.
start here Set your startup-place. Most people choose to start in the privacy of their workroom, but you can also start in any other room (e.g. in an area that you are working on.)
busy [flags] This command sets your busy level. There are too many flags to explain them all here. Let me just say that it is possible to make yourself completely deaf with this command.

Well, those were not all commands by far, 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.


Directories under 3.0

The paths under 3.0 have changed radically. I will point out what the interesting directories are.

/doc This directory contains only subdirectories. As the name suggests, those directories are filled with docs.
/examples This directory contains a growing number of examples of various objects. If you want to make something, see if there is an example of it somewhere in this directory.
/doc/man/efun This directory contains all manuals for the external functions that are provided by LPC. If you search for a function that is not specific for any object in particular, look in this directory. You can also "man <external function>" to look at its description.
/doc/sman This directory contains all manuals for the local functions of most objects that are often inherited. There is a lot to be found in here! It is probably easier to do "sman <local function>". "sman" stands for source manuals; the sources of the functions can be examined by doing "sman -s <local function>". If you only know a name partially, you can do "sman -k *<partial name>*", that will show all function names that match.
/std This directory contains all objectfiles that need to be inherited if you make an LPC object. Multiple inheritance is allowed in LPC, but usually inheriting one objectfile will suffice. Inherit eg.:
armour.c for amours, like a chainmail, shield, helmet.
coins.c for money, like some platinum coins
container.c for a container, like a bag
creature.c for a very basic creature, like a snail.
drink.c for a drink, like brandy
food.c for food, like bread or an apple
herbs.c for herbs, like blackberries
monster.c for a human-like monster, like an orc
object.c For generic objects, like a painting
spells.c for an object that defines spells, like a scroll.
weapon.c for weapons, like a two-handed broadsword.
/lib Here you find inheritable modules that do stuff for you. If you want to make a shop, for example, a lot of convenience routines have been implemented for you in "/lib/trade.c". Just inherit that module as well in your shoproom, and you can use those routines. Here is a list of some library modules:
guild_support.c for make guildshadows
herb_support.c for creating herbs more easily
skill_raise.c for making guildrooms where skills are trained
store_support.c for making shops
trade.c for things that use trading with coins
/sys In 3.0 you use a lot of values to access things. In order to keep things transparent to the wizards, all values are hidden behind logical sounding names. This also enables the shifting of values without anyone noticing it in his objects. Therefore you should use always defines instead of their real definitions. In this directory you will find almost all of the includes that are interesting to include, such as:
macros.h handy macros.
stdproperties.h standard properties of all kinds of things.
wa_types.h weapon and armour types.
ss_types.h standard skill types.
formulas.h standard conversion formulas.
money.h money definitions
/secure The 3.0 security system is more tight than that of 2.4.5. More and more the access to certain functions is checked. The first object that is loaded when the GameDriver is rebooted is the security master, or master for short. Some functions can only be called via this object, e.g.: SECURITY->query_domain_lord("Aurora");. The name SECURITY is defined in the file /secure/std.h
/d In this directory all the domain directories are located. As you probably have noticed, all domains start with a capital letter.
  • /d/Earth
  • /d/Aurora
  • /d/Qwerty
  • /d/Acrys
  • /d/Space
  • /d/Standard/
    This is the domain that contains all startup-areas of all races, the wiz-area, the race-specific souls and much more. All directories can be read by you. Make that, you can not read any files here.
  • /d/Discarded
/home/ This directory is the home of the hard working wizards. The keepers and arches also have their directories in this directory. You will probably not be able to read any files in them ;-).
/home/<your_name> This is your directory. You have read and write access to it. As usual this directory can also be accessed as ~. E.g.: ~/workroom.c is your workroom, whereas ~avatar/workroom.c is Avatar's home office.
/d/<Your_domain>/common This is the common directory of the domain you're in. Every wizard in the domain has write-access on it. That can be usefull if someone wants to debug your stuff when you are away.
/d/<Your_domain>/open/
/d/<Your_domain>/<your_name>/open/
Everyone (including people outside your domain) has read-access to these directories. That is why you don't need to write in the /open directory, simply put your open stuff in your own open directory!

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:


LPC 3.0

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.

Types

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 easily, and maybe even directly at the compilation and not wait until the bugous function is called.

LPC 3.0 knows these types:

void
Used for functions that do not return anything
int
Integers, ranging from -(2^31) to 2^31
int *
An array of integers, e.g. ({ 1, 2, 3 })
string
A string of characters and special characters, e.g. "\tHi!\n"
string *
An array of strings, e.g. ({ "foo", "bar" })
object
An object as you get with clone_object()
object *
An array of objects
mapping
A special array, e.g. ([ "foo":"f", "bar":"b" ])
mixed
Anything of the above.
mixed *
An array of mixed values.

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.

Type qualifiers

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:

static <basic type>
The function can only be called by this object. It can't be directly accessed with call_other.
private <basic type>
This function is only callable from other functions in the same compilation unit (the same file and files that include or are included by the file). It can't be directly accesessed with call_other, and it can't be called from functions in other modules in the inheritance chain.
public <basic type>
This function is normally callable. It is the same as if you didn't put a qualifier at all.
nomask <basic type>
A nomask function can't be hidden by an inheriting function. Neither can it be shadowed.
varargs <basic type>
A note to the compiler that the function may be called with a variable number of arguments.

Varargs and nomask can be combined with any of the other modifiers and with each other...

Applied to global variables:

static <basic type>
This variable will not be saved when the object is saved with save_object() or changed by a restore_object().
private <basic type>
This variable can only be accessed by functions in the same compilation unit.
public <basic type>
This is the same as a variable with no modifier.

Arrays

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:

arr[index]
This will give you the "index"th element of the array arr. Note that the elements of an array are numbered from 0 to the length of the array minus one (sizeof(arr)-1)).
member_array(elt, arr)
With this you can check if a given element is a member of the array you want to check. If it is a member, the index will be returned. Since this means that 0 can be the return value, -1 will be returned if the element is not included in the array.
explode(str1, str2)
This function returns an array that consists of the loose strings that come into existence after the string str1 is divided in parts by cutting on every occurence of str2. Eg. if you did explode("Humpty Dumpty fell of a wall.", " "), you would get ({ "Humpty", "Dumpty", "fell", "of", "a", "wall." })
implode(arr, str)
This function returns a string that is made of all elements of the array arr, with the string str placed between them. E.g.: implode(({ "foo","bar","zukini."}), " and ") would return "foo and bar and zukini."
arr[from..to]
This notation returns a piece of an array from element from to element to. The function slice_array, which did exactly the same, is obsolete. E.g.: ({ 1, 2, 3, 4 })[1..2] returns ({ 2, 3 }).
filter(arr, fun, obj)
This function returns an array with all elements in it for which the function fun in object obj returned 1. The elements for which 0 was returned are left out. fun is the function name, enclosed in quotes.
map(arr, fun, obj)
This function returns an array in which all elements have been replaced by whatever the function fun in the object obj returns for each element. fun is the function name, enclosed in quotes.
sort_array(arr, fun, obj)
This function returns the sorted version of arr, by using the function fun in object obj as less-equal function. Say eg., you want your array of strings to be sorted alphabetically. The you would make a function str_less_eq() like this:
And then you would do: Et voila! An alphabetically sorted array.

Mappings

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.


Objects in 3.0

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.

Example:

#inherit "/std/room"
#include <default.h>

void
create room()
{
  set_long("foo bar avatar\n");
  set_short("fooski");
  ...
}

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.

Objects and properties

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. ( #inculde <stdproperties.h>) 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 <stdproperties.h>
  #include <default.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.

Some examples

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.

room.c

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 <stdproperties.h>
  #include "default.h"

  void
  create_room()
  {
     set_short("Entrance");  /* This will show up when the player */
                             /* is in "brief" mode.  No newline!  */

     /* Set the long description of the room */
     set_long( "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" );

     add_exit( MYDIR+"rooms/church.c", "south", 0, 1); /* add exit */
     // #define MYDIR in your default.h file to point to your directory.
     // This has two advantages: It save you some work since typing MYDIR+
     // is a lot shorter the e.g. /d/Aurora/common/wizschool and
     // when the area is moved (e.g. from /d/Aurora/unfinished) to an other
     // area all that needs to be modifified is a single line of text.

     /* Make the corner examinable */
     add_item(({ "corner", "round corner", "edge" }), // Multiple desc...
      "You desperately search for a corner, but you cannot locate one. "+
      "How is this possible? You are starting to get a headache.\n");

     add_prop(ROOM_I_INSIDE, 1); /* This is an indoors room */
  }

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.

object.c

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 <stdproperties.h>
  #include "default.h"

  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.   */
    set_long("This is a dark lightbulb.\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.

Most of the time you can also leave out the set_pname and set_pshort part. The parser will try to make its own plural, which often is correct. However 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 */

  inherit "/std/object";

  #include <macros.h>        /* Some useful macros /*
  #include <stdproperties.h> /* We want to use standard properties */
  #include "default.h";

  int lighted; /* Global variable to indicate if the bulb is lighted */
               /* In this file we'll use lighted==1 to indicate that */
	       /* The bulb is lighted and ligted==0 to indicate that */

               /* NB: All uninitalized values default to 0           */

  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; NOT NEEDED since this initilizes to 0, but putting
     // it in the code may make the code easier to understand.
     // Also add some text explaining what value this var may have and
     // what they mean.
  }

  void
  init()
  {
     ::init();
     add_action("do_light", "light"); /* Add the command 'light' */
  }

  string
  my_short()
  {
    // We'll return a different description depending if the lightbulb
    // is lighted or not.

    if (lighted)
       return "lightbulb (bright)";
    return "lightbulb (dim)";
  }

  string
  my_pshort()
  {
    if (lighted)
      return "lightbulbs (bright)";
    return "lightbulbs (dim)";
  }

  string
  my_long()
  {
     // The long description for this lighbulb

     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 we reach this function then the player entered 
    // a command which started with 'light'.

    if (str != "bulb") /* Did the player type 'light bulb'? */
    {

       /* Set message if everything fails.
        * Without this a command like 'light fietbel' would display 'What?'.
	* Now it will display 'Light what?
	*/
       notify_fail("Light what?");

       // If we want the muddriver to continue working on the command then
       // We need to return 0. This probable results in a fail message.
       // If we recognize the command and do something ourselves then we
       // need to return 1.
       return 0; 
    }

   // IF we got here then the player typed 'light bulb'

   if (lighted)
   {
      notify_fail("The bulb is already lighted.");
      return 0; 
   }

   // Now we know that the bulb is not yet lighted and that the player
   // issued the command 'light bulb'

   write("You light your lightbulb.\n"); /* Message to the player */

   /* Message to the rest of the room. NameofthePlayer lights
    * his/her lightbulb
    */
   say( QCTNAME(this_player())+ " lights "+
        this_player()->query_possessive() +
	" lightbulb.\n");
   lighted = 1;

    /* Command has been done
     * No need for the driver to continue with it (and possibly sent a
     * failure message to the player too
     */
   return 1
  }

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().

OUTERSPACE addition: If you include the file <default.h> then you can use HIS(x), HE(x) and HIM(x) which will return the correct form for all three sexes (G_MALE, G_FEMALE and G_NEUTER).

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.

container.c

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 <stdproperties.h>
  #include "default.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 ;-).

monster.c

Monsters are essentially NPC's. This means that they have almost all the qualities of a normal player including a lot of commands such as emotions and object manipulation. They are also included among creatures that players can be introduced to. Let's make an ugly nazgul by the name of Dschik.


  /* Dschik, the ugly nazgul */

  inherit "/std/monster";
  #include <stdproperties.h>
  #include <ss_types.h>
  #include "default.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 */
    // be reasonable with your stats. Above monster is illegal  since
    // the amount of experience gained on the avarage stats of a monster.
    // This example monster is quite easy to kill for the amount of exp.
    // that it is worth.

    set_chat_time(16);        /* Set the time between speaking */
    add_chat("Go away!");     /* Add some lines to say. Random */
    add_chat("Who are you?"); /* choice between them.          */
    add_chat("Go hither, foul creature!");

    set_cchat_time(7); /* 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");

    // Do not add to many monsters in a single room with short act time.
    // They will spam the room with text.
  }

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. Please note that this monster also has the default heigh of 1.60 meters and that most rat/rabbits/bee's etc are quite a bit smaller. If you ever make a monster that is significantly small then do set these properties to a sensible value.

This monster is not very tough. Novices begin with all stats set to 10.

STR
Strength, determines how much one can carry or how hard a player can hit.
DEX
Dexterity, determines how good someone is in handling weapons.
Also evading other people weapons, excuting tasks that require mimbleless etc.
CON
Constitution, determines the maximum number of hitpoints.
INT
Intelligence, determines the maximum number of mana.
WIS
Wisdom
DIS
Discipline, determines how quickly someone will wimp out. Someone with low DIS is a chicken, high DIS means she's bold.
Also, somebody with los dis may not attack a strong person (safety) and if you team up with a group that you need to have trust in the leaders discipline... else you can not join him/her.

armour.c

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 <wa_types.h>      /* Weapon and armour types /*
  #include <formulas.h>      /* Some handy conversion formulas */
  #include <stdproperties.h> /* Standard properties */
  #include "default.h"

 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, this is why we needed <wa_types.h> */
                       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 self-explanatory... 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.

weapon.c

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. Do not forget to give your monster some skill with the weapon, otherwise it will not inflict much dammage with it.

  /* A copper katana */

  inherit "/std/weapon";
  #include <wa_types.h>      /* Weapon and armour types */
  #include <formulas.h>      /* Some handy conversion formulas */
  #include <stdproperties.h> /* Standard properties */
  #include "default.h"

  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   /* read man balance for legal values */
     23, /* Penetration  /* read man balance for legal values */
     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 */
  }

VBFC

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:

It is possible to specify arguments that should be passed to the function, as well as an object in which the function should be called. To be more exact, one can do: "@@my_func:object_to_call|arg1|arg2...|argN@@". If you don't provide the object_to_call, this_object() is assumed.

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:

  1. set_long(dependant_long());
  2. set_long("@@dependant_long");

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.
TODO check of dit nog waar.

Sometimes VBFC contructions do not work, e.g. sometimes if you use

you will get to see a weird string. In that case, use the function process_string() to fix things:

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:

  1. set_long(dependant_long());
  2. set_long(process_string("@@dependant_long"));

have exactly the same effect. The outcome of the process_string() will always be "The copper katana looks like a standard weapon.\n" and the long description will be set to that string.

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.


Skills

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 <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.


Debugging

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 outerspace 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 Outerspace 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:

"d/Genesis/fatty/donut.c line 28:Illegal LHS"
The LHS stands for Left Hand Statement. Line 28 contains an equal-sign ("="), the part on the left is not of the same type as the part on the right. So, you may not make them equal.
"d/Genesis/fatty/donut.c line 12:Newline in string"
On that line, a string was declared, but not closed with a doublequote ("). E.g. set_short("donut);
"d/Genesis/fatty/donut.c line 186:Variable SS_EATING not declared !"
This one is clear. On line 186 a variable is used that has not been declared previously. Don't be misled by the name `variable'. It can well be that you forgot to #include some file that does define it. Since #defines are usually names consisting of only capitals, we probably forgot to #include or #define something here.
"d/Genesis/fatty/donut.c line 20:Return type not matching: "int ""
At that line you return a value that is not of the same type as the function promised to return in its declaration. Either adjust the declaration, or return something of the correct type.
"d/Genesis/fatty/donut.c line 27:Wrong number of arguments to eat"
The function eat() was not declared to get the number of arguments given to it on line 27. That might be an error of you, but perhaps you want to give not always the same number of arguments. In that case you forgot to give the function the following declaration: "varargs eat(...)".
"d/Genesis/fatty/donut.c line 59:Undefined function smear"
There are two possibilities: you forgot to define the function (or mistyped its name), or you did define it, but below the function that calls it. If you don't declare functions with types, the gamedriver is not picky about that last fault, but if you do, you should either move the declaration of the function above the call to it, or place a prototype (e.g. "int smear();") on top of the file.
"d/Genesis/fatty/donut.c line 100:syntax error"
This is a mean one. A syntax error means to say that the the syntaxrules of LPC were violated. This could mean that you forgot a semicolon, bracket or brace ("}") somewhere. It could also mean something completely different, like you put to "+"-signs next to each other, without something like a number or text between them. Look closely at the lines preceding the line of the error.
"d/Genesis/fatty/donut.c line 85:Illegal to redefine nomask function"
The name of the function on that line has already been used before. Probably in the object it inherits. Whoever wrote that object didn't want us to redefine that function, and declared it "nomask".
"d/Genesis/fatty/donut.c line 85:Missing type for argument"
If the functions are declared with types, their arguments should also have types. E.g. "void foo_gnu(object gnat)".
"d/Genesis/fatty/donut.c line 93:Bad type for argument 1 (object vs string)
The function called in line 93 was declared to receive a different type of argument then what was given to it. E.g. "foo_gnu("bar");", where foo_gnu() was defined as above.

Interesting documents:

You can do "man <subject>" if you want to, "more /doc/man/general/<subject>" is also possible.

/doc/man/general/*


Constants for different metals

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
Aluminum 2.7
Chrome 7.1
Copper 8.9
Gold 19.3
Iron 7.9
Lead 11.3
Magnesium 1.7
Platinum 21.4
Silver 10.5
Tin 7.3
Zinc 6.9
Alloy Rho
Brass 8.5
Bronze 8.9
Cast iron 7.3
Stainless steel 7.8
Steel 6.9

Example: If you think your gold bar measures about 1.5 ltr, it will weight about 1.5*19.3 = 29 Kg.