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:
command (push nudge)— fires only for those command keywords.spell (42 30 126)— fires only for those spell numbers.spell ('lightning bolt' 'chain lightning')— spell names also accepted.
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:
beforeruns before the default action. Advisory: the default action proceeds regardless.handleruns in place of / alongside the default action and is the phase that can intercept it (below).afterruns after the default action completes.
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:
- a filter that does not match (the handler is skipped), or
- a
require [cond]that is false, or anunless [cond]that is true (the guard abandons the current handler and advances to the next).
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.