Mocha: Ishar MUD Online Creation Language

Advanced Material


Mocha allows a creator to define special reactions and behaviors for rooms, mobiles, and objects. Simple examples include rooms in which players are unable to see, talking mobiles, and objects which change form when a certain spell is cast upon them. Each behavior is written into a named document called a function. The name of the function is used within the virtual world to identify the behavior described by the function. Mocha functions are further grouped into libraries. One library exists for each area in the MUD, and other libraries can be added by the gods. The latter libraries are named for the type of behaviors they provide: a "trap" library for generic trap functions, an "example" library for sample Mocha code, etc.


Basic material



Intermediate material

Each Mocha function is associated with rooms, objects, or mobiles. Once someone writes a function, any creator can (with permission) use the name of the function to mark items of the appropriate type (e.g., rooms for a room function) as having that particular behavior. Mocha functions are generally customizable, so that a single function can manage many similar behaviors.

Mocha works by allowing a creator to define special responses to events in the virtual world. When certain things happen, the MUD signals an event by telling everything that might want to know about the event. For example, when a player types, "say hello," the MUD tells the room, any mobiles in the room, any objects worn or carried by the player, and any objects in the room that the player is trying to say hello. Normally, none of these things care about such an event, and the MUD handles it in the usual fashion, in this case by telling everyone in the room what was said. If, by chance, a nearby mobile responds to being greeted, that mobile might react to the event by preparing to speak. Responding to a specific event is called handling the event, and the section of the function which describes the way an item responds is called a handler for that event.

The MUD signals a vast number of events, including nearly every command in the game as well as a number of events with more context. Each event has a name, used by a creator to mark the corresponding handler in a function. The say command, for example, uses the name command_say. Other command event names have a similar form: they begin with "command_" and end in the name of the command. Many other events begin with "do_."

When a player quaffs a potion, for example, the MUD first signals a command_quaff event to everything nearby. If nothing handles this event, the server figures out which potion is to be quaffed and signals a do_quaff event to the potion. Since a potion is most likely to exhibit any unusual behavior only when quaffed, most creators prefer to provide a handler for the do_quaff event rather than for the command_quaff event when writing a potion function. In the typical case, a handler for command_quaff must first check whether the player intends to quaff the potion or something else, then check whether the player is in a proper position to quaff the potion. By handling do_quaff instead, the creator allows the MUD to take care of these considerations and needs only to specify the actual effects of quaffing the potion.

When the MUD calls a handler, it passes along several pieces of information about the event. Each piece is called a parameter of the handler, and together, along with their order, they form the handler's interface. A handler for a typical command event, for example, receives the item with the special behavior, the person issuing the command, and the arguments passed to the command. If the player Asylenth types "get 2.key bag," a room function's handler for command_get would receive a reference to the room, a reference to Asylenth, and a package of information including the strings "2.key" and "bag." The handler can then use these pieces of information to make decisions and define behavior.

When a handler is finished, it must return a piece of information to the MUD to tell the MUD what to do next. A handler can return one of three distinct values. One value tells the MUD to continue with the usual processing of the event; a second tells the MUD to stop immediately; and the third tells the MUD that a player associated with the event has been killed.

At this point, we are ready to start talking about the actual syntax of Mocha, or how the text of a function must appear.


Mocha Types

Types include: The type action is followed by an action specifier: a set of characters surrounded by brackets. Actions are used with the show_action routine, and the action specifier depends on the way the action is to be used.
P -- a person appears in the show_action call
V -- a victim appears in the show_action call
O -- an object appears in the show_action call
s -- a string appears in the show_action call
d -- an integer appears in the show_action call

P, V, and O should appear at the start of the specifier. d and s may appear more than once, but the order must match the order of the "%d" (integer) and "%s" (string) escapes in the action string.


The List Type

In addition to the basic types, Mocha permits lists of most basic types in the data definition for a function. For example, you might use an integer list to record the virtual numbers of items sold in a shop or an action[P] list to hold the lines spoken by a mobile.

