Chapter 6: Defining functions

When your script repeats the same logic in multiple handlers, you can extract it into a named function with def. This chapter covers def, top-level const, blocks as values, and how to organize larger scripts.

Defining a function with def

def creates a named command you can call like a builtin:

def greet { <person>
  do "say Hello, [name $person]! Welcome to the shop."
  do "bow [name $person]"
}

after enter {
  require [isplayer $actor]
  greet $actor
}

after command (say) {
  require [keyword $args hi hello]
  greet $actor
}

def is top-level only — it must be outside any handler. It cannot be nested inside a handler body.

Named constants with const

const binds a name to an immutable value:

const QUEST_VNUM 5001
const REWARD_VNUM 5020
const GREETING 'Welcome, adventurer!'

after enter {
  do "say $GREETING"
}

after command (say) {
  require [keyword $args quest]
  if [isholding $actor $QUEST_VNUM] {
    oload $REWARD_VNUM $actor
    do "say Here is your reward!"
  }
}

Using constants makes scripts easier to update — change the vnum in one place instead of hunting through every handler.

Top-level structure

A script file has three kinds of top-level forms, in any order:

  1. def name { ... } — function definitions.
  2. const name value — named constants.
  3. phase event { ... } — handlers.
# Constants at the top
const SHOP_GREETING 'Welcome to my shop!'

# Helper functions
def announce { <msg>
  echo [room $self] $msg
}

# Handlers
after enter {
  require [isplayer $actor]
  announce $SHOP_GREETING
}

Blocks as values

A block { ... } is a value, like a number or a string. You can store it in a variable and call it later:

after command (say) {
  require [keyword $args test]
  let action { do "say This came from a block!" }
  [$action]
}

[$action] calls the block stored in $action. This is how you pass behavior around — for example, as a predicate to select or each.

Blocks with parameters

Blocks can take parameters, declared with <...>:

let double { <n> [* $n 2] }
let result [$double 5]
do "say Double of 5 is $result"

The & sigil

&name takes a def'd function and returns it as a block value, useful when you need to pass a function to something like select:

def is_warrior { <c>
  [eq [class $c] 'Warrior']
}

after command (say) {
  require [keyword $args warriors]
  let warriors [select [people [room $self]] &is_warrior]
  each $warriors { <w>
    do "say [name $w] is a warrior."
  }
}

Without &, the bare name is_warrior in argument position would be the string "is_warrior". &is_warrior gives you the function as a value to pass to select.

Closures

Inner blocks can read variables from their enclosing scope:

after command (say) {
  require [keyword $args above]
  let threshold [level $actor]
  let stronger [select [people [room $self]] { <c>
    [gt [level $c] $threshold]
  }]
  each $stronger { <c>
    do "say [name $c] is above level $threshold."
  }
}

The block { <c> [gt [level $c] $threshold] } can see $threshold from the enclosing handler body. This is a closure — the block captures the enclosing variable.

def functions cannot see $actor or $self

def functions live at the top level, outside any handler. They do not see handler-injected variables like $actor or $self — those only exist inside a handler body. If your function needs them, pass them as arguments:

def tell { <target msg>
  do ">$target $msg"
}

after command (say) {
  require [keyword $args help]
  tell $actor "I offer: heal, bless."
}

Example: a mob with multiple services

const HEAL_COST 100

def tell { <target msg>
  do ">$target $msg"
}

after command (say) {
  require [keyword $args heal]
  tell $actor "I can heal you for $HEAL_COST gold."
  tell $actor "Say 'pay heal' when ready."
}

after command (say) {
  require [keyword $args bless]
  tell $actor "I will bless you freely."
  spell $actor 30 bless
  tell $actor "There you go."
}

after command (say) {
  require [keyword $args help services]
  tell $actor "I offer: heal, bless."
  tell $actor "Say the service name to learn more."
}

The tell function takes the target as its first argument, avoiding do ">$actor ..." in every handler.

Key points

Next: Chapter 7: Advanced handlers