Index / Last updated: May 2002

Tips on Writing JotaCode


Basic Tips

The Four Object Types

Everything on the ifMUD is one of four object types: player, room, exit, or thing.

TypeLocated In May containCreated by
Playerin rooms things@pcreate [3]
Roomin void exits, players, things@dig
Exit [1]in rooms or things (n/a)@open (in a room)
@action (in a thing)
Thingin rooms or players exits [2]@create

[1] Exits on objects are usually called "actions".
[2] Things cannot contain other things. However, if you really want a container...
[3] Only wizards can use the @pcreate command.


dbref numbers

Every object on the ifMUD has a unique dbref number. An object's dbref number is assigned when the object is created. You cannot change an object's dbref number.

Dbref numbers for objects begin at 0 and go up from there. If an object is recycled, its dbref number is also recycled. The ifMUD will always choose the lowest available positive integer for a newly created object.

Some negative dbref numbers have special meaning; for example, -3 means "nowhere".

There are several ways to find out what an object's dbref is:


Creating a Room


Creating a Thing


Creating an Exit


Creating an Action

Actions are exits on things. They behave the same way as room exits -- they are exits, in fact -- but the steps to creating them are slightly different.


Advanced Tips

Some fields that @fieldloop skips over and ignores

action
The destination of an exit, that is, the destination room's dbref. A value of -3 means that the destination is "nowhere".
contents
The contents of the room, object, or player. This is a comma-separated list of dbrefs.
description
The description of the object, normally set by the @desc command.
home
The dbref of the home of the object, normally set by the @link command.
location
The dbref of the location of the object.
name
The names of the object, normally set when the object was created or by the @name command. This is a semicolon-separated list of names.
owner
The dbref of the object's owner.
type
The object's type, represented as a number from 1 to 4. See the help for the @type function.
zone
The dbref of the object's zone, normally set by the @zone command.

Understanding @print

@print is a curious function that seems to do nothing, but it is nevertheless quite versatile. What @print does do is evaluate all its arguments, concatenates the results together into one string, and passes that string up to its caller. What @print doesn't do is actually print anything.

Use @print to concatenate strings. For example, if you wanted to append field "y" to the end of field "x":

  @s("x", @print(@g("x"),@g("y")) )

Use @print to group a set of statements into one block. For example, @tellroom must have exactly three arguments:

  @tellroom(@location("%#"),-1,@print(
    "The value of \"x\" is ", @g("x"),
    " and the value of \"y\" is ", @g("y"), "."
  ));

You don't usually need to bother using @print with just one argument. For example, the following two lines do the same thing:

  @succ east = You walk east.
  @succ east = @print("You walk east.")

Exception 1: Use @print with one argument if it's a bare string at the beginning of the field, and there's code following it. Eg:

  @succ east = @print("You walk east."); @s("travelled","1");