Constant lists are specified as a number of items separated by commas and placed in braces (e.g., {1, 2, 3}). Unlike the similar spell constant value, which takes the form {<level>, "<spell name>"}, constant list elements must all have the same type.

void <list>.append (<list type> value)
adds a value to the end of a list

void <list>.delete (<list type> value)
removes the first instance of a specified value from a list, or does nothing if the list does not contain the value; use remove if you know the position, as delete must perform a search

void <list>.destroy
destroy empties a list

boolean <list>.has_member (<list type> value)
checks whether a value is in a list or not

integer <list>.index (<list type> value)
finds the position of a given value, or returns no_match if the value is not in the list

void <list>.insert (integer position, <list type> value)
pushes a value into a list at a particular position; the position must be between 1 and the length of the list + 1 (which is the same as append)

integer <list>.length
returns the number of elements in a list

<list type> <list>.member (integer position)
returns the member of a list at a given position

void <list>.remove (integer position)
removes the member of a list at a given position

void <list>.replace (<list type> old_value, <list type> new_value)
replaces the first instance of old_value in a list with new_value, or does nothing if the old_value does not appear in the list; if you know the position of the element to be replaced, use set instead, as replace must perform a search

void <list>.set (integer position, <list type> value)
replaces the element at a particular position in a list with value

All list positions are 1-based. Any attempt to use positions outside of the list bounds (with the exception of insert, as noted above) results in a runtime error. Lists are currently limited to 100 elements, and trying to grow them beyond that limit also results in a runtime error.


The Body of a Handler

After the signature, a creator must write the body of the handler. Mocha makes use of a syntax and style much like the programming language C with a few pieces drawn from the language C++. The body of a handler consists of some number of variable declarations followed by some number of statements, all surrounded by a pair of braces.

Ignoring the variable declarations for now, let us write an extremely simple example of a handler. Let's say that we want a certain mobile to do twice the normal amount of damage plus one. Forget for now the fact that we might simply change the mobile's damage dice rather than writing a function--we'll make the behavior more complex later. To intervene when the mobile makes an attack, we must catch the do_attack event, which has the following signature:

special
do_attack (person mob, person vict, object wpn, integer type, integer amt,
           integer quality)
For this handler, mob refers to the mobile; vict refers to the person being attacked; wpn refers to the weapon being used in the attack and has the value none when the mobile is unarmed; type is the type of damage being done (drawn from a list of values representing different types); amt is the amount of damage, with 0 indicating a miss, and quality is a measure of the success of the attack. Unlike most handler parameters, several of do_attack's parameter are returned to the MUD, allowing a handler to change the amount, type, and quality of damage while allowing the MUD to take care of sending the damage messages and killing the victim if appropriate.

For our purposes, the only important value is the amount of damage, which we intend to double, then add one. The following handler accomplishes the task:

special
do_attack (person mob, person vict, object wpn, integer type, integer amt,
           integer quality)
{
    amt = 2 * amt + 1;

    return ignored;
}
The body of our handler consists of two statements and a blank line. Mocha ignores white space (spaces, tabs, and carriage returns) except in strings and names. We could not, for example, have written amt as "a m t."

The first statement is an assignment statement. The expression on the right hand of the equal sign is assigned to the parameter on the left hand side. In this case, Mocha finds the number represented by amt, multiplies it by two, adds one, and pushes it back into the amt parameter. Note that an assignment is irrevocable--we can no longer read the original value of the parameter on the left hand side (although in this case we could recompute it fairly easily using the current value).

The second statement returns a value to the MUD. In this case, we return the value ignored, telling the MUD that it should go ahead and process the attack with the altered damage value. Each statement is followed by a semicolon. If we were to test this handler in the MUD, we would find (perhaps to our surprise) that mobiles using this function never miss an attack. Even when we make the odds of hitting incredibly low, they manage to do a single point of damage every time they try to hit. Why is this so?

