Discworld Documentation:

LPC for Dummies

Written by Drakkos Wyrmstalker

N.B - This is a work in progress... a living document if you like. If it appears to be dead when you view it, don't worry. It's most likely just playing possum.

Comments on these chapters and tutorials can be e-mailed to Drakkos.

Tutorial Three - Putting It All Together - Simple Objects

Right. So far we've covered how to create an NPC, and how to create a room. Now we're going to take a look at how to code objects and how to make these objects available to NPCs and rooms alike. Now, like both our previous tutorials, we start with the comment, indicating what our file is, when it was coded, and who coded it:

Comments, And Inherits, And Setup, Oh My!

/*
    This is a basic object!
    Written by Drakkos.
    04/10/2000
*/

Now what? Well, like in both our room and NPC, we want to inherit a file. But it's slightly more complicated for an object, since there is such a wide variety of objects to choose from. Are we creating a weapon? A piece of food? A backpack or other container? There is a huge range of possible inheritables in the mudlib. To a degree, this is also true of NPCs and rooms, but it is possible to create a fully functioning example of both without worrying about the specialisations available. Let's take a look at some of the most common inheritables we may wish to use to create an object:

"/std/object"
The very low level object that has all the fundamental functionality (such as setting the name of an object), but none of the specialised functionality that other objects use. You would use this object if you were coding something that needed all the basic methods of an object, but is not previously defined elsewhere in the mudlib.
"/obj/weapon"
This object contains all the functionality needed to create your very own weapons of death and destruction. You would need this file if you were creating swords, or axes, or any other kind of hand-held, death-dealing piece of equipment.
"/obj/food"
This file contains the code for creating tasty foods, and all the nifty stuff for cutting food into pieces, or decaying. You would use this to code things such as apples, or cakes, or biscuits.
"/obj/clothing"
This is the file that deals with making clothes, and contains the functionality for wearing them, having them protect from the elements, and so on. Would be used when you were coding a coat, or a hat, or a set of sexy underwear (*rowr*!).
"/obj/vessel"
This is the file that contains the functionality for making containers for holding liquids. It would be used when coding a cup, or a glass, or a Thermos flask, for example.

There are many more potential inheritables... having a good look through the /std/ and /obj/ directories will reveal many more examples. For the purpose of this tutorial, however, we're only going to make our selections from the list above.

To start with, we are going to approach coding an object in the same way we have with our NPC and room. However, later in the tutorial, we will also look at 'virtual objects' and how these can be used to allow simple objects to be created very quickly.

So, let's give our NPC some clothes. A nice pair of waterproof dungarees to hold in the slime. So we're going to select /obj/clothing from our list above:

inherit "/obj/clothing";

Simple!

Now, like with almost all objects, we need a setup() method. Again, this will look very similar to what we've already seen with our NPC and our room, but with a few subtle differences. Like with the NPC, we need a set_name(). Clothing is something that can be touched, looked at, and manipulated by a player, so a name is required so that this is possible. We also need a set_short, and a set_long. These do exactly the same things as with our other examples. In the case of an object, we also need a set_weight() which determines how heavy the object is (One unit = 1/9 of a pound).

We also need a set_value(), which determines how valuable the object is in brass coins (4 brass coins = 1 Ankh-Morpork pence). So let's plug them into our setup() function:

void setup() {
    set_name("dungarees");
    set_short("pair of waterproof dungarees");
    set_long("This is a pair of waterproof dungarees. Luckily they "
        "are also ooze proof.\n");
    set_weight(45);
    set_value(4000);
}

But we're not done yet. Although this object will load fine, attempting to clone it will send the object to **Null-space**. Objects that have subtle errors in their setup will often do this. In this case, the error is that although we have all the values set for manipulating the object, as a piece of clothing there is another piece of information that needs to be set... the type of clothing it is. A list of valid clothing types and the body areas they cover is contained in the clothing handler (/obj/handlers/clothing_handler).

For this example, we're going to choose the type 'robe' for our dungarees, since it's roughly equivalent to what we want. We include the line set_type ("robe") in our setup, like so:

void setup() {
    set_name("dungarees");
    set_short("pair of waterproof dungarees");
    set_long("This is a pair of waterproof dungarees. Luckily they "
        "are also ooze proof.\n");
    set_weight(45);
    set_value(4000);
    set_type("robe");
}

So, now we update our object and clone it again. It clones fine, and we can wear it using 'wear dungarees'. Cool! But let's 'look' at the dungarees. Hmm... something is wrong. Their condition is 'complete wreck'. But we just cloned them! But we forgot to give them a condition. If we put the line 'setup_clothing(50000)' in our setup, we'll give our clothing a condition (50000), and set it to have that condition when cloned.

Finally, it's a little limiting to have to refer to our 'pair of waterproof dungarees' as just 'dungarees'. If we try and refer to them as, for example, 'pair of dungarees', we get warned that we can't find a match. We also can't refer to 'waterproof dungarees', or even 'pair of waterproof dungarees'. Wouldn't it be nice if there was a way we could? Well... there is! The function add_adjective() allows for adjectives to be added to objects. It will take either a single adjective as an argument:

add_adjective("waterproof");

Or an array of arguments:

add_adjective(({"pair of", "waterproof"}));

So we'll use the array method to add our adjectives, meaning we can now refer to our pair of dungarees, our waterproof dungarees, our pair of waterproof dungarees, or even just dungarees. Well... that was painless, wasn't it? And we now have a nice pair of dungarees for our blob. But how do we actually get our blob to wear them?

Moving Objects Into NPCs

Well, let's go back to our NPC code. The first thing we need to decide is when we want our NPC to have our new dungarees. Well, since the ooze is painful on the eyes, we want the NPC to have the dungarees as soon as he appears... so let's put the relevant code in the blob's setup(). The efun we need to clone a copy on an object is clone_object(). This takes the path of the file as an argument, and returns an object pointer to the cloned variable:

object ob = clone_object("/w/your_name/simple_object");

And we can then use the move() method of object ob to move the dungarees into this_object():

ob->move(this_object());

Once we have the dungarees in our NPC's inventory, we want him to wear them. The function call init_equip() will inform our NPC to wear/hold every piece of equipment in his inventory that he can. So, after putting all of that into our NPC, his setup now looks like:

void setup() {
    object ob = clone_object("/w/your_name/simple_object");
    
    set_name("blob");
    set_short("grey blob");
    set_long("This is a grey blob. It is grey. It is also quite "
        "blobby.\n");
    basic_setup("human", "warrior", 10);
    set_gender("male");
    add_adjective(({"oozing", "grey"}));
    set_main_plural("grey blobses");
    add_alias("porridge");

    add_respond_to_with(({ "@say",({"blob", "grey"}), }),
        "say Yes, I am a grey blob.");
    add_respond_to_with(({ "@say",({"ooze", "blue", "cardboard"}),
        ({"porridge", "bing", "womble"}), }),
        "' Yes, I'm oozing quite nicely, like grey "
        "blobs do. Like porridge!");
    load_chat(20,({ 2, ": oozes around.",
        1, "' I'm very grey.",
        2 , "' I'm a blob.", }) );
    load_a_chat(20,({ 2, ": oozes all over you.",
        1, "' Lemme alone!.",
        2 , ": sobs bitter, slimy tears.", }) );

        ob->move(this_object());
        init_equip();
    }

Now, let's go to our blob's lair. He appears, and we look at him... and lo and behold, he's wearing a nice pair of waterproof dungarees! Neat! Note that the code for cloning and moving the dungarees into the NPC could have cut out the middle stage of storing the returned reference from clone_object, and been written more compactly as simply:

clone_object("/w/your_name/simple_object")->move (this_object());

Since we're never actually referring to 'ob' anywhere else in the code, it is safe to do this as a matter of brevity.

If the object we wish to provide for our NPC is part of the standard library... in other words, if it lives in the /obj/ directory, then we can use the armoury to request a copy of the item. To do this, we #include armoury.h at the top of our file. Armoury.h #defines the location of the armoury file as ARMOURY. We can then use the function request_item() to request an object. Request_item() takes two arguments... the first is the item to be request, the second is the percentage condition of the item:

object ob = ARMOURY->request_item("short sword", 75);
ob->move(this_object());

This would request a short sword and set is condition at 75% of the maximum. As above, we can combine these two function calls into a single line:

ARMOURY->request_item("short sword", 75)->move(this_object());

Which would request the item from the armoury, and move the return value of request_item() into this_object(). You should always use the armoury if you are providing your object with one of the standard weapons, armours, clothes or jewellery since the armoury will keep track of filename changes for you.

Virtual Objects And Coding Weapons

Right, so we've got our NPC all clothed. Let's give him a nice weapon too, to defend himself from nasty leetle people who want to hurt him. And to do this, we're going to have a look at virtual objects. Virtual Objects (or Vobjects) are objects that are first cloned from a base inheritable, then configured. This is similar to what happens when you inherit a file then configure it, and is a very easy way of creating simple objects. A virtual object cannot have its own local functions, however, and so is limited to objects that have no special characteristics. Virtual objects are more efficient for the MUD since they use up less memory (although they do use more CPU cycles initially). The nature of a virtual object is defined by its filename extension: .wep - Virtual weapon .arm - Virtual armour .sca - Virtual scabbard .clo - Virtual clothing .ob - Virtual object .r - Virtual room .mon - Virtual monster. If we wanted to create a nice weapon for our NPC, for example, we'd call the file simple_weapon.wep. Much like with an object as described above, we need to set the various aspects of our weapon. In a Vobject, we do this with config lines, which are of the form '::value::'. For example, if we wanted to create our dungarees above as a virtual object, we'd write the file simple_object.clo as:

::name:: "dungarees"
::short:: "pair of waterproof dungarees"
::long:: "This is a pair of waterproof dungarees. Luckily they "
"are also ooze proof.\n"
::adjective::({"pair of", "waterproof"})
::weight:: 45
::value:: 4000
::type:: "robe"
::setup:: 50000

Then we'd clone it as above (however, since it is a virtual object, we need to include the filename extension in the clone_object: "/w/your_name/simple_object.clo" rather than just "/w/your_name/simple_object").

So, to our weapon. We create a file simple_weapon.wep, and set it up with the basics we need: name, short, long, adjectives, weight and value:

::name:: "mop"
::short:: "dirty mop"
::long:: "This is a dirty mop, dripping with ooze.\n"
::adjective:: "dirty"
::weight:: 20
::value:: 2000

We also need some information on what kind of attacks our mop has. These are done using the function add_attack() in a .c file. In our virtual object, we use ::attack::. The format is (name of attack, chance it happens, ({fixed damage, number of faces, number of dice}), skill needed, type of attack, and function). Let's break that down a little.

The name of the attack is a symbolic label we can use to refer to that particular attack. The chance is the percentage chance that particular attack will be called. The next argument is an array of integers, and these are in order, the number of sides each dice has, the number of dice, and a constant value to be added to the damage. So the arguments ({10, 5, 10}) would mean 'Start off with ten points of damage, then add the total of rolling ten five sided dice', giving a range of 20-60 points of damage. Skill needed is the leaf fighting skill the weapon is checked against, and type of attack is the type of attack (is it pierce, slash, blunt, etc). Function is a function pointer... if the function exists, then it will be called every time this attack is made. So, we want to add a few attacks to our weapon. Let's give it two blunt attacks... one called 'prod', and the other called 'whack':

::Attack:: "prod", 50, ({ 5, 6, 6 }), "blunt", "blunt", 0
::Attack:: "whack", 50, ({ 10, 10, 5 }), "blunt", "blunt", 0

And finally, like with our clothing, we need to setup the weapon so that it has a maximum and current condition:

::setup:: 10000

And once we put it all together, we get:

::name:: "mop"
::short:: "dirty mop"
::long:: "This is a dirty mop, dripping with ooze.\n"
::adjective:: "dirty"
::weight:: 20
::value:: 2000
::Setup::10000
::Attack:: "prod", 50, ({ 5, 6, 6 }), "blunt", "blunt", 0
::Attack:: "whack", 50, ({ 10, 10, 5 }), "blunt", "blunt", 0

Tada! We clone this into our NPC using the shorthanded notation above. We put this before our init_equip():

clone_object("/w/your_name/simple_weapon.wep")->move (this_object());

And voila! We have an NPC with nice clothes, and a nice weapon to defend himself with. And now the world of objects is open to you. The first steps you should take when writing an object of an unfamiliar kind, however, is to look for examples. Find objects already of that type and see how they work. Then, if you feel brave, check the code for the inheritable to see if you can make any sense of that. But don't be afraid to experiment with what is available, and if you run into problems there will almost always be someone online willing to help.

Chapter Summary

And that's it for our third tutorial! We're now intimately acquainted with NPCs, rooms, and objects... and we even know how to use them together. The next tutorial will go over rooms, NPCs and objects once more, and explain some of the other things you can do with them if you want to spruce them up. :-)

Support Files

/d/learning/newbie/introduction/examples/simple_object.c
/d/learning/newbie/introduction/examples/simple_weapon.wep
/d/learning/newbie/introduction/examples/simple_object.clo