Chapter 6

Procs

He rubbed the lamp, and the genie appeared, saying: 'What is thy will?'
--Aladdin and the Wonderful Lamp, The Arabian Nights

There are two types of procedures. Verbs are visible to players as commands. The other type of procedure does not show up as a command. This is called a proc. In almost every other way, the two are identical.

1. Creating a Proc

Procs are useful for defining commonly needed pieces of code. Rather than repeat the same code each time it is used, a proc can be written for the purpose.

As an example, you might have various situations in which a mob can be hurt. In each case, you would need to check if the mob was fatally injured. Rather than doing that over and over (and maybe forgetting in one place by mistake) you could define a proc to handle it.

mob
   var/life = 100

   proc/HurtMe(D)
      life = life - D
      if(life < 0)
         view() << "[src] dies!"
         del src

This example defines a proc called HurtMe, which takes, as an argument, the amount of damage to do. The damage is subtracted from the mob's life and then a fatality check is made. If the life has dropped below zero, the mob gets deleted.

The if statement used here is one of the many procedure instructions that will be described in the sections that follow. For now it suffices to know that the block of code indented beneath the if statement will only be executed when the specified condition is true.

2. Executing a Proc

The syntax for executing a procedure is the same as accessing a variable, with the addition of the arguments to the procedure in parentheses. In the following example, we use the HurtMe proc when the mob drinks some poison.

obj/poison
   name = "can of soda"
   verb/drink()
      set src in view(0)
      usr.HurtMe(25)  //OUCH!
      del src         //one use only (please recycle)

The terminology a programmer normally uses is to call rather than execute a procedure. The two mean the same thing. Think of the procedure as an ephemeral spirit that you can call upon to do your bidding. (You have to amuse yourself somehow during the stretch of tedious coding that occasionally pays a visit ... and sometimes not so occasionally.)

The same syntax is used to call both procs and verbs, though verbs are usually only executed by players. One good way to easily distinguish between the two is to capitalize proc names. Since verbs are usually lowercase, this conveniently differentiates the two. This also prevents any name overlaps between procs and verbs. The compiler considers such conflicts an error, since it would not be able to tell when you call the duplicated procedure whether you wanted to call the proc or the verb.

When a procedure is called, the computer executes each statement one at a time, starting from the top. Some statements, like the if statement you just saw, may cause blocks of code to be skipped or in some cases executed multiple times. However, aside from these special instructions, each command is processed sequentially. When there are no more instructions, the procedure is complete. A programmer calls this returning, because the point of execution goes back to the caller of the procedure (if there was one).

3. Proc Inheritance

Just as with verbs, objects may override the procs they inherit from their parents. The syntax is the same. The original definition is marked by its position under a proc node. After that, it may be overridden, but the redefinition stands on its own without a proc node.

One might, for example, want certain mobs to behave differently when damaged.

mob/DM
   var/vulnerable

   verb/set_vulnerability(v as num)
      vulnerable = v

   HurtMe(N)
      if(vulnerable)
         ..()

This code allows the DM to become vulnerable or invulnerable at will. In the redefinition of HurtMe, the vulnerability is first checked. If the DM has chosen to be vulnerable (maybe to test out a situation), the parent proc is invoked, which in this case calls the original definition of HurtMe to do the damage.

4. Flexibility of Arguments

When you call a procedure, you are allowed to pass in as many arguments as you want. Any that you don't supply will be given the value null. This flexibility allows you, for example, to add additional variables in the redefinition of a proc. Any calls to the proc in which the caller does not use these additional parameters will set them to null.

You can also define fewer variables in the redefinition of a proc. This is usually just a matter of convenience when the redefined proc does not make use of the parameters. For example, the DM.HurtMe proc could be rewritten like this:

mob/DM/HurtMe()
   if(vulnerable)
      ..()

Since it doesn't make use of N, the amount of damage, we didn't even bother to define that parameter. Calls to this proc will still accept the argument. More importantly, the parent proc still receives the arguments even though they were not defined in the child proc. That works because by default, when no arguments are specified to ..() those that were passed to the current proc are passed to the parent.

5. Global Procs

Some procedures may have nothing to do with any particular object. These can be defined at the top level for global access. Such procs typically perform some self-contained computation.

DM has many pre-defined global procedures (like view() and locate()) which generate repeatedly used results. To distinguish these from user-defined procedures, they are called instructions.

5.1 Defining A Global Proc

A game in which the astrological signs play an important role, for example, might rely on a procedure like the following:

proc/Constellation(day)
   //day should be 1 to 365
   if(day > 354) return "Capricorn"
   if(day > 325) return "Sagittarius"
   if(day > 295) return "Scorpio"
   if(day > 265) return "Libra"
   if(day > 234) return "Virgo"
   if(day > 203) return "Leo"
   if(day > 172) return "Cancer"
   if(day > 141) return "Gemini"
   if(day > 110) return "Taurus"
   if(day > 79)  return "Aries"
   if(day > 50)  return "Pisces"
   if(day > 20)  return "Aquarius"
   return "Capricorn"   //day 1 to 20

A second procedure could handle converting from day in month to day in year, which is what this procedure requires. The code determines which astrological sign applies to the specified date and then makes use of the return statement, which ends the procedure and sends the specified value back to the caller. The details of all this syntax will be given shortly.

