Program initialization and execution

A script is compiled once, when its owner entity loads. A script that defines no handlers produces no response to events — def/const declarations alone are inert.

Top-level structure

A script is a sequence of top-level forms (Declarations and scope):

Script = { Newlines } { (Def | ConstTop | Handler) Newlines } .

Anything else at the top level is a compile-time error.

Initialization

Top-level declarations are prepared as described in Declarations and scope. Initialization runs once, the first time an event fires on the owner. Once initialization has completed (or errored), it never runs again; an error during initialization aborts the current execution.

Handlers

A handler binds a block to a phase and an event on the owner:

Handler = Phase Event [ Filter ] Block .
Phase   = 'before' | 'handle' | 'after' .
Filter  = '(' { word } ')' .

The handler body is a block invoked with zero arguments; instead, the event provides the per-event injected names ($self, $actor, …) before the body runs. These are ordinary lexical bindings, visible to the body and to blocks nested inside it (Declarations and scope). Consequently a handler body may not declare a <...> parameter list (not even the empty <>) — a compile-time error.

Events

The recognized events are:

command  idle     fight    give    chat    enter   leave   load
tick     spell    combat   death   wear    remove  put     get
getfrom  drop     wield    eat     drink   sacrifice  search

The item-manipulation events (wear, remove, put, get, getfrom, drop, wield, eat, drink, sacrifice) all fire on the object being acted on: a handler attached to that object's script fires, with $self bound to the object and $actor to the creature performing the action. For put and getfrom, $object is bound to the container; for the rest it is left unbound. (The container's own script is not invoked — only the moved item's.) The default action is the normal completion of the verb (the item being equipped, removed, transferred, consumed, etc.), and is suppressed in the usual way if the handle body marks the event handled — see Interception.

The search event fires on the object being searched when a player runs search <obj>. The object's script is invoked with $self bound to the object and $actor to the searcher; no $object is bound. An intercepting handle search suppresses the default object description (the same text look <obj> would have shown). search fires only for the search command keyword, not for look or examine.

Per-event bindings

Every handler body receives $self, the script's owner (a creature, object, or room). In addition, the event provides these bindings before the body runs:

Event(s) Bindings (besides $self)
command $actor (the commander), $arg (the raw post-verb line as a string), $args (a word iterable over the same line)
spell $actor (the caster), $spell (int spell number)
give $actor (the giver), $object (the item given)
put, getfrom $actor, $object (the container); the moved item is $self
get, drop, wear, remove, wield, eat, drink, sacrifice, search $actor; the item is $self ($object is unset)
enter, leave, chat, fight $actor
death $actor (the deceased — see Death below), $killer (may be null)
load, tick, idle, combat (none beyond $self)

$actor is the canonical name for "the creature that triggered the event." For command handlers, the post-verb tail is available in two shapes: $arg is the raw string (use streqi, eq, interpolation in do lines); $args is the same content as a word iterable, read with first, count, keyword, each, etc. (Built-in functions). $arg and $args are special only inside a command handler; elsewhere they are ordinary names.

after command (say sayto) {
  require [keyword $args chat gossip]
  store $self 'state' 'gossip'
}

These bindings follow the lexical-scope rules in Declarations and scope:

handle command (push) {
  each $args { <a> do "say arg from [name $actor]: $a" }
}

Death

The death event covers both "I am dying" and "someone died in my presence" — the role is distinguished by $actor identity rather than by event name. All three phases bind the same names: $self is the script's owner; $actor is the deceased; $killer is the killer (may be null for deaths with no attributable killer, such as drowning or deathtraps). The dier's own handler sees $self == $actor; the dier detection idiom is therefore [eq $actor $self].

Audience. All three phases fire on the dier, the room, and every other mob in the room with a script — in that order. The dier receives its own after death handler; this is well-defined because the deceased remains in the world — dead, but not yet removed — through the after phase, and is removed only once it completes.

Intercept. handle death can abort the death: any successful action command in the body (Interception) suppresses the death. A suppressed death skips the death cry, the corpse, the removal, and the after death phase entirely. HP is not restored automatically — the intercepting script is responsible for healing the dier (e.g. via damage with a negative amount, or a heal spell). Handle ordering is dier → room → witnesses; the first handler that suppresses short-circuits all subsequent handle handlers.

after death placement. The dier is removed only after the death event is fully processed, so after death runs while $actor still resolves cleanly to the (now dead) creature.

before death {
  if [eq $actor $self] {
    do "say I... regret nothing!"
  }
}

handle death {
  require [eq $actor $self]
  require [has $self 'phylactery']
  damage $self -100
  do "emote's wounds knit themselves shut."
}

after death {
  do "emote bows over the body of [name $actor]."
}

Filters

A parenthesized filter restricts which event instances fire the handler:

A filter item that is not a recognized command or spell is a compile-time error. The events tick, load, idle, and combat have no natural selector and reject any filter at compile time. A handler with no filter fires for every instance of its event.

Phases

Each event can be handled in up to three phases:

Interception

The default action is suppressed when the handle-phase body has produced a successful action command (Built-in functions). A command that fails to take effect (for example an oload of a vnum that does not exist) does not count.

Commands that only affect computation, variables, or execution flow never suppress the default action. This includes store/recall: persistent entity state is the handler's own bookkeeping, in the same category as let/set, not a world-altering action.

There is no explicit mechanism to block an event; suppression is determined solely by whether an action command succeeded. before and after handlers never suppress the default action.

require / unless and handler chaining

Multiple handlers may exist for the same (phase, event); they are tried in source order. A handler runs to the end of its body and then the script terminates — there is no implicit fall-through to the next handler. The only ways to advance to the next matching handler are:

If there is no next handler, the script terminates.

after command (say sayto) {
  require [hasabbrev [first $args] [alias [mob 1200]]]
  require [keyword $args equip me]
  do ">$actor Sure thing, bud."
}

After-handler timing

An after handler observes the post-event world state. Its output is delivered before the player's next prompt, so a coherent turn — event output, after-handler output, prompt — is presented in order.

Self-suppression

For command, enter/leave, and spell events, a handler does not fire when the owner mobile is itself the actor (a mob does not trigger its own command/move/spell handlers); chat similarly skips the speaker being the owner. This prevents a script from reacting to its own induced actions.