Variables, scope, and memory
Scripts need to hold values while they run and remember facts between runs. Those are two different mechanisms: variables live only for the duration of one handler, while persistent state and tags survive from one event to the next. Mixing them up is a common early mistake, so this chapter draws the line clearly.
Local variables — let, set, const
let creates a fresh, mutable local;
set rewrites an existing one;
const creates one that can never be set.
The name is written without $ when declaring; the $ appears only
when reading:
after command (tally) {
let n 0
set n [+ $n 1]
const LABEL "items"
do "say $n $LABEL counted."
}
let shadows any outer binding of the same name; redeclaring a name
already bound in the same scope is a compile-time error, and set of an
unbound name (or a const) is too.
Local variables start fresh on every invocation and vanish when the handler ends — they do not carry from one event to the next. For that you need persistent state, below.
Scope: where names are visible
Names live in two separate namespaces that never collide. A bareword in
command position (the first word of a statement, or inside [...]) resolves
in the command namespace — built-ins and your
defs. A $name resolves in the variable
namespace — parameters, let/const locals, the handler-injected names
($self, $actor, …), and top-level consts. So let count 0 does not
shadow the count built-in.
Blocks are lexically scoped: a name resolves up the chain of scopes where the block was written. A nested block sees the locals and injected names of the handler that encloses it:
after command (introduce) {
let greeting "well met"
each [/> $self room people] { <p>
do "say $greeting, [name $p]." # the block sees $greeting and $self
}
}
The counter idiom
Because set searches outward through
enclosing scopes, a block can update a variable owned by the scope around
it. That is how you accumulate across a loop:
after command (count-players) {
let total 0
each [/> $self room creatures] { <c>
if [isplayer $c] {
set total [+ $total 1] # mutates the outer 'total'
}
}
do "say I count $total players."
}
The inner block reaches total through its enclosing scope, so each pass
updates the same variable. (Structuring scripts
shows the same trick used to build a reusable counter with def.)
Persistent state — store, recall, forget
To remember something between handler runs, write it to a game entity's
persistent state: string-keyed slots attached to a creature, object,
or room. This is not the variable namespace — you never read a slot
with $name:
store<entity> <key> <value>— write a slot.recall<entity> <key>— read it, or null if never written.forget<entity> <key>— delete it.
The entity is usually $self, the owner, so its memory lives as long as it
does:
# remember a mood set on one event, react to it on another
after command (say) {
require [keyword $args calm peace]
store $self "mood" "serene"
echo $actor "$self settles into a tranquil state."
}
after command (provoke) {
if [eq [recall $self "mood"] "serene"] {
do "say I was at peace... until now."
forget $self "mood"
}
}
A first-visit greeting is the canonical use — store a counter and check it:
after enter {
require [isplayer $actor]
let seen [recall $self "visits"]
if [isnull $seen] {
echo $actor "$self studies you, a stranger."
store $self "visits" 1
} else {
echo $actor "$self nods — you have been here before."
}
}
Slots are the script's own bookkeeping; they are not the entity's game
attributes. A store $self "hp" 50 writes a slot named hp that has
nothing to do with real hit points — those are read through built-ins like
level and position.
Per-player flags — tags
For a simple yes/no fact attached to a player (not the script's owner),
tags are lighter than slots. tag adds a marker,
untag removes it, and
hastag tests it. Tags live on players only, so
guard with isplayer:
after command (say) {
require [isplayer $actor]
if [not [hastag $actor "greeted"]] {
do "say Welcome! I will remember you."
tag $actor "greeted"
}
}
after command (forget-me) {
require [isplayer $actor]
untag $actor "greeted"
do "say Very well — you are a stranger to me once more."
}
The greeting fires once; the greeted tag, carried by the player,
suppresses it on later visits — even at a different mob that checks the same
tag — until untag clears it. Note that
if [not [hastag …]] { … } is the conditional form: the bare
unless guard takes a condition and
no body. Use a slot on $self to remember something about the owner;
use a tag to remember something about the player.
See also
- Variables · Scope · Persistent state
- Structuring scripts —
def, top-levelconst, and closures over locals. - Reacting to events — guarding a handler on recalled state.