MOO is an object-oriented language with prototype-based inheritance. Objects contain properties (data) and verbs (code).
| Component | Description |
|---|---|
| ID | Unique integer identifier (#0, #1, ...) |
| Name | String name property |
| Owner | Object that owns this object |
| Parents | List of parent objects (inheritance) |
| Children | List of child objects |
| Location | Object this is contained in |
| Contents | Objects contained in this |
| Flags | Permission and state flags |
| Properties | Named values with permissions |
| Verbs | Named code blocks with permissions |
#0 // System object (root)
#1 // First created object
#-1 // Common "nothing" sentinel
#-2 // Waif class markerSpecial objects:
#0- System object, accessed via$$nothing- Typically#-1$failed_match- Typically#-2$ambiguous_match- Typically#-3
| Flag | Value | Description |
|---|---|---|
| USER | 1 | Is a player object |
| PROGRAMMER | 2 | Can write/edit code |
| WIZARD | 4 | Full administrative access |
| READ | 16 | Object is readable |
| WRITE | 32 | Object is writable |
| FERTILE | 128 | Can be used as parent |
| ANONYMOUS | 256 | Anonymous (garbage-collected) |
| INVALID | 512 | Object has been invalidated |
| RECYCLED | 1024 | Object slot is recycled |
WIZARD > PROGRAMMER > USER > (no flags)
Wizard: Can do anything Programmer: Can write verbs, create objects User: Can login, execute commands None: Limited to public access
child = create(parent);
// child inherits from parentchild = create({parent1, parent2});
// child inherits from both parentsAlgorithm: Breadth-first search, left-to-right order.
For property/verb lookup with parents {A, B} where A has parent X and B has parent Y:
Search order: obj → A → B → X → Y
Implementation:
- Start with queue containing [obj]
- Pop from front of queue (FIFO)
- Check for property/verb on current object
- If found, return it (first match wins)
- If not found, append current object's parents to end of queue
- Repeat until queue is empty or match found
- Track visited objects to prevent infinite loops in cycles
Diamond inheritance example:
D
/ \
B C
\ /
A
If A has parents {B, C}, and both B and C inherit from D:
- Search order: A → B → C → D
- If both B and D define property
x, B's value is found first - D is visited only once despite being reachable through both paths
Key properties:
- First match wins (left-to-right precedence)
- Each object visited at most once (cycle-safe)
- All immediate parents checked before any grandparents (breadth-first)
| Function | Purpose |
|---|---|
parent(obj) |
Get first parent |
parents(obj) |
Get all parents (list) |
children(obj) |
Get all children |
ancestors(obj) |
Get all ancestors |
descendants(obj) |
Get all descendants |
chparent(obj, new_parent) |
Change single parent |
chparents(obj, parents_list) |
Change parent list |
isa(obj, parent) |
Check inheritance |
Properties are named values attached to objects.
// Access
value = obj.property_name
value = obj.(expr) // Dynamic name
// Assignment
obj.property_name = value
obj.(expr) = value| Permission | Bit | Description |
|---|---|---|
| Read | r | Property can be read |
| Write | w | Property can be modified |
| Chown | c | Property owner can change |
Defined properties: Exist on the object itself Inherited properties: Come from parent(s)
// Check where property is defined
property_info(obj, "name")
// Returns: {owner, perms} or E_PROPNFA property can be "cleared" to explicitly inherit from parent:
clear_property(obj, "name");
// Now reads from parent
is_clear_property(obj, "name"); // Returns 1| Function | Purpose |
|---|---|
properties(obj) |
List all property names |
property_info(obj, name) |
Get owner and perms |
add_property(obj, name, value, info) |
Add new property |
delete_property(obj, name) |
Remove property |
set_property_info(obj, name, info) |
Change perms |
clear_property(obj, name) |
Clear to inherit |
is_clear_property(obj, name) |
Check if cleared |
Every object has these:
| Property | Type | Writable | Description |
|---|---|---|---|
.name |
STR | Yes | Object name |
.owner |
OBJ | Yes* | Object owner (*via chown) |
.location |
OBJ | No** | Container object (**via move) |
.contents |
LIST | No** | Contained objects (**via move) |
.programmer |
INT | Yes | Programmer flag (0/1) |
.wizard |
INT | Yes* | Wizard flag (0/1, *wizards only) |
.r |
INT | Yes | Read flag (0/1) |
.w |
INT | Yes | Write flag (0/1) |
.f |
INT | Yes | Fertile flag (0/1) |
Mutability notes:
.locationand.contentsare read-only. Attempting to write raises E_PERM.- These are modified only via
move()operations. - Flag properties (programmer, wizard, r, w, f) treat any non-zero value as true; only 0 clears the flag.
Verbs are named code blocks attached to objects.
// Call syntax
obj:verb_name(args)
obj:(expr)(args) // Dynamic name| Permission | Bit | Description |
|---|---|---|
| Read | r | Verb code can be read |
| Write | w | Verb code can be modified |
| Execute | x | Verb can be called |
| Debug | d | Debug info available |
Verbs can specify expected arguments:
| Specifier | Meaning |
|---|---|
this |
Called on this object |
none |
No direct object |
any |
Any value accepted |
- Parse command:
verb dobj prep iobj - Find verb on dobj's ancestor chain
- Check verb argument specifiers match
- Check execute permission
- Call verb with context variables
Inside a verb, these are automatically set:
| Variable | Type | Description |
|---|---|---|
this |
OBJ | Object verb is defined on |
player |
OBJ | Player who initiated action |
caller |
OBJ | Object that called this verb |
verb |
STR | Verb name as called |
args |
LIST | Arguments passed |
argstr |
STR | Original argument string |
dobj |
OBJ | Direct object |
dobjstr |
STR | Direct object string |
iobj |
OBJ | Indirect object |
iobjstr |
STR | Indirect object string |
prepstr |
STR | Preposition string |
| Function | Purpose |
|---|---|
verbs(obj) |
List all verb names |
verb_info(obj, name) |
Get verb metadata |
verb_args(obj, name) |
Get argument specs |
verb_code(obj, name) |
Get verb source |
add_verb(obj, info, args) |
Add new verb |
delete_verb(obj, name) |
Remove verb |
set_verb_info(obj, name, info) |
Change metadata |
set_verb_args(obj, name, args) |
Change arg specs |
set_verb_code(obj, name, code) |
Change source |
new_obj = create(parent);
new_obj = create(parent, owner);
new_obj = create({parent1, parent2});Semantics:
- Allocate new object ID
- Set parent(s)
- Set owner (caller if not specified)
- Inherit properties from parent(s)
- Call
initializeverb if defined
recycle(obj);Semantics:
- Call
recycleverb if defined - Clear all properties
- Remove all verbs
- Remove from parent's children
- Remove from location's contents
- Mark object slot as recycled
valid(recycled_obj)returns0- Accessing recycled object raises
E_INVIND - Object ID may be reused later
recreate(obj_id, parent);Semantics:
- Recreate a recycled object slot
- Must be wizard to use
- Object gets new parent and properties
Objects can contain other objects:
obj.location- What contains thisobj.contents- What this contains
move(what, where);Semantics:
- Remove
whatfrom old location's contents - Set
what.locationtowhere - Add
whattowhere.contents - Call
exitfuncon old location (if defined) - Call
enterfuncon new location (if defined)
Errors:
E_RECMOVE: Can't move object into itself/descendantE_PERM: No permission to move
| Function | Purpose |
|---|---|
move(what, where) |
Move object |
contents(obj) |
Same as obj.contents |
locations(obj) |
Chain of containing objects |
occupants(location) |
All objects inside (recursive) |
valid(obj)Returns:
1if object exists and is not recycled0if object is recycled or never existed
| Condition | Test |
|---|---|
| Exists | valid(obj) |
| Is player | is_player(obj) |
| Is programmer | obj.programmer |
| Is wizard | obj.wizard |
| Inherits from | isa(obj, parent) |
anon = create(parent, $nothing, 1); // Third arg = anonymous- No persistent ID (garbage collected)
- Cannot be stored in database permanently
- Collected when no references remain
- Useful for temporary data structures
Anonymous objects with circular references are detected and collected.
w = new_waif();- Lightweight (less overhead than objects)
- Prototype-based
- Properties accessed via
.:syntax - Garbage collected
w.:name = "example";
value = w.:name;All waifs inherit from their "waif class" object, which provides:
- Default property values
- Verb implementations
Every object has an owner:
- Owner has full control
- Owner can grant permissions to others
| Action | Required |
|---|---|
| Read property | Owner, or r flag |
| Write property | Owner, or w flag |
| Call verb | Owner, or x flag |
| Read verb code | Owner, or r flag |
| Modify object | Owner or wizard |
| Create child | Owner/wizard, and f flag |
caller_perms() // Returns permission object
set_task_perms(obj) // Change task permissionsWizards can:
- Access any property
- Call any verb
- Modify any object
- Use privileged builtins
The system object is accessed via $ in MOO code. It is the root of the object hierarchy and contains server hooks.
Required Properties:
| Property | Type | Description |
|---|---|---|
server_options |
MAP | Server configuration (see §12.4) |
maxint |
INT | Maximum integer value: 9223372036854775807 (2^63-1, 64-bit signed) |
minint |
INT | Minimum integer value: -9223372036854775808 (-2^63, 64-bit signed) |
nothing |
OBJ | "Nothing" sentinel (typically #-1) |
failed_match |
OBJ | Failed match sentinel (typically #-2) |
ambiguous_match |
OBJ | Ambiguous match sentinel (typically #-3) |
Required Verbs:
| Verb | Signature | When Called |
|---|---|---|
server_started |
() |
After database load, before connections |
checkpoint_started |
() |
Before database checkpoint |
checkpoint_finished |
(success) |
After checkpoint (1=ok, 0=fail) |
user_connected |
(player) |
Player successfully logged in |
user_disconnected |
(player) |
Player connection closed |
user_reconnected |
(player) |
Player reconnected (replaced connection) |
do_login_command |
(conn, line) |
Command from unlogged connection |
Common base objects (conventional, not required):
$nothing // #-1, parent for orphan objects
$room // Base for locations
$thing // Base for portable objects
$player // Base for player objects
$exit // Base for exit objects
$container // Base for containersThe login object (often $login or pointed to by #0.login) handles authentication.
Typical Verbs:
| Verb | Purpose |
|---|---|
authenticate |
Verify username/password |
create_player |
Create new player object |
welcome_message |
Return welcome text |
connect_message |
Return "use connect or create" text |
#0.server_options is a MAP controlling server behavior:
| Key | Type | Default | Description |
|---|---|---|---|
bg_ticks |
INT | 30000 | Background task tick limit |
bg_seconds |
INT | 3 | Background task time limit |
fg_ticks |
INT | 60000 | Foreground task tick limit |
fg_seconds |
INT | 5 | Foreground task time limit |
max_stack_depth |
INT | 50 | Maximum call stack depth |
connect_timeout |
INT | 300 | Seconds before unlogged connection times out |
dump_interval |
INT | 3600 | Seconds between automatic checkpoints (alias: checkpoint_interval) |
checkpoint_interval |
INT | 3600 | Seconds between automatic checkpoints (alias: dump_interval) |
max_queued_output |
INT | 65536 | Max bytes buffered per connection |
name_lookup_timeout |
INT | 5 | DNS lookup timeout |
protect_* |
INT | 0/1 | Builtin function protection flags |
Note: dump_interval and checkpoint_interval are aliases for the same setting. Implementations should accept both names. If both are set, checkpoint_interval takes precedence.
A minimal MOO database requires:
-
#0 (system object) with:
server_optionsproperty (empty map is valid, server uses defaults)do_login_commandverb (can just return 0 to deny all logins)server_startedverb (can be empty)
-
At least one wizard object (recommended but not strictly required):
- WIZARD flag set (for bootstrapping)
- Allows use of wizard-only builtins to build the database
Type validation: Setting server_options to invalid values (wrong types, out-of-range) will cause load_server_options() to raise E_INVARG.
- Optional but conventional:
$nothing(#-1) for sentinel$roomas location base$playeras player base
No builtin bootstrap mechanism is required. Creating a minimal database from scratch requires manual database file creation or external tooling.
Recommended bootstrap approach:
- Create empty database file with #0 (system object)
- Add minimal required properties (
server_optionsas empty map) - Add stub verbs for hooks (
do_login_command,server_started) - Create wizard object for administration
- Save database
Implementations may provide:
- Command-line tool to create minimal database
- Database generation utility
- Example minimal database file
MOO code cannot bootstrap itself - the server must load a database before executing MOO code.
type Object struct {
ID int64
Name string
Owner int64
Parents []int64
Children []int64
Location int64
Contents []int64
Flags ObjectFlags
Properties map[string]*Property
Verbs map[string]*Verb
Anonymous bool
}type Property struct {
Name string
Value Value
Owner int64
Perms PropertyPerms
Clear bool // Inheriting from parent
}type Verb struct {
Name string
Owner int64
Perms VerbPerms
ArgSpec VerbArgs
Code []string // Source lines
Compiled *Program // Bytecode (cached)
}func (db *Database) FindProperty(obj int64, name string) (*Property, error) {
visited := make(map[int64]bool)
return db.findPropertyRecursive(obj, name, visited)
}
func (db *Database) findPropertyRecursive(obj int64, name string, visited map[int64]bool) (*Property, error) {
if visited[obj] {
return nil, nil // Cycle detection
}
visited[obj] = true
o := db.GetObject(obj)
if prop, ok := o.Properties[name]; ok && !prop.Clear {
return prop, nil
}
for _, parent := range o.Parents {
if prop, err := db.findPropertyRecursive(parent, name, visited); prop != nil {
return prop, nil
}
}
return nil, ErrPropNotFound
}