[MUD-Dev] An Idea... Mud Development Framework

Jeremy Lowery macr0 at mindspring.com
Fri Jan 12 04:55:06 CET 2001


<EdNote: Duplicate HTML copy deleted.  Formatting cleaned up.  I'd
much rather you did this than me.>

I've been developing a mud codebase for the past few months. At a 
certain point I realized that I was treading on all too common 
territory, and my ambitions began to take over. In a rush of creative 
energy (or hysteria), I thought up the idea of a mud programming 
framework. My goal is the think of a base that supports the widest 
variety of different game ideas. I'd been twittling with pieces of the 
thing in my mind for some time now. Below is my first thoughts on the 
system. Next ideas would be the implementation of serialization and a 
scripting language to support the framework, along with precisie object 
definition information. Will you folks tell me what you think? 
Inpracticallities? Has someone already done this? Holes? Of course, if 
you notice how I write. It seems like this thing already exists! Err. it 
doesn't :)
 
  MDK (Mud Development Kit)
  Jeremy Lowery

Purpose

The MDK's purpose is to provide a scriptable object-oriented MUD 
developing environment.

The MDK Framework

The MDK system is the framework that is used to develop muds. MDK
implements a class-object hierarchy, design patterns, and polymorphic
data-types to support the extensible nature of the development
environment (DE).

The Object Factory

The object factory manages all objects created in the DE. The factory
also includes built in support for object serialization via standard
C++ file streams. The factory maintains the file structure of all game
objects internally in a special resource file named ofrdb.mlib (object
factory reference database). The factory also needs to keep track of
all objects created in the game. This is accomplished by a unique
virtual index number assigned to every single "thing" created in game
(all of which are objects).

Advantages include:

  a.. Item copying identification
  b.. System Snapshots

The Notifier

The object notifier is a singleton object that processes and
dispatches object-based messages. Based on the message scope and type,
the notifier dispatches the message to the corresponding target
object(s). The notifier also has built in security features that allow
message destruction before processing for invalid message usages. The
notifier and object factory work closely together to handle
object-creation and message dispatching. Indeed, they share the same
object pool.

Game Messages

Object interaction is achieved by using the notifier. All objects must
override member functions that parse system messages directed to them
by other objects.

In addition to simple object messages, the DE also supports message
scope resolution for message sending objects. These specifiers can be
used to send messages at a certain game-level scope (More on this a
little later). All messages are identified by a unique integer
constant abbreviated MM_ (for mud message). Associated with messages
is a generic message handling data-type identifying message specific
data. Unlike most MUD systems, the MDK does not explicitly bind client
messages to character objects. One of the main advantages this
approach exposes is the use of a common socket-polling connection for
every kind of game manipulation that is possible. Aside from
characters playing the game, this also allows administrators,
builders, and developers to have parallel game access, but via
different interfaces.

System Messages

The framework can also send messages to game objects. Unlike game
message, system messages know no boundary. Of course, it is the
object's responsibility to actually do something with the
message. Common examples of system message include time messages,
shutdown messages, client disconnection, and persistence messages
(i.e. messages that tell objects to save themselves on a regular basis
incase of system failure).

The Network

One of the only things that need to be hard-coded into the system is
the actual game server. Of course, in reality it is just another
object. It sends messages to other objects and receives them just as
any other object does (via a method called message/object pooling
discussed later). Not only does the network processes in route
messages, it also binds client connects to game objects (mainly
characters). The network then dispatches the messages to the object
with a special client-specifying message. Although this can also be
manipulated to include other objects such as items or rooms (not just
characters). All objects that can be manipulated by clients exposes a
command interface that is used for client manipulation.  Scripts to
manipulate the objects programmatically can also use this
interface. For example: the Mobile object exposes a message-handling
interface that includes a talk command. In a script, a NPC can use the
exact same speaking interface that players use to communicate.

Client-Binding

All clients must always be binded to some object that exposes a
command interface. Even if this object is not an in-game
representation of something (such as a character). Other interface
objects may include a character loader object, a shopping object, or a
menu object.

Game Objects

Game objects are anything that exists within the game
framework. Examples include items, characters, creatures, and rooms.
Objects must also include built-in object serialization in their
definition. They must also implement message handling of one kind or
another. Objects can alternatively implement a command interface.
NOTE: These are the only two ways the framework has direct access to
the object. This may seem like a dire restriction. However, upon
closer inspection, it is really all an object could possibly need. All
objects implement a standard interface for generic object
manipulation.

Object Pools

Objects obviously need a way to be grouped together in a programmatic
fashion. Internally, they are structured as a linked list. However,
object pools expose more general (albeit useful) functionality. Object
pools are never independent entities (except in some extreme examples
such as the global object pool). Instead, they are always composed
inside a proxy class.  The proxy object then implements indirect
manipulation of the object pool.  Besides standard link-list
manipulation, pools also implement common game-specific collection
information such as a ContentDescription(string lists) property. Also,
even though object pools are hidden from outside object manipulation,
the objects inside the pool still pick up game messages. This is
implemented by a mandatory registration of all object pools upon
creation of its proxy object. Of course in some object
pools(i.e. ContainmentPools), the pool objects loose access to most
game messages. It only seems practical that an item inside a closed
backpack is inaccessible from the outside world. (even though messages
sent from inside the backpack can be picked up.)

Object Ownership

The framework supports object-to-object (OtO) ownership. One should
note that there is a distinct difference between object inheritance
and ownership (more on that later). If ownership resembles anything,
it is fancy definition by composition. OtO ownership comes in a
distinct variety of flavors. These include containment, possession,
and relevance.

  a.. Containment - the object is inside another object
    a.. A weapon is inside a backpack.

  b.. Possession - the object has exclusive control of another object
    a.. A player possesses a weapon.

  c.. Relevance - the object has a child-like relationship to another
  object.
    a.. Exits are relevant to specific rooms.


Object ownership is implemented using object pools. These pools are
derived from the more generic object pool definition. They are
creatively named ContainmentPool, PossessionPool, etc.

The Life of an Object

Generally, all objects live the same life inside the program. They are
created by the Object Factory, load their data from disk, sit around
processing messages, and eventually die. Of course, the most
interesting part is the message processing. All objects are inherently
aware. They know when they change ownership, when to update
themselves, and when they are about to be destroyed among other
things. Many times, objects will never need to process certain
messages. Either these messages will never get sent to the object, or
the object doesn't care. For example a room object will never receive
a MM_OWNERCHANGE message, and a broadsword shouldn't care what it
belongs to whether it be the ground or some crazy dwarf. Of course, it
could care. Maybe this broadsword doesn't like dwarves? The point is,
the broadsword can pick and choose what it thinks is important solely
from the broadsword's point of view.

Immediate Message Pools (IMPs)

There is also another special kind of object pool called a message
pool. Message pools are used for game efficiency. The name is rather
misleading because messages aren't really stored in the pool. However,
messages can use the pools for faster resolution to their target. The
idea lies in the fact that there could be thousands, if not millions,
of objects in a standard mud at any given time. It makes no sense to
have a player execute a move message and the dispatcher then has to
look up the target(amongst thousands of other objects) to get the
room. There should be a way to achieve faster resolution of the most
common game messages that will be sent all of the time. This problem
only exists because there is no direct upward transversal of object
ownership. So why isn't there upward transversal?


Why Child-Parent Relationships Don't Work

I believe that introducing such object variables would take away from
the extensibility of the system, and make programming the system
harder. The parent ship of an object from the child's prospective can
become blurry. Let's say a kobold is holding a short sword. Does the
sword belong to the kobold? Intuitively, yes it does. But it could
actually belong to the kobold's hand. Maybe it belongs to the body of
the kobold, and then the body belongs to the kobold object. In this
scenario, the exact relationship of the objects must be known for the
short sword to tell the kobold something (Whatever the dire message
may be!).  Sure, this simple example could easily be resolved with a
little standardization. However, it relies on assumptions. And
assumptions can turn out to be bad.

Let's step through a little scenario. The kobold object has a hand
object in its object pool. The hand object then has the short
sword. In battle a crazy dwarf chops off the kobold's hand. The hand
still has the sword, but it's lying on the ground. The kobold then
runs away in fear. Now, let's say the short sword processes a time
message, and it decides that it's going to rust. The sword tells its
parent, the hand, that it's rusting. The hand doesn't care, so in turn
it sends the message to its parent (the kobold). But now the kobold is
not here! We've got to make the hand understand this, or else the
kobold miles away will know about its old sword rusting!  I'm sure you
can see where this is going. Nowhere! If something changed, everything
below it would need to be changed as well, and everything would become
tightly coupled.

Enter IMPs and the Message Framework

Instead of the method I just described, I will introduce an
alternative approach. The short sword rusts. It then screams, "I'm
rusting!" The sword's "voice" can only reach the room it is in. Every
object in the room hears the sword's cry. A goblin corpse hears it and
asks, "Is that sword mine? No! I don't care." Alternatively, if the
arm were attached, the kobold would recognize it was its sword, and
the appropriate text message could be sent "Your short sword is
rusting." IMPs are the medium level containment devices that are used
to dispatch local game messages: a medium between the extremes of a
game-wide message and a message sent specifically to a target. A good
way to look at it is like this. The IMP is every object's "parent." 
They don't really exist in game, so practical rules don't apply. There
are basically 3 levels of IMPs: system(SMPs), zone(ZMPs), and
room(RMPs). An IMP has access to the top-most object in its "level." 
IMPs serve as message safety nets. IMPs also implement message
"scope."  Depending on the "scope specifier" of a message, it can die
at any particular IMP. All message are processed from RMP to ZMP to
the SMP.

Message Scope Specifiers:

  RMP_FT        - room message pool fall through. If a room objects 
                does not process the message, it falls through to a
                ZMP and then to the SMP.

  RMP_NFT       - no fall through. If a room object does not process 
                the message, it dies instantly.

  ZMP_FT        - processing begins on the zone level with fall 
                through.

  ZMP_NFT       - processing only occurs in the ZMP

  SMP           - processing only occurs on the system level.

Messages can also specify their stop point. A message can die once it
finds an object to process it, or the message can go through the
entire IMP net.

Message Stop Point Specifiers:

  a.. DOOC      - Die on Object Contact
  b.. DOMP      - Die on the Message Pool

Specialized Message Pools

The Framework also makes heavy use of MPs, although on a more
specialized level. For example, the network maintains a Player Message
Pool (a pool that includes references to all players). This allows for
the bypassing of the message framework for certain situations. Other
uses of message pools will be mentioned as needed.

Message Pools and Object Pools: The Relation

Let's run through another scenario. A player drops an axe on the
ground. First, the actual item is removed from the player's object
pool. The object pool then sends a message to the axe object. This
message is sent directly to the axe, because the pool has the object
reference. If the axe needs to do anything special when it's dropped
(like maybe turn to dust), it receives this message now. The player
object then send two messages. First it sends a text message to the
player's client and then a MM_RNMMSG (Room Not Me Message) RMP_NFT
DOMP saying that it's dropping the axe. The room object pool picks up
the message and dispatches it to all objects in the room. As the
message is iterated through the object pool, the objects ask, "Did I
send this message? No? Send the message. Yes? Do nothing." When the
object answers, "No. Send the message.", another message is generated.

Network Object Pools : A Specialization

There is a big difference between message dispatching via the network
and other game objects. Because network I/O is a very big part of a
MUD game, the network maintains a large connection of very small
object pools (RelevancePools to be specific). These pools are actually
pairs. They include socket reference objects (low level network) and
another object that exposes a command interface (like a game
character). Also, all command interface objects include an object pool
reference to their network pool counterpart. This gives direct access
to/from the client connection. In this care, the object pool is
actually a very specialized message pool.  Thus this is called
object/message pooling.


The Notifier and IMPs

Early in this paper, I said that the Notifier "dispatches messages." 
How does it fit in with IMPs? Well, the Notifier is the master of the
IMPs. It handles the creation of IMPs, the relationships of different
MPs, and the MP hierarchy. The notifier also provides a backdoor for
the system into the game world. By using the Global Object Pool (All
of the objects in the game) GOP, and the MP hierarchy, a system
routine can observe, intercept, and send game messages. Common uses of
the Notifier include scanning all text for cuss words, detecting
spamming, routine system rebooting, sending administrators important
texts, and other daemon-like tasks.
_______________________________________________
MUD-Dev mailing list
MUD-Dev at kanga.nu
https://www.kanga.nu/lists/listinfo/mud-dev



More information about the mud-dev-archive mailing list