Looking back to the definition of the do_attack event, we see that when the mobile misses an attack, the MUD still signals the event, but sends 0 as the amount of damage. Our handler blindly doubles the 0 and adds 1, resulting in a hit for a single point of damage. To prevent these specious hits, we must add a conditional statement:

special
do_attack (person mob, person vict, object wpn, integer type, integer amt,
           integer quality)
{
    if (amt > 0)               // This is the new line.
        amt = 2 * amt + 1;

    return ignored;
}
The conditional statement appears in the first line of the handler body; to the right of the condition is a C++-style comment: when Mocha sees two adjacent slashes outside of a string, it ignores the rest of the line. A conditional statement begins with the keyword if, followed by a condition (a piece of information with type boolean), and completed by another statement. Note that no semicolon separates the first two parts of the conditional statement from the statement making up the third part. Also note that the latter statement is indented to indicate that it is only executed when the condition is true.

The condition in this case is that the amount of damage is greater than zero--that is, that the mobile has actually hit the victim. If the mobile misses, amt represents the value zero and the condition evaluates to false. When the condition is false, the statement following the condition is ignored. As we expect, the mobile hits at a normal rate after our change.


Variable Declarations

Variable declarations are very much like parameters: each specifies a type and a name and represents a piece of information that will be used with

Routines

Routines are like handlers in form, but do not correspond to any event. Instead, routines are used to perform tasks for handlers. If, for example, I want the same effect for quaffing or drinking from a container, I can define a single routine, perhaps called "drink_or_quaff," and have both the do_quaff and the do_drink handlers make use of that routine.

The data definition for a function specifies the information used by the function during its operation. A room that remembers the person who last occupied it, for example, would do so by declaring a space for that information in its data definition. The data definition is divided into four sections, each of which has a distinct meaning. The first section defines constant values for use in the handlers and routines. In many cases, functions become more readable when symbolic names such as "attack_delay" replace numeric values, such as 18. The reason is hopefully obvious. The second section defines public values: values which can be overridden by adding extra descriptions to items making use of the function. I might, for example, declare "attack_delay" as a public variable rather than a constant, setting the default value to 18. If someone else uses my function and wants whatever is attacking to attack more quickly, they can add an extra description called ".ATTACK_DELAY" and set the text to a smaller number. One copy of attack_delay exists for each virtual number using the function, so other people's selection of options will not affect my items. The third section defines private data: data which cannot be overridden by extra descriptions, but which also create only one copy per virtual number. A group of mobiles with a shared group of enemies might make use of private information. The fourth section defines the data needed for each instance of an item. Since only one room with a given virtual number exists, this section does not appear in room functions. Instance data might be used for a sac of poison that must remember who drank it (to apply damage over time); clearly such a function with only one place to store the name of the drinker would not work.


Mocha is a little like C, a little like C++, and a lot like neither.

With Mocha, a creator can attach individual functionality to any mobile, object, or room. Functions can be further customized through the use of extra descriptions, allowing each function to be used with a variety of items.

Each function in Mocha is given a unique name and is stored in a file with that name. Files are then further grouped into libraries: one library exists for each area in the MUD; other libraries can be added by the gods and might include, for example, a "trap" library for generic trap functions.

A Mocha file consists of a data description and a number of event handlers. An event handler is called whenever that particular event occurs. The "command_kill" event, for example, is called whenever someone attempts to attack another person in the presence of the item using the function in which the handler is defined.

The data description defines and names the variables used for the event handlers in the file. The description can instead name another Mocha file, in which case all of the event handlers and data are inherited from the named file. Individual event handlers can then be overridden, allowing a creator to slightly alter an existing function without rewriting it entirely.


The Data Definition

<function type> {
        int num_tries = 3;
    public:
        action[PVd] to_victim = "@n strikes you %d times!\n";
    private:
        integer max_uses = 4;
    instance:
        person victim;
};
The function type defines the type of the function and must be person, object, or room.