5.2 Calling A Global Proc

A global procedure is called just like any other. If the proc returns a value, this can be used anywhere an expression is expected.

The term expression means any piece of code which produces a single value as its result. The simplest type of expression is a constant value such as a number or a text string. More complicated expressions may involve variables, operators, procedure calls, and so on.

Here is an example of how to call and use the value returned by the procedure we just defined.

var/day_of_year = 1

mob/DM/verb/set_date(day as num)
   set desc = "Enter the day of the year (1-365)."

   day_of_year = day
   world << "The sign of [Constellation(day)] is in force."

This verb gives the DM the ability to change the time of year, after which everyone is notified about the shift in the heavens. The procedure call, in this case, is simply embedded in some text like any other expression would be.

6. The Procedure Language

In the next chapter we will explore the pre-defined object procs. Now that you know how to override and define new procs of your own, you will be able to create objects even more customized to your needs. Before embarking on that adventure, however, you need to master the language of procedures (or they will master you when you journey into their land). Depending on your familiarity with other programming languages (specifically C and its derivatives), you may choose how thoroughly to read the following material.

6.1 Statements

The fundamental unit of a procedure is the statement, a command that tells the computer to perform some action. So far you have seen statements that assign variables, generate output, and call other procedures.

Such statements are normally placed on a line by themselves. They can, however, be grouped together on a single line by placing a semicolon between them. It is also possible to make a statement span several lines by placing a backslash at the end of all but the last line.

Statement
Statement; Statement; ...
Statement Part 1 \
Statement Part 2

In addition to such simple statements, there are also compound ones like if which can combine several of these simple statements into one. The following sections will describe all the variations on a statement that DM understands.

7. Return Values

Every type of procedure returns a value. Even verbs return one, though it is rarely used. When a procedure finishes without explicitly returning anything, the special value null is passed back.

7.1 The return statement

You have just seen an example using the return statement. It's general format is:

return [expression]

This statement causes the proc to cease execution. If the optional value is specified, it is passed back to the caller. The term expression means any sequence of code that produces a value. This could be a simple constant, a mathematical computation, or even the result of another procedure call.

7.2 The . (dot) variable

If the procedure finishes without using return, or if return is used without a following expression, the value passed back to the caller is contained in the . (dot) variable. This variable can be assigned and used like any other. Its default value is null. That is why, if it is never modified and no return value is specified, the procedure returns null by default.

The choice of whether to use return or the dot variable is purely a matter of convenience. In some cases, the value you want to return may be computed before you are ready to finish the procedure (because there is still some processing to do). Then it would make sense to use the dot variable. Another time is when you wish to specify a different default return value.

The name of the dot variable was chosen to be suggestive of the current procedure in the same way it is used in many file systems to represent the current directory. This coincides nicely with the dot dot notation to represent the parent procedure (and the parent directory in a file system). The analogy goes even further, as you shall see in the discussion of type paths.

8. The if statement

To conditionally execute a block of code, one uses the if statement. The general syntax is:

if(expression)
Statement1
Statement2
.
.
.

Or

if(expression) Statement

The first format may have multiple statements in the indented block beneath it. The second condensed form is for a single statement. All compound statements in DM have these two formats. For brevity, they will be listed from now on in the condensed format, with the understanding that the single statement can be replaced by several in an indented block.

Another way to group several statements together would be to put braces around them. Then they can be placed on a single line or spread across multiple lines as desired.

if(expression) {Statement1; Statement2; ...}

The statements inside the if statement are said to be its body. These are only executed if the conditional expression is true. DM does not have special values to stand for true and false. Instead, every type of value has truth or falsity associated with it. You will see how that works in a moment.

8.1 The else clause

Before taking a closer look at the conditional expression itself, the else clause should be mentioned. When the condition is false, an alternate body of statements may be executed. By combining the two, an entire sequence of alternate conditions may be tested. The syntax for doing this takes the following general form:

if(expression1) Statement1
else if(expression2) Statement2
.
.
.
else Statement3

Proceeding from top to bottom, each expression is tested. As soon as one is found to be true, the corresponding body of statements is executed. Note that the first condition found to be true takes effect and the rest are ignored. If none are found to be true, the final else body is executed.

9. Boolean Expressions

When an expression like the conditional one in the if statement is interpreted as true or false, a programmer calls it a boolean value. In DM, there are three ways for an expression to be false: it can be null, 0, or "" (an empty text string). Every other value is true.

It is customary in DM, when you want a true or false constant, to use 1 and 0 for the purpose. These are most often used to set a flag variable (like opacity) or as the return value of a procedure. You could define constants TRUE and FALSE for this purpose if you are so inclined.

9.1 Boolean Operators

Boolean expressions are such a basic element of procedure code that there are a number of special operators for use with them. These all result in a boolean value of 1 or 0, depending on their arguments. The boolean operators and most of the others in DM are identical to those used in the C language (and its derivatives like C++ and Java).

( An operator is a special symbol, like = or !, that performs some action or computation. Those operators which precede their argument are said to be prefix, and the reverse are postfix. Those that have arguments on both sides are infix. )

9.1.1 ! the logical NOT operator

The ! operator computes the logical NOT of the expression that follows. In other words, if the expression is true, it returns 0; and if the expression is false, it returns 1.

!expression

The following example uses the ! operator to toggle a boolean value. The term toggle simply means to flip it from true to false or from false to true.

mob/verb/intangible()
   density = !density
   if(density) usr << "You materialize."
   else usr << "You dematerialize."

See how it works?

9.1.2 && the logical AND

The && operator computes the logical AND of two expressions. It is true if both expressions are true; otherwise it is false. For efficiency, the second argument is not evaluated if the first one is false. This is often convenient when the second expression is a procedure call with a side-effect that you don't want to happen if the preceding expression was false. This behavior is known as short-circuiting. The value of the AND expression is equal to the value of the last argument to be evaluated. (The convenient short-circuiting behavior comes from C. Unlike C, however, the && and || operators return the last argument to be evaluated rather than 1 or 0. That handy little gem comes from Perl.)

expression1 && expression2

Here is an example that uses the && operator to ensure that both the poker and pokee are dense in a typical poking operation.

mob/verb/poke(mob/M)
   if(density && M.density)
      view() << "[usr] pokes [M]!"
   else
      view() << "[usr]'s finger passes through [M]!"

9.1.3 || the logical OR

The || operator computes the logical OR of two expressions. It is true if either expression is true; otherwise it is false. As with the && operator, unnecessary evaluations are avoided. If the first expression is true, the second will not even be processed. The entire expression gets the value of the final argument to be evaluated.

expression1 || expression2

An example using the short-circuit behavior displays some alternate text if the player's description is blank.

mob/verb/look()
   set src in view()
   usr << (desc || "You see nothing special.")

9.1.4 == the equality test

The == operator compares two values. If they are identical, it evaluates to 1 and otherwise 0. Note that a single = is the assignment operator, which is a totally different creature. Unlike the C language, DM will not allow you to use = in an expression (like an if statement) so you don't have to worry about accidentally using the wrong one.

When used on numbers, the result is a straightforward comparison of numeric values. When used on references, it is the reference that is compared, not the object being referenced. So if two objects are created that are identical in every way, but which are still in fact separate objects, the result of a comparison will be false rather than true. Since identical text strings are always combined into a single data object to save memory, comparison of text references does produce the expected result--namely a case-sensitive comparison.

expression1 == expression2

The following example uses the == operator to see if you are laughing at yourself.

mob/verb/laugh(M as mob|null)
   if(!M)
      view() << "[usr] laughs."
   else if(M == usr)
      view() << "[usr] laughs at \himself."
   else
      view() << "[usr] laughs at [M]."

9.1.5 != and <> inequality tests

The != and <> operators test two values for inequality. They may be used interchangeably. The result is the reverse of the == operator.

9.1.6 Relative comparison operators

The operators >, <, >=, and <= test if the left-hand expression is greater than, less than, greater than or equal to, and less than or equal to the right-hand expression. These only apply to numerical expressions. Any other type of data (like null or a text string) is treated like 0.

expression1 > expression2
expression1 < expression2
expression1 >= expression2
expression1 <= expression2

9.1.7 Combining boolean operators

Often, several boolean operators are used together in an expression. When this is done, one must be careful that the order in which the computer evaluates them is the same order intended. To force a particular order of evaluation, parentheses can be inserted to group arguments and operators as desired.

For example, the same arguments and operators grouped in different ways can yield different results:

 1  ||  0  &&  0     //equals 1
 1  || (0  &&  0)    //equals 1
(1  ||  0) &&  0     //equals 0

As you can see, when no parentheses are used, && is evaluated before ||. This means that && has a higher order of operations than ||. One can always use parentheses to ensure correctness, but if you do start taking advantage of the implicit order of operations, you can help remind yourself of it by spacing things suggestively:

1   ||   0 && 0      //equals 1

All the boolean operators are listed in figure 6.9 from highest order of operations to lowest. Those that fall on the same line have an equal priority and are therefore evaluated from left to right as they occur in the expression.

Figure 6.9: Order of Boolean Operations

( ) !

> < >= <=
== != <>
&&
||

10. Mathematical Operators

Operators exist for all basic mathematical computations. From these, other more complex functions may be constructed. All mathematical operations use floating point arithmetic unless otherwise stated. Any non-numerical arguments will be treated as 0.

In addition to these operators, there are some useful built-in mathematical procedures (like one for rolling dice). These will be described in chapter 16.

10.1 Arithmetical Operators

The arithmetical operators are +, -, *, and /. These perform addition, subtraction, multiplication, and division. The - operator may also be used (in prefix form) for negation.

expression1 + expression2 20pt (addition)
expression1 - expression2 (subtraction)
expression1 * expression2 (multiplication)
expression1 / expression2 (division)
- expression (negation)

C programmers should note that division yields the full floating point result rather than just the integer portion. For example, 3/2 yields the expected result of 1.5.

10.2 ** the power operator

The ** operator raises the left-hand value to the power of the right-hand value. Do not mistakenly use ^ for this purpose, since it has a completely different meaning (described below).

expression1 ** expression2

10.3 % the modulus operator

The % operator is used to find the remainder of a division. The expression A % B is read "A modulo B" and is equal to the remainder of A divided by B. This operator only works on integer arguments.

expression1 % expression2

This operator is often used in cyclical events. For example, you could define a procedure that makes the sun rise, warning people about especially ominous days.

var
   day_count
   day_of_week  //0 is Sunday

