He rubbed the lamp, and the genie appeared, saying: 'What is thy will?'
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.
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.
This example defines a proc called
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.
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
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).
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.
This code allows the DM to become vulnerable or invulnerable at will. In
the redefinition of
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
Since it doesn't make use of
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.
A game in which the astrological signs play an important role, for example,
might rely on a procedure like the following:
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.
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.
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.
1. Creating a Proc
mob
var/life = 100
proc/HurtMe(D)
life = life - D
if(life < 0)
view() << "[src] dies!"
del src
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.
2. Executing a Proc
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)
3. Proc Inheritance
mob/DM
var/vulnerable
verb/set_vulnerability(v as num)
vulnerable = v
HurtMe(N)
if(vulnerable)
..()
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
DM.HurtMe
proc could
be rewritten like this:
mob/DM/HurtMe()
if(vulnerable)
..()
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
5.1 Defining A Global Proc
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
5.2 Calling A Global Proc
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."
|
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.
|
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.
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.
7.2 The . (dot) variable
|
|
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.
|
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.
|
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.
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
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 9. Boolean Expressions
""
(an empty text string). Every other value is true.
TRUE
and FALSE
for this purpose if you are so inclined.
|
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?
|
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]!"
|
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.")
|
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]."
|
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:
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:
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.
( ) !
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.
9.1.7 Combining boolean operators
1 || 0 && 0 //equals 1
1 || (0 && 0) //equals 1
(1 || 0) && 0 //equals 0
1 || 0 && 0 //equals 1
Figure 6.9: Order of Boolean Operations
> < >= <=
== != <>
&&
||
10. Mathematical Operators
|
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
.
|
|
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."
|
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.
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.
( ) ++ -- -(negation)
10.5 Order of Mathematical Operations
Figure 6.10: Order of Mathematical Operations
**
* / %
+ -
|
|
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.
|
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.
|
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.
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.
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.
( ) ~
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.
11.5 Bit Shifting
11.6 Order of Bitwise Operations
Figure 6.11: Order of Bitwise Operations
<< >>
&
^
|
12. Assignment Operators
|
|
|
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.
|
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).
|
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.
|
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.
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
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.
In this example, the
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
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:
In this example, a variable is added to
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.
. : /(path)
> < >= <=
&
&&
?
= += -= etc.
15.1 / the parent-child separator
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
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
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.
dragon
and snake
to
/mob/npc/dragon
and /mob/npc/snake
).
15.3 : the look-down path operator
obj/corpse
icon = 'corpse.dmi'
mob
dragon
icon = 'dragon.dmi'
:corpse //add to corpse definition
var/dragon_meat
/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
Figure 6.12: Order of All Operations
( ) ! ~ ++ -- -(negation)
**
* / %
+ -
<< >>
== != <>
^
|
||
|
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.
|
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.
|
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.
|
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.
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.
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.
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.
The goto statement causes execution to jump to the specified label
in the code.
18. Jumping Around
18.1 break and continue statements
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])"
18.2 goto statement
|
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.
|
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
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!"