The data definition includes four possible sections which must be defined in the correct order. The first is constants, which allows the creator to use symbolic names for values. In the example above, the name "num_tries" has been assigned the integer value 3. Whenever "num_tries" is encountered in the event handlers, it is treated as the integer value 3. The second section, from "public:" to "private:" is a list of variables unique to the item type (e.g., having a single copy for each type of mobile rather than a copy for each instance of the mobile). The value of a public variables is automatically overridden by extra descriptions. If, in the example, the item had an extra description with keyword ".TO_VICTIM," the string used for that item would be taken from the extra description rather than the default shown above. The third section of the data definition, from "private:" to "instance:," is again related to types of items rather than individual items, but cannot be overridden by extra descriptions. The last section, after the "instance:" marker, is a list of variables created for each instance of an item. Were the definition above used with a mobile, each instance of the mobile would have its own victim.

A special construction in the public section allows a creator to define symbolic names for a particular integer variable. The variable should be initialized with "one_of { <value name 1>, <value name 2>, ...}." The names are then defined as constants for the event handlers, and the default value for the variable is the first value mentioned in the one_of construction.

base <base function>
This form derives the function type and data definition from the file <base function>. All event handlers are also inherited, but can be overridden by simply redefining them later in the file.


Built-in Operations

  • Arithmetic operators
    +
    plus
    -
    minus
    *
    times
    /
    divide
    %
    modulo

  • Comparison operators
    ==
    equality
    !=
    inequality
    <
    less than
    <=
    less than or equal
    >=
    greater than or equal
    >
    greater than

  • Assignment operators
    =
    assign
    +=
    add and assign
    -=
    subtract and assign
    *=
    multiply and assign
    /=
    divide and assign
    %=
    take modulo and assign
    &=
    bitwise and and assign
    |=
    bitwise or and assign
    ^=
    bitwise xor and assign
    ++
    increment and assign (pre- or post-)
    --
    decrement and assign (pre- or post-)

  • Logical operators
    &&
    logical and
    ||
    logical or
    ^^
    logical xor
    !
    logical not

  • Bitwise operators
    &
    bitwise and
    |
    bitwise or
    ^
    bitwise xor
    ~
    bitwise not

  • Conditional operator
    ? :
    conditional

Arithmetic, assignment, and bitwise operators require integer arguments and return integer results. Comparison operators require two arguments of the same type and return boolean results; ordering comparisons (all but equality and inequality) require integer arguments. Logical operators require boolean arguments and return boolean results. The conditional operator requires a boolean condition and results of the same type. Note that these operators do allow for implicit casts---one can compare an entity with a person, for example.

Mocha Statements

Empty
;
The empty statement does nothing.

Expression
<expression>;
Expression statements include calls to routines, assignments, and any other expression evaluations.

Compound
{
    <zero or more statements>
}
A single statement can always be replaced by a group of statements surrounded by braces.

Conditional
if (<boolean expression>)
    <statement executed when the expression evaluates to true>
else
    <statement executed when the expression evaluates to false>
The conditional statement first evaluates the expression in parentheses, then executes one of two statements depending on whether the expression evaluates to true or false. The else and second statement are optional.

Return
return;
return <value>;
Stops execution of the current handler or routine and returns a value to the caller (the MUD is the caller for any handler). The first form is used only for routines with return type void. The second form is used in any other case, and the type of the value must match the return type of the handler or routine.

Repeat loop
repeat (<integer expression>)
    <loop body statement>
Repeat the body of the loop up to 100 times.

Content loops
for each <object/person variable> in (<expression>)
    <loop body statement>

for each <object/person variable> on (<expression>)
    <loop body statement>

for visible <object variable> in (<expression>)
    <loop body statement>

for visible <object variable> on (<expression>)
    <loop body statement>
Repeat the body of the loop for each object or person with the specified relation to another object, person, or room. each includes all nested interior levels, while visible includes only those items at the specified level or stacked above that level. For the purposes of Mocha, a person's inventory is considered to be in them, and a person's equipment is considered to be on them.

Player loop
for each player <person variable>
    <loop body statement>
Repeat the body of the loop for each player in the game. Note that some players might not be in a state where they should hear ...

Connection loop
for each connection <person variable>
    <loop body statement>
Repeat the body of the loop for each connection to the MUD. Note that some of the players might not be in a state where they can hear banter in the MUD. Check with the is_hearing member routine, which returns a boolean.

Break
break;
Terminate a loop or exit from a switch statement case immediately.

Continue
continue;
Terminate the current iteration of a loop and continue with the next iteration.

Switch
switch (<integer expression>>) {
    1, 3 {
        <zero or more statements>
    }
    default {
        <zero or more statements>
    }
}
To handle multiple cases, a creator can use the "switch" command in place of multiple if-else commands. Switch takes an integer expression argument and executes the case corresponding to the value of the expression. Cases must be labeled with one or more constant integer values or the special label "default," which handles all values (and must appear only on the last case if it appears). The syntax in Mocha is as follows:


Built-in Functions

show_action show_action is really more of a function than a command, but since it takes a variable number of arguments, we describe it here. The first argument to show_action is an action variable. The next few arguments specify the recipients of the action message and should be chosen from the following:

After the recipient arguments, if 'P' appears in the action specifier, then the next argument is a player (an expression of type person). If 'V' appears in the action specifier, the next argument is the victim (also an expression of type person). Next is an object provided that 'O' appears in the specifier. The remainder of the arguments are integers or strings in the order given by the action specifier.

Also see the room_echo function.

min, max, and range

min, max, and range are integer functions returning respectively the minimum of two expressions, the maximum of two expressions, and the value of an expression bounded by two other expressions.


The Event Handlers

The most common event handler is the command, which has the following prototype:

special
command_<command_name> (<func_type> item, person pc, arguments args)

Here the <command_name> might be anything from kneel to follow to kill. The pc is the person who issued the command, the item is the item carrying the function (room, mobile, or object), and the arguments are the additional arguments to the command.

special
do_attack (person mob, person pc, integer type, integer amt)

The mob has attacked the pc and inflicted damage. Type and amt can be changed.


Valid Values, Exceptions, and Hidden Entities

Before a handler calls a function, it should check that all of the values to be passed to the function are valid. Some functions allow the value none to be passed in place of some arguments, while others do not. Other functions require that an integer parameter appear in a list of constants. All functions reject entities which have been destroyed. If a function rejects one of the arguments passed by a handler, it signals an exception and stops executing the handler. A message is sent to the author of the function and the last editor of the function describing the problem. One message will be sent each time the handler fires and signals an exception. To prevent exceptions, a handler should check the validity of arguments using the following functions:
boolean ent.valid;
Returns true if the entity ent is valid (not destroyed and not none), false otherwise.

boolean evt.valid;
Returns true if the event evt is valid (not cancelled and not none), false otherwise.

boolean ex.valid;
Returns true if the exit ex is valid (not destroyed and not none), false otherwise.

boolean spl.valid;
Returns true if the spell spl is valid (not a non-existent spell), false otherwise.

In addition to checking for invalid items, a handler may also want to check whether or not an entity should be touched to prevent annoying immortals with "nohassle" flags. While the only way for a handler to affect such an entity is for the entity to initiate some action in the presence of the handler (i.e., functions such as find_person will never return someone with a nohassle flag), the person might still prefer not to be subject to the handler's antics. The following functions help a handler to make such decisions:

boolean ent.usable;
Returns true if the handler has the permission of the entity ent (or ent's owner) to affect it, otherwise returns false.

boolean nohassled (person pc);
A special form of usable for persons--returns the same as given above.


The MUD Function Interface


Code Examples

suck_blood -- a specialized mobile attack
troll_leader -- a speaking mobile


Keywords: