Handlers, events, and phases

A script does nothing on its own. It reacts to events — someone says something, a creature enters the room, a tick passes — through handlers. A handler binds a block to one phase of one event on the script's owner.

after command (say) {
  echo $actor "A cold draft raises the hair on your neck."
}

This handler fires after someone in the room uses the say command. A script with no handlers never responds to anything; def/const declarations alone are inert.

Handler shape

<phase> <event> [ (<filter>) ] { <body> }
handle command (push nudge) { ... }      # only the push and nudge commands
spell (42 30) { ... }                      # only those spell numbers
after tick { ... }                         # no filter allowed

Phases

Each event can be handled in up to three phases, which run around the event's default action (the normal effect of the command or event):

Suppression (interception)

When a handle-phase body performs a successful action command — an echo, do, emit, oload, trans, and so on — the event's default action is suppressed: it does not happen. That is how a handle handler replaces the normal behavior with its own.

A command that fails (for example an oload of a vnum that does not exist) does not suppress. And commands that only touch computation, variables, or flow — let, set, store/recall, if — never suppress: persistent state is the handler's own bookkeeping, not a world-altering action. before and after handlers never suppress.

There is no explicit "block this event" command; suppression is decided solely by whether a handle-phase action command succeeded.

Event kinds

There are 23 event kinds, grouped below by what makes them fire. Each entry names the circumstance that triggers the event; for the names it binds, see Bound variables.

Creature actions in the owner's reach

These fire on a mob or room when a creature acts within it. The owner is never triggered by its own actions (see Self-suppression).

Item manipulation

These fire on the object being acted on — the handler lives on that object's script, with $self the object and $actor the creature acting.

Combat and death

Time and lifecycle (no actor)

Phase availability. Most events support all three phases, but the restricted ones above do not: fight and load run only in after; idle, combat, and tick run only in handle. A handler written for a phase an event never fires in is simply dead — it never runs.

Bound variables

A handler body never declares parameters. Instead the event injects names into the body's scope before it runs. Every handler gets $self, the owner; the rest depend on the event:

Event(s) Bound (besides $self)
command $actor (who ran it), $arg (the raw post-verb line, a string), $args (the same line as a word iterable)
spell $actor (caster), $spell (int spell number)
give $actor (giver), $object (the item)
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
enter, leave, chat, fight $actor
death $actor (the deceased), $killer (may be null)
load, tick, idle, combat (none beyond $self)

$self is the script's owner — a creature, object, or room. $actor is the canonical "the creature that triggered this." Reading a name the event does not provide — $actor in a tick handler, say — is a run-time error.

$arg and $args are two views of a command handler's post-verb text:

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

The block sees $actor because the injected names are lexical: nested blocks see them through their enclosing scope. (A top-level def does not — pass it what it needs.)

Death

All three death phases bind the same names: $self (owner), $actor (the deceased), $killer (may be null). The dier's own handler sees $self == $actor. A handle death can abort the death by performing a successful action command — but it must heal the dier itself (HP is not restored automatically).

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

Guards

Multiple handlers can match the same (phase, event). They are tried in source order, and a handler that runs its body to the end terminates the script — there is no implicit fall-through to the next handler. The only ways to advance to the next matching handler are:

The two guards are require and unless. Each takes a single bool condition and no body:

require [cond]      # if cond is false, abandon this handler, try the next
unless  [cond]      # if cond is true,  abandon this handler, try the next

Put guards at the top of a handler to state its preconditions. If a guard fails, the script moves on to the next matching handler; if there is none, the script ends.

after command (say) {
  require [ge [level $actor] 10]
  do "say only veterans hear this."
}

Only an actor of level 10 or higher reaches the do; a lower-level actor fails the require and the handler is abandoned. unless is the negation — unless [isplayer $actor] abandons the handler when the actor is a player.

Chaining guards lets several handlers share an event, each claiming the cases it wants:

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

Cross-tick handlers

A handler normally runs to completion within one game tick. To stretch across ticks, use pause n, which suspends the handler for n ticks and resumes with its locals intact:

after enter {
  do "bow $actor"
  pause 3
  do "emote points to a sign."
}

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 react to its own induced actions (and chat skips the speaker being the owner). This keeps a script from triggering itself in a loop.

See also