proc/NewDay()
   day_count = day_count + 1
   day_of_week = day_count % 7  //0 through 6

   if(day_of_week == 1)
      world << "It's Monday!"
   else
      world << "A new day dawns."

10.4 Increment and Decrement

Adding and subtracting 1 from a variable are such common operations that special operators exist for the purpose. The increment operator ++ adds 1 to a variable. The decrement operator -- subtracts 1 from a variable.

Each of these operators has a prefix form and a postfix form. Which one is used controls whether the value of the expression as a whole is taken from the variable before or after its value is modified. The prefix form modifies the variable and returns the result. The postfix form modifies the variable but returns its original value.

++ expression
-- expression

expression

++
expression --

The previous sun-rising example could make use of the increment operator.

   day_count = day_count + 1  //long-hand
   day_count++                //short-hand
   ++day_count                //or even this

In this case, it didn't matter whether we used the prefix or postfix version, because we weren't using the value of the resulting expression. Only the side-effect of incrementing the day_count variable matters, and that is the same in either case.

We could even combine the increment of day_count with the following line that makes use of it, like this:

   day_count = day_count + 1    //increment
   day_of_week = day_count % 7  //use incremented value

   day_of_week = ++day_count % 7 //increment and use it

Notice that we used the prefix increment. That is because we wanted day_count to be incremented first and then used to compute the weekday. The postfix increment would have used the existing value of day_count to compute the weekday and then increment. The two would end up one day out of sync that way. Of course, in this example that wouldn't matter much, but in some situations it could be important.

10.5 Order of Mathematical Operations

Just like the boolean operators, the mathematical symbols are evaluated in a particular order. When the default order is not desired, parentheses can be used to form smaller expressions that are evaluated first.

Figure 6.10 summarizes the order of operations of the mathematical symbols from highest to lowest. Operators on the same line have equal precedence and are therefore evaluated as they appear in the expression from left to right.

Figure 6.10: Order of Mathematical Operations

( ) ++ -- -(negation)

**
* / %
+ -

11. Bitwise Operations

It is sometimes useful to pack several flags into one variable. The mob.sight variable is an example of this. Each flag is represented by a single on/off bit in the value. For example, in the case of mob.sight, the possible values are:

( Don't get too attached to the specifics of this variable. Tom doesn't like it, and I have a feeling he may stage another insurrection to squelch it. That heartless demagogue! Why, without him, DM would still be nice simple assembly language. )

#define BLIND     1 //binary 00001
#define SEEINVIS  2 //binary 00010
#define SEEMOBS   4 //binary 00100
#define SEEOBJS   8 //binary 01000
#define SEETURFS 16 //binary 10000

Each value is a power of two, which allows us to generate unique numbers by combing them.

To make it easier to manipulate individual bits, there are a number of bitwise operators (inherited from C). When using these operators, the arguments should be 16 bit integers (in the range 0 to 65535). (65535 is simply sixteen 1's in binary; that's the largest 16 bit number.) Anything outside this range will be truncated.

11.1 ~ the bitwise NOT

The ~ operator performs a bitwise NOT of its argument. For each of the 16 bits in the argument, if the bit is 1, the corresponding bit in the result will be 0, and vice versa. That is a lot like the ! operator except the latter doesn't care which bits are on--only that at least one bit is on. The ! operator also works with other values besides 16 bit integers.

~ expression

11.2 & the bitwise AND

The & operator performs a bitwise AND of its arguments. For each pair of bits in the arguments, the corresponding bit in the result will be 1 if both are 1, and 0 otherwise. Note that this is analogous to the logical && operator except that it processes each bit individually rather than the value as a whole.

expression1 & expression2

The & operator is most often used to test if a particular bit flag is set. For example mob.sight & SEEINVIS would be non-zero (i.e. true) if the SEEINVIS flag is set and 0 otherwise.

11.3 | the bitwise OR

The | operator performs a bitwise OR of its arguments. For each pair of bits in the arguments, the corresponding bit in the result will be 1 if either is 1, and 0 otherwise. Note that this is analogous to the logical || operator except that it processes each bit individually rather than the value as a whole.

expression1 | expression2

The | operator is most often used to combine several bit flags together. For example, mob.sight might be set to SEEMOBS | SEEOBJS to give someone x-ray vision of objects through walls. Actually, you can use + for this purpose as long as you never include the same flag more than once.

11.4 ^ the bitwise XOR

The ^ operator performs a bitwise exclusive OR of its arguments. For each pair of bits in the arguments, the corresponding bit in the result will be 1 if exactly one of them is 1, and 0 otherwise.

expression1 ^ expression2

The ^ operator is most often used to toggle a bit flag. For example, mob.sight = mob.sight ^ SEEINVIS would turn on the SEEINVIS flag if it is off, and vice versa.

11.5 Bit Shifting

The << and >> operators perform left and right bit shifts. They are almost never used in DM programs but are included because they are standard C operators. When used outside of an expression (as a statement) these operators have quite a different meaning (inherited from C++); in that case they serve as input/output operators. You will almost always use them in that form.

11.6 Order of Bitwise Operations

The order in which bitwise operators are evaluated is listed in figure 6.11 from highest to lowest. Those on the same line have equal precedence and are therefore evaluated from left to right as they occur in the expression.

Figure 6.11: Order of Bitwise Operations

( ) ~

<< >>
&
^
|

12. Assignment Operators

The = operator causes the left-hand variable to be assigned to the right-hand expression. As noted earlier, this is quite different from the == symbol, which performs a comparison.

( The = and == operators have the same meaning in the C language. Unlike C, however, assignment in DM is not itself an expression. That prevents the easily made mistake of using = when you really wanted == in a conditional statement. )

In an assignment, numbers and references are simply copied to the specified variable. The actual contents of a reference are not duplicated--only the reference itself is. See section 5.6 for a discussion of references and variable data.

expression1 = expression2

12.1 Combining Other Operations with Assignment

When you want to add something to a variable, you could do it like this:

variable = variable + 26

However, DM provides a nice abbreviation for this since it is such a common operation. Instead you can just type:

variable += 26

This is not just a special case for the + operator. It works for all of them. In general, the following two statements are equivalent.

1.10pt expression1 = expression1 (operator) expression2
2.10pt expression1 (operator)= expression2

13. ? the Conditional Operator

The ? operator tests a boolean expression. Two additional expressions are specified: one that takes effect if the boolean expression was true, and the other if it was false. For efficiency, only the required expression of the two is evaluated.

boolean expression ? true expression : false expression

The following example uses the ? operator in place of an if statement.

mob/verb/intangible()
   density = !density
   usr << (density ? "You materialize." : "You dematerialize.")

Ok, this looks like Greek to anyone but a hard-core C programmer (or a Greek). Still, once you train your eye to read it, you can walk around feeling superior to everyone else.

14. Dereference Operators

With a reference to an object stored in one variable, the object's own variables and procedures can be accessed using the operators described in the following sections. This sort of operation is known as dereferencing a variable, because it requires the computer to access the data pointed to by a reference.

14.1 . the "strict" dereference

The . (dot) operator is used to access a variable or procedure belonging to an object. To do this, one must have a reference to the object stored in a variable of the appropriate type. The type does not have to be completely defined--just enough to get to the definition of the variables and procedures that will be accessed.

object.variable 20pt Or 20pt object.procedure()

Unlike most other DM operators, space is not allowed on either side of the dot operator. The variable and procedure names must be on either side of it with no separation.

The requirement that the object be of a known type is merely to allow the compiler to do better error checking. It won't let you try to access a variable or procedure that does not belong to the specified type and is therefore known as the "strict" dereference operator.

That's at compile-time. At run-time, you may in fact have assigned the variable to an object which isn't even of the same type (like a mob in an obj variable). That is ok. In fact, the dot operator will still work in that case as long as the requested variable exist for the object in question. In this case, we would say that the object has a compatible interface.

If at run-time it turns out that the object doesn't have the requested variable, the procedure ceases execution immediately. This is called a procedure crash. (Even worse is a world crash in which the entire world dies.) The most common case is a null object. Some debugging output will be generated describing the exact cause to help you track down the problem. Chapter 19 will discuss debugging methods in greater detail.

The following four verbs illustrate various properties of the dot operator.

mob/verb
   summon1(M as mob)
      M.loc = loc  //compile-time error!
   summon2(mob/M)
      M.loc = loc  //this works
   summon3(obj/M as mob)
      M.loc = loc  //this works
   summon4(mob/M as mob|null)
      M.loc = loc  //could be run-time error!

The first version of the summon verb will not compile. The input type of M was defined to be mob, but the variable type was left undefined, so the compiler does not know that M has a loc variable.

The second version takes care of the variable type and makes use of the fact that the default input type is computed from the variable type.

The third version is wacky, but it works. We told the compiler it is an obj var and we told the client to take mobs as input. Since both obj and mob have a loc variable, both the compiler and the server are happy. You obviously wouldn't want to do things this way, but you could change the input type to obj|mob and it would make more sense.

The fourth version runs the risk of a proc crash. It should instead check if M is null and avoid dereferencing it in that case. Another method would be to assign M a default value (such as usr).

14.2 : the "lax" dereference

The : operator allows you to take things one step further by making the compile-time checks for validity even less strict. It works just like the dot operator, except the full object type need not be specified. As long as at least one object type derived from the specified one has the requested variable, the compiler will allow it. It is left up to you to make sure only compatible objects are used at run-time (or a crash results).

variable:variable 20pt Or 20pt variable:procedure()

Like the dot operator, the : may not have any spaces between it and its arguments.

The most common use for this operator is when the object type is not defined at all. In that case, the compiler only checks to make sure that at least one object type in the entire tree has the specified variable or procedure. This technique should not be used out of laziness but when you have legitimate reasons for leaving the object type unspecified. One reason would be if a variable may contain references to objects of types with no common ancestor (like obj and mob). Most of the time, however, it is best to make use of the compiler's type checking capabilities.

The following verbs exhibit two methods for extending the above summon command to take both mobs and objs as arguments.

mob/verb
   summon1(obj/M as obj|mob)
      M.loc = loc
   summon2(M as obj|mob)
      M:loc = loc

The first example keeps the compiler happy by lying about the variable type. M might be an obj, but it might also be a mob. In either case, they both have a loc variable, so it will work at run-time.

The second case keeps the compiler happy by using the : operator to do lax type checking. Since no type is declared for M, the compiler just checks to make sure that some type of object has a loc variable. Since there are several which do (obj, mob, and turf being examples) the check succeeds.

The bottom line is that neither the strict nor lax operator has any influence on the value of the variable itself. They just control how the compiler does type checking. At run-time all that matters is whether the object has the requested variable.

15. Path Operators

The . and : operators may also be used in path expressions along with the normal / separator that you have already seen. Their meaning in this context is reminiscent of the way they behave in dereference expressions. All the path operators have in common the need to be directly adjacent to their arguments with no intervening space.

path-expression/path-expression
path-expression.path-expression
path-expression:path-expression

Paths are used in two contexts in DM. One is in object definitions. In this case the path is used to create nodes in the code tree. The second context is in expressions, where path values refer to object types. In this case, the type reference must always start with a path operator to be properly recognized. That is easy to remember, because one almost always would want to begin with /, as you will see shortly.

15.1 / the parent-child separator

The / operator is used in a path between a parent and its child. In the context of an object definition, that is equivalent to a newline and an extra level of indentation.

At the beginning of a path, this operator has the special effect of starting at the root (or top level) of the code. Normally, a path is relative to the position in the code where it is used. For example, if you are inside the obj node and you define scroll, you are really defining /obj/scroll, which is how you would refer to that type elsewhere in the code. A path starting with / is called an absolute path to distinguish it from other relative paths.

15.2 . the look-up path operator

The . (dot) operator in a path searches for the specified child starting in the current node, then its parent, its parent's parent, and so on. It is for this upward searching behavior that we call it the look-up operator. Obviously the node you are looking up must already exist for this to work.

The most common use for this operator is in specifying the type of an ancestor object. For example, one might want to define groups of mob species who always come to each other's aid in combat.

mob
   var/species_alignment
   dragon
      species_alignment = .dragon
      red
      green
      black
         species_alignment = .black
   snake
      species_alignment = .snake
      cobra
      winged
         species_alignment = .dragon
      pit_viper
         species_alignment = .dragon/black

In this example, the species_alignment variable is intended to indicate what group of creatures a given type of mob treats as allies. This is done by storing an object type in the variable. Two mobs with identical values for species_alignment will be friends.

In this example, dragons are aligned with each other except the black one, which is self-aligned. Snakes are aligned with each other except the winged ones, which are aligned with the dragons, and the pit vipers, which are aligned with the black dragon.

By using the dot operator, we avoided the use of absolute paths. It's not only more compact but less susceptible to becoming invalid when certain code changes are made (like moving dragon and snake to /mob/npc/dragon and /mob/npc/snake).

15.3 : the look-down path operator

The : operator searches for a child starting in the current node and then in all its children if necessary. It is for this reason that it is called the look-down path operator. At the beginning of a path, it causes the search to take place from the root.

The previous example could be changed to have : substituted for the dot operator. For instance, .dragon/black could be replaced by :black or :dragon:black or /mob:dragon:black, depending on how ambiguous the name `black' is. If both /mob/dragon/black and /obj/potion/black exist, then you would need to include enough information to distinguish between the black dragon and the black potion. Otherwise the wrong one might be selected instead.

The dot and : path operators are similar in meaning when operating on paths and variables. The dot operator accesses either a node or variable defined at the specified position or above. The : operator accesses a node or variable defined at the specified position or below.

One powerful way of using the various path operators is to modify an object definition from somewhere else in the code. This is sometimes useful when working in large projects that are split between several files. More will be said about that topic in chapter 19. For now, this is a facetious example:

obj/corpse
   icon = 'corpse.dmi'

mob
   dragon
      icon = 'dragon.dmi'

      :corpse  //add to corpse definition
         var/dragon_meat

In this example, a variable is added to /obj/corpse from inside the definition of mob/dragon. This would presumably then be used by the dragon code in some way. For example, when a dragon dies and produces a corpse, the dragon_meat could be set to 1. Another dragon coming across such a corpse might protect it against scavengers. There would be better ways of accomplishing the same thing, but the point is that we were able to put the definition of the variable near the only place in the code where it would be used--a nice way to organize things.

16. Order of All Operations

The boolean, bitwise, mathematical, conditional, and assignment operators may all be used in the same statement. When no parentheses are used to explicitly control the order of operations, it is necessary to know what order the compiler will enforce.

Figure 6.12 lists all the DM operators from highest to lowest order of operation. Each line contains operators of equal priority. These will be evaluated in order from left to right as they appear in an expression.

Figure 6.12: Order of All Operations

. : /(path)

( ) ! ~ ++ -- -(negation)
**
* / %
+ -

> < >= <=

<< >>
== != <>

&

^
|

&&

||

?

= += -= etc.

17. Loop Statements

There are a variety of ways to execute the same block of code several times in a sequence. This is called looping because the point of execution moves down through the block of code and then jumps back up to the top to do it all over. Every form of loop includes some way of terminating the loop; otherwise it would continue forever. Usually this takes the form of a boolean condition that must be met each time the loop repeats.

Each form of loop is convenient in different situations. The syntax and uses of each kind will be described in the sections that follow.

17.1 for list loop

One very common task in DM programming is doing some operation on each item in a list. The for loop is designed for this purpose.

for(variable as input-type in list) Statement

The statements inside the for loop are its body. The syntax for defining the body is the same as the if statement. A single statement may be included on the same line or multiple statements may be placed in an indented block beneath the for statement.

Each item in the specified list which is of the indicated input type is in turn assigned to the supplied variable. After each assignment, the body of the for loop is executed. A single pass through the for loop (or any other) is called an iteration. The entire process is often termed "looping over or through a list".

( Mathematicians and computer scientists have no regard for the preposition, and will often just pick one at random to suit their purposes. For example, after finally accepting the statement "f of x is a map under the real domain onto the range of f" I found the remainder of calculus to be relatively straightforward. )

Notice that the syntax of the for loop is very similar to that of a verb argument definition. A variable is supplied in which a value from a list will be stored and all values not belonging to the desired input type are filtered out. The only difference is that the for loop variable is not being defined in this statement. It must be defined in the preceding code.

The same input types can be used in the for loop as in an argument definition. See section 4.5.1 for a complete list. Several can be used in combination by using the | operator.

( Incidentally, you should now see why | is used in this case. Each input type is actually a bit flag which can be ORed together bitwise. )

As is the case with argument definitions, convenient default values are supplied for both the input type and the list. If no input type is specified, all items not compatible with the variable type are automatically filtered out. If no list is specified, it defaults to the contents of the entire world (world.contents or simply world). That is different from verb arguments, which use the view() list by default.

The body of the for loop will of course use the loop variable in some way. For example, an inventory list could be displayed by looping over each object in the player's contents.

mob/verb/inventory()
   var/obj/O
   usr << "You are carrying:"
   for(O in usr)
      usr << O.name

The statement could have been for(O as obj in usr), but that would be redundant in these case since we defined the variable to have that type.

One subtle point arises when you modify the contents of the list you are looping over. For example, there might be situations when you would want the player to drop everything in the inventory.

mob/verb/strip()
   var/obj/O
   for(O in usr)
      O.loc = usr.loc  //drop it

This will actually work as expected. If one were to do all the work of looping through the list directly (which you shall see how do in section 10.2), it would be easy to make a mistake in cases like this because the contents of the list are changing with each iteration. DM handles cases like this by making a temporary copy of the list at the beginning of the for loop.

However, there is one list that DM considers too cumbersome to handle in this way, and that is the world.contents list. Do not loop over the contents of the whole world if you are simultaneously changing that list (i.e., creating or destroying objects). It will not necessarily work as you expect. If need be, you can create your own temporary copy of the list using techniques described in chapter 10.

17.2 for conditional loop

There is a second form to the for loop. You might think of this as the manual version; you could use it to loop over a list, but it would not automatically handle the process for you like the other syntax. The advantage is you can use it to do anything you want, the way you want.

for(initialization; condition; iteration) Statement

There are three parts to the for loop: an initialization statement, a conditional expression, and an iteration statement. The initialization statement is executed once before any iterations take place. Then at the beginning of each iteration, the condition is tested. If it is false, the for loop is finished. Otherwise, the for loop body is executed. (It may be a block of multiple statements.) Finally, at the end of the body, the iteration statement is executed, the condition is tested, and the process is repeated until the condition becomes false.

( Some of you will recognize this as the C-style for loop. However, be careful not to use a comma, as you would in C, to pack several statements into one of the loop control statements. In DM, the comma is identical to the semicolon in this context. To pack several statements together, you should instead surround them with braces { }. )

Suppose, for example, that you wanted to create a variable number of objects. The simplest way would be to use a for loop.

obj/scroll/medicine
   verb/cast(N as num)
      var/i
      for(i=1; i<=N; i++)
         new/obj/medicine(usr)

This example defines a medicine scroll which allows the player to create as much medicine as desired. (You would probably want to build in a cost per medicine by subtracting from the player's magic power or something.) The new command will be described in detail in section 7.2. It creates an object of the specified type at the given location. In this case, the location is the user's inventory.

17.3 while loop

The while loop is a simpler version of the for loop. It only takes a condition parameter, and leaves the initialization and iteration control up to the rest of the code. Like the for loop, the condition is tested at the beginning of each iteration. If it is false, the loop is finished.

while(condition) Statement

This loop is mainly useful in situations where the initialization and iteration statements are unnecessary or can be combined with the condition. For example, the for loop example can be made to work with a simple while loop.

obj/scroll/medicine
   verb/cast(N as num)
      while(N-- > 0)
         new/obj/medicine(usr)

This has precisely the same effect of making N medicine objects but does so in a different way. Once you become familiar with the increment and decrement operators, compact code like this may seem more appealing. Otherwise, you could obviously decrement N at the bottom of the while loop body.

17.4 do while loop

The do while loop is similar to the while loop, except the condition is tested at the end of an iteration rather than the beginning. The effect this has is to guarantee that the body of the loop is executed at least once. In certain situations, that is just what one needs.

do
Statement
while(condition)

For example, one could make the medicine verb work without any argument at all (when the player is in a hurry for some medicine).

obj/scroll/medicine
   verb/cast(N as num|null)
      do
         new/obj/medicine(usr)
      while(--N > 0)

Now it is possible to just type "(cast medicine)" to make a single medicine object. The same could have been accomplished with a default argument of 1.

18. Jumping Around

The loop and conditional statements exist because they provide a structured syntax for very common operations. Sometimes, however, they may not quite fit your requirements. Several less-structured instructions exist to help you adapt the other control-flow statements to any possible purpose. These are described in the following sections.

18.1 break and continue statements

The break and continue statements are used to terminate an entire loop or the current iteration of a loop, respectively. These are useful when you have a situation that doesn't exactly fit any of the simple loop statements. They are placed in the body of the loop--usually inside an if statement to be executed when some condition is met.

One could use the continue statement when listing all the players in the game, to avoid including the user in the list.

mob/verb/who()
   var/mob/M
   for(M in world)
      if(!M.key) continue   //skip NPCs
      if(M == usr) continue //skip self

      if(M.name == M.key) usr << M.name
      else usr << "[M.name] ([M.key])"

This displays all the other players (and their real key name if they are using an alias). Of course the example could be rewritten without continue by rearranging the statements. However, in more complex situations, the use of continue and break can sometimes clarify the code immensely.

18.2 goto statement

The goto statement causes execution to jump to the specified label in the code.

goto label
.
.
.
label

The label is actually just a node marking the destination point in the code, and can precede or follow the goto statement. The argument to goto is actually the path to the destination node. As a convenience, the path has an implicit dot in front. That means most of the time, you only need to specify the name of the label and no further path information. See section 6.15.2 on path operators.

The goto statement should be used only when it clarifies the code. In most situations, the more structured loop and conditional statements are preferable. One situation where it may prove useful is when there is some wrapup code that needs to be executed before returning from a procedure, and there are multiple points within the rest of the procedure that need to wrap up and return. Rather than repeating the same wrapup code everywhere (and possibly forgetting to do it in some places) you can put it at the bottom of the procedure with a label in front of it.

A simple example is not possible, because in any simple situation, you would not want to use goto. However, the general structure would be something like this:

proc/Example()
   //Complex code with the occasional:
   goto wrapup

   //Final code that goes straight down to wrapup
   //unless it returns.

   wrapup:
   //Do wrapup stuff here.

As illustrated by this example, you can put an optional colon on the end of the label. This helps distinguish the node from other statements. It also happens to be the standard way labels are declared in most programming languages.

It is important to clarify that the label in the code doesn't do anything. It is just a marker. If execution reaches the label, it skips right over and processes the next statement.

18.3 Block Labels

In the previous section, you saw how to label a point in the procedure code and jump to it. This is a very flexible technique, but it lacks structure and can therefore produce source code that is tangled and difficult to understand. Sometimes you may want to combine the functionality of goto with the loop instructions break and continue. To do that, you need to use block labels.

A block label is the same as a goto label, except it goes at the top of a block of indented code. Like the goto label, the block label doesn't do anything. Execution skips right over it and starts at the first statement in the block. The block label can even be used as the destination of a goto statement. However, its real purpose is with break and continue.

Both break and continue work, by default, with the inner-most loop containing them. However, if the name of a block is specified, they apply to that block instead. The break statement causes execution to jump to the end of the block; continue causes the next iteration of a loop directly contained in the block to take place.

The following example makes use of a labeled block to steal food from people.

obj/scroll/free_lunch/verb/cast()
   var/mob/M
   var/obj/food/F

   victim_loop:
      for(M in view())
         if(M == usr) continue victim_loop
         for(F in M)
            M << "Thanks for the contribution!"
            F.loc = usr  //grab the snack
            continue victim_loop
         usr << "[M] has nothing to offer."

There are two loops in this example, an outer one and an inner one. The outer one loops over all the creatures in view of the user. It has been labeled victim_loop. The first continue statement is used to prevent the user from stealing her own food. It would work with or without the victim_loop label, since that is the loop directly containing the continue statement.

The inside loop iterates over the food carried by the victim. Notice that it is not really a loop at all, because at the end of the very first iteration it continues the outer loop. That is a common trick used to find the first item derived from a given type (in this case /obj/food). In this case, it was necessary to explicitly use the victim_loop label because otherwise continue would have applied to the inner loop rather than the outer one. That would have resulted in the spell being much too greedy and stealing all of the food of each victim rather than just one item each.

19. switch statement

The switch statement is used to simplify certain lengthy chains of else if statements. It takes an expression and then executes a block of code corresponding to that value. (The DM switch statement is similar to its counterpart in C, but has an improved syntax that avoids common errors. For example, at the end of one if body the point of execution automatically skips to the end of the switch statement, rather than running into the code for the next case as it does in C.)

switch (expression)
if(constant1) Statement
if(constant2) Statement
.
.
.
else Statement

Multiple constants may be supplied in one of the inner if statements by separating them with commas. A range of integers may also be specified using the syntax lower-bound to upper-bound.

The stellar example used earlier in this chapter can be efficiently rewritten to use the switch statement.

proc/Constellation(day)
   //day should be 1 to 365
   switch(day)
      if(355 to 365) return "Capricorn"
      if(326 to 354) return "Sagittarius"
      if(296 to 325) return "Scorpio"
      if(266 to 295) return "Libra"
      if(235 to 265) return "Virgo"
      if(204 to 234) return "Leo"
      if(173 to 203) return "Cancer"
      if(142 to 172) return "Gemini"
      if(111 to 141) return "Taurus"
      if(80  to 110) return "Aries"
      if(51  to  79) return "Pisces"
      if(21  to  50) return "Aquarius"
      if(1   to  20) return "Capricorn"
      else           return "Dan!"