Programming for CoffeeMud 5.3

CoffeeMud logo

Overview

The purpose of this document is to assist those who wish to add custom Items, MOBs, Behaviors, Properties, or other objects to CoffeeMud. The reader should be familiar with Java programming, and should be experienced with writing and compiling Java classes. The object oriented notions of class inheritance and polymorphism, as well as the Java constructs of interfaces should be at least vaguely familiar to you before attempting to build classes for CoffeeMud. Also, it is expected that all of the ideas presented in the Archons Guide and Game Builders Guide are completely familiar. The difference between a GenItem and a StdItem, or a GenMob and a GenPostman will not be explained in this document.

It is not expected that someone would wish to dive in and make wholesale changes to the CoffeeMud system right away, but is more likely wanting to fill in a functional gap in the system for their own needs. For this reason, this document is not organized as a comprehensive guide to programming CoffeeMud. Instead, it is designed to be a quick reference for those who wish to create the spot MOB, Behavior, Item, or Property for use on their maps.

With this in mind then, let's start out with some brief development instructions, and then and in no particular order, discuss the several essential object types in CoffeeMud are presented.

Recompiling

Rebuilding CoffeeMud

In Microsoft Windows

  • Go to your coffeemud directory and edit the make.bat, the first line will be something like: SET JAVACPATH= C:\jdk1.5.0_05\bin\javac This might be different for you depending on where your java development package is installed.

  • Save the bat file.

  • Run the bat file by double clicking on it. This will compile the mud, making all the .class files.

In Unix

  • Go to your coffeemud directory and edit the makeUNIX.sh, the first line will be something like: Java_Home=/home/knoppix/j2sdk1.4.2_16 This might be different for you depending on where your java development package is installed.

  • Save the shell script.

  • Issue this command: chmod 755 makeUNIX.sh

  • Execute the shell script. This will compile the mud, making all the .class files.

Introducing External Components

If you perused the coffeemud.ini file as mentioned in the Installation Guide, you may have noticed and wondered about the section at the end which lists the default load paths for the several CoffeeMud objects.

By default, the CoffeeMud engine dynamically loads the vast majority of its object code at boot time by referring to the paths specified in the coffeemud.ini file. The value %DEFAULT% is always used as a substitute for the default CoffeeMud object path. For instance, if you installed CoffeeMud at "C:\CoffeeMud\", then "BEHAVIORS=%DEFAULT%" in the ini file would load "C:\CoffeeMud\com\planet_ink\coffee_mud\Behaviors\*.class" into its behavior set.

This default object boot paths may be removed or added-to using semicolon delimited paths, or even replaced with your own object boot directories. In fact, when adding objects to your CoffeeMud boot sequence, it is recommended that you place your objects in a separate directory path inside your CoffeeMud folder and add its path to the coffeemud.ini file under the proper setting. The order in which you place multiple paths in a single entry is also significant, as the CoffeeMud ClassLoader will load the files in the order in which they appear listed. For instance:

MOBS=%DEFAULT%;/resources/examples/MyClass.class;/resources/otherclasses

Will cause the default CoffeeMud versions of the mob classes to be loaded first, followed by MyClass.class, followed by all the class files in the resources/otherclasses folder in your CoffeeMud package. Also notice that the ClassLoader follows the rules of the CMFS described in the Archons Guide, meaning that the forward slash is always the proper path separator, and that no folder outside of your CoffeeMud package may be referenced. Also bear in mind that case is sensitive when naming Java class files, even in these boot paths.

When writing these custom classes for your special object boot directory(s), it is important to keep a number of things in mind:

  • Do not mix your object types in the same directory! Never try to boot custom items from the same directory from which you boot your custom mobs. It will only confuse you and CoffeeMud.

  • Java Packaging is irrelevant, you may package your classes or not.

  • Implement or extend a class that implements the proper interfaces. If you are coding mobs, this would mean the MOB interface. If you are coding locales, the Room interface, etc, etc. See the section on the object type you are coding for the proper interface to implement. You may get around this requirement by extending one of the base classes, such as StdItem, StdMOB, StdContainer, StdRoom, StdAbility, GenMob, GenItem, GenContainer, etc.

  • Make sure the ID() method in your classes always matches the name of your class. You will understand this better as you reference the object sections below.

  • Try to make the name() methods in your classes return name values unique among all objects, especially objects of that type. This is not a hard fast rule, and breaking it will not cause malfunction in the system, but breaking this rule WILL make writing help files impossible.

  • Class files loaded directly in the CoffeeMud classpath can not be reloaded at run-time using the UNLOAD and LOAD commands. If you plan on making changes to your classes during run-time, place them in their own directories.

  • As a general rule, you may import any "interfaces.*" packages in the base CoffeeMud structure, any core Java packages, the CoffeeMud "core.*" package, and any single base CoffeeMud class you may be extending. Do not import more than that. Use the CMClass getter methods if you need to create new instances of CoffeeMud classes, and use the CMLib methods to access her code libraries.

Complete Default Import List

import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import java.io.IOException;
import java.util.*;
Text

Text

Before we get started with objects, needs must the topic of text display be covered. Throughout the system you will see text being sent to the user. Since a mud is a text producing engine, this should be no great surprise. However, within that text you will often see different kinds of codes and tags which affect the output. For instance, consider the following lines:

msg=CMClass.newMsg(mob,target,this,affectType,"<S-NAME> reach(es) for <T-NAMESELF>.");
mob.location().show(mob,null,CMMsg.MSG_OK_ACTION,"<S-NAME> regain(s) <S-HIS-HER> feet.");

Focusing only on the text for a moment, you will notice that special tags are used to designate a player name, or the name of the target of a spell. You will also notice that (s) and (es) is used to modify the proper form of a verb. These are key features of the CoffeeMud text engine. Here is a more complete list of available tags:

<S-HIS-HER> Outputs 'Your' if Observer=Source, otherwise 'His'/'Her'.
<S-HIM-HER> Outputs 'You' if Observer=Source, otherwise 'Him'/'Her'.
<S-NAME> Outputs 'You' if Observer=Source, otherwise the Name.
<S-NAMESELF> Outputs 'Yourself' if Observer=Source, otherwise the Name
<S-NAMENOART> Outputs 'You' if Observer=Source, otherwise the Name minus any prefix articles (a, an, some, etc..).
<S-HE-SHE> Outputs 'You' if Observer=Source, otherwise 'He'/'She'
<S-SIRMADAM> Outputs 'Sir'/'Madam'
<S-IS-ARE> Outputs 'Are' if Observer=Source, otherwise 'Is'.
<S-HAS-HAVE> Outputs 'Have' if Observer=Source, otherwise 'Has'.
<S-YOUPOSS> Outputs 'Your' if Observer=Source, otherwise the Name`s
<S-HIM-HERSELF> Outputs 'Yourself' if Observer=Source, otherwise the 'Himself'/'Herself'
<S-HIS-HERSELF> Outputs 'Yourself' if Observer=Source, otherwise the 'Hisself'/'Herself'
<T-HIS-HER> Outputs 'You' if Observer=Target, otherwise 'His'/'Her'.
<T-HIM-HER> Outputs 'You' if Observer=Target, otherwise 'Him'/'Her'.
<T-NAME> Outputs 'You' if Observer=Target, otherwise the Name.
<T-NAMESELF> Outputs 'Yourself' if Observer=Target, otherwise the Name
<T-NAMENOART> Outputs 'You' if Observer=Target, otherwise the Name minus any prefix articles (a, an, some, etc..).
<T-HE-SHE> Outputs 'You' if Observer=Target, otherwise 'He'/'She'
<T-SIRMADAM> Outputs 'Sir'/'Madam'
<T-IS-ARE> Outputs 'Are' if Observer=Target, otherwise 'Is'.
<T-HAS-HAVE> Outputs 'Have' if Observer=Target, otherwise 'Has'.
<T-YOUPOSS> Outputs 'Your' if Observer=Target, otherwise the Name with an '`s'
<T-HIM-HERSELF> Outputs 'Yourself' if Observer=Source, otherwise the 'Himself'/'Herself'
<T-HIS-HERSELF> Outputs 'Yourself' if Observer=Source, otherwise the 'Hisself'/'Herself'

Occasionally, you will find color/font codes embedded in system strings. For instance:

msg.append("^!You are thirsty.^?\n\r");

These codes are as follows:

^N Normal
^! Bold
^H Highlight
^_ Underline
^* Blink
^/ Italics
^. Reset (turns off reverse)
^^ Generates an untranslated "^" character
^? Restores previous color
^f You-Fight
^e Fight-You
^F Fight
^S Spell
^E Emote
^T Talk
^Q Channel Background
^q Channel Foreground
^x Important message 1
^X Important message 2
^Z Important message 3
^O Room Title
^L Room Description
^J Weather
^D Direction
^d Door
^I Item
^M MOB
^U Unexplored Direction
^u Unexplored Door
^w White
^g Green
^b Blue
^r Red
^y Yellow
^c Cyan
^p Purple
^W Dark White
^G Dark Green
^B Dark Blue
^R Dark Red
^Y Dark Yellow
^C Dark Cyan
^P Dark Purple
^< < character. Used for MXP tags only.
^> > character. Used for MXP tags only.
^& & character. Used for MXP tags only.

As you might have guessed, it is preferred that the system colors (the last 16 codes) be used sparingly, in favor of the more customizable codes above.

Mozilla Javascript

JavaScripting:

JavaScript is an interpreted scripting language which is used in various parts of CoffeeMud. The CoffeeMud engine integrates the Rhino Javascript interpretor package from Mozilla in such places as the Scriptable behavior, the JRun command, the ClassLoader, the Quest engine, and the web server.

In lieu of a complete write up on the syntax of this language, it is suggested that you read the documentation available from the authors of the interpretor here: http://www.mozilla.org/js/ and http://www.mozilla.org/rhino/.

If you are familiar with writing Javascript for web browsers, there are several differences you will need to adjust to when using Javascript in CoffeeMud. A minor difference is that the Rhino interpretor requires that all variables be declared before use. A more important difference is that Javascript Strings are not the same as Java String objects, and that confusing them can lead to errors. To get around this problem, all of the implementations of Javascript in CoffeeMud, with the exception of the ClassLoader, Javascript in CoffeeMud, with the exception of the ClassLoader, provide a special method to convert Javascript strings into Java Strings before passing them to Java methods or objects which will require them. An example is below:

var javascriptstring=' this is a javascript string ';
var javastring=toJavaString(javascriptstring);
// and now javastring is a real-live Java-compliant and Java-friendly string

When writing Java classes in JavaScript, however, this method is only available through the CMLib object in the core package. This is how you would access the toJavaString() method from a class written in JavaScript:

var javascriptstring=' this is a javascript string ';
var javastring=Packages.com.planet_ink.coffee_mud.core.CMLib.toJavaString(javascriptstring);
// and now javastring is a real-live Java-compliant and Java-friendly string

The most important difference between coding Javascript for CoffeeMud and for browsers is that there is no HTML DOM (Document Object Model), and therefore several of the libraries you are used to are probably missing, such as Math. For this reason, it is necessary for you to learn the CoffeeMud object packages in order to get access to useful data and useful libraries. And now you understand why the JavaScripting notes are kept in the Programming guide. :)

To access the CoffeeMud object packages, you will need to make use of the Packages object to reference external packages. So long as the imported objects are in your CoffeeMud classpath, they can be accessed and used. For instance, to use the CoffeeMud pow(x,y) function in CMParms.java:

var lib=Packages.com.planet_ink.coffee_mud.core.CMLib;
// the above creates a reference to the CoffeeMud Library as a shortcut
var value=lib.math().pow(4,2);
// now we can access the math() library from our shortcut.

Depending upon the context from which your script runs (the Scriptable behavior, JRun command, the Quest engine, or the http/web server), certain other objects are made available to assist scripts in properly interacting with their environment. When writing Java classes in JavaScript for the ClassLoader, however, you must always use the Packages.com.planet_ink.coffee_mud.core.CMLib reference to access special methods such as the toJavaString method discussed above.

In the Scriptable behavior, several methods are made available to access objects which are related to the event which triggered the scripted code. These methods include MOB source(), Environmental target(), Environmental host(), Item item1(), Item item2(), String message(), and MOB monster(). The JRun command provides the methods MOB mob(), int numParms(), String getParm(int i), and String getParms(). The web server makes the ExternalHTTPRequests request() object available, as well as the method void write(String s). The quest engine makes the current running Quest object available from the method Quest quest() and the current state of the quest setup script available in a custom QuestState object referencing method called QuestState setupState()

The last piece of general information about JavaScript in CoffeeMud concerns writing Java classes for the CoffeeMud ClassLoader. Any JavaScript file *.js included in the ClassLoader boot paths (see the previous section) will be loaded and treated just like any Java compiled *.class file. The JavaScript file would be parsed, compiled, and loaded at boot time. For the most part, writing Java Classes in JavaScript is extremely similar to writing Java Classes in Java. Base classes may be extended (using the special CoffeeMud //extends command in your JavaScript), interfaces may be implemented (using the special CoffeeMud //implements command in your JavaScript), super class variables and methods may be accessed (using the this.variableName and this.super$methodname() syntax), and super class methods may be overridden by JavaScript functions of the same name and number of parameters. Class files written in Javascript *.js files may also be loaded and unloaded at runtime using the LOAD and UNLOAD Archon commands, which gives them a step up on native Java classes in the JVM classpath.

Writing your first JavaScript

Below is an example of a Java Class written in JavaScript. Examples of Embedded JavaScript in CoffeeMud Virtual Pages (cmvp) web files can be found in the Web Server Guide. Examples of Embedded JavaScript in a Scriptable MOBPROG script can be found in the Scripting Guide.

Our Java Class example is called GenLemming.js. It is a sample MOB class to demonstrate extending the GenMob class to create a type of modifiable mob for your maps. In this example, we add functionality to make all mobs in the world created from the GenLemming base suicidal. To use this class, save the code somewhere in our CoffeeMud folder under the name "GenLemming.js", and add an object path reference to it in the MOBS entry in your coffeemud.ini file, as described in section one.

//extends com.planet_ink.coffee_mud.MOBS.GenMob

function ID(){return "GenLemming";}

var lib=Packages.com.planet_ink.coffee_mud.core.CMLib;

The first lines of our class include the special //extends command which informs the CoffeeMud ClassLoader that this JavaScript class will extend GenMob, thereby inhereting all functionality of the maleable GenMob.

The ID() method is required in all CoffeeMud classes. It must be the simple name of the class, and must match the name of the JavaScript file containing it. For example, GenLemming.js contains class GenLemming and returns an ID of "GenLemming". They all match exactly.

Lastly, we define the variable "lib" to act as a shortcut to the CoffeeMud core Libraries.

Now, moving on; since we don't have the ability to write constructors in JavaScript, any initial fields we need to set whenever a new instance of our class is created must be done in the CoffeeMud newInstance() method as shown here:

function newInstance()
{
    var lemm=this.super$newInstance();
    lemm.setName("a generic lemming");
    lemm.setDisplayText("a generic lemming is waiting to commit suicide");
    return lemm;
}

There are several interesting points to make here. One is to notice that the function has no explicit return type, which is part of the JavaScript standard of being "weakly typed". Also notice the syntax for calling the SuperClass version of the newInstance() method -- this.super$newInstance(). This is very different from Java syntax and should be noted.

And now we move on to overriding our first GenMob method, tick.

var countdown = 10;

function tick( host, tickID )
{
  if( !this.amDead() )
  {
    countdown--;
    if( countdown <= 0 )
    {
      lib.combat().postDeath( null, this, null );
      countdown = 10;
    }
  }
  return this.super$tick( host, tickID );
}
     

The public boolean tick(Tickable host, int tickID) method from the standard MOB interface is designed to be called every CoffeeMud tick (about 4 seconds). Now, the tick method in StdMOB, which is extended by GenMob, handles things like recovering hit points, automatic combat rounds, and other important periodic activities. It is explained further in Core Topic 3.

For our GenLemming, we create a variable to count down the ticks from 10 to 0. When the countdown variable reaches 0, we call the postDeath(MOB killer, MOB killed, CMMsg msg) method, which is part of the CombatLibrary in the core libraries. "lib" is the variable we defined above as a shortcut to our core libraries. The last thing we do is return control to the SuperClass version of the tick method.

Now we'll get creative and implement a message previewer (see the Core Topic 1 below for more information on message previewing and handling). This method will be called when any event happens in the same room as the GenLemming mob. We will use this fact to look for, capture, and modify the message string which will inform the room of our impending death. Since it is the previewing method, it will be called BEFORE the activity actually takes place, giving us a chance to make our modifications before anyone actually sees the message strings.

function okMessage(host,msg)
{
  if( ( msg.isSource( this ) )
      &&( msg.isOthers( "DEATH" ) )
      &&( msg.othersMessage() != null )
    )
  {
    msg.setOthersMessage("<S-NAME> jumps off a cliff!!!");
  }

  return this.super$okMessage(host,msg);
}

In our message previewing method, we will check every message that comes our way, acting only if this particular GenLemming is the source of the message, that he appears to be dying to others in the room ( isOthers( "DEATH" ) ) and that the message being given to others in the room is a non-null string. In these conditions, we modify the message which others in the room see. When our condition is not met, we return control to the SuperClass-GenMob version of okMessage.

Getting the Message

Core Topic 1: Getting the Message:

CoffeeMud is essentially a distributed message passing and handling system, where the actions and events that occur in the system are represented as messages (Common.interfaces.CMMsg) which are then previewed, modified, cancelled, and/or reacted to by handlers. Understanding this idea is key to fully understanding how CoffeeMud really works, so let's take a second and peruse this concept in more detail.

Messages in CoffeeMud, at least as we are talking about them here, always represent Events. Events such as a mob picking up an item, swinging a sword at an opponent, taking damage from a fireball, or getting pricked by a poisonous needle. These events can never actually occur in CoffeeMud unless a proper message is generated for them first. These messages, in the code, implement the interface Common.interfaces.CMMsg, and are typically an instance of the class Common.DefaultMessage.

Messages are created at the moment that the event needs to occur. This moment can be triggered by the player entering a command into their telnet client and pressing Enter. It can also by triggered by the mindless algorithms which animate the mobs. Either way, when the moment has come, a message is created, and it looks like this:

CMMsg msg = CMClass.getMsg(mob, targetMOB, this,
     CMMsg.MSG_CAST_ATTACK_VERBAL_SPELL,"^S<S-NAME> invoke a spell at <T-NAME>s feet..^?",
     CMMsg.MSG_CAST_ATTACK_VERBAL_SPELL,"^S<S-NAME> invoke(s) a spell at your feet.^?",
     CMMsg.MSG_CAST_ATTACK_VERBAL_SPELL,"^S<S-NAME> invokes a spell at <T-NAME>s feet.^?"
     );

The above message was taken from the code for the Grease spell, which calls the core.CMClass.getMsg method to construct a CMMsg object. Constructing the CMMsg object does not actually make anything happen, but it is the vital first step. The message we constructed here, in this case, utilizes every major component of a message. These components are, in order:

  • Source

    The source of any message must always be a valid reference to an instance of the MOB interface. In short, all events that occur in the system are a direct result of the activity of a MOB. This is on the theory that the universe is controlled and governed by sentience. In the extremely rare instances where a mob is not readily available to provide a message source, one should be instantiated -- even if it is just a blank, new StdMOB.

  • Target

    The target of a message may be null, or any valid reference to an instance of the Environmental interface, which includes Items, MOBs, Rooms, Exits, etc. The type and context of message you wish to generate will typically tell you intuitively whether the source is doing something to someone or something else, or is acting independently. This is usually another mob or an item, but you will find examples of all kinds of targets in the code.

  • Tool

    The tool of a message may be null, or any valid reference to an instance of the Environmental interface, which includes Items, Abilities, MOBs, Rooms, Exits, etc. The tool represents something which the source is utilizing to accomplish the task or generate the event. This is typically either an Ability object (like a Spell or Skill being used), or an Item object (like a weapon in an attack event).

  • Source Code

    This is an encoded integer which represents what the source MOB is actually doing. We'll break down this code below.

  • Source Message

    This is the string which the source MOB will see should the event occur successfully.

  • Target Code

    This is an encoded integer which represents what is happening to the target. If there is no target, this number will typically have the value of 0 (CMMsg.NOEFFECT).

  • Target Message

    This is the string which the target MOB (if it is a MOB) will see should the event occur successfully. If there is no target, this string is null.

  • Others Code

    This is an encoded integer which represents how any other objects (such as MOBs, Items, Rooms, Exits) other than the source and target, in the same room, perceive the event. If the event is completely imperceptible by anything other than the source, it may be 0 (CMMsg.NOEFFECT)

  • Others Message

    This is the string which other MOBs in the same room as the source and target MOBs will see should the event occur successfully. If the event is completely imperceptible by other MOBs, it may be null.

The Source Code, Target Code, and Others Code is easily the most complicated aspect of a Message. For this reason, numerous pre-configured message codes have been created in the CMMsg interface, all of which begin with the characters MSG_. Although we will not go into the meaning of each of these messages (that will be left to the reader to search the code for instances of messages which use the codes, and learn from the context in which they are used), we can at least break down these codes so that they can be better understood.

These coded integers all have two parts, the Major aspect (or the Major Code) and the Minor aspect (or the Minor Code). They may be referenced off of an already constructed CMMsg object using such methods as sourceMajor() and sourceMinor(). These methods will automaticallyy break down a sourceCode() into the components we will discuss.

The Major code is a series of significant bits in the integer, each of which gives some new meaning to the message. These bits are as follows:

Bit mask CMMsg Equate Variable(s) Meaning
2048

MASK_HANDS

Message includes small movements.
4096

MASK_MOVE

Message includes large, full-body movements.
8192 MASK_EYES Message includes visual information.
16384 MASK_MOUTH Message include mouth movement, or consumption.
32768 MASK_SOUND, MASK_SOUNDEDAT Message includes auditory information.
65536 MASK_ALWAYS Override mask which flags the message as something which Must occur, regardless of the state of the source or target.
131072 MASK_MAGIC Message has a magical nature.
262144 MASK_DELICATE Message includes very fine, delicate movements, such as thief skills.
524288 MASK_MALICIOUS Message represents an attack of some sort.
1048576 MASK_CHANNEL Message is part of public channel conversation.
2097152 MASK_OPTIMIZE Message implementation should be optimized for repetition.

The above masks can be quite confusing. It is best to examine the several MSG_ equates in the CMMsg interface to see how they are properly or improperly used. Remember a MSG_ equate is a completely constructed Code, complete with the appropriate Major and Minor aspects.

The Minor Code represents the more specific activity being performed, and is a simple integer ranging from 0 (NO EFFECT) to 2047. The officially recognized Minor codes are exhaustively listed in the CMMsg interface, and all begin with the prefix TYP_. These types cover every sort of major event which occurs in the CoffeeMud engine, including getting items, casting spells, entering or leaving rooms, etc, etc..

CMMsg msg = CMClass.getMsg(attacker,target,weapon,CMMsg.MSG_WEAPONATTACK,
          "<S-NAME>attack(s) <T-NAME>!");

The core.CMClass has many different getMsg signatures to make message construction quick and painless. The above is an example where only a single Code and a single message text are provided. In constructors where only one Code or message text field are provided, it is assumed that the code and message texts will be the same for source, target (if any) and others.

CMMsg objects also have value() and setValue(int) methods for modifying an integer not found in the constructor. This number is used for several different purposes in message construction, from the amount of damage in a TYP_DAMAGE message, to the amount of experience in a TYP_EXPCHANGE message. This number is also used to determine whether or not a standard saving throw was made. Value defaults to 0, but, after running through a message which contains a savable event, the value will be >0 if the save was made.

Message Previewing

Once a Message has been constructed, it is time to actually put the message out into the system. There is a standard form for the sending of almost all messages. If the source of the message is a MOB called "SourceMOB", this standard form looks like this:

CMMsg msg = CMClass.getMsg(SourceMOB,TargetMOB,weapon,CMMsg.MSG_WEAPONATTACK,
          "<S-NAME>attack(s) <T-NAME>!");

if(SourceMOB.location().okMessage(SourceMOB,msg))
{
     SourceMOB.location().send(SourceMOB,msg);
}

The location() field on a MOB refers to the Room in which the mob is. Room's are always the top level at which messages are previewed, and then executed or sent. The first line (where the message is constructed) has already been examined. The second line, in which the Room method okMessage() is called, is the preview step. In this step, the message is evaluated before it actually happens. The first parameter to okMessage() is called the "host" object, and it refers to the object to which the one you are sending the message should refer back to. This parameter is rarely used, except by Behaviors, and it is always safe to use the source of your message as this value. The second parameter to okMessage() is the message we constructed. The third line, where the Room send() method is called, is the execution step.

In the preview step, the room object will examine the message to see if there is anything which it might not like, wish to modify, or wish to flag about the Message it has been handed. If the Room object does not like the message, it will return false. Returning false from okMessage() is always an order to cancel, and not execute the message. Under any other circumstances, true may be returned to allow the message to go forward. The Room will also make calls to the okMessage() methods on every other MOB in the room, Exit from the room, Item in the room, spell effects which may be on the room, and behaviors of the room. The MOB who receives the okMessage() call will, in turn, pass the Message to the okMessage() methods in every Item the MOB is carrying or wearing, every spell effect on the MOB, and every behavior of the MOB. Items wills also make okMessage() calls on their spell effects. Any of these calls may modify or flag the message they receive. Any of these calls may also return false. If any object which previews a message returns false, the Room okMessage() method will also return false, ordering the message to be totally canceled. For this reason, okMessage() methods are careful about returning true unless they have a really good reason not to.

Inside the okMessage() methods of every Item, MOB, Behavior, Ability (spell effect), Exit, and Room the Messages may (as we mentioned) be examined and modified, flagged, or canceled. As we have already covered how Messages are canceled (by returning false). Let us turn now to the manner in which Messages are modified or flagged.

Message Flagging and Modification

Message modification is very rare. When it happens though, it is done by calling one of the several modify() methods on the Common.interfaces.CMMsg object. These methods allow the source, target, and all other fields to be updated. Message modification should also happen during the preview step so that any changes made to the message are made before the message is executed. It is also often wise, after making a change to a message, to recursively call the okMessage method on the room again so that the modifications can be previewed, but this is not always necessary.

Message flagging is somewhat less rare. Messages may be flagged when a combat strike is successful, or when a saving throw is made against a spell Effect, or for any other reason the Message constructing code may wish. Flagging is done by calling the aforementioned CMMsg.setValue(int) method, and using it to change the value to something other than the default of 0. Flagging using the setValue() method lets the code which constructed the Message know that something significant with relation to the Message has occurred. The meaning of this value will vary depending upon the type of message being generated, and upon the purpose to which the creator of the message wishes to put it. The value is read using the int value() method on CMMsg.

Message Execution

Once the okMessage() method on a Room object has returned true, and any code which may need to check or handle modifications to the Message have executed, the Message is sent. The proper way to send a Message is through the Room objects, by calling one of the following Messages: Room.send(MOB SourceMOB, CMMsg msg) or Room.sendOthers(MOB SourceMOB, CMMsg msg). The first method handles a standard Execution, while the second allows every relevant object except the SourceMOB to handle Execution. The first method should almost always be called.

The send() methods will then begin calling other methods in other objects. These other methods are called the executeMsg() methods, and are usually of the form public void executeMsg(Environmental myHost, CMMsg msg);. These methods are responsible for Executing the contents of the message. The Room method will make executeMsg() method calls on itself, and on every Exit, Item, MOB, spell effects (Ability object), and Behavior associated with that Room. As in the okMessage() case, the MOBs will in turn call the executeMsg() methods on their own Items and spell effects. Items will then call the executeMsg() methods on their own spell effects, and so on.

Of course, not every object in your game will handle and react to the Execution of every Message sent. Most of the time, a given object will be ignoring the Message altogether. However, each object knows precisely which Messages are important for it, and watch carefully for them in both their okMessage() and executeMsg() methods. In general, every Message which is previewed in an objects okMessage() method is handled in the executeMsg() method of the same object, though this is by no means always true. In general, the following object types handle the following types of Messages:

  • MOBs

    Any Message which has the mob instance as a target is both Previewed and Executed. Any Message which has the mob as a source is typically Previewed, and (lacking a target) may also be Executed.

  • Items

    Any Message which has the item instance as a target.

  • Exits

    Any Message which has the exit instance as a target or tool.

  • Rooms

    Any Message which has the room instance as a target.

  • Ability(spell effects)

    Any Message pertaining to the MOB or Item which is affected by the spell or skill.

  • Behavior

    Any Message pertaining to the object instance which has this behavior.

Now that you are completely confused, it will make you at least a bit happier to know that Room objects have several short-cut methods for creating, previewing, and executing messages. They include the following:

public boolean show( MOB source,
                     Environmental target,
                     int allCode,
                     String allMessage
                   );

public boolean show( MOB source,
                     Environmental target,
                     Environmental tool,
                     int allCode,
                     String allMessage
                   );

public boolean show( MOB source,
                     Environmental target,
                     Environmental tool,
                     int srcCode,
                     int tarCode,
                     int othCode,
                     String allMessage
                   );

public boolean show( MOB source,
                     Environmental target,
                     Environmental tool,
                     int srcCode,
                     String srcMessage,
                     int tarCode,
                     String tarMessage,
                     int othCode,
                     String othMessage
                   );

public boolean show( MOB source,
                     Environmental target,
                     Environmental tool,
                     int allCode,
                     String srcMessage,
                     String tarMessage,
                     String othMessage
                   );

public boolean showOthers( MOB source,
                           Environmental target,
                           int allCode,
                           String allMessage
                         );

public boolean showOthers( MOB source,
                           Environmental target,
                           Environmental tool,
                           int allCode,
                           String allMessage
                         );

public boolean showSource( MOB source,
                           Environmental target,
                           int allCode,
                           String allMessage
                         );

public boolean showSource( MOB source,
                           Environmental target,
                           Environmental tool,
                           int allCode,
                           String allMessage
                         );

public void showHappens(int allCode, String allMessage);

public void showHappens( int allCode,
                         Environmental like,
                         String allMessage
                       );

The first methods (show) is very commonly used; it constructs a message with the given source and target (no tool), and with the given Code and text message applying to source, target, and others. The showHappens() methods will do the same, but will also construct a blank MOB object to act as the source, for those instances where a source MOB is not readily available. The showOthers() methods behave like the first, but do not allow the source MOB to preview or execute the message, while the showSource() methods ONLY allows the source MOB to preview and execute the message.

All four of those methods will construct a CMMsg object, give the Message to the Room object for previewing ("okMessage"), and then, if the Message is not canceled, will call the Room "send" method for execution and return true. If the Message was canceled, false will be returned.

Message Trailers

The final subject we will discuss in the area of Messages and Message handling regards another rare technique called Message Trailer adding. Message Trailers are CMMsg objects which have been added to another CMMsg instance using the CMMsg.addTrailerMsg(CMMsg msg) method. The Message passed to this method is constructed in the usual way. This method may be properly called at any point during the Preview or Execution stage of Message handling, by any Previewing or Executing object. When it is performed is not important, because any Messages added using this method are not Previewed or Executed until after the Room object has completely finished sending the host Message to all interested objects.

Constructing and adding messages which act as message trailers can serve many purposes, but the most important of which is that the trailer messages only happen IF the host message also happens, and only happen AFTER the host message happens. This can be useful for the timing of subsequent messages which are dependent on others.

The State of Things

Core Topic 2: The State of Things:

In most systems, it is typical for all of the data variables which describe a particular object to be coded directly inside that object. While this is also true in CoffeeMud, many important data fields, along with the appropriate "getter" and "setter" methods, are stored in separate special data-storage, or state objects. These data-storage objects provide access to numerous important properties for those objects. These storage/state objects are routinely copied and then the copies are modified by other objects which have a spacial relationship with them. For instance, a MOB object may copy one or more of its state objects, and then allow the local Room object, or his inventory Item objects, or spell Effect objects to modify the copy, The copied state object modifications are stacked on each other. Confused? Well, keep reading!

Each instance of the several Environmental objects (MOBs, Items, Exits, Rooms) have a particular storage/state object called their Environmental Stats. This object implements the Common.interfaces.EnvStats interface, and is typically an instance of the Common.DefaultEnvStats class. Access to this state object is available through each core.interfaces.Environmental objects EnvStats baseEnvStats() and EnvStats envStats() method calls. Since Rooms, MOBS, Items, Exits, and Areas are all Environmental objects, that means that they all have baseEnvStats and envStats methods as well.

EnvStats state object fields

Field name Relevant objects Meaning
level Item, MOB, Exit Experience level (see Archon's Guide)
ability Item, MOB Magical level (see Archon's Guide)
rejuv Item, MOB Rejuvenation rate (see Archon's Guide)
weight Item, MOB Weight of the object
height Armor, MOB Size of the object
armor Item, MOB Protection level (see Archon's Guide)
damage Item, MOB Damaging ability
speed Item, MOB Attack speed
attackAdjustment Item, MOB Attack level
replacementName Item, MOB, Exit New displayable name of the object
sensesMask Item, MOB, Exit, Room Bit mask of relevant sensory abilities.
disposition Item, MOB, Exit, Room Bit mask of relevant disposition state

Although most of these fields are better described in the Archon's Guide, there are two whose nature may not be readily apparent: the sensesMask and the disposition. These two integers are bitmaps. The value of each bit is defined by equates in the Common.interfaces.EnvStats interface. The equates which refer to the bits for sensesMask all begin with "CAN_", while the equates which refer to the bits for disposition all begin with "IS_".

Now, as mentioned previously, all Environmental objects have two methods for accessing their EnvStats. One is core.interfaces.Environmental.baseEnvStats() and the other is core.interfaces.Environmental.envStats(). The difference between these two methods is very significant. The EnvStats state object returned by the "baseEnvStats" method refers to the permanent, unmodified, "base" state of the Environmental object. The "envStats" method, however, returns the modified, less permanent, "current" state of the Environmental object. The EnvStats object returned by the "envStats" method is always copied and derived from the "baseEnvStats" values, after all relevant modifications have been made to it. How the current state object goes from its base values (baseEnvStats) to its current values (envStats) is our next topic.

We must now introduce two other Environmental interface methods significant to this topic. One is the recoverEnvStats() method, while the other is the affectEnvStats(Environmental affected, EnvStats affectableStats) method. The "recoverEnvStats" method is also located on every Environmental (Item, MOB, Exit, etc) object and is the method which turns the baseEnvStats() values into their current envStats() values. This method call works by copying the base values into the current values and then allowing certain other objects to have an opportunity to affect the copy. Only after all opportunities to modify the copied values have been exhausted, does the recoverEnvStats() method return. In essence, recoverEnvStats() allows the Environmental objects baseEnvStats() to be updated, with that updated state object made available through envStats().

The way in which the current EnvStats state object is modified by the recoverEnvStats() method call is by making repeated internal calls to the affectEnvStats(Environmental affected, EnvStats affectedStats) methods on other relevant objects. These methods will then have the opportunity to change the values in the current state object (affectedStats parameter) however they wish. The relevant objects which may change the state of an Environmental are as follows:

  • MOBs

    Room object being occupied, something being Ridden, the MOBs Character Class object, the MOBs Race object, the Items in the MOBs inventory, and finally the Ability objects which are affecting the MOB (spell effects).

  • Items, Exits, Areas

    Ability objects which are affecting it (spell effects).

  • Rooms

    Area object which this room is a part of, Ability objects which are affecting it (spell effects), Items in the Room, and MOBs in the Room.

Here is an example:

Gunker the Thief wears Full Plate Armor (Item), and has the Shield spell cast on him. His base Armor rating is 100. When he puts on the Plate Armor, the "recoverEnvStats" method is called on Gunker's MOB object. That method in turn calls the "affectEnvStats" method on the Plate Armor and the Shield spell Effect. Both of those methods improve the Armor rating on Gunker's MOB's current EnvStats by some number. Thus, Gunker becomes harder to hit in combat. Also, when Gunker picked up the Plate armor, the weight of the armor was added to Gunker's overall carried weight by increasing the weight value in Gunker's EnvStats object.

I know, this is probably still confusing.

Confusing or not, however, we still have to consider two other state objects, both of which are only available from the MOB object. One of which is the CharStats object, and the other of which is the CharState object.

The CharStats objects are most closely analogous to the EnvStats objects. For instance, there are CharStats baseCharStats() and CharStats charStats() method calls from a MOB object, as well as a recoverCharStats() method call. All of these work similarly to the ones described above for EnvStats. The fields on a Common.interfaces.CharStats object are somewhat more straight forward however. Most of the fields of a CharStats object are referenced using the int getStat(int) and setStat(int,int) methods on a CharStats object. Both of these methods require, as their first parameter, an integer code which corresponds to the specific stat being set or read. These stat parameters are defined as equates within the CharStats interface, and include:

STAT_STRENGTH, STAT_INTELLIGENCE, STAT_DEXTERITY, STAT_CONSTITUTION, STAT_CHARISMA, STAT_WISDOM, STAT_GENDER, STAT_SAVE_PARALYSIS, STAT_SAVE_FIRE, STAT_SAVE_COLD, STAT_SAVE_WATER, STAT_SAVE_GAS, STAT_SAVE_MIND, STAT_SAVE_GENERAL, STAT_SAVE_JUSTICE, STAT_SAVE_ACID, STAT_SAVE_ELECTRIC, STAT_SAVE_POISON, STAT_SAVE_UNDEAD, STAT_SAVE_MAGIC, STAT_SAVE_DISEASE, STAT_SAVE_TRAPS, STAT_MAX_STRENGTH_ADJ, STAT_MAX_INTELLIGENCE_ADJ, STAT_MAX_DEXTERITY_ADJ, STAT_MAX_CONSTITUTION_ADJ, STAT_MAX_CHARISMA_ADJ, STAT_MAX_WISDOM_ADJ, STAT_AGE, STAT_SAVE_DETECTION, STAT_SAVE_OVERLOOKING.

In addition to these equates defined and read through the getStat() and setStat() methods, there is also the Race object available through getMyRace() and setMyRace() methods, as well as Character Class and Character Class level methods. See the Common.interfaces.CharStats java file for more information on those methods and how they work.

Like the EnvStats above, those objects listed as able to modify the EnvStats current state object are the same objects which are able to modify the CharStats state objects. Rereading the section on EnvStats will make clear how the CharStats objects are modified in the same analogous manner, using repeated calls toaffectCharStats(MOB affected, CharStats affectedStats) methods on related objects.

The last state object to consider is the Common.interfaces.CharState objects on MOBs. The CharState object represents those fields which are constantly in flux: Hit Points, Mana, Movement, Hunger, Fatigue, and Thirst.

Unlike EnvStats and CharStats, there are three CharState objects to consider for MOBs: the base CharState object (available throughCharState baseState() method, the adjusted base CharState (or max state) object available through the CharState maxState() method and modified by recoverMaxState(MOB affected, CharState affectedState) methods on related objects, and lastly the current CharState object available through the CharState curState() and refreshed or reset to maximums using the MOBs resetToMaxState() method.

The relationship between the above objects is as follows: The base CharState object represents the maximum values for the state variables BEFORE modification by magical armor or spells. The adjusted base CharState object (Max State) represents the maximum values for the state variables AFTER modification by magical armor or spells. The current CharState object (curState) represents the current hit points, mana points, etc available to the MOB.

In the case of the CharState objects, adjustment by relevant objects is initiated by calling the MOBs recoverMaxState() method. This method allows the same objects who modify the EnvStats and CharStats above to modify the maximum CharState values as well.

Once again, to understand one of them fully is to understand them all.

Tick Tock

Core Topic 3: Tick Tock

Our last Core Topic will cover the ability of the mobs, items, exits, abilities, spell effects, behaviors, and other objects to perform tasks on a regular, timed, basis. The tasks to be performed are always located within an method called boolean tick(Tickable ticking, int tickID). All Environmental objects define this method, and Behavior objects do as well.

These methods are called on a regular, timed basis whenever the object instance in question has been properly set up to do so, and at a defined frequency and interval. The "ticking" parameter is usually a reference to the object itself, or to the host object in the case of Behaviors. The "tickID" parameter describes what sort of regular timed event is occurring. These events are defined as equates in the core.interfaces.Tickable interface, and include IDs such as TICKID_MOB, TICKID_AREA, TICKID_EXIT_REOPEN and others. See the java file of that interface for more defined tickID values.

Before we get into the methods by which an object instance are properly set up for regular calls to its tick() method, it may be worthwhile to discuss which regular ticks are setup by the system by default. These tick events cover the most commonly used objects under the most common circumstances, and so may be just the events you already needed! They include:

  • MOBs

    All MOBs have their tick() method called once per core.interfaces.MudHost.TIME_TICK (4 seconds), with the "tickID" defined by Host.MOB_TICK. MOBs will, in turn, call the tick() methods on their own Behaviors, and Ability objects affecting them. If any of these dependent objects return "false" from their own tick methods, then the object will cease to receive any further tick method calls.

  • Exits, Items, Rooms

    Whenever a Behavior is added to any of these objects, they will begin to have their tick methods called once per MudHost.TIME_TICK (4 seconds), with the tickID defined by Tickable.TICKID_ITEM_BEHAVIOR, TICKID_EXIT_BEHAVIOR, or TICKID_ROOM_BEHAVIOR. Deletion of the last behavior from the host object will stop this tick event from occurring again.

  • Areas

    All Area objects have their tick methods called once per core.interfaces.MudHost.TIME_TICK, with the tickID defined by Tickable.TICKID_AREA.

  • Ability

    Whenever an Ability object is added as an Effect (using the addEffect() Environmental method) to a non-MOB object by using the proper Ability invoke procedure (see below), then the Ability object itself will gain it's own regular calls to its tick method. The tickID for this call is also Tickable.TICKID_MOB, so as to make consistant the tickID for all spell and similar effects.

To sum up, MOBs have regular tick calls which they use to perform their own periodic tasks, as well as to allow their Behavior and spell effects to perform tasks. The other objects have circumstantial ticks in certain instances.

Now, to add a new periodic call to the "tick" method on an Environmental object, one needs only to make a method call like this:

CMClass.threads().startTickDown(theEnvObject,Tickable.MY_TICK_ID,TIME_TICK,NUM_TICKS);

The second parameter is the tickID which will be used when the tick() method on the "theEnvObject" object is called. The third parameter is the time interval, in milliseconds, between each event. The fourth parameter is the number of time intervals between each call to the tick() method. The total time between each call to the tick() method, therefore, will be TIME_TICK * NUM_TICKS.

Now, the above version of startTickDown() can be used to create timed events of any duration. However, all objects in the base distribution of CoffeeMud operate on a single default time interval of 4000 milliseconds as defined by Tickable.TIME_TICK. For this reason, a different version of the startTickDown() method is called which does not include the TIME_TICK parameter, utilizing the standard 4 second delay instead:

CMClass.ThreadEngine().startTickDown(theEnvObject,Host.MY_TICK_ID,NUM_TICKS);

Stopping any of these tick calls can be done by simply returning "false" from the tick method itself, or manually using the following:

CMClass.ThreadEngine().deleteTick(theEnvObject,Host.MY_TICK_ID);

You may also stop tick calls to an object by using the Environmental objects destroy() method.

Core Libraries

Core Topic 4: Core Libraries

The CoffeeMud engine contains numerous Java classes whose purpose is to perform much of the underlying game functionality. Some of these Java classes are Core classes, some are Library classes, and some are Common classes.

Core classes are those classes found in the com.planet_ink.coffee_mud.core package. Like the interfaces, they may not be extended or overwritten without risking problems. The core classes, and their general purpose is:

Core Class Name Purpose
B64Encoder Encode and decode text<->binary using Base64
CMath Converting strings to numbers, performing bit-wise and other arithmetic operations
CMClass Main ClassLoader -- get all your objects from methods here!
CMFile FileSystem manager, get all your file data from this class
CMLib The non-core library reference object. * See below for more information.
CMParms Methods for parsing strings and determining parameter values in many different ways.
CMProps Properties manager, for reading INI file values from coffeemud.ini and other places.
CMSecurity The security manager, for evaluating player and system security flags.
CMStrings Methods to manipulate, pad, and filter strings.
Directions Methods to handle the different compass directions.
DVector A multi-dimensional version of java.util.Vector
Log The file and console logging manager.
Resources The object-resource manager, usually with lots of StringBuffers keyed by String names.
Scripts The human-language readable strings loader.

You should check out the javadocs for those classes for more information on the core classes.

* CMLib also refers to some of the sub-core classes, such as the Database access objects, and the Threading engine. While they are considered core, they are accessed through CMLib as if they were non-core libraries. Most core classes can also be accessed through CMLib methods, making it a one-stop shop.

Now, non-core libraries, or Libraries proper, are located in the com.planet_ink.coffee_mud.Libraries package, and each one implements a unique interface from the com.planet_ink.coffee_mud.Libraries.interfaces package. This is unique, that each class in the Libraries package implements a unique interface all its own, but that is not the only unique thing about this package. Libraries are also singletons -- there is never more than 1 instance of each Library. Moreover, these singletons are all accessed from a single accessor class, CMLib, which we mentioned earlier.

Here is a map of how the classes, interfaces, and CMLib methods are all mapped together:

Library class name ...Libraries.* Library interface name ...Libraries.interfaces.* CMLib method name ...core.CMLib Purpose of the Library
BeanCounter MoneyLibrary beanCounter() Handle money and currency.
CharCreation CharCreationLibrary login() Login and create new players.
Clans ClanManager clans() Handle all clans.
CMAble AbilityMapper ableMapper() Maps CharClasses to Skills/Abilities.
CMChannels ChannelsLibrary channels() Handles public channels.
CMColor ColorLibrary color() ANSI and color code conversions.
CMEncoder TextEncoders encoder() Compression library.
CMJournals JournalsLibrary journals() Handles public journal commands.
CMLister ListingLibrary lister() Handles listing tables nicely.
CMMap WorldMap map() Find areas and rooms.
CoffeeFilter TelnetFilter coffeeFilter() Filters/transforms text going to and from a player.
CoffeeLevels ExpLevelLibrary leveler() Leveling and Experience gaining functionality.
CoffeeMaker CMObjectBuilder coffeeMaker() Generic Mob/Item and CoffeeXML generators.
CoffeeShops ShoppingLibrary coffeeShops() Handles manipulation of shop inventories.
CoffeeTables StatisticsLibrary coffeeTables() Maintains player usage stats.
CoffeeTime TimeManager time() Real-life data/time display.
CoffeeUtensils CMMiscUtils utensils() Misc stuff-- law, traps, titles, resets.
CommonMsgs CommonCommands commands() Methods for getting, talking, common-stuff
Dice DiceLibrary dice() Random number generator.
EnglishParser EnglishParsing english() Player command line parsing helpers.
ColumbiaUniv ExpertiseLibrary expertise() Maintains expertise skill flags
Factions FactionManager factions() Handles the factions and faction system.
MUDLaw LegalLibrary law() Handles legal and property matters.
MUDFight CombatLibrary combat() Combat and Death routines.
MUDHelp HelpLibrary help() Handling help-file entries.
MUDTracker TrackingLibrary tracking() Methods for NPC movement, and for tracking.
MUDZapper MaskingLibrary masking() Zapper-mask parsing and evaluation.
Polls PollManager polls() Handle the public polls.
Quests QuestManager quests() Quest Manager system.
RawCMaterials MaterialLibrary materials() Manipulate/Create raw resource objects
Sense CMFlagLibrary flags() Sensory and Disposition bitmap/flag handling.
Sessions SessionsList sessions() Container for player connection objects.
SlaveryParser SlaveryLibrary slavery() Geas and Slavery order parsing and execution.
SMTPclient SMTPLibrary smtp() E-Mail sending routines.
Socials SocialsList socials() Socials container and parser.
StdLibrary NONE NONE SuperClass of other libraries.
TimsLibrary ItemBuilderLibrary itemBuilder() Methods for normalized item evaluation.
XMLManager XMLLibrary xml() General XML parsing.

Libraries are lastly unique in that it is almost useless to write new ones, unless you are doing the most serious additions and enhancements to your system. However, they are designed especially so they they can be extended and overridden. They have their own entry in the coffeemud.ini file called LIBRARY. By writing classes that extend the base CoffeeMud library classes, and overriding their methods, you can make changes to the most basic CoffeeMud algorithms, even at run-time! Libraries also have an entry in the coffeemud.ini file, LIBRARY, so that you can specify your custom extended versions of them after the %DEFAULT% string, thus allowing your changes to be loaded at boot-time.

The last set of classes to discuss under this topic are neither Core class or Libraries, but form parts of the cores of other classes, including many of the Libraries. These are the Common classes. They are part of the com.planet_ink.coffee_mud.Common package. Like the Libraries, they each implement their own unique interface from the com.planet_ink.coffee_mud.Common.interfaces package. However, unlike the Libraries, numerous instances of each class will exist in your mud, and they are created from the core CMClass loader, much like MOBs, Items, Rooms, and so forth. The javadocs are also a good place to learn about these classes, but here is a brief list of them to wrap up our last Core Topic.

Common Class Description
DefaultArrestWarrant An object created every time a law is broken.
DefaultCharState Contains a mobs hit points, mana, movement, fatigue, hunger, and thirst.
DefaultCharStats Contains a mobs class, race, saving throws, and basic stat scores.
DefaultClan Represents a single Clan.
DefaultClimate A single climatary system for a given area.
DefaultCMIntegerGrouper An object for maintaining a players room visitation memory.
DefaultCoffeeShop Represents a single shop store inventory.
DefaultCoffeeTableRow Represents a days worth of game player usage statistics.
DefaultEnvStats Contains an objects attack bonus, armor bonus, weight, height, rejuv rate.
DefaultFaction Represents a single faction.
DefaultLawSet Represents a single set of laws and legal policies.
DefaultMessage A CoffeeMud event message (CMMsg).
DefaultPlayerStats Represents player-specific fields like prompts, friends, alias, etc.
DefaultPoll A single poll object.
DefaultQuest Container for a single timed quest.
DefaultRoomnumberSet Container for a players area visitation memory.
DefaultSession Connection object for handling a players telnet session with the mud
DefaultSocial Object for a single social command.
DefaultTimeClock Object representing a single calendar/time system that spans areas, possibly the whole world.
Core Libraries

Core Topic 5: Stat

In the uncoming sections, you will learn about the layout and design of each of the several major CoffeeMud class types. Sometimes, however, you may want to expand or extend the classes to include new or extraneous data. If so, this last core topic is for you.

All of the classes which implement the Environmental interface (including Abilities, Exits, Areas, Rooms, MOBs, and Items) or the PlayerStats interface (Player MOBs) or several others, will include the following methods for accessing and modifying certain internal data fields:

    public String[] getStatCodes();
    public int getSaveStatIndex();
    public String getStat(String code);
    public void setStat(String code, String val);

The getStatCodes() method returns a string array of the "names" of the internal data fields which this class makes available to the outside world. These names can then be used in the getStat(String) method to retreive the string-rendered values of those data fields, and the setStat(String,String) method can be used to change them. This is a very versitile way of reading and writing the particular aspects of a data object, especially when you don't know for sure what kind of object it is (or you don't care).

However, the reason this might interest you is because of the method we havn't mentioned yet: getSaveStatIndex(). The purpose of this method is quite simple: It returns the number of stat code names (from getStatCodes() ) that are already handled by the existing database code. This means that, for a stock CoffeeMud system, this method will return the same number as getStatCodes().length would. However, should you decide to add to the stat name strings defined by getStatCodes() and then extend getStat and setStat methods to manage your own internal variables, you have only to make sure your new name is at the end, and that getSaveStatIndex() stops short of that number, to make sure that CoffeeMud will go ahead and save your new data to the database (and restore it, of course). Exporting to cmare files and other features are also included this way.

Here is an example:

public class MySpecialGenMob extends GenMOB 
{
    public String ID()
    {
        return "MySpecialGenMob;
    }

    private String myvariable=""; 
    public String[] getStatCodes() 
    { 
        List L=java.util.Arrays.asList(super.getStatCodes());
        L.add("MYVALUE"); 
        return (String[])L.toArray();
    }

    public String getStat(String code) 
    {
        if(code.equalsIgnoreCase("MYVALUE"))
        {
            return myvariable;
        }
        else
        {
            return super.getStat(code); 
        }
    }

    public void setStat(String code, String value)
    {
        if(code.equalsIgnoreCase("MYVALUE"))
        {
            myvariable=value;
        }
        else
        {
            super.setStat(code,value); 
        }
    }

    public int getSaveStatIndex()
    {
        return super.getStatCodes().length;
    }

You'll notice above that the getStatCodes() method is extended to add a new stat code name "MYVALUE" which will appear at the end of the string list. The getStat and setStat methods are adjusted accordingly to support the new code. Lastly, the getSaveStatIndex method is overridden to return the length of the base classes codes instead of mine, that way the getSaveStatIndex for MySpecialGenMob will return one less than the getSaveStatIndex for GenMOB, the super class.

And just by doing those simple things, we now have a new variable which the CoffeeMud engine will ensure is written to the database. However, this capability is limited to the following types of objects: generic mobs, generic items, playerstats, or any exits, rooms, or area classes.

Since players are technically implemented as StdMOB objects, you will need to modify the appropriate methods in the DefaultPlayerStats object to achieve the same effect for players. To add a new savable stat code to those already defined in DefaultPlayerStats, the first thing we'd do is add a new variable name (CODE name) to the end of the CODES list above. Make sure you don't insert it anywhere, as the order is significant. Add it to the end, in uppercase, as so:

protected static String[] CODES={"CLASS","FRIENDS","IGNORE","TITLES",
[...this continues...]
"ACCTEXPIRATION","INTRODUCTIONS", "MYNEWVARIABLE"};

Next, you would add the define a variable to hold your new data, and provide the ability to GET the data to the getStat method:

    private String myNewString="";

    public String getStat(String code) 
    { 
        switch(getCodeNum(code)) 
        { 
            case 0: return ID();
            case 1: return getPrivateList(getFriends());
[...this continues...]
            case 18: return ""+accountExpiration; 
            case 19: return getPrivateList(introductions);
            case 20: return myNewString;
        } 
        return ""; 
    }

Next, you would add the ability to SET the data to the setStat method:

public void setStat(String code, String val) 
{
  switch(getCodeNum(code)) 
  { 
      case 0: 
        break; 
      case 1: 
        friends=getHashFrom(CMLib.xml().returnXMLValue(val,"FRIENDS"));
        break;
[...this continues...]
      case 19: 
        introductions=getHashFrom(CMLib.xml().returnXMLValue(val,"INTROS"));
        break;
      case 20: myNewString=val;
        break;
  }
}

And the last step would be to modify the getSaveStatIndex method by HIDING our new variable from its count of codes by subtracting one from the new CODES count in order to reach the old one:

public int getSaveStatIndex()
{
    return super.getStatCodes().length - 1;
}

This last step is a bit hard to understand, but remember that: the CODES from 0 to getSaveStatIndex() are all already handled by the internal codebase. Since we added a new code to the list, this method, if left unchanged, would return a number that is ONE larger than it used to be. However, that would be implying that our new variable is also already handled by the codebase, which isn't true. By subtracting one, we tell the CoffeeMud engine that 0 to getSaveStatIndex()-1 is handled by the system, but any codes beyond that need special treatment, which is true.

commands

Commands

Commands are exactly what they sound like: LOOK, QUIT, KILL, GET, and all the other things you type into the mud are handled by CoffeeMud Commands.

A Custom Command may or may not belong to any particular package, though it is important that the ID() of the Command be unique in the system. A custom Command imports the same packages mentioned in the first section of this document under Complete Default Import List as well as com.planet_ink.coffee_mud.Commands.StdCommand.

public class DoNothing extends com.planet_ink.coffee_mud.Commands.StdCommand
{
  public DoNothing(){}

  private String[] access={ "DONOTHING" };
  public String[] getAccessWords(){ return access; }

All Commands should extend StdCommand for conformity's sake, though it is not required so long as your class implements the com.planet_ink.coffee_mud.core.interfaces.Command interface. In our example above, we have an empty constructor, but we do define some access words.

Access words are what you think they are: the words which, when typed, allow the users to activate the command. We define a string array containing one such access word in this case, and then define our Command interface method getAccessWords() to return that array. Our String array may contain as many strings as you would need to provide sufficient words to activate this command.

public double actionsCost(MOB mob, Vector cmds){ return 1.0; }
public double combatActionsCost(MOB mob, Vector cmds){ return 1.0; }

public boolean canBeOrdered(){ return true; }

The next two methods, actionsCost() and combatActionsCost(), designate how LONG it takes to execute this command. A value of 0 means that the command always happens instantly. A value greater than 0 will always take that many free actions to complete. A standard player may perform 1 action in a given 4 second period, or 2 actions during combat in the default combat system -- they retain their 1 action per tick in other combat systems). Action costs may be partial as well, costing 0.5 (1/2 action) or other values.

The canBeOrdered() method designates whether this command represents an action which a creature or player might reasonably be ordered to do by another player. Archons and those with the "ORDER" security code are exempt from this flag.

  public boolean securityCheck( MOB mob )
  {
    return CMSecurity.isAllowed( mob, mob.location(), "IMMORT");
  }

And speaking of security, this method returns whether or not the given mob may even have access to this command. If the securityCheck() method returns false, the command and its access words will behave as if they do not even exist, returning "Huh?" should a player attempt to use it. Returning true, however, means only that the execute method below may be accessed. Any further security would have to be implemented there.

In the above example, we call the isAllowed() method in CMSecurity with the mob reference, the room in which the mob is located, and the security code "IMMORT". This asks the Security module whether this mob, at this location, is authorized to perform functions designated by the "IMMORT" security code.

  public boolean preExecute(MOB mob, Vector commands, int secondsElapsed, double actionsRemaining)
    throws java.io.IOException;
  {
    if( secondsElapsed == 0 )
    {
      mob.tell( "You are preparing to do nothing." );
    }

    if( secondsElapsed == 3 )
    {
      mob.tell( "You almost ready to do nothing." );
    }
  }

The preExecute() method is very rarely implemented, but it is important to mention in light of the actionsCost() and combatActionsCost() values above. The purpose of the method is to give the player or the room status messages when the player does not yet have enough actions to execute the command. The method will be called immediately if a player does not have enough actions to execute the command, and will present a secondsElapsed value of 0. It will then be called again every second or so with updated values until the player has enough actions to proceed.

  public boolean execute( MOB mob, Vector commands )
    throws java.io.IOException
  {
    String parameters = CMParms.combine( commands, 1 );
    if( parameters.length() == 0 )
    {
      mob.tell( "Nothing done." );
    }
    else
    {
      mob.tell( "Nothing, not even '" + parameters + "', done." );
    }
    return false;

  }
 

Our last method is where the command actually does its work. The mob given would be the MOB object trying to execute this command, while commands is a Vector of parameters.

The parameter commands is never null, and by convention is a Vector of strings starting with the access word used to execute the command. For instance, if the user entered:

donothing never "and always" never
       

The commands Vector would be size 4, and contain "donothing", "never", "and always", and "never" respectively. In the case of this command, we use one of the String utilities to recombine the last 3 parameters back into one string "never and always never", and then issue a message to the mob depending upon whether there were any parameters at all. Since this command requires a command word to access it, it is reasonable to assume that the 0th element in the commands vector is the word "donothing", which means we can safely ignore it.

MOBs

MOBs

MOBs, or "Moveable OBjects", are the creatures and characters which the players fight. In CoffeeMud, they are among the simpler objects to code. This is not because they are uncomplex. In fact, they are MOST complex. However, this complexity comes due to the myriad of Items, Behaviors, Properties, and Abilities that are added to them. Short of these numerous additions, a MOB by himself is rather simple!

This simplicity is important however, and should be carefully considered before you run off to create new MOBs. If you are creating a new MOB because you want a creature to have some new kind of ability, then are you sure it is not a new Ability you want to write? If the new MOBs behavior is complex and unique, are you sure it's not a new Behavior you wish to code? Otherwise, the best reasons to be coding MOBs are actually three: because you have a particular kind of monster that is used prolifically in your world, and you want to save memory by coding him as a special mob that extends StdMOB, or because you want to code special player capabilities by creating your own class that both extends StdMOB and has an ID() of "StdMOB", or because you want to add special NPC monster capabilities by creating your own class that extends GenMob and has an ID() of "GenMob".

So, if you are sure this is what you want to do, carry on! The directory for your custom coded MOB objects should be specified using the "MOBS" entry in the coffeemud.ini file. See the section above on Rebuilding CoffeeMud for more information on this feature.

Coding a new MOB

A Custom MOB may or may not belong to any particular package, though it is important that the ID() of the MOB be unique in the system. A custom MOB imports the same packages mentioned in the first section of this document under Complete Default Import List as well as (in this case) com.planet_ink.coffee_mud.MOBS.StdMOB because our sample mob extends it.

A MOB class must extend either StdMOB or GenMob, StdShopKeeper or GenShopKeeper, StdRideable or GenRideable depending on the basic capabilities, and customizability you would like. Although Generic objects are more customizable at run-time, they also take a long time for the system to load and build, and take up a lot of database disk space, and more memory. For this reason, using Standard instead of Generic wherever possible is always good. Another reason for extending StdMOB is because all players in the game use the "StdMOB" class as a basis, which means that special player fields and capabilities can be coded by both extending the StdMOB class, and also giving your custom class an ID() of "StdMOB". If you do this, however, make sure your class is loaded after the %DEFAULT% list in your coffeemud.ini file.

As was stated, each unique MOB must also have a custom ID() method as shown below. Notice that the ID() is the same as the name of the class. This is no accident -- this is required!

public class MyNewMOB extends com.planet_ink.coffee_mud.MOBS.StdMOB
{
  public String ID(){ return "MyNewMOB";}

All of your customizing will be done inside the constructor: name, displayText, description, etc, etc.

  public MyNewMOB()
  {
    super();

    setName( "a new mob" );
    setDescription( "It`s furry with 2 legs" );
    setDisplayText( "My new mob is standing here." );

    Factions.setAlignment( this, Faction.ALIGN_NEUTRAL );
    setMoney( 0 );
    setWimpHitPoint( 2 );
    baseEnvStats().setDamage( 4 );

    baseEnvStats().setAbility( 0 );
    baseEnvStats().setLevel( 1 );
    baseEnvStats().setArmor( 30 );
    baseEnvStats().setSpeed( 1.0 );
    baseEnvStats().setAttackAdjustment( 30 );
    baseEnvStats().setWeight( 85 );
    baseEnvStats().setSensesMask( EnvStats.CAN_SEE_DARK|EnvStats.CAN_SEE_INFRARED );
    baseEnvStats().setDisposition( EnvStats.IS_FLYING );
    baseCharStats().setCurrentClass( CMClass.getCharClass( "Fighter" ) );

    baseCharStats().setMyRace( CMClass.getRace( "Dog" ) );
    baseCharStats().getMyRace().startRacing( this, false );
    baseCharStats().setStat( CharStats.STAT_GENDER, (int)'F' );
    baseCharStats().setStat( CharStats.STAT_STRENGTH, 18 );
    baseCharStats().setStat( CharStats.STAT_INTELLIGENCE, 14 );
    baseCharStats().setStat( CharStats.STAT_WISDOM, 13 );
    baseCharStats().setStat( CharStats.STAT_DEXTERITY, 15 );
    baseCharStats().setStat( CharStats.STAT_CONSTITUTION, 12 );
    baseCharStats().setStat( CharStats.STAT_CHARISMA, 13 );
    baseCharStats().setStat( CharStats.STAT_SAVE_COLD, 50 );

    baseState.setHitPoints( CMLib.dice().roll( baseEnvStats().level(), 20, 20 ) );
    baseState.setMana( CMLib.dice().roll( baseEnvStats().level(), 50, 100 ) );

    recoverMaxState();
    resetToMaxState();
    recoverEnvStats();
    recoverCharStats();
  }

You can see here that the basic stats have been filled out, from level to attack speed, alignment and weight. For numeric values, higher is always better, except for Armor, which is always best low, and comes down from 100. You'll notice above that two commands are required to set the Race of the creature. Also, you should realize that the numerous saving throws, and senses as well as dispositions (sneaking, hiding) are not represented above, but can easily be added using the format shown.

It is very important to note the last four commands. These commands "reset" the MOB, and "implement" the scores which are given in the several areas. recoverEnvStats(), for instance, must be called whenever a change is made to the baseEnvStats() object. Ditto for "CharStats" and "MaxState".

Now, suppose we wanted to add an Effect or Behavior or Ability to your MOB. The proper place for such a statement would be in the same above constructor, among the other commands. Preferably before the several "recover" commands, but after the several stat definitions. Of course, all of this is unnecessary for a new GenMOB object.

    addNonUninvokableEffect( CMClass.getAbility( "Fighter_Berzerk" ) );

    Ability A = CMClass.getAbility( "Prop_Resistance" );
    if( A != null )
    {
      A.setMiscText( "Fire 200%" );
      addNonUninvokableEffect( A );
    }

    addAbility( CMClass.getAbility( "Poison" ) );

    addBehavior( CMClass.getBehavior( "MudChat" ) );
    addBehavior( CMClass.getBehavior( "Mobile" ) );
    addBehavior( CMClass.getBehavior( "CombatAbilities" ) );

The commands above will make the MOB permanently Berzerk, gives it the ability to Poison folks while in combat, allows the MOB to Chat with other players, and to walk around in its area. The last behavior gives the MOB the wisdom to use its Poison ability while in combat.

If your MOB extends StdShopKeeper, you will need to add your inventory manually through the getShop() object access method as shown below. The getShop() method returns an instance of the com.planet_ink.coffee_mud.Common.interfaces.CoffeeShop interface, which stores inventory for the shopkeepers. In creating the shopkeepers, you will also need to specify the type of ShopKeeper.

    setWhatIsSold( ShopKeeper.ONLYBASEINVENTORY );

    Weapon sword = (Weapon)CMClass.getWeapon( "Longsword" );
    getShop().addStoreInventory( sword, 35, -1, this );
    Armor mail = (Armor)CMClass.getArmor( "FullPlate" );
    getShop().addStoreInventory( mail, 35, -1, this );
    Item waterskin = CMClass.getItem( "Waterskin" );
    getShop().addStoreInventory( waterskin, 35, -1, this );

You'll recall from the Archon's Guide that there are many different types of ShopKeepers, including trainers, pet sellers, weaponsmiths, and others.

StdRideable MOBs will require a few other settings as well!

    setRideBasis( Rideable.RIDEABLE_LAND );
    setMobCapacity( 2 );

The last thing is to give the MOB equipment, armor, and weapons. The following commands will do the trick!

    Weapon sword = (Weapon)CMClass.getWeapon( "Longsword" );
    addInventory( sword );
    sword.wearIfPossible( this );

    Armor mail = (Armor)CMClass.getArmor( "FullPlate" );
    addInventory( mail );
    mail.wearIfPossible( this );

    Item sack = (Item)CMClass.getItem( "StdContainer" );
    addInventory( sack );

    Item waterskin = (Item)CMClass.getItem( "Waterskin" );
    addInventory( waterskin );
    waterskin.setContainer( sack );

And that's all there is to creating a new standard MOB. Easy, huh? Well, obviously, the real complexity of MOBs comes when the Behaviors and Abilities are programmed, but that is not covered here, of course.

There is also the advanced topic of extending the capabilit