Sanity's Edge Area HOWTO
This tutorial covers the essentials in coding an area that will work on Sanity's Edge. It does not extend to cover theme and balance issues, but does include some general guidelines on style and the way things are done on SE. Some concepts may be over simplified to the point of being not quite true. This is to avoid making things complicated and does not matter in the context of this tutorial.
A simple plan.
Most areas start out with a basic idea, occasionally becoming more complex as the coding progresses. As this is purely an introductory tutorial, we will create a two room area with an npc and a couple of things to look at. Many new coders have made the mistake of taking on too big a project to begin with and most of the time they give up after spending a lot of effort in trying to get their complex ideas working. Keep it simple and enjoy the feeling of seeing your first area released.The first room.
This will be the starting point for our area, Buckey's Engineering. Buckey does not do a whole lot of business, but he needs a nice office. Let's start with a simple room and work from there.
/* reception.c
* The reception to Buckey's Engineering.
* Coram, 18th February 2003.
*/
inherit "/std/room";
void
create_room()
{
set_short("Buckey's reception");
set_long("This is the reception to Buckey's Engineering. A "+
"white table faces the entrance, below the company logo. "+
"A fancy holographic display dances from frame to frame "+
"of an advertising video clip.\n");
}
So what have we done? The first four lines make up a comment. When you begin a line with "/*" the mud driver ignores anything between it and the following "*/". This allows us to explain inside our code files what is going on. In a simple room such as this we probably only want to include the file name, a brief description of the purpose, and our name and the date. In more complex files it is helpful to explain what is happening for when someone else has to work on your code later on.
The next line states that we want to base our room on the basic room provided by the mudlib. Without going too deeply into object oriented concepts, in LPC we can reuse existing objects (and all the wonderful complex code in them) by inheriting them and making any changes we need to. All rooms in the mud use /std/room as their base.
The most important function in any room is create_room(). A function is a bit of code in an object that can be run independantly of the rest of the code in the object. In this case, we can put all our code that makes this room different from all the others inside the create_room() function, which is called when a room is created (funny that). The "void" on the line above indicates the return type, which we can safely ignore for now.
Now, inside create_room(). Rooms need to have a short and a long description. When you wander around Sanity's Edge with brief mode on you see a single line every time you walk into a room. This is the short description. When you use the look command you see the long description. We use the set_short() and set_long() functions to define these. When a description will go more than one line you can use the "+" sign to run the text over multiple lines. You may have wondered what the "\n" at the end of set_long() does. This means "new line" and wherever you see it indicates the end of that line of output (don't worry about this too much for now).
That is the basic room complete. It is not very interesting yet so we should give the players something to look at and let them move around.
Things to see, places to go.
There are two main ways to put stuff into a room; cloning objects and by use of the add_item() function.
There are all sorts of objects within the mud. Rooms, npcs, weapons, clothing and food are all objects. The same way we based our room on the mudlib's standard room, these objects are all based around the simplest object type, /std/object. If we wanted to put an object in Buckey's reception that a player might want to take with them (eg. a paper weight) we could use /std/object as the base and then change the way it looks like we did for the room.
A lot of the time it is not necessary to put a real object in the room, such as when we wish to alter the way a room looks. The hologram projected onto the desk would be rather strange if you could actually take it. This is where the add_item() function comes in. It allows us to "fake" an object. We can put descriptive elements in the room that a player may look at but not mess with.
void
create_room()
{
set_short("Buckey's reception");
set_long("This is the reception to Buckey's Engineering. A "+
"white table faces the entrance, below the company logo. "+
"A fancy holographic display dances from frame to frame "+
"of an advertising video clip.\n");
add_item( ({"hologram", "display", "holographic display"}),
"A fancy display shows off some of Buckey's achievements.\n");
add_exit("/d/Edge/coram/buckey/office", "south");
}
Here the subject of arrays has been inadvertently introduced. An array is a list of values, beginning with "({" and ending with "})". The way we have used it here allows the player to "look at hologram" or "look at display" to see what Buckey has been up to. Of course we are not limited to just the one item. Many rooms have five or more such items. In this case we would ordinarily add items for the logo and desk, since we talk about them in the long description of the room.
The other addition is an exit to a room. A room without a way to leave is not very good (unless you are a cruel coder). Using add_exit() is as simple as the other functions we have used so far. The first argument it takes is the location of our destination object, the second is the command we need to type to get there (usually a direction).
One down, one to go.
/* reception.c
* The reception to Buckey's Engineering.
* Coram, 18th February 2003.
*/
inherit "/std/room";
void
create_room()
{
set_short("Buckey's reception");
set_long("This is the reception to Buckey's Engineering. A "+
"white table faces the entrance, below the company logo. "+
"A fancy holographic display dances from frame to frame "+
"of an advertising video clip.\n");
add_item( ({"hologram", "display", "holographic display"}),
"A fancy display shows off some of Buckey's achievements.\n");
add_exit("/d/Edge/coram/buckey/office", "south");
}
So now we have a completed reception area that leads to Buckey's office. You can see how easy it is to create rooms. The hardest part is using your imagination. At the moment we have an exit leading to a room that does not yet exist. Time to do something about that. On to the office.
The second room.
Buckey's office will require a little more work than the reception. This is where we will introduce cloning of objects. Back when we were putting stuff in the room to look at we came to two ways of doing it; cloning and add_item(). Players must be able to interact with npcs (kill them!) so add_item() is no good to us. Instead, we will go through creating Buckey the npc and inserting him into the room.But first, the office itself.
/* office.c
* Buckey's spiffy office.
* Coram, 19th February 2003.
*/
void
create_room()
{
set_short("Buckey's office");
set_long("Buckey's office has a single desk in the middle of "+
"the room in front of a chair. Bright light shines in "+
"through the window.\n");
add_item("window", "A glass window, letting in lots of light.\n");
add_item("desk", "A solid looking desk with a metal surface.\n");
add_item("chair", "A fairly plain looking chair.\n");
add_exit("/d/Edge/coram/buckey/reception", "north");
}
Okay, there we have the basic room. There are a couple of things to look at and an exit leading back to the reception. All we are missing is Buckey to make the scene complete.
Creating a NPC
Just like we based the room on the /std/room code, we shall similarly use the /std/monster code to create our NPC. We do it in pretty much the same way, but call some different functions (who has heard of monsters with exits?).
/* buckey.c
* Buckey, the engineer and businessman.
* Coram, 19th February 2003.
*/
inherit "/std/monster";
#include <ss_types.h>
void
create_monster()
{
set_name("buckey");
add_name("man");
set_short("Buckey");
set_long("Buckey is a middle-aged businessman, struggling to make "+
"his engineering business profitable. He looks over worked "+
"but still possesses a gleam in his eye.\n");
set_gender(0);
set_stats( ({ 30, 30, 40, 35, 30 30 }) );
set_skill(SS_UNARM_COMBAT, 15);
set_skill(SS_DEFENCE, 15);
add_chat("Hi, my name is Buckey - may I help you?");
add_chat("Welcome to Buckey's Engineering!");
add_act("smile");
add_act("sigh");
}
Using #include
Here is something we have not looked at up until now. Quite frequently there are obscure numerical values (such as skill identifiers) that we need to use, but do not need to remember. It is a lot easier to remember a name than a number, for most people. We can create files with useful information we don't want to remember that we can share with code all over the mud.
#include works by taking the named file (in this case ss_types.h) and, basically, pasting in its contents in place of the #include line. This is handled by the pre-processor. Before the file is actually compiled by the driver, the pre-processor checks for things it needs to do like this. The difference between inherit and #include is that inherit makes use of an already compiled object, and #include inserts code into our object before it has been compiled. It is not too important to understand this initially. With time and practice you will begin to understand the difference (and then see that you didn't need to know anyway).
Monster madness
By now you are used to some of the functions as we used them in our rooms. The remainder are equally simple.
The set_gender() function takes one argument. A value of 0 creates a male npc, 1 is female and 2 neuter. Because we are making a human monster we most likely want it to be male or female. Other types of npcs (dogs and cats, for example) are based on a different monster file which requires a little more setting up.
To set up the npcs stats we use the set_stats() function. This takes an array as its argument. Instead of a list of string values, which we used in the add_item() in our first room, we provide a list of integers corresponding (in order) to the STR, DEX, CON, INT, WIS and DIS of the npc.
Buckey will not last long in a fight if we do not give him any skills, which is where the set_skill() command comes in. This takes two arguments; the first is the skill identifier, the second is the value we wish to set. Notice that the skill identifiers are in capitals? That means we are using a definition from a file we are #include'ing into our code. The ss_types.h file contains the identifiers for all of the common skills in the game, and allows us to type in SS_DEFENCE instead of remembering that the defence skill happens to be skill number 24.
If we want Buckey to do more than stand around waiting for someone to kill him we can have him say and do things by using add_chat() and add_act(). It should be fairly clear how to do this from the code above. You can use any emotes you like in the add_act() function, including the "emote" command where a predefined emotion does not exist.
There is plenty more we could do to make Buckey more interesting. We could clothe him, have him wander aimlessly around the streets, or swear at you and throw grenades if you attack him. As with most things on Sanity's Edge, you are limited only by the time you wish to put into developing your monster and the amount of complexity in your code.
Cloning
Now that Buckey exists we need a way to put him in his office. A useful function we can use in our rooms is reset_room(). Ever been in a room when suddenly all of the monsters have appeared at once? That occurs when the reset_room() function of the room was called to tell it that it is time to put everything back to normal. This is how the mud keeps freshly stocked with juicy monsters to kill.
object buckey;
void
reset_room()
{
if(!buckey)
{
buckey = clone_object("/d/Edge/coram/buckey/buckey");
buckey -> move(this_object());
}
}
Previously I have mentioned integers, strings and arrays. These are all data types. Another data type is object, which is used when we wish to record a pointer to an object in the game. This object can be a weapon, tshirt, drink, or anything else that happens to be a real object (ie. not an add_item() or anything like that).
Our buckey object pointer lets us keep track of Buckey. We can test if he exists or not by using "if(!buckey)" and if he does not (for instance if someone killed him or we only just created this room) we can create him and make sure that the buckey pointer is set, then put him in his office.
A handy thing about object pointers is they help us tell our objects what we want them to do. In this case since we have just cloned Buckey, we want him to be present in his office. So, we move him there with the move() function. The move() function happens to take an object pointer as an argument, which we handily find out by using this_object(). If that is at all confusing, do not worry. All you need to know is that this code will work for any objects you wish to move into your rooms.
Lastly, we need to make sure that when the office is created Buckey is moved in right away. The reset_room() function is called at intervals while the mud is running. It could be many minutes before Buckey goes to his office if we wait. To make it immediate, we can call reset_room() ourselves in our create_room() function. This gets us to the code below.
/* office.c
* Buckey's spiffy office.
* Coram, 19th February 2003.
*/
object buckey;
void
reset_room()
{
if(!buckey)
{
buckey = clone_object("/d/Edge/coram/buckey/buckey");
buckey -> move(this_object());
}
}
void
create_room()
{
set_short("Buckey's office");
set_long("Buckey's office has a single desk in the middle of "+
"the room in front of a chair. Bright light shines in "+
"through the window.\n");
add_item("window", "A glass window, letting in lots of light.\n");
add_item("desk", "A solid looking desk with a metal surface.\n");
add_item("chair", "A fairly plain looking chair.\n");
add_exit("/d/Edge/coram/buckey/reception", "north");
reset_room();
}
There we have it. Two functioning rooms complete with decorations and a npc to interact with. You could put this code into the game as it is and it would work, but there are a couple of things we can improve on.
Tidying up
As I said, the code above will work, but once our area is ready for release we need to move it out of my coder directory to where it will go in the game. That means every time we typed in "/d/Edge/coram/buckey/" we would have to go back and change it to the new directory. In our 3 object area (2 rooms plus Buckey) that is not a big change. Imagine if we had to do that for the CMD tower! This is where we can use our own #include file to make things easier./* defs.h * Definitions for Buckey's Engineering area. * Coram, 19th February 2003. */ #define BUCK_ENG "/d/Edge/coram/buckey/"Now we can use BUCK_ENG instead of "/d/Edge/coram/buckey/" just like we used SS_DEFENCE instead of the number 24. All we need to do is #include our defs.h file into the other files. No matter if this is a three object area or three hundred, if we have done it properly we can move the files anywhere and make it all work by just changing this one file.
For a small area like this with only a few objects, we can dump them all in the one directory and it is neat and clear. What about Athena? It has a few floors, lots of object and lots of different npcs. For these larger areas we often split files up into different directories. Each floor of a building may have its own directory, all the npcs may be in another, and yet another directory for miscellaneous objects. For your first areas this probably is not important, but it is a good thing to keep in mind when you begin tackling larger projects.
That brings us to the end of this tutorial. I hope it has been helpful to you and inspired you to begin creating new areas for other players to explore. So lastly, what does the code look like now? Click here to see the code with all the changes put in.