Exception 2: Use @print with one argument if you need leading spaces (such as for ASCII graphics). Eg:

  @desc big M = @print("  /\  /\%c /  \/  \%c/        \");

"if" statements

A @switch function can be used as a multibranched if-statement by setting its first argument to 1, and by setting the arguments that would normally be case values to expressions. Think of it as @switch(1,expr1,block1,expr2,block2,...). For example, translate this C code:

  if (x < 2)
    n = 5;
  else if (y > 12)
    n = 10;
  else
    n = 15;

to:

  @switch(1, @lt(@g("x"),2), @s("n",5),
    @gt(@g("y"),12), @s("n",10), @s("n",15))

"for" statements

There are three ugly ways to do this: via @strloop, via @fieldloop, or via recursion.

Here's the @strloop method. Create a dummy string with as many characters as you want to iterate through the loop, and use a field as your loop variable. For example, this C code:

  for (i = 0; i < 10; i++)
    sprint("%d\n",i);

becomes:

  @s("i",0);
  @strloop("xxxxxxxxxx","x",@print(
    @g("i"), "%c",
    @s("i",@add(@g("i"),1))
  ));

Does that mean that if you want a loop to iterate a hundred times, you need a dummy string that's a hundred characters long? Yes.

If you don't know exactly how many iterations you want, you'll have to make the dummy string as large as it could be, and use @substr to chop it to the proper size. So, if the above example should have n iterations (with maximum value of 10 for n), that gets coded as:

  @s("i",0);
  @strloop(@substr("xxxxxxxxxx",0,@g("n")),"x",@print(
    @g("i"), "%c",
    @s("i",@add(@g("i"),1))
  ));

The @fieldloop and recursive methods of faking a for-loop shall be (currently) left as an exercise. Examine the beer in the Toyshop for a recursive example.


Arrays

Fake an array with a set of fields with the same prefix. For example:

  @field #9999 = month_1 : January
  @field #9999 = month_2 : February
  ...
  @field #9999 = month_12 : December

Use @fieldloop if you don't care what order the fields are processed, eg:

  @fieldloop(9999,"month_",@print("[%v] "))

If your array must be ordered, you'll need to use an ugly "for-loop" to process it:

  @s("i",1);
  @strloop("xxxxxxxxxxxx","x",@print(
    "[", @g(@print("month_",@g("i"))), "] ",
    @s("i",@add(@g("i"),1))
  ));

Function Calls

Function calls are done with either @execute or @call. What's the difference? @execute evaluates a string expression, and @call evaluates a field value. That means that the following two pieces of code do the same thing:

  @execute( @getfield(10900,"myfunction") )
  @call(10900,"myfunction")

Unfortunately, the only way to "pass arguments to a function" or "get a result from a function" is to use fields. Choose a naming convention for these fields and enforce it rigorously. A silly example:

  @field calculator = x_plus_y_equals_z :
    @s("arg1_sum",@g("x")); @s("arg2_sum",@g("y"));
    @call("%!","sum"); @s("z",@g("ret_sum"));
  @field calculator = sum :
    @s( "ret_sum", @add(@g("arg1_sum"),@g("arg2_sum")) );

On the subject of recursion: avoid using recursion if you can. You probably don't really need it; many things written recursively can be rewritten into a standard loop. Note that the above trick to fake argument passing won't work for recursive function calls.

The ifMUD enforces a limit on the number of times @execute or @call is called by one action; the current limit is 64. If you are seeing this error, you must reduce the number of times you call @execute or @call in your code. The workaround is to concatenate all the pieces of code that you want to execute into one giant temporary field, and then @call that field.


Container Objects

You can't really put things inside other things, but this is a pretty close way to fake it.

Examine the southwest exit from the lounge for an example of this sort of trick. [more TODO]


Zones

Everything on the ifMUD, by default, belongs to zone 0. Zone 0 is controlled by the object with dbref #0, a room called the Void. Any exits defined in the Void (such as "xyzzy" or "recap") can be used not just when standing in the Void, but when standing anywhere in zone 0, that is, almost everywhere in the MUD. Also note that any actions on any objects in the Void are also useable anywhere in zone 0. Since you cannot actually visit the Void personally, many of the global commands are listed by the global command "globals".

It is also worth noting that one of the global commands in zone 0 defines the ten standard directions. It is this global exit you are using when you try to go north in a room without a north exit, and see the [There's no obvious exit in that direction.] message, instead of the default [Not a valid command. Try typing help.] message. When you @open an actual north exit, the local north overrides the global north.

You can assign rooms and things to a new zone of your own creation using the @zone command. For example, to add stuff to zone 1000:

  @zone here = #1000
  @zone banana = #1000
  ...etc...

What this means is: 1) in any room in zone 1000, you can use any exits/actions in object #1000, and 2) any object in zone 1000 can't be taken to a room outside zone 1000 (unless it was in a room outside zone 1000 to start with).

Object #1000, of course, controls zone 1000. You must either own object #1000 or object #1000 must be set zoned before you may assign anything to zone 1000. Object #1000 should be either a room or a thing. And don't assign object #1000 to zone 1000! Object #1000 should remain in zone 0.

This also means that zones nest, that there is a zone tree, and that zone 0 is the root zone. It's perfectly legal to have room #3000 in zone 2000, object #2000 in zone 1000, and object #1000 in zone 0. So, in this case, when you type "north" in room #3000, the mud will first look for a north exit/action in room #3000, then in #2000, then in #1000, and then finally in #0, using the first one it finds. In practice, though, you'd probably only define a zone that's a subzone of zone 0, and not worry about the nesting of zones.

Why would you use a zone? Mostly to add global actions to your area that aren't already defined. For example, you might define the "sit;sit on *" action, so that a player can try sitting in every room and on every thing in that zone. Or maybe you want to redefine the global commands, like "xyzzy", so they do something different in your area. You could even cleverly add a "fog;dense fog" action that does nothing, but can be examined in every room, even though it's defined only once. You would normally put all these exits/actions in a room called "My Zone" or somesuch; a room that's not accessible to the public, nor has any normal directional exits that exit that room either.

The other use of zones is to confine things to a set of rooms, usually puzzle-related props in the appropriate puzzle rooms. If you have assigned your magic hula hoop to zone 1234, then when a player holding the hula hoop leaves zone 1234, even by teleporting, he or she will will automatically drop the hula hoop before leaving the zone.

I should also note, that (as I write this) there is no @unzone command, nor any way to clear an object's zone field. The current workaround is to set the zone to 0, eg:

  @zone mything = #0