LPC

The LPMud programming language

Edition pre-3.01

July 1996

by Ronny Wikh (rw@cd.chalmers.se)
 

Copying Conditions

This tutorial was produced in good faith for use by people who like to program muds for their own pleasure. That means that I won't charge for using or distributing it provided that this is the purpose it's being used for.

Specifically, I want to make sure that those who run commercial muds don't provide this manual as an aid to further their monetary aims. If they want it they can either pay me a lot of money for it or produce one of their own.

Please read the copyright statement in the printed section of this manual to get the full text. The gist of it, however, is exactly what I just related here.


Introduction

This tutorial is intended to be something that anyone can read and learn from. Now, this is, of course, impossible, so let's amend that a bit. It's a tutorial that anyone with at least a bit of knowledge of programming and a will to learn can use. You don't have to know about C before you start, and I believe that even true virgins might be able to learn how to code. They will, of course, have a much harder job though.

Experienced coders, even mud coders, will need to read the tutorial since it explains concepts that are unique for this game, but they will be able to skim most of it and focus only on the trickier bits. I leave the selection of what is and what is not important to you, the reader, since you're the only one who knows how much you need to learn.

As the LPC language in actual application is closely knitted to the mudlib which it is used in, I will also cover part of that. However, I will not teach you how to use the mudlib in detail, you'll have to read other documents for that. All of this makes this tutorial pretty specific for games, and Genesis in particular. Keep this in mind if you are reading it on another game.

I hope you'll find the tutorial useful and worth the effort it takes to read. It sure took a lot of effort to write, so it'd better not be for nothing! :)


i - Acknowledgments

I'd like to start by thanking Thorsten Lockert and Christian Markus, perhaps better known as Plugh and Olorin, for their help in proofreading this text, suggesting improvements and in general being supportive and providing constructive feedback.

Without them this rather lengthy project would have taken even longer (mind-boggling, but true) to complete and ended up a lot worse.


ii - Tutorial Setup

The manual is divided into three progressively more advanced chapters. The first chapter will be explain basics about coding and LPC without delving too deep into specifics.

The second chapter will be for a more intermediate level of coding, explaining in full all the aspects of functions and operators that might have been treated a bit too easy in the first chapter.

The third and final chapter handles whatever might be left when chapter two is done. Well, not everything; the tutorial will not explain all the intricacies of the gamedriver and the mudlib. If you are looking for info about creating your own mudlib or doing very advanced stuff you'll have to learn that from reading the actual code.

If you are a newbie wizard you might feel a bit taken back at first, looking at this rather thick tutorial. However, it's quite necessary that you read all of it and leave nothing for the future. You will undoubtably have to at least recognize subjects from all three chapters, even though mostly you will actually only use the information in the first two. Heck, there's a lot of old wizards out there who hardly even master the first one, a scary thought! Among other things that's one of the fundamental reasons why I'm writing this manual...

The manual is fairly extensive, but it's for learning LPC for domain coding only. This means that it's not a complete LPC reference manual. Some of the more esoteric and unusual efuns and functionalities are not covered since they only would be used by mudlib coders or gamedriver hackers. This manual is not intended for them. Sorry, if that's what you were looking for you'll have to keep on searching for another source.

A small note about gender. Throughout the text I've used male pronouns. This is not because I scoff the thought of female coders, it's because the English language is male-oriented. Fact of life, like it or not. I guess I could have added a `(or she)' comment after all occurrences of the male `he', but that strikes me as more than just a bit silly. Until the English language comes up with a strictly neutral pronoun I'll stick to using the all-inclusive male one.

Chapters that describe efuns/sfuns/lfuns/macros in detail have a subcaption with the names of the discussed items within brackets for easy finding if you're just browsing the tutorial later.


iii - History of LPC

LPC is the interpreted mud language created by Lars Pensjoe for his invention LPMUD, an interactive multiuser environment suited for many purposes, games not the least among them. Since the first appearance in 1988 the language has changed dramatically.

Development was taken over by other people at Chalmers Datorfoerening, majorly Jakob Hallen, Lennart Augustsson and Anders Chrigstroem around 1990. They extended and refined the language extensively, but, as the name LPC hints of, it still retains its links to the language 'C'. The main differences lie in the object structure that's imposed on the language as well as some new data types to faciliate game programming. The language is not as free-form as 'C', but on the other hand it's more suitable for the purpose which it was created for - programming in a game environment.


iv - Gamedriver/Mudlib

The distinction between gamedriver and mudlib might seem hard to define at times, but it's really very simple. The gamedriver is the program that runs on the host computer. It is basically an interpreter in conjunction with an object management kernel, almost a small operating system in its own right. It defines and understands the LPC language, interprets it and executes the instructions given to it through the LPC objects in the game.

The mudlib is the collection of LPC objects that make up the basic game environment. While the gamedriver knows nothing about what it actually is doing, the mudlib does. (The mudlib can conversly be said to have no idea about how it does what it does, while the gamedriver does). It contains the basic set of LPC objects that are used in the game, the players, the monsters, the rooms etc. Individual efforts (domain code) are stored and handled separately and uses both the services of the gamedriver and the mudlib.


v - Administrative Setup

The game can be said to have been divided into three distinctive parts as suggested above; The gamedriver, the mudlib and the domain code. The gamedriver and mudlib I have already explained. The domain concept is a way to organize the way code is written. A domain is principally a group of wizards working towards a predefined goal. This project can be limited in space, an actual area in the game world, or as intangiable as a guild or sect that the players can become members of.

Within a domain there is a leader, a domain Lord. He is the local taskmaster who decides what goes on and in which direction work should progress. In the domain all code is shared easily and usually is strongly interconnected.

Naturally there have to be ties between different domains as well, but these are usually weaker and actual code is seldom shared.

As a newbie wizard you will try to find a domain in which to enroll, that sounds interesting and inspiring to work with.


vi - Writing code

It might seem premature to tell you what your code should look like before you have learnt how to write it. However, it is a fundamental subject of great importance. Someone jokingly said that writing code correctly will make your teeth whiter, your hair darker and improve your sexlife. Well... it might not do that, but it'll certainly improve the overall quality of what you produce, at a very low cost. Mainly it's a matter of self-discipline.

Here are some good arguments for making the effort:

What follows here is a rather lengthy instruction of how to put down your code in writing. Read it now even though you might not fully understand what is being discussed, then go back and re-read it later after having learnt the skills necessary. Doing that will make sure you'll remember the correct way of formatting your code.

  1. One indent level is 4 spaces long, no more, no less. A new indent level is started at the beginning of each block.
  2. Reserved words have a space after them, before the opening (, if any.
    while (test)
        statement;
    
  3. Block brackets start and end in the same column; the column of the first letter in a statement opening a block.
    if (this)
    {
        statement;
    }
    else if (that)
    {
        another_statement;
    }
    else
    {
        default_statement;
    }
    
    Now, this is almost a religious matter for some coders. Representatives of another sect preaches that the block opening brace should be on the end of the line of the block statement and how you do this really isn't that important. Not as long as you do it the way I say, or you'll burn in COBOL hell forever :) No, seriously, pick one of the two ways of doing it and stick to it. The only real important thing is that you keep the indention-level straight throughout the code. If we all agree on something, it's that wavy indention-levels is something not to be tolerated.
  4. Several arguments on a line separated by a comma have a space following the comma. ;-separated lists and binary operators have a space both in front of, and after the operator.
    int a, b, c;
    
    for (a = 0 ; a < 10 ; a++)
    {
        b = function_call(a, b * 2);
        c = b * 3 / 4;
    }
    
  5. If a loop statement has no body, put the ending ; on a separate line.
    while (!(var = func(var)))
        ;
    
    The reason for this is that if you should put it on the same line, it's very easy to miss real mistakes like this one just out of pure laziness:
    for (i = 0 ; i < 100 ; i++);
    {
        <code that gets executed only once, but always>
    }
    
  6. All #define and #include statements should be placed at the top of the file. It's possible to spread them out, but that will just be confusing.
  7. The same goes for prototypes and global/static variables used in the file. Clump them all together, with a proper comment header, in the top of the file. It's possible to spread them out, but oh how easy it is to miss them when reading the code later...
  8. Declarations of functions have the return type on a separate line from the function name.
    public void
    my_function(int a, int b)
    {
        < code >
    }
    
  9. Break long lines of code in proper places so that they don't wrap on their own beyond the end of a 80-width screen. It looks ugly and becomes hard to read, not to mention print.
  10. The file should begin with a proper header following this outline:
    /*
     * <filename>
     *
     * <Short description of what the file does, no more than 5-7 lines.
     * ...
     * ... >
     * 
     * Copyright (C): <your name and year>
     *
     */
    
    Read the game Copyright statement NOW in order to know what rules apply to code you produce for the game, in the game. It ought to reside in the file `/doc/COPYRIGHT'. If not, simply ask one of the game administrators.
  11. Start every function with a header looking like this:
    /* 
     * Function name: <Function name>
     * Description:   <Short description of what the function does,
     *                 usually no more than three lines.
     * Arguments:     <A list of all arguments, one per line
     *                   arg1 - description no longer than the line.
     *                   arg2 - next argument, etc. >
     * Returns:       <What the function returns>
     */
    
    If the function doesn't take any arguments, or doesn't return anything, simply remove those lines in the header.
  12. Put suitable comments in the code here and there when doing something that might look a bit obscure. Remember also that on your (assumed) level of competence, a lot of things are obscure :) Use your own judgement.
    /*
     * Comment for code following this comment,
     * telling what it does
     */
    < code >
    
  13. Make sure all function names and local variables are written in lowercase alphabetical characters, possibly spacing words with an underscore (e.g. function_name()). Global variables should be written using the first letter of the word capitalized (e.g. int GlobalTime;). #defines should be written in capitals (e.g. #define AMOEBA "one celled creature"). Doing this makes it easy to see what kind of symbol is being handled at all times.

Now, the easiest way of getting the basic stuff done properly is to use the emacs editor, set up to use a modified c++ mode. The c++ mode understands about ::-operators but needs a few hints on tabstops etc. Put these lines of code into your .emacs file and all will work just as it should:

;; emacs lisp script start

(setq auto-mode-alist (append '(
  ("\\.l" . my-c++-mode)
  ("\\.y" . my-c++-mode)
  ("\\.c" . my-c++-mode)
  ("\\.h" . my-c++-mode))
    auto-mode-alist))

(defun my-c++-mode () (interactive)
  (c++-mode)
  (setq c-indent-level 4)
  (setq c-brace-offset 0)
  (setq c-label-offset -4)
  (setq c-continued-brace-offset -4)
  (setq c-continued-statement-offset 4))

  ;; emacs end

An added advantage of using emacs is that later, when debugging other coder's attempts at writing code, correcting their horrible indention is as easy as typing 'M-<', 'M->', 'M-x indent-region'.


LPC basics

This chapter will teach you the very basics of programming, essential for understanding what follows. It will also introduce you to the concept of object oriented programming and explain some of the mudlib.


Basic programming concepts

We begin with he basic programming principles and the structure of LPC and the LPC environment.

What is programming?

This is a very philosophical question really. However, lets stick to the practical side and leave the zen bit to those who go for that kind of stuff.

Programming basically is the art of identifying a problem and putting the solution into symbols a computer can understand. A good programmer has a highly developed ability to see how a given problem can be split into smaller problems for which he has several solutions, and he also knows which particular solution he should pick to make the result as effective in terms of memory and speed as possible.

A programmer, as the previous passage suggests, actually tells the computer how to solve a problem. A computer can not come up with a solution to a problem itself. However, it's a lot quicker than you are, so problems that you can solve but would take you several lifetimes or simply just 'too long' are handled quickly by the computer.

What you need to learn is that special way of thinking that allows you to do this 'subdividing' thing where you see the steps needed to get from the beginning state to the solved state. You also need to learn the methods that make up these steps. Naturally this tutorial won't teach you 'how to program', it will only teach you the language used to put down the program in.

Who'll teach you programming then, in case you don't already know how to? Well... primarily other wizards in the game, and then yourself. Hard work in other words, there's never any shortcuts unfortunately, no matter what you need to learn. However, since this is a very amusing game let's hope you'll have great fun while acquiring the skills.

Compiled/Interpreted code

Programs are nothing but files containing instructions suited for the computer to understand. To program is to write lists of instructions in such a way that the computer reaches a predefined goal. Usually a program is compiled - translated - into a low-level code expressed in binary symbols (high and low electrical states in computer memory) which the computer understands with ease. The actual language you use to program in is merely a convenient go-between, a compromise which both you and the computer can understand. The reason you compile code is that the translation step is fairly complicated and time-consuming. You'd rather just do that once and then store the result, using it directly over and over again.

LPC however, isn't compiled, it's interpreted. The instructions are read and translated to the computer one by one, executed and forgotten. Well, this is not 100% true. In fact, the gamedriver which is the program running on the host computer translates the LPC code into an intermediate simple instruction code. This set of instruction codes makes up the code part of the so called 'master object' held in computer memory. When you run an LPC program, the instruction set is traced, line by line as described above, causing the computer to execute a predefined set of actions defined by the instructions.

The difference between having interpreted and compiled code is that while the compiled code is quicker, the interpreted version is much esier to modify. If you want to change something in the compiled version you have to make the change in the source code, then recompile and store the new version, then try it out. With interpreted code you just make the change in the source and run it. With LPC you need to instruct the gamedriver to destroy the old master object instruction set as well, but more about that later.

Programs

LPC programs are described above as files containing instructions to the computer written in the language LPC. These files must be named something ending in the letters `.c' (e.g. `test.c') so that the gamedriver knows what it's dealing with; an LPC program. Program names can be any string of printable characters < 32 characters in length, beginning with an alphabetical letter. However, in practice it is recommendable that you limit yourself to < 16 letter strings that are made up of ordinary lowercase alphabetical letters only. If you want the name to be made up by two words, separate them with the `_'- character (e.g. `my_file_name.c').

Objects

An object in LPC is simply a copy of an existing and loaded program in computer memory. When a program is loaded into memory to produce a master object, the code is compiled to produce the instruction list described earlier and a chunk of memory is associated to it as specified by the program code for use for internal global variables (described later). When a copy, a clone of this program is made, a special reference called an object pointer is created. That pointer is given a reference to the master code instruction list and a unique chunk of memory. If you clone the object another time, a new pointer is created and a new chunk of memory allocated. When an object is destroyed its associated memory is freed for use by other objects, but the instruction list is kept untouched. An object is always cloned from the master object. If you want to change the program you must update the master object to instruct the gamedriver that a new list of instructions is to be compiled from the source code.

However, any already existing clones will not change just because the master does. They will keep their reference to the old instruction list. It's important that you remember this so that you don't believe that the behaviour of an old cloned object changes just because you have updated the master object. As you see it's possible to have clones of objects in the game that behave differently, simply because they are made out of different source codes, just cloned between updates and changes in code. This could be a great source of confusion, so keep it in mind.

Object makeup

An object is comprised of something called functions and variables. A function is a set of instructions you can reference by a name. A variable is a kind of container you can store data in for use by the functions. Some functions are already defined by the gamedriver, they are called external functions, or efuns. Functions defined in LPC code are called local functions, or lfuns. To confuse matters further there is a set of functions that count as efuns, but are written in LPC. These functions are called simulated efuns or sfuns.

An efun basically is a function that is impossible to create in LPC. Take for example the function write() that allows you to present text on the screen of a player. It's impossible to make that up from other functions in LPC, so it has to be in the gamedriver. This efun is available in all LPC programs. Efuns also have no idea about what environment they are used in. They don't care one bit if they are used to simulate strawberry tasting, or as part of a game.

A function like add_exit() on the other hand, that adds an exit in a room is only available in room type objects. It is written in LPC. The lfuns as a rule are part of the makeup of the environment in which the objects are used. The example add_exit() for instance is well aware of such ideas as directions and travel costs, a very limited and specific concept.

The function creator() is a good example of the third case. It's a function that is available in every object, it returns information about who created a certain object. This information is very specific to the environment since it deals with such notions as code localization. This kind of function is easy to write in LPC but on the other hand it must be available in all objects, as if it was an efun. Due to this fact the special object `/secure/simul_efun.c' is made to be automatically available from all other objects in the game, you'll find all sfuns in there. This functionality is perfectly transparent to you; you just use them as you use any other efun and you don't have to be aware of that it really is an sfun.


Basic LPC

LPC if fairly similar to the language C, although some differences exist. As the experienced coder will find, it basically is a bit simplified with some new convenient types added and a set of functions to handle those types. Some inconsistencies exist but they are not serious enough to cause any problems as long as you are aware of them.

Comments

It might sound strange that I start off with this, but there'll be comments everywhere, so you need to be able to recognize them from the very start.

There are two kinds of comments:

<code> // This is a comment stretching to the end of the line.

<code> /* This is an enclosed comment */ <code>

As you can see, the first type of comment starts off with the // characters and then stretches all the way to the end of the line. If you want more lines of comments, you'll have to start off those as well with new // characters.

The second type is a type that has a definite length. They start with /* and end with */. This kind of comment is useful when you have to write something that will stretch over several lines, as you only have to write the comment symbol in the start and the beginning.

NB! The /* */ comment can not be nested. I.e. you can not write something like this for example:

/* A comment /* A nested comment */ the first continues */

What will happen is that the comment will end with the first found */, leaving the text the first continues */ to be interpreted as if it was LPC code. Naturally this won't work and instead you'll get an error message.

Data types

The object holds information in variables. As the name hints these are labeled containers that may hold information that varies from time to time. It processes information in functions that both use and return data of various kinds.

In principle only one kind of data type is needed, a sort of general container that would cover anything you wanted to do. In reality it's much preferred if you can distinguish between different types of information. This might seem only to add to your programming problems, but in fact it reduces the risk of faulty code and improves legibility. It much improves on the time it takes to code and debug an object.

In LPC it is possible to use only that 'general purpose' data type I was talking about before. In the first versions of the language it was the only kind available. However, with the LPC we have today it is much preferrable if you avoid that as much as you can. In fact, start all your programs with the following instruction on a single line:

#pragma strict_types

This is an instruction to the gamedriver to check all functions so that they conform to the situation they are used in, and cause compile errors otherwise. This is a very great help in detecting programming errors early so that you don't wonder what's going on later when things don't quite turn out the way you wanted.

Now, the following types of data types are defined:

`void'
`Nothing' This data type is used exclusively for functions that don't return any data at all.
`int'
`Integers' Whole numbers in the range -2147483648 to 2147483647. e.g. 3, 17, -32, 999.
`float'
`Floating point numbers' Decimal numbers of any kind in the approximate range 1.17549435e-38 to 3.40282347e+38. e.g. 1.3, -348.4, 4.53e+4. The range values are approximate since this might vary from mud to mud as it's platform dependant. If there are any FORTRAN fossils around there, beware that numbers like 1. or .4711 are not recognized as floats, you have to specify both an integer and a decimal part, even if they only are 0.
`string'
`Character strings' Strings are simply a series of printable characters within quotes, e.g. "x", "the string", "Another long string with the number 5 in it". Strings can contain special characters like newline ("\n") to end a line. A lot of LPC expressions can handle strings directly, unlike usual C. This makes strings very handy and easy to use.
`mapping'
`Associated list' Mappings are another handy LPC invention (memory expensive, use with care!). A mapping is simply a list of associated values. Assume you want to remember the ages of people, like Olle is 23, Peter is 54 and Anna is 15. In LPC you can write this as ([ "Olle":23, "Peter":54, "Anna":15 ]). As you can see the value to the right has been associated to the value to the left. You can then extract the associated value through a simple indexing operation using the left hand value as index.
`object'
`Object pointer' They are references to LPC programs that has been loaded into memory.
`function'
`Function pointer' They are references to LPC functions.
`Array'
All of the above can appear in arrays, indicated by a * in front of the variable name in the declaration. Arrays in LPC are more like lists than proper arrays. A number of functions and operator facilities exist to make them easy and quick to use.
`mixed'
This, finally, is a general descriptor covering all the other, a sort of jack-of-all-trades type. Again, let me stress the fact that using it except when absolutely necessary only provokes mistakes. Hm, as pointed out to me this might sound a bit too strict. The mixed type is used for good reasons fairly often. What I mean is that when a regular type can be used, it should. Don't substitute it for mixed just because you feel lazy.

Variable declarations

A variable is a string of letters identifying an information 'box', a place to store data. The box is given a name consisting of < 32 characers, starting with an alphabetic letter. Custom and common sense dictate that all variables used inside a function consist of lowercase letters only. Global variables have the first letter in uppercase, the rest lowercase. No special character other than the '_' used to separate words is ever used. Variables should always be given names that reflect on their use. You declare variables like this:

<data type> <variable name>, <another variable>, ..., <last variable>;
e.g.
    int        counter;
    float      height, weight;
    mapping    age_map;

Variables must be declared at the beginning of a block (right after the first '{') and before any code statements. Global variables, variables that are available in all functions througout the program, should be declared at the top of the file.

When the declarations are executed as the program runs, they are initially set to 0, NOT to their 'null-state' values. In other words for example mappings, arrays and strings will all be set to 0 and not to ([]), ({}) and "" as you might believe. It is possible to initialize variables in the declaration statement, and it's even a very good habit always to initialize arrays and mappings there:

<data type> <variable name> = <value>, etc.
e.g.
    int        counter = 8;
    float      height = 3.0, weight = 1.2;
    mapping    age_map = ([]);
    object     *monsters = ({});

The reason why arrays and mappings should be initalized in the declaration statement to their 'NULL' values (({}) and ([]) respectively) is that otherwise they are initialized to 0, which is incompatible with the proper type of the variable and might cause problems later.

Function declarations

A function must give proper notification of what kind of data type it returns, if any. A function is a label much like a variable name, consisting of < 32 characters, starting with a letter. Custom and common sense dictate that all function names should be lowercase and only contain the special character '_' to separate words. Use function names that clearly reflect on what they do. A function declaration looks like this:

/*
 * Function name: <Function name>
 * Description:   <What it does >
 * Arguments:     
 * Returns:       <What the function returns>
 */
<return type>
<function name>(<argument list>)
{
   <code expressions>
}
/*
 * Function name: compute_diam
 * Description:   Compute the diameter of a circle given the 
 *                circumference.
 * Variables:     surf_area - the surface area
 *                name - the name given the circle
 * Returns:       The circumference.
 */
float
compute_diam(float surf_area, string name)
{
    float rval;
	    
    // Circumference = pie * diameter
    rval = surf_area / 3.141592643;
    write("The diameter of " + name + " is " + ftoa(rval) + "\n");

    return rval;
}

The argument list is a comma-separated list of data types, much like a variable declaration where you specify what kind of data will be sent to the function and assign names to this data for later use in the function. The data recieved will only be usable inside the function, unless you explicitly send it out through a function call.

(In order to save space and improve on legibility in the manual I won't put a header to all my short example functions).

A function that doesn't return anything should be declared as void.

void
write_all(string mess)
{
    users()->catch_msg(mess);
}

Statements and Expressions

We need to define what a statement and what an expression is in order to be able to proceed.

Statements

A statement is sort of a full sentence of instructions, made up from one or more expressions. Statements usually cover no more than a single line of code. Sometimes it's necessary to break it up though if it becomes too long, simply to improve on legibility. For most statements you simply break the line between two words, but if you are in the middle of a string you need to add a backslash (\) at the end of the line in order for the gamedriver to understand what's going on.

write("This is an example of \
      a broken string.\n");

However, breaking a statement with backslash is extremely ugly and makes the code hard to read. In fact, it's usually possible to break the line naturally at the end of a string, between two operators of some kind, or even just split the string in half and add the two parts together with the + operator. The only time the backslash really is necessary is in #define-statements, handled later.

write("This is a better way of " +
      "breaking a string.\n");

Statements in LPC are usually ended with a ;, which also is a good place to end the line. There's nothing stopping you from entering another statement right after, other than that it will look awful.

Expressions

An expression is an instruction or set of instructions that results in a value of some kind. Take +, for example. It uses two other expressions to make up a result. A variable is an expression since it yields its contents as a result. The combination of the following two expressions and an operator is a valid expression: a + b, a and b being variables (expressions) and + being the operator used on them. a = b + c; is a full statement ending in a ;.

Function calls are valid expressions. They are written simply as the name followed by a set of matched parentheses with the arguments that the functions uses listed inside. Take the simple function max() for example, that returns the max of the two arguments. To determine the maximum of 4 and 10, you would write max(4, 10) as the expression. Naturally the result must be either stored or used.

The block statement

There are a lot of statements, for example conditional statements, that in certain circumstances execute one specified statement and never else. Suppose you want to have several statements executed and not just one? Well, there's a special statement called block statement that will allow you to do that. A block is defined as starting with a { and ending with a }. Within that block you may have as many statements of any kind (including variable definitions) as you like. The block statement is not ending with a ;, even though it doesn't matter if you accidentally put one there.

The ';' statement

As stated ; is mostly used to terminate statements, however it's also a statement in its own right.

The ; on it's own will simply be a null-statement causing nothing to happen. This is useful when you have test-clauses and loops (described later) that perform their intended purpose within the test or loop clause and aren't actually intended to do anything else.

Scope and prototypes

Scope is a term defining where a function or variable declaration is valid. Since programs are read top down, left right (just like you read this page), declarations of functions and variables are available to the right and below of the actual declaration. However, the scope might be limited.

A variable that is declared inside a function is only valid until the block terminator (the terminating }) for that variable is reached.

< top of file >
int GlobCount;

// Only GlobCount is available here

void
var_func(int arg)
{
    int var_1;

    // GlobCount, arg and var_1 is available here
    < code >

    {
        string var_2;

        // GlobCount, arg, var_1 and var_2 is available in this block
        < code >
    }

    // GlobCount, arg and var_1 is available here
    < code >

    {
        int var_2;
        mapping var_3;

        // GlobCount, arg, var_1, var_2 and var_3 is available here
        // NB this var_2 is a NEW var_2, declared here
        < code >
    }

    // GlobCount, arg and var_1 is available here
    < code >
}

// Here only GlobCount (and the function var_func) is available

Function declarations follow the same rule, though you can't declare a function inside another function. However, suppose you have these two functions where the first uses the second:

int
func_1()
{
    < code >
    func_2("test");
}

void
func_2(string data)
{
    < code >
}

Then you have a problem, because the first function tries to use the second before it is declared. This will result in an error message if you have instructed the gamedriver to require types to match by specifying pragma strict_types as suggested earlier. To take care of this you can either re-arrange the functions so that func_2 comes before func_1 in the listing, but this might not always be possible and the layout might suffer. Better then is to write a function prototype. The function prototype should be placed in the top of the file after the inherit and #include statements (described later) but before any code and look exactly as the function declaration itself. In this case:

< top of file, inherit and #include statements >

void func_2(string data);

< the actual code >

Operator expressions

The LPC language defines a large set of operators expressions, simply expressions that operate on other expressions. What follows here is a list of them. I've used a condensed notation so that the text won't take all of the page before getting down to actual explanations.

`E'
Any expression, even a compound one.
`V'
A variable.

Miscellaneous operators

  1. (E) E is evaluated before doing anything outside the parenthesis. This is useful for isolating expressions that need to be done in a specific order, or when you are uncertain about prescedence (described later).
  2. E1, E2 E1 is evaluated first and the result stored, then E2 is evaluated and the result thrown away, lastly the stored result of E1 is returned as the value of the entire expression.
    The statement 'a = 1, 2, 3;' will set 'a' to contain '1'.
    
  3. V = E The variable is given the value of the expression. The result of this entire expression is also the value of E.
    'a = b = 4;' will set a and b to be 4. It can also be written 
    'a = (b = 4)' to illustrate the order of execution.
    

Arithmetic operators

  1. E1 + E2 The expressions are evaluated and the results added to each other. You can add integers, floats, strings, arrays and mappings. Strings, arrays and mappings are simply concatenated - pasted together to the end of the first argument. It's also possible to add integers to strings, they will then be converted to strings and pasted to the end of the string.
  2. E1 - E2 E2 is subtracted from E1. You can subtract integers, floats and any type from arrays of the same type. For arrays the item, if it exists in the array it is subtracted from, is removed from the array. If it doesn't exist in the array, the array is returned intact.
  3. E1 * E2 E1 is multiplied by E2. This only works on integers and floats.
  4. E1 / E2 E1 is divided by E2. This only works on integers and floats.
  5. E1 % E2 The remainder of the expression 'E1 / E2' is returned. This only works with integers.
    '14 % 3' will yield 2 as the remainder. '14 / 3' will be 4, and 
    4 * 3 + 2 = 14 as a small check.
    
  6. -E Return E with reversed sign. This only works on integers and floats.
  7. E++, ++E The expression 'E' is incremented by one. If the operator is in front of the expression, the incrementation is done before the expression is used, otherwise afterwards.
    'a = 3; b = ++a;' will yield the result 'a = 4, b = 4', while
    'a = 3; b = a++;' will yield the result 'a = 4, b = 3'.
    
    This only works on integers.
  8. E--, --E The expression 'E' is decremented by one. If the operator is in front of the expression, the decrementation is done before the expression is used, otherwise afterwards.
    'a = 3; b = --a;' will yield the result 'a = 2, b = 2', while
    'a = 3; b = a--;' will yield the result 'a = 2, b = 3'.
    
    This only works on integers.

Boolean operators

Boolean (binary) operators are applicable only to integers with the exception of the & operator which also works on arrays. Internally an integer is 32 bits long. However, in the following examples I will only show the ten last bits as the others are 0 and can be ignored with the one exception of the ~-operator.

  1. E1 & E2 E1 and E2.
    1011101001   (= 745)
    1000100010 & (= 546)
    ------------
    1000100000   (= 544) => 745 & 546 = 544
    
    Used on two arrays, this function will return a new array that holds all elements that are members of both of the argument arrays.
  2. E1 | E2 E1 or E2.
    1011101001   (= 745)
    1000100010 | (= 546)
    ------------
    1011101011   (= 747) => 745 | 546 = 747
    
  3. E1 ^ E2 E1 xor (exclusive or) E2.
    1011101001   (= 745)
    1000100010 ^ (= 546)
    ------------
    0011001011   (= 203) => 745 ^ 546 = 203
    
  4. ~E 1-complement of E (invert E).
    00000000000000000000001011101001 ~ (= 745)
    ----------------------------------
    11111111111111111111110100010110   (= -746) => ~745 = -746
    
    NB! The above example might be hard to understand unless you really know your binary arithmetic. However, trust me when I say that this is not a typo, it's the way it should look. Read a book on boolean algebra (the section on two-complement binary arithmentic) and all will be clear.
  5. E1 << E2 E1 is shifted left E2 steps.
    5 << 4 => 101(b) << 4 = 1010000(b) = 80
    
  6. E1 >> E2 E1 is shifted right E2 steps.
    1054 >> 5 => 10000011110(b) >> 5 = 100000(b) = 32
    

Conditional (logical) operators

  1. E1 || E2 Returns true if E1 or E2 evaluates as true. Will not evaluate E2 if E1 is true.
  2. E1 && E2 Returns true if both E1 and E2 evaluates as true. Will not evaluate E2 if E1 is false.
  3. !E Returns true if E is false & vice versa.

Comparative operators

  1. E1 == E2 Returns true if E1 is equal to E2, can be used on all kinds of types, but see the special section later on arrays and mappings, it works differently on them from what you might think.
  2. E1 != E2 Returns true if E1 isn't equal to E2, can be used on all kinds of types, but see the special section later on arrays and mappings, it works differently om them from what you might think.
  3. E1 > E2 Returns true if E1 is greater than E2, can be used on all types except arrays and mappings.
  4. E1 < E2 Returns true if E1 is less than E2, can be used on all types except arrays and mappings.
  5. E1 >= E2 Returns true if E1 is greater or equal to E2, can be used on all types except arrays and mappings.
  6. E1 <= E2 Returns true if E1 is less or equal to E2, can be used on all types except arrays and mappings.

Prefix allocation

All of the arithmetic and boolean operator expressions can be written in a shorter way if all you want to do is compute one variable with any other expression and then store the result in the variable again.

Say that what you want to do is this a = a + 5;, a much neater way of writing that is a += 5;. The value of the second expression is added to the first and then stored in the first which happens to be a variable.

You write all the others in the same way, i.e. the variable, then the operator directly followed by = and then the expression.

a >>= 5;       // a = a >> 5;
b %= d + 4;    // b = b % (d + 4);
c ^= 44 & q;   // c = c ^ (44 & q);

Precedence and Order of evalutaion

The table below summarizes the rules for precedence and associability of all operators, including those which we have not yet discussed. Operators on the same line have the same precedence, rows are in order of decreasing precedence, so, for example, *, / and % all have the same precedence, which is higher than that of + and -.

Note that the precedence of the bit wise logical operators &, ^ and | falls below == and !=. This implies that bit-testing expressions like

if ((x & MASK) == 0) ...

must be fully parenthesized to give proper results.

  1. () [] Left to right
  2. ! ~ ++ -- - (type) * & Right to left
  3. * / % Left to right
  4. + - Left to right
  5. << >> Left to right
  6. < <= > >= Left to right
  7. == != Left to right
  8. & Left to right
  9. ^ Left to right
  10. | Left to right
  11. && Left to right
  12. || Left to right
  13. ?: Right to left
  14. = += == etc. Right to left
  15. , Left to right

Conditionals

Conditional statements are used a lot in LPC, and there is several ways of writing them. A very important concept is that 0 is considered as false and any other value as true in tests. This means that empty listings ({}), empty strings "" and empty mappings ([]) also are evaluated as true since they aren't 0. You have to use special functions to compute their size or determine content if you want test them, more about that later however.

The if/else statement

The most common conditional statement is naturally the if statement. It's easy to use and can be combined with an else clause to handle failed tests. It's written like this:

if (expression) statement;
e.g.
    if (a == 5)
        a -= 4;

If you want to handle the failed match, you add an else statement like this:

if (expression) true-statement else false-statement;
e.g.
    if (a == 5)
        a -= 4;
    else
        a += 18;

The switch statement

If one variable has to be tested for a lot of different values, the resulting list of `if-else-if-else' statements soon gets very long and not very easy to read. However, if the type of the value you are testing is an integer, a float or a string you can use a much denser and neater way of coding. Assume you have the following code you want to write:

if (name == "fatty")
{
    nat = "se";
    desc = "blimp";
}
else if (name == "plugh")
{
    nat = "no";
    desc = "warlock";
}
else if (name == "olorin")
{
    nat = "de";
    desc = "bloodshot";
}
else
{
    nat = "x";
    desc = "unknown";
}

The better way of writing this is as follows:

switch (name)
{
case "fatty":
    nat = "se";
    desc = "blimp";
    break;

case "plugh":
    nat = "no";
    desc = "warlock";
    break;

case "olorin":
    nat = "de";
    desc = "bloodshot";
    break;

default:
    nat = "x";
    desc = "unknown";
}

The workings of this statement is very simple really: switch sets up the expression value within the parenthesis for matching. Then every expression following a case is examined to find a match.

NB! The case expression must be a constant value, it can't be a variable, function call or other type of expression.

After a match has been found the following statements are executed until a break statement is found. If no matching value can be found, the default statements are executed instead.

NB! While it's not mandatory to have a default section, it's highly recommended since that usually means that something has happened that wasn't predicted when writing the program. If you have written it that way on purpose that's one thing, but if you expect only a certain range of values and another one turns up it's usually very good to have an error message there to notify the user that something unexpected happened.

If you forget to put in a 'break' statement the following 'case' expression will be executed. This might sound like something you don't want, but if in the example above the names `fatty' and `plugh' both should generate the same result you could write:

case "fatty":
    /* FALLTHROUGH */
case "plugh":
    < code >
    break;

... and save a bit of space. Writing code with switch doesn't make it any quicker to execute, but a lot easier to read thereby reducing the chance of making mistakes while coding. Remember to put the /* FALLTHROUGH */ comment there though, or it might be hard to remember later if it was intentional or an omission of a break statement, particularly if you have some code that's executed previously to the fallthrough. A good idea is usually to add an extra linefeed after a break statement just to give some extra 'breathing space' to improve on legibility.

The ?: expression

This is a very condensed way of writing an if/else statement and return a value depending on how the test turned out. This isn't a statement naturally, it's an expression since it returns a value, but it was hard to explain earlier before explaining the if/else statement.

Suppose you want to write the following:

if (test_expression)
    var = if_expression;
else
    var = else_expression;

You can write that much more condensed in this way:

var = test_expression ? if_expression : else_expression;
e.g.
    name = day == 2 ? "tuesday" : "another day";

It can be debated if writing code this way makes you code easier or harder to read. As a rule it can be argued rather successfully that one expression of that kind does make it clearer, but that a combination of several only makes it worse. Something like this definately isn't an improvement:

name = day == 2 ? time == 18 ? "miller time" : "tuesday" : "another day";

Loop statements

Basically there are two kinds of loop statements which incorporate the use of conditional statements within them, i.e. they can be programmed to execute only until a certain state is reached.

The for statement

If you want a simple counter you should use the for statement. The syntax is as follows:

for (initalize_statement ; test_expression ; end_of_loop_statement)
    body_statement;

When first entered, the for statement executes the initialize_statement part. This part usually is used to initialize counters or values used during the actual loop. Then the actual loop starts. Every loop starts by executing the test_expression and examining the result. This is a truth conditional, so any answer not equal to 0 will cause the loop to be run. If the answer is true the body_statement is executed, immediately followed by the end_of_loop_statement. In the body_statement you usually do what you want to have done during the loop, in the end_of_loop_statement you usually increment or decrement counters as needed.

Throughout the previous section I used the word usually a lot. This is because you don't have to do it that way, there's no rule forcing you to make use of the statements in the way I said. However, for now let's stick to the regular way of using the for-statement. Later on I'll describe more refined techniques.

Assume you want to compute the sum of all integers from 7 to 123 and don't know the formula ((x2^2 + x1^2) / 2). The easiest (if not most efficient) way of doing that is a loop.

result = 0;
for (count = 7 ; count < 124 ; count++)
    result += count;

What happens is that first of all result is set to 0, then the actual for-statement is entered. It begins by setting the variable count to 7. Then the loop is entered, beginning by testing if count (= 7) is less than 124, it is so the value is added to count. Then count is incremented one step and the loop entered again. This goes on until the count value reaches 124. Since that isn't less than 124 the loop is ended.

NB! The value of count after the for-statement will be 124 and not 123 that some people tend to believe. The test_expression must evaluate to false in order for the loop to end, and in this case the value for count then must be 124.

The while statement

The while statement is pretty straight-forward, you can guess from the very name what it does. The statement will perform another statement over and over until a given while expression returns false. The syntax is simple:

while (<test expression>)

Note carefully that the test expression is checked first of all, before running the statement the first time. If it evaluates as false the first time, the body is never executed.

a = 0;
while (a != 4)
{
    a += 5;
    a /= 2;
}

The break and continue statement

Sometimes during the execution of switch, for or while statements it becomes necessary to abort execution of the block code, and continue execution outside. To do that you use the break statement. It simply aborts execution of that block and continues outside it.

while (end_condition < 9999)
{
    // If the time() function returns 29449494, abort execution
    if (time() == 29449494)
        break;

    < code >
}

// Continue here both after a break or when the full loop is done.
< code >

Sometimes you merely want to start over from the top of the loop you are running, in a for or while statement, that's when you use the continue statement.

// Add all even numbers
sum = 0;
for (i = 0 ; i < 10000 ; i++)
{
    // Start from the top of the loop if 'i' is an odd number
    if (i % 2)
         continue;

    sum += i;
}

Arrays and Mappings

It's time to dig deeper into the special type array and mapping. Their use might look similar but in fact they are very different from each other as you will see.

To both of these data types there exists a number of useful (indeed even essential) efuns that manipulates them and extracts information from them. They will be described in total later however, only some are mentioned here.

How to declare and use arrays

Arrays really aren't arrays in the proper sense of the word. They can better be seen as lists with fixed order. The difference might seem slight, but it makes sense to the computer-science buffs :)

Arrays are type-specific. This means that an array of a certain type only can contain variables of that single type. Another restrictions is that all arrays are one-dimensional. You can't have an array of arrays. However, the mixed type takes care of these limitations. A mixed variable can act as an array containing any data type, even other arrays. As a rule you should try to use properly typed arrays to minimize the probabilities of programming mistakes however.

You declare an array like this:

<type> *<array name>;
e.g.
    int *my_arr, *your_arr;
    float *another_arr;
    object *ob_arr;

The initial values of these declared arrays is '0', not an empty array. I repeat: they are initialized to 0 and not to an empty array. Keep this in mind!

You can allocate and initialize an array like this:

<array> = ({ elem1, elem2, elem3, ..., elemN });
e.g.
    my_arr = ({ 1, 383, 5, 391, -4, 6 });

You access members of the array using brackets on the variable name. (Assume val here is declared to be an integer).

<data variable> = <array>[<index>];
e.g.
    val = my_arr[3];

LPC, like C, starts counting from 0, making the index to the fourth value = 3.

To set the value of an existing position to a new value, simply set it using the = operator.

    my_arr[3] = 22;	// => ({ 1, 383, 5, 22, -4, 6 })
    my_arr[3] = 391;	// => ({ 1, 383, 5, 391, -4, 6 })

If you want to make a subset of an array you can specify a range of indices within the brackets.

<array variable> = <array>[<start_range>..<end_range>];
e.g.
    your_arr = my_arr[1..3];

... will result in your_arr becoming the new array ({ 383, 5, 391 }); If you give a new value to an old array, the previous array is lost.

e.g.
    my_arr = ({ });

... will result in my_arr holding an empty array. The old array is deallocated and the memory previously used is reclaimed by the gamedriver.

If you index outside an array, an error occurs and execution of the object is aborted. However, range indexing outside the array does not result in an error, the range is then only constrained to fall within the array.

If you want to create an empty array, initialized to 0 (no matter the type of the array, all positions will be set to 0 anyway) of a given length, you use the efun allocate().

<array> = allocate(<length>);
e.g.
    your_arr = allocate(3);	// => your_arr = ({ 0, 0, 0 }); 

Concatenating (adding) arrays to each other is most easily done with the + operator. Simply add them as you would numbers. The += operator works fine as well.

my_arr = ({ 9, 3 }) + ({ 5, 10, 3 }); // => ({ 9, 3, 5, 10, 3 })

Removing elements from an array is easiest done with the -/-= operator, however, be aware that it is a general operator that will remove all items found that match the item you want to remove.

my_arr -= ({ 3, 10 }); // => ({ 9, 5 })

If you want to remove a single item in the middle somewhere that might have been repeated, you have to use the range operator of course.

my_arr = ({ 9, 3, 5, 10, 3 });
my_arr = my_arr[0..0] + my_arr[2..4]; // => ({ 9, 5, 10, 3 })

NB! Beware this difference!!!! One is a list, the other an integer!

    <array> my_arr[0..0]   // = ({ 9 })
    <int>   my_arr[0]      // = 9

How to declare and use Mappings

Mappings are lists of associated values. They are mixed by default, meaning that the index part of the associated values doesn't have to be of the same type all the time, even though this is encouraged for the same reason as before in regard to the mixed data type.

Mappings can use any kind of data type both as index and value. The index part of the mapping in a single mapping must consist of unique values. There can not be two indices of the same value.

This all sounds pretty complicated, but in reality it's pretty simple to use. However, it will be a lot easier to understand once we get down to actually seeing it used.

You declare a mapping just like any other variable, so let's just start up with a few declarations for later use:

mapping my_map;
int     value;

Allocating and initializing can be done in three different ways:

1:	<mapping_var> = ([ <index1>:<value1>, <index2>:<value2>, ... ]);

2:	<mapping_var>[<index>] = value;

3:	<mapping_var> = mkmapping(<list of indices>, <list of values>);

The first is straight-forward and easy.

1: my_map = ([ "adam":5, "bertil":8, "cecar":-4 ]);

The second works so that in case a given mapping pair doesn't exist, it is created when referenced. If it does exist the value part is replaced.

2: my_map["adam"] = 1;    // Creates the pair "adam":1
   my_map["bertil"] = 8;  // Creates the pair "bertil":8
   my_map["adam"] = 5;    // Replaces the old value in "adam" with 5.
	...

The third requires two arrays, one containing the indices and one containing the values. How to create arrays was described in the previous chapter.

3: my_map = mkmapping(({ "adam", "bertil", "cecar" }), ({ 5, 8, -4 }));

Unlike arrays there's no order in a mapping. The values are stashed in a way that makes finding the values as quick as possible. There are functions that will allow you to get the component lists (the indices or values) from a mapping but keep in mind that they can be in any order and are not guaranteed to remain the same from call to call. In practice though, they only change order when you add or remove an element.

Merging mappings can be done with the +/+= operator just as with mappings.

my_map += ([ "david":5, "erik":33 ]);

Removing items in a mapping, however, is a bit trickier. That has to be done by using the special efun m_delete() (also described later).

my_map = m_delete(my_map, "bertil");
my_map = m_delete(my_map, "david");

As you see the mapping pairs has to be removed one by one using the index as an identifier of which pair you want to remove. Another thing you now realize quite clearly is that the indices in a mapping has to be unique, you can't have two identical 'handles' to different values. The values however can naturally be identical.

Individual values can be obtained through simple indexing.

value = my_map["cecar"]; // => -4

Indexing a value that doesn't exist will not generate an error, only the value 0. Be very careful of this since you might indeed have legal values of 0 in the mapping as well. i.e. a value of 0 might mean that the index has no value part but also that the value indeed is 0.

value = my_map["urk"]; // => 0

The preprocessor

The preprocessor is not a part of the LPC language proper. It's a special process that is run before the actual compilation of the program occurs. Basically it can be seen as a very smart string substitutor; Specified strings in the code is replaced by other strings.

All preprocessor directives are given as strings starting with the character `#' on the first column of the line. You can put them anywhere, but as you'll be told later most of them do belong in the beginning of the code.

The #include statement

This is by far the most used preprocessor command. It simply tells the preprocessor to replace that line with the contents of an entire other file before going any further.

Data you put in include-files is usually data that won't ever change and that you want to put into several files. Instead of having to write those same lines over and over with the cumulative chance of putting in copying errors as you go, you simply collect that data into one or more files and include them into the program files as necessary.

The syntax is very easy:

#include <standard_file>
#include "special_file"

NB! Note the absence of a ; after the line!

The two different ways you write this depend on where the file you want to include exists. There's a number of standard include files in the game, spread around in a number of different directories. Rather than having to remember exactly where they are, you can just give the name of the file you want to include then.

#include <stdproperties.h>
#include <adverbs.h>

If you want to include files that aren't part of the standard setup, for example files of your own, you have to specify where they are. You do that either relative to the position of the file that uses it or by an absolute path.

#include "/d/Genesis/login/login.h"
#include "my_defs.h"
#include "/sys/adverbs.h"	// Same as the shorter one above

When you include standard files, always use the <>-path notation. The reason isn't only that it becomes shorter and easier to distingues but also that if the files move around your program will stop working. If you use the <>-notation they will always be found anyway.

Include files can have any name, but as a rule they are given the '.h' suffix to clearly distinguish them as include files.

It is even possible to include c-files, i.e. to include entire files full of code. However, doing that is very bad form. Do not do that EVER! Why? Well, for one thing error handling usually has a bad time tracing errors in included files, the line numbers gets wrong. Also, since you include the uncompiled code into several different objects, you will waste memory and CPU since these identical included parts has to be compiled and stored separately for each object that uses them. Apart from all this just reading the code will be a chore better not even contemplated.

What has the extension of the file name really to do with the contents then? Well... actually nothing at all. However, the convention is to keep code, functions that are to be executed, in c-files and definitions in h-files. Usually the mudlib reflects on this convention and might not recognize anything but c-files as code sources.

The #define statement

This is a very powerful macro or substitute preprocessor command that can be abused endlessly. You are wise if you use it with caution and only for simple tasks.

The syntax is as follows:

#define <pattern> <substitute pattern>
#undef <pattern>

Any text in the file that matches <pattern> will be substituted for <substitute pattern> before compilation occurs. A #define is valid from the line it is found on until the end of the file or an #undef command that removes it.

Although defines can be written just as any kind of text, it is the custom (do this!) to use only capitals when writing them. This is so that they will be easily distinguishable for what they are since no one (not you either!) ever writes function or variable names with capitals.

Place all defines in the beginning of the file, or the poor chum who next tries to look at your code will have the devil's own time of locating them. If it's someone you asked for help (since your badly written code most likely won't work) he probably will tell you to stick the file someplace very unhygienic and come back later when you've learned to write properly.

Simple defines are for example paths, names and above all constants of any kind that you don't want to write over and over.

#define MAX_LOGIN  100          /* Max logged on players */
#define LOGIN_OB   "/std/login" /* The login object      */
#define GREET_TEXT "Welcome!"   /* The login message     */

Wherever the pattern strings above occur, they will be replaced by whatever is followed by the pattern until the end of the line. That includes the comments above, but they are removed anyway later.

tell_object(player, GREET_TEXT + "\n");

A comment on the // form is not a good thing since it doesn't end until the end of the line.

#define GREET_TEXT    "Welcome!"    // The login message

...will be translated into the previous example as:

tell_object(player,    "Welcome!"    // The login message + "\n");

...which will have the effect of commenting away everything after the //, all the way until the end of the line.

If a macro extends beyond the end of the line you can terminate the lines with a \ which signifies that it continues on the next line. However, you must break the string right after the \, there must NOT be any spaces or other characters there, just the linebreak.

#define LONG_DEFINE  "beginning of string \
                      and end of the same"	

Function-like defines are fairly common and often abused. The only really important rule is that any argument to the macro must be written so that they are used enclosed in parenthesis. If you don't do that you can end up with some very strange results.

1: #define MUL_IT(a, b) a * b        /* Wrong */
2: #define MUL_IT(a, b) (a * b)      /* Not enough */
3: #define MUL_IT(a, b) ((a) * (b))  /* Correct */

What's the big difference you may ask? Well, look at this example:

result = MUL_IT(2 + 3, 4 * 5) / 5;

   Expanded this becomes:

1: result = 2 + 3 * 4 * 5 / 5;       // = 14, Wrong
2: result = (2 + 3 * 4 * 5) / 5      // = 12, Just as wrong
3: result = ((2 + 3) * (4 * 5)) / 5  // = 20, Correct!

Abuse of defines usually involves badly formulated macros, complicated macros used inside other macros (making the code almost impossible to understand) or humungous arrays or mappings in defines that are used often. The basic rule is to keep macros short and fairly simple. Do that and you'll never have any problems.

The #if, #ifdef, #ifndef, #else and #elseif statements

These are all preprocessor directives aimed at selecting certain parts of code and removing other depending on the state of a preprocessor variable.

The #if statement looks very much like a normal if statement, just written a bit differently.

Assume you may have the following define somewhere:

#define CODE_VAR  2

or
	
#define CODE_VAR  3

Then you can write

#if CODE_VAR == 2
    <code that will be kept only if CODE_VAR == 2>
#else
    <code that will be kept only if CODE_VAR != 2>
#endif

You don't have to have the #else statement there at all if you don't want to.

It's sufficient to have the following statement to 'define' a preprocessor pattern as existing:

#define CODE_VAR    /* This defines the existance of CODE_VAR */

Then you can write like this:

#ifdef CODE_VAR
    <code that will be kept only if CODE_VAR is defined>
#else
    <code that will be kept only if CODE_VAR isn't defined>
#endif

or

#ifndef CODE_VAR
    <code that will be kept only if CODE_VAR isn't defined>
#else
    <code that will be kept only if CODE_VAR is defined>
#endif

Again, the #else part is optional.

The #if/#ifdef/#ifndef preprocessor commands are almost only used to add debug code that you don't want to have activated all of the time, or code that will work differently depending on other very rarely changing parameters. Since the conditions have to be hard-coded in the file and can't change during the course of the use of the object this is something you very rarely do.


Essential LPC and Mudlib

This chapter will teach you what you need to know in order to actually produce code in the game environment. It will avoid the more complicated and unessential subjects, leaving them for chapter three. You will be taught a lot more about he mudlib and the workings of the gamedriver, knowledge necessary to produce working and effective code.


Peeking at things to come

In order to provide you with examples of what I'm trying to teach you, I need to explain a few functions in advance. They will be repeated in their correct context later, but here's a preview so that you'll know what I'm doing.

To present things on the screen for the player to read, you use the efun write(). There's two special characters that's often used to format the text, `tab' and `newline'. They are written as \t and \n respectively. The `tab' character inserts 8 space characters and `newline' breaks the line.

void write(string text)
e.g.
    write("Hello there!\n");
    write("\tThis is an indented string.\n");
    write("This is a string\non several lines\n\tpartly\nindented.\n");

    /* The result is:

       Hello there!

               This is an indented string.

       This is a string
       on several lines
               partly
       indented.
     */

If you have an array, mapping, or simply a variable of any kind that you want displayed on the screen for debugging purposes the sfun dump_array() is very handy. You simply give the variable you want displayed as an argument and the contents (any kind) will be displayed for you.

void dump_array(mixed data)
e.g.
    string *name = ({ "fatty", "fido", "relic" });
    dump_array(name);

    /* The result is

       (Array)
       [0] = (string) "fatty"
       [1] = (string) "fido"
       [2] = (string) "relic"
     */

LPC revisited

Let's start by ripping off the rest of the LPC that was avoided in the first chapter. We need this in order to be able to actually create working objects in the game environment.

Function calls

There are two kinds of function calls, internal and external. The only kind we have discussed so far is the internal one even though the external call has been displayed a few times.

Making object-internal function calls

[call_self]

Making an internal function call is as simple as writing the function name and putting any arguments within parentheses afterwards. The argument list is simply a list of expressions, or nothing. (A function call is naturally an expression as well).

<function>(<argument list>);
e.g.
    pie = atan(1.0) * 4;

There is another way of doing this as well. If you have the function name stored in a string and wish to call the function, you use the efun call_self():

call_self(<"function name">, <argument list>);
e.g.
    pie = call_self("atan", 1.0) * 4;

If you use call_self() and specify a function that doesn't exist, you will get an error message and the execution of the object will be aborted.

Making single object-external function calls

[call_other]

An external call is a call from one object to another. In order to do that you need an object reference to the object you want to call. We haven't discussed exactly how you acquire an object reference yet, but assume for the moment that it already is done for you.

mixed <object reference/object path>-><function>(<argument list>);
mixed call_other(<ob ref/ob path>, "<function>", <arg list>);
e.g.
    /*
     * Assume that I want to call the function 'compute_pie' in the
     * object "/d/Mydom/thewiz/math_ob", and that I also have the
     * proper object pointer to it stored in the variable 'math_ob'
     */
    pie = math_ob->compute_pie(1.0);
    pie = "/d/Mydom/thewiz/math_ob"->compute_pie(1.0);
    pie = call_other(math_ob, "compute_pie", 1.0);
    pie = call_other("/d/Mydom/thewiz/math_ob", "compute_pie", 1.0);

As you can see, the efun call_other() works analogous to call_self().

If you make an external call using the object path, the so called master object will be called. If the object you call hasn't been loaded into memory yet, it will be. If an external call is made to a function that doesn't exist in the object you call, 0 will be returned without any error messages. Calls to objects that have bugs in the code will result in an error message and the execution of the object that made the call is aborted.

What's the big deal with call_self() then? Why can't you just use call_other() all the time with the same result? Well, it has to do with access to functions in an object, a part of LPC that I haven't gotten around to explaining yet. However, the difference is that call_self really works just like any internal call in regard to function modifiers and function access, while call_other() is a pure external call. Remember this for the future when differences between internal and external access to functions are discussed.

Making multiple object-external function calls

You can call several objects at once just as easily as a single one. If you have an array of path strings or object pointers, or a mapping where the value part are path strings or object pointers you can call all the referenced objects in one statement. The result will be an array with the return values if you call using an array and a mapping with the same index values as the calling mapping if you give a mapping.

(array/mapping)	<array/mapping>-><function>(<argument list>);
e.g.
    /*
     * I want a mapping where the indices are the names of the players
     * in the game, and the values are their hitpoints.
     */
    object *people, *names;
    mapping hp_map;

    // Get a list of all players.
    people = users();

    // Get their names.
    names = people->query_real_name();

    // Make a mapping to call with. Item = name:pointer
    hp_map = mkmapping(names, people)

    // Replace the pointers with hitpoint values.
    hp_map = hp_map->query_hp();

    // All this could also have been done simpler as:
    hp_map = mkmapping(users()->query_real_name(), users()->query_hp());

Inheriting object classes

Assume that you want to code an item like a door, for example. Doing that means that you have to create functionality that allows the opening and closing of a passage between two rooms. Perhaps you want to be able to lock and unlock the door, and perhaps you want the door to be transparent. All of this must be taken care of in your code. Furthermore, you have to copy the same code and make small variations in description and use every time you want to make a new door.

After a while you'll get rather tired of this, particularly as you'll find that other wizards has created doors of their own that work almost - but not quite - the same way your does, rendering nifty objects and features useless anywhere but in your domain.

The object oriented way of thinking is that instead of doing things over and over you create a basic door object that can do all the things you want any door to be able to do. Then you just inherit this generic door into a specialized door object where you configure exactly what it should be able to do from the list of available options in the parent door.

It is even possible to inherit several different objects where you can combine the functionality of several objects into one. However, be aware that if the objects you inherit define functions with the same names, they will indeed clash. Just be aware of what you are doing and why, and you won't have any problems.

The syntax for inheriting objects is very simple. In the top of the file you write this:

inherit "<file path>";
e.g.
	inherit "/std/door";
	inherit "/std/room.c";

NB! This is NOT a preprocessor command, it is a statement, so it does NOT have a # in front of it, and it is ended with a ;. As you see you may specify that it's a c-file if you wish, but that's not necessary.

The child will inherit all functions and all variables that are declared in such a way as to permit inheriting. If you have a function with the same name as a function in the parent, your function will mask the parent one. When the function is called by an external call, your function will be executed. Internal calls in the parent will still go to the parent function. Often you need to call the parent function anyway from the child, you do that by adding :: to the internal function call.

void
my_func()
{
    /* 
     * This function exists in the parent, and I need to
     * call it from here.
     */
    ::my_func();        // Call my_func() in the parent.
}

It is not possible to call a masked function in the parent by an external call, it is only available from within the object itself. If an object inherits an object that has inherited another object, e.g. C inherits B that inherits A, then masked functions in A is only available from B, not from C.

Shadows: Masking functions during runtime

There's a functionality called shadowing available in LPC. Purists tend to use the word 'abomination' and scream for its obliteration, since it goes against most of what's taught about proper control flow and object purity. For gaming purposes it's rather useful, although it can cause a host of problems (particularly when it comes to security). Use it with caution!

It's possible to make an object shadow another object. What happens then is that the functions and global variables in the shadow object that also exist in the shadowed object mask the original. Calls to the shadowed functions will go to the shadow instead. The shadow will for all practical appearances 'become' the object it shadows. As you can see this is done in runtime, and not during compilation.

This is all I will say in this respect right now. How to create shadows and use them will be handled in detail later. For now this is what you need to know.

Type identification

[intp, floatp, functionp, stringp, objectp, mappingp, pointerp]

Due to the fact that all variables are initialized to 0, and that many functions return 0 when failing, it's desirable to be able to determine what kind of value type you actually have received. Also, if you use the mixed type it's virtually essential to be able to test what the variable contains at times. For this purpose there's a special test function for each type that will return 1 (true) if the tested value is of the asked for type, and 0 if not.

@bullet{int intp(mixed)}
Test if given value is an integer
@bullet{int floatp(mixed)}
Test if given value is a float
@bullet{functionp(mixed)}
Test if given value is a function pointer
@bullet{int stringp(mixed)}
Test if given value is a string
@bullet{int objectp(mixed)}
Test if given value is an object pointer
@bullet{int mappingp(mixed)}
Test if given value is a mapping
@bullet{int pointerp(mixed)}
Test if given value is an array

NB! These functions test the type of the value, NOT the value itself in the sense of truth functionality. In other words intp(0) will always evaluate as true, as will mappingp(([])).

Type qualifiers

The very types you assign variables and function can have qualifiers changing the way they work. It's very important to keep them in mind and use the proper qualifier at the proper time. Most work differently when applied to variables rather than functions, so a bit of confusion about how they work usually is quite common among the programmers. Try to get this straight now and you'll have no problems later

The static variable qualifier

This is a problematic qualifier in the respect that it works differently even for variables depending on where they are! Global variables, to begin with, are (as you know) variables that are defined in the top of the file outside any function. These variables are available in all functions i.e. their scope is object-wide, not just limited to one function.

It is possible to save all global variables in an object with a special efun (described later). However, if the global variable is declared as static, it is not saved along with the rest.

static string   TempName;        // A non-saved global var.

The static function qualifier

A function that is declared static can not be called using external calls, only internal. This makes the function 'invisible' and inaccessable for other objects.

The private function/variable qualifier

A variable or function that has been declared as private will not be inherited down to another object. They can only be accessed within the object that defines it.

The nomask function/variable qualifier

Functions and variables that are declared as nomask can not be masked in any way, neither by shadowing nor inheriting. If you try you will be given an error message.

The public function/variable qualifier

This is the default qualifier for all functions. It means there is no limits other than those which the language imposes on accessing, saving and masking.

The varargs function qualifier

Sometimes you want a function to be able to receive a variable amount of arguments. There's two ways of doing this and it can be discussed if it's correct to put both explanations in this chapter, but it's sort of logical to do so and not too hard to find.

A function that is defined varargs will be able to receive a variable amount of arguments. The variables that aren't specified at the call will be set to 0.

varargs void
myfun(int a, string str, float c);
{
}

The call myfun(1); will set a to 1, str and c to 0. Make sure you test the potentially unused variables before you try to use them so that they do contain a value you can use.

There's another way as well. You can specify default values to the variables that you're uncertain about. Then you don't have to declare the function varargs and you will have proper default values in the unused argument variables as well.

void
myfun(int a, string str = "pelle", float c = 3.0);
{
}

This function must be called with at least one argument, the first, as it wasn't given a default value. The call myfun(1, "apa"); will set a to 1, str to "apa" and c to 3.0.

The function data type, part 2

There's one data type that I more or less ignored earlier, and that's the function type. Just as there's a type for objects, functions have a type as well. You can have function variables and call assigned functions through those variables. Mostly the function type is used in conjunction with other functions that use them as parameters.

You declare a function type just as any variable:

<data type> <variable name>, <another variable>, ..., <last variable>;
e.g.
    function my_func, *func_array;

Assigning actual function references to them, however, is a bit tricker. You can assign any kind of function to a function variable; efun, sfun or lfun is just the same. You can even assign external function references.

Assigning a function reference requires that the function already is defined, either itself or by a function prototype in the header. Let's assume for now that you're only interested in the simple reference to the function.

<function variable> = <function name>;
<function variable> = &<function name>();
e.g.
    my_func = allocate;
    my_func = &allocate();

Usage of the new function reference is done just as with the ordinary function call.

int *i_arr;

i_arr = allocate(5);  // Is the same as...
i_arr = my_func(5);   // ... using the function assignment above.

This will be enough for now. Later I'll explain how to create partial function applications, internal and external function declarations and how to use them in complex function combinations.

switch/case part 2

The LPC switch statement is very intelligent, it can also use ranges in integers:

public void
wheel_of_fortune()
{
    int i;

    i = random(10);     // Get a random number 0 - 9
                        // Strictly speaking, this local variable isn't
                        // necessary, it's just there to demonstrate the
                        // use and make things clearer. I could have 
                        // switched on 'random(10)' directly instead if
                        // I had wanted to.

    switch (i)
    {
    case 0..4:
        write("Try again, sucker!\n");
        break;

    case 5..6:
        write("Congrats, third prize!\n");
        break;

    case 7..8:
        write("Yes! Second prize!\n");
        break;

    case 9:
        write("WOOOOPS! You did it!\n");
        break;

    default:
        write("Someone has tinkered with the wheel... Call 911!\n");
        break;
    }
}

catch/throw: Error handling during runtime

It happens now and then that you need to make function calls you know might result in a runtime error. For example you might try to clone an object (described later) or read a file. If the files aren't there or your privileges are wrong you will get a runtime error and execution will stop. In these circumstances it is desireable to be able to intercept the error and either display some kind of message or perform other actions instead. The special LPC function operator catch() will do this for you. It returns 1 (true) if an error occurs during evaluation of the given function and 0 (false) otherwise.

int catch(function)
e.g.
    if (catch(tail("/d/Relic/fatty/hidden_donut_map")))
    {
        write("Sorry, not possible to read that file.\n");
        return;
    }

It's also possible to cause error interrupts. This is particularly useful when you want to notify the user of an unplanned for event that occured during execution. Typically you want to do this in the 'default' case of a switch statement, unless (naturally) you use default as a sort of catch-it-all position. In any case throw() will generate a runtime error with the message you specify. A catch() statement issued prior to calling the function that uses throw() will naturally intercept the error as usual.

throw(mixed info)
e.g.
    if (test < 5)
        throw("The variable 'test' is less than 5\n");

Array & Mapping references

In comp sci terms, arrays and mappings are used as reference by pointer and the other types as reference by value. This means that arrays and mappings, unlike other variables, aren't copied every time they are moved around. Instead, what is moved is a reference to the original array or mapping. What does this mean then?

Well... simply this:

object *arr, *copy_arr;

arr = ({ 1, 2, 3, 4 });    // An array

copy_arr = arr;	             // Assume (wrongly) that a copy_arr becomes
                             // a copy of arr.

// Change the first value (1) into 5.
copy_arr[0] = 5;

... Now... this far down the code it's logical to assume that the first value of copy_arr is 5 while the first value or arr is 1. That's not so however, because what got copied into copy_arr was not the array itself, but a reference to the same array as arr. This means that your operation later where you changed an element, changed that element in the original array which both variables refer to. copy_arr and arr will both seem to have changed, while in fact it was only the original array that both refered to that changed.

Exactly the same thing will happen if you use mappings since they work the same way in this respect.

So... how do you get around this then? I mean... most times you really want to work on a copy and not the original array or mapping. The solution is very simple actually. You just make sure that the copy is created from another array or mapping instead.

              _ This is just an empty array
             /
copy_arr = ({ }) + arr;
                    \_ This is the one we want to make unique

In this example copy_arr becomes the sum of the empty array and the arr array created as an entirely new array. This leaves the original unchanged, just as we wanted. You can do exactly the same thing with mappings. It doesn't matter if you add the empty array or mapping first or last, just as long as you do it.


LPC/Mudlib interafce

There's a lot of stuff you want to do, like handling strings and saving data to files, that's not exactly LPC. It's part of the 'standard function package' that most programming languages sport. This chapter will teach you the basics of how to do all the things you need in order to create LPC objects.

Objects in the game share a certain set of common properties, the ones you always can rely on to be there for any kind of object are these:

@bullet{creator}
The object is created by someone. The identity of this creator is set depending on the file-system location of the source code. If the object resides in the directory of a domain-active wizard, the creator is said to be the name of that wizard. Otherwise the domain name is used. For mudlib objects the creator usually is root for admin objects and backbone for the rest.
@bullet{uid/euid}
The uid (User ID) of an object defines the highest possible privilege level of an object. The uid itself is is only used to affect the euid (Effective User ID) of the same or another object. The euid is later checked in situations where the privilege of the object needs to be examined i.e. file access (reading/writing/removing) and object creation.
@bullet{living}
In order for an object to be able to receive command-lists or issue commands it has to be living.

Definition of standard and library objects

As I have explained earlier the gamedriver really knows very little about the actual game, actually as little as possible. Instead the mudlib is entrusted to take care of all that. So, what we have done is to try to work out what basic functionality is needed, things like how objects should interact with players, moving, light-level descriptios etc. Then we have created basic object classes that implement these functionalities in actual code.

A domain wizard doesn't have spend endless hours trying to figure out how to make an object work in relation to others in respect to basic functionality. Instead he just makes his object inherit the standard object suitable for the task he wants to code. Then he just adds the bits and pieces of code to the object that is necessary to make it unique and do the things that are special for that particular object.

A consequence of this naturally is that all objects in the game rely 100% on the fact that a certain type of object (room, monster, gadget) has a certain set of common functionality. They simply have to have that in order to be able to interact in the agreed way, if they didn't, if people had different ways of solving the same problem, the objects would only work with a certain wizard's area and never outside of it. It would then not be possible use a sword all over the game, it wouldn't even be possible to move it around from place to place. Naturally this means that we enforce this unity, and therefore it is impossible to create (and use) objects that don't inherit these special objects. Sure, as you can see for yourself later, it is possible to create a sword that doesn't make use of the standard weapon object, but it is perfectly impossible to wield it...

The standard objects provide certain basic functionality that must exist in all objects and they also make a bit of sanity checking on values of certain variables, but the latter really is a very minor functionality.

There are standard objects for a lot of purposes, the most important one is `/std/object.c' though.

The base object class, /std/object.c

This is the all purpose object class. ALL objects in the game must inherit this object somewhere along the line if they are to be 'physically' present somewhere. Using any kind of standard object usually insures that this one is inherited as well, since they already make use of it.

The standard object defines the following conventions:

@bullet{inventory}
An object can contain other objects. In reality that is nothing but a list of objects that are said to be held inside the object owning the list. However, it is very easy to visualize this as the inside of a bag, inside a room, inside a box etc.
@bullet{environment}
The object that surrounds the object that is being used as reference. In other words the reference object exists in the inventory of the environment object. An object can have a multitude of objects in its own inventory, but it can only have one environment object. All objects start out with no environment.
@bullet{command set}
A list of catch-phrases linked to functions that the object makes available to other so called living objects in the game either in the environment or inventory of itself. These living objects can issue such a catch-phrase and the command-giving object will execute the linked function.
@bullet{properties}
Properties are a pure mudlib convenience. They really is nothing but a mapping with certain reserved names indexed to object variables that affect certain generally accessable states. Typical properties are weight, value and light-level, but also more abstract concepts like the ability to be dropped, taken or sold. The applicable set of properties vary from object type to object type. Wizards may add their own properties if they wish, but they must then be careful to define names that won't mistakenly be used by other wizards for other purposes, or advertise the names so that people won't use them by mistake.
@bullet{light}
An object has a certain light level. Usually it's just as any kind of object - not affecting the environment at all, but it's possible to have both light- and darkness-sources.
@bullet{weight/volume}
These values determine how much an object weight and how much room they take. For 'hollow' objects like bags it also determines how much they can hold.
@bullet{visibilty}
Some objects may be easier to find than others.
@bullet{names and descriptions}
What the object is called and how a player will see it in the game.

Standard object classes

There exists a number of standard object classes for use in various situations. You will need to read the separate documentation on each and every one of them in order to fully learn to use them. However, this summary of the available classes will at least point you in the correct direction as you go.

As stated earlier you have to inherit most of these objects in order for your derived objects to work at all. However, it is possible to create new versions of some of them on your own. Again, I must emphasize that this is not a good idea since you then deviate from the common base of object functionality that is in use in the game. Problems that occur because of this actually are tricky to catch and track down since it isn't the first thing you suspect that someone has done.

`/std/armour.c'
Armour of any kind
`/std/board.c'
Bulletin boards
`/std/book.c'
A book with pages you can open, turn and read
`/std/coins.c'
The base of all kinds of money
`/std/container.c'
Any object that can contain another
`/std/corpse.c'
Corpse of dead monsters/players/npcs
`/std/creature.c'
Simple living creatures, basically a mobile that can fight
`/std/domain_link.c'
Use this as a base to preload things in domains
`/std/door.c'
A door that connects two rooms
`/std/drink.c'
Any type of drink
`/std/food.c'
Any type of food
`/std/guild (directory)'
Guild related objects (the guild and the shadows)
`/std/heap.c'
Any kind of object that can be put in heaps
`/std/herb.c'
Herbs
`/std/key.c'
Keys for doors
`/std/leftover.c'
Remains from decayed corpses
`/std/living.c'
Living objects
`/std/mobile.c'
Mobile living objects
`/std/monster.c'
Monsters of any kind
`/std/npc.c'
A creature which can use 'tools', i.e. weapons.
`/std/object.c'
The base object class
`/std/poison_effect.c'
Handle effects in poison of any kind
`/std/potion.c'
Potions
`/std/receptacle.c'
Any kind of closable/lockable container
`/std/resistance.c'
Handle resistance against various kinds of things
`/std/room.c'
Any kind of room
`/std/rope.c'
Rope objects
`/std/scroll.c'
Scrolls
`/std/shadow.c'
Used as base when creating shadows
`/std/spells.c'
Spell objects, tomes etc
`/std/torch.c'
Torches/lamps etc
`/std/weapon.c'
Any kind of weapons

Standard library objects

These objects are more of the order of 'help' clases. They don't qualify as objects in their own right, but they provide neat functionality for what they do.

`/lib/area_handler.c'
Big general areas
`/lib/bank.c'
Provides money changing support
`/lib/cache.c'
Cache for frequent file access
`/lib/guild_support.c'
Support functions for guilds
`/lib/herb_support.c'
Support functions for herbs
`/lib/more.c'
More (file browsing) functionality
`/lib/pub.c'
Bar/pub functionality
`/lib/shop.c'
Shops of all kinds
`/lib/skill_raise.c'
Training of skills
`/lib/store_support.c'
Support functions for stores
`/lib/time.c'
Time handling routines
`/lib/trade.c'
Trade related support functionality

How to obtain object references

Objects, as previously described, comes in two kinds - master objects and clones. In general you tend to use cloned objects. At least for objects that are being 'handled' in the game, objects that you can move about, touch, examine etc, or any object that exist in more than one copy. Making exclusive use of only the master object is usually only done for rooms, souls or dameon objects of various kinds.

Naturally any object in the game must have a master object. An object is loaded into memory and the master object created when a function (any function call, even to a non-existing function) is called in it. Cloning it just makes identical copies of it. If you destroy the master object, the gamedriver will have to load it again later before making any new clones. Naturally this is what you do every time you have made changes to the object that you want to become active. Destroying the master object won't change the already existing clones, of course. You'll have to replace them separately.

The mudlib in fact works so that loading an object is made by calling a non-existing function in the object and updating it simply destroys the master object.

How to get the object references then? Well, that depends on the situation. An object reference is either an object pointer or a string path, referring to the object source in the mud filesystem. Obtaining them is different depending on the situation however. Let's go through them all.

Object references relative to the current object

[this_object, previous_object, calling_object]

An object can always get the object reference to itself. Use the efun this_object():

object this_object()
e.g.
    object ob;

    ob = this_object();

In order to find out which object called the currently running function in an object using an external call, you can use the efun 'previous_object()':

object previous_object(void|int step)
e.g.
    object p_ob, pp_ob;

    p_ob = previous_object();     // The object calling this function.
    pp_ob = previous_object(-2);  // The object calling the object
                                  // calling this function.

If you supply no argument or -1, the function will return the immediately previous object that called. Decrementing the argument further will return even more previous callers, i.e previous_object(-4) returns the object that called the object that called the object that called your object. If indeed the chain of calling objects was that long. When you exceed the length of the calling chain beyond the first object that made a call, the function will return 0.

As I hope you noticed, this call only checks for external calls, not internal. There is a corresponding efun that works just the same but for any type of call (internal or external) that has been made:

object calling_object(void|int step)

The usage is the same however.

So... how do you know if the object reference you just received is a valid object or not (i.e. 0 or something else)? Well, use the nice efun objectp() as described earlier. It returns 1 if the argument is a valid object pointer and 0 otherwise.

int objectp(mixed ob)
e.g.
    if (objectp(calling_object(-2)))
        write("Yes, an ob calling an ob calling this object exists!\n");
    else
        write("No such luck.\n");

Creating objects

[setuid, getuid, seteuid, geteuid, creator, set_auth, query_auth, clone_object]

First of all you must make sure that the object that tries to create a new object has the privileges required to do so. The rules are pretty simple actually: An object with a valid euid can clone any other object. A valid euid is anything except 0. The euid 0 is the default uid and euid on creation of an object, and it's used as meaning 'no privileges at all'.

However, usually the choice of euids you can set is pretty limited. If you're a wiz it's usually limited to your own name. A Lord can set the euid in an object to be his, or any of the wizard's in the domain (unless one of the wizards is an Archwiz, then that one is excempt as well). And naturally objects with 'root' uid can set any euid they like.

So... the uid of the object determines what choice of euids you have. You set the uid to the default value by adding this sfun call:

void setuid()
e.g.
    setuid();

Simple eh? Doing that sets the uid to the value determined by the location of the object source-file in the mud filesystem. The rules for this is the same as for the creator value described earlier. You can get the creator value of an object with the sfun creator(), it simply returns the string setuid() would use for that object.

string creator(mixed reference)
e.g.
    string my_creator;

    my_creator = creator(this_object());

To get the actual uid value that is currently used, you the sfun getuid()

string getuid()
e.g.
    string curr_uid;

    curr_uid = getuid();

So.. the uid is now set to the highest privilege giver. The euid however, is still 0. Since the euid determines the actual privileges used in an object this means that the object still has no privileges at all.

To set the euid you use the sfun seteuid(), the argument given will be set as euid if allowed (it's tested). The function returns 0 on failure and 1 on success. If you don't send any argument, the euid is set to 0, 'turning it off' so to speak.

int seteuid(void|string priv_giver)
e.g.
    if (seteuid("mrpr"))
        write("Yes! I'm the ruler of the UNIVERSE!\n");
    else
        write("Awwwww....\n");

Naturally there's a corresponding sfun to return the current euid:

string geteuid()
e.g.
    write("The current euid = " + geteuid() + "\n");

The sfuns setuid(), getuid(), seteuid() and geteuid() are all using the efuns set_auth() and get_auth(). They are used to manipulate a special authority variable inside the object in the gamedriver. The gamedriver will call a validizing function in the master object (security) if you try to use set_auth() to make sure that you are privileged to do so. The reason is that it's possible to store any kind of string in the authority variable, and the way we use it is merely a convention, something that we have decided is the best way of solving security.

When you try to perform a privileged operation, like writing to a file or cloning an object the gamedriver calls other special functions in the master object to make sure you have the right privileges. They all depend on that the information stored in the authority variable is formatted in the special way we want for it to work properly. Due to this fact you are not allowed to use set_auth() in any other way than already is allowed by setuid() and seteuid(), so there's really no use in doing that at all. query_auth() is not protected but you won't find much use for that information anyway.

The information stored in the authority variabl