Chapter 7: Advanced handlers

This chapter covers the three handler phases, how multiple handlers chain together, how to intercept the default action, and more event types.

The three phases

Every event has three phases:

before command (drop) {
  do "say Are you sure you want to drop something?"
}

after command (drop) {
  do "say You dropped it!"
}

The before handler fires before the item is dropped. The after handler fires after. In both cases, the drop still happens normally.

Intercepting with handle

A handle handler can suppress the default action. If the handler body executes a successful action command (like do, oload, trans, etc.), the default action is suppressed:

handle command (north) {
  do "say You shall not pass!"
}

When a player types north, the mob says the line and the do counts as a successful action — so the actual movement north is suppressed. The player stays in the room.

Compare this with after:

after command (north) {
  do "say Safe travels!"
}

Here the player moves north normally, and the mob speaks after.

What counts as an action

Commands like do, force, oload, mload, trans, damage, spell, tag, echo, emit are action commands. If they succeed, they suppress the default.

Commands that only affect variables or flow control (let, set, store, recall, require) do not count as actions and do not suppress anything.

Multiple handlers and chaining

You can have several handlers for the same phase and event. They are tried in source order. A handler that runs to completion stops the chain — later handlers are not reached.

The chain advances to the next handler only when:

after command (say) {
  require [keyword $args quest]
  do "say Let me tell you about the quest."
}

after command (say) {
  require [keyword $args shop]
  do "say The shop is to the east."
}

after command (say) {
  do "say I don't understand. Try 'quest' or 'shop'."
}

If the player says "quest", the first handler matches and runs — the others are skipped. If the player says "shop", the first handler's require fails, advancing to the second, which matches. If the player says anything else, both require guards fail, and the third handler (the fallback) runs.

This pattern gives you an if/elif/else chain across handlers.

The unless guard

unless is the inverse of require:

after enter {
  unless [isplayer $actor]
  do "say A mortal approaches."
}

Wait — that is backwards. unless advances to the next handler if the condition is true. So this says: "unless the actor is a player, skip to the next handler." The mob only speaks when $actor is not a player.

That means unless [isplayer $actor] is equivalent to require [not [isplayer $actor]]. Use whichever reads more naturally.

Event types

Beyond command and enter, here are the other events:

tick

Fires periodically on the mob. No $actor is available.

after tick {
  if [random 20] {
    do "say Anyone need a drink?"
  }
}

[random 20] is true 20% of the time. Tick handlers are great for ambient behavior.

fight

Fires when someone initiates combat with the mob.

after fight {
  do "say You dare challenge me, [name $actor]?"
}

combat

Fires each combat round while the mob is fighting. No $actor.

after combat {
  if [random 30] {
    do "say Feel my wrath!"
  }
}

death

Fires when a creature dies. $actor is the deceased, $killer is the killer (may be null).

before death {
  require [eq $actor $self]
  do "say I'll be back..."
}

after death {
  require [not [eq $actor $self]]
  do "emote bows solemnly."
}

The first handler fires when the mob itself dies. The second fires when someone else dies in the room.

A handle death can even prevent the death — if it performs a successful action:

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

This heals the mob and suppresses the death.

give

Fires when someone gives an object to the mob. $object is the item.

after give {
  do "say Thank you for the [name $object], [name $actor]."
}

load

Fires once when the mob is first loaded into the world. No $actor.

after load {
  do "emote stretches and looks around."
}

idle

Fires when the mob has nothing else to do. No $actor.

after idle {
  randomly { do "yawn" } or { do "stretch" } or { do "look" }
}

spell

Fires when a spell is cast. $actor is the caster, $spell is the spell number. Filters take spell numbers:

after spell (42) {
  do "say Impressive magic, [name $actor]!"
}

Object and room events

Scripts can also be attached to objects and rooms:

The randomly form

randomly picks one branch at random:

after tick {
  randomly {
    do "say What a lovely day."
  } or {
    do "say I wonder what's for dinner."
  } or {
    do "yawn"
  }
}

Each branch has an equal chance. The or keyword separates branches.

The pause command

pause suspends the script for a number of game ticks, then resumes:

after enter {
  do "say Welcome."
  pause 3
  do "say Make yourself at home."
}

The mob says "Welcome," waits 3 ticks, then says the second line. Local variables survive across the pause.

Self-suppression

A mob does not trigger its own command, enter, leave, or spell handlers. This prevents infinite loops — a do "say ..." in a command (say) handler will not re-trigger the handler.

Key points

Next: Chapter 8: Debugging