# Scripting

A script is a list of instructions Merlin runs in order. Each line starts with a verb that names the action. This guide covers every verb, wait type, and control structure in the scripting language, along with the rules that make each one predictable. The language is the same in every game. The examples use game variables such as `$health` and `$target`, which come with your game's module. To see the ones your game has, go to **Settings > Variables**.

## Write and run a script

1. In the Scripts panel, create a script named hello.
2. Enter these lines:

    ```
    echo I am about to wave.
    send wave
    echo I waved!
    ```

3. Save the script.
4. To run it, type `.hello` in the command line.

You see:

```
Merlin Client: I am about to wave.
> wave
You wave.
Merlin Client: I waved!
```

`echo` prints a message only you see. Merlin marks those lines so you can tell them from game text. `send` types a command into the game, exactly as if you typed it: you see the `> wave` go out and the game's reply come back.

### Put a script in a group

To put a script in a group, make this its first line:

```
#group {Combat}
```

The Scripts panel then shows the script under a Combat heading. The line changes nothing about how the script runs. It's the one line in a script that starts with `#`. You can type the group line yourself, or assign the group in the script editor. A group line you type yourself appears in the editor later.

### Run a script by name

To run any script by name, type a period followed by its name, such as `.hello` or `.mine`. Text after the name becomes arguments, which are values the script can read. See [Arguments](#arguments). The period is for the command line. Inside a script, start another script with `run hello`, and hand it values the same way: `run hello Marcus 3`. A `send .hello` line is refused before the script starts, with a message pointing you to `run`.

- **If that script is already running, or paused,** `run` leaves it alone, and the script goes on. A note says so in debug mode.
- **A script can't run itself.** To start a script over, use the Scripts panel, or type the period and its name in the command line.

### Script names

Capitalization doesn't matter in a script's name. A script's name is one word, because the words after it are its arguments. An older script with a space in its name still loads. The editor marks the name until you change it, and `run` can't start the script until you do.

## How a script runs

- **Lines run in order, one at a time.** A script never skips ahead or runs two lines at once.
- **A pause holds the script.** A `pause` followed by a number holds the script at that line for that many seconds, until the wait ends. See [Waiting](#waiting).
- **A script ends when it reaches its last line, or hits `stop`.** You can also stop a script yourself from the Scripts panel. A script can end another script by name, or every script; see [stop](#stop-end-this-script-another-script-or-every-script).

**Tip:** If a script seems stuck, open the Script Output panel. The top of it shows the line the script is on and what it's waiting for.

### Comments

A line that starts with `//` is a comment: a note for you, not for Merlin. Merlin skips it entirely; it changes nothing about how the script runs. It's the same mark in every file Merlin reads. A bare `pause 6` doesn't explain why the script waits six seconds; a comment above it can.

```
// Wait out the post-combat cooldown before hiding
pause 6
send hide
```

A comment is always a whole line. To note a command, put the comment on its own line above it. **Important:** A line that starts with `#` isn't a comment. If a script has one, Merlin doesn't start the script, and tells you which line to fix. The group line, `#group`, is the only exception; see [Put a script in a group](#put-a-script-in-a-group).

## stop: end this script, another script, or every script

`stop` on its own ends the script it's in. Put a script's name after it, and it ends that script instead. The script with the `stop` line keeps going.

```
stop Hunt
send sheathe sword
```

This reads as: end the Hunt script, then sheathe your sword. You see:

```
> sheathe sword
```

- **The name is the script's name as the Scripts panel shows it.** Capitalization doesn't matter.
- **A name Merlin doesn't know is an error,** and the script doesn't start.
- **If the named script isn't running,** nothing happens, and the script goes on. A note says so in debug mode.

`stop all` ends every running script, including the one it's in. It does the same as Abort All Scripts.

```
action stop all when You are bleeding
```

This reads as: the instant the game says `You are bleeding`, end every script. Both forms work in an action body. A script can't be named all.

## Output

Output is what a script produces: text you see, text the game sees, sound, or attention. These verbs are how a script speaks:

| Verb | Effect |
|---|---|
| `send <text>` | Sends a command to the game |
| `echo <text>` | Prints a message visible only to you |
| `debug <text>` | Like echo, shown only when debug mode is on |
| `play <sound>` | Plays a sound file |
| `speak <text>` | Says the text out loud |
| `beep` | Plays the system alert sound |
| `flash` | Flashes Merlin's icon when you're in another app |

To turn debug mode on, type `/debug` in the command line. Merlin answers `Debug messages ON.` To turn it off, type `/debug` again. Debug mode starts off each time Merlin opens.

### speak: say it out loud

`speak` reads its text aloud in the system voice, the way a trigger's spoken alert does. It can say a value, which `beep`, `flash`, and `play` can't.

```
speak $monstercount wraiths
```

This reads as: say how many wraiths are in the room. With three in the room, the voice says "three wraiths," and the script goes on at once.

- **A trigger's spoken alert always wins.** A `speak` line that arrives while an alert is speaking is dropped, and a note says so in debug mode.
- **The newest `speak` line wins over an older one still being said.** The cut-off line is noted in debug mode too.
- **Nothing waits in line.** When the script stops or ends, only the words already being said finish.
- **While Merlin is muted, nothing is said.**
- **Missing and empty values:** A value that's missing is an error on that line, and nothing is said. An empty value says nothing, quietly. `speak` with nothing after it is refused before the script starts.
- **A line is cut to 500 characters.**
- **`speak` works in an action body:** `action speak $arg1 whenre ^(\w+) wanders in`.

## Waiting

After some actions, such as an attack, the game makes you wait a few seconds before you can act again. That wait is the roundtime, also called the delay. If your script ignores it, the script sends commands too early. The game then queues them and runs them later. Relying on the game's queue risks the script malfunctioning. To avoid this, use wait verbs. Each one is for a different situation.

### The built-in safeguard

Merlin gives you one safeguard without asking. A script's next command isn't sent until the game has answered the last one, from any script, with its prompt. That means two `send` lines in a row go out one reply apart. This doesn't replace the wait verbs: it spaces commands; it doesn't wait out roundtime. A `pause` waits out a delay only if the game announces it before the pause ends. If the delay is announced later, the next command can go out during it, so choose a pause long enough to cover the action's roundtime. If the game ever fails to answer a command, Merlin says so after five seconds and sends the next one anyway. A delay the game has announced doesn't count as silence. A command sent during a delay is answered when the delay ends, so Merlin waits for that end, and five seconds more, before it says anything.

### Pausing a script freezes its waits

A script paused in the middle of a `waitfor`, `waitforre`, `waiteval`, or `matchwait` doesn't run out of time while it's paused, however long you're away. When you resume it, it picks up where it was, and a line that arrived while it was paused still counts.

- **A paused script sends nothing,** not even a command that was already waiting its turn.
- **A `walkto` in progress stops walking when you pause.** When you resume, it asks the map for the same room again from wherever you stand.

### pause (wait N seconds, plus any roundtime)

`pause 3` waits at least 3 seconds. If the character is still in roundtime when the 3 seconds end, it keeps waiting until the roundtime is over.

- **Fractions work:** `pause 0.5` waits half a second.
- **The most is 600 seconds,** as for every wait. A longer number is cut to 600, and the script says so.

Choose a pause length that comfortably covers the action's roundtime. For example, an attack can cause a 3- or 4-second roundtime (the game prints `[4 sec. delay]`), so `pause 5` clears it with room to spare. **Tip:** Use `pause` by default, unless you have a specific reason to use another wait.

### delay (wait exactly N seconds)

`delay 3` waits precisely 3 seconds, ignoring roundtime. Use it when the wait has nothing to do with roundtime, for example, pacing out a line of dialogue or emotes so each one has time to be read. The most is 600 seconds, as for `pause`.

### later (do one line N seconds from now, while the script carries on)

`later 90 send stand` schedules one line to run 90 seconds from now. The script doesn't wait. It goes straight on to its next line, and the scheduled line runs when its time comes.

```
later 90 send stand
send sit
waitfor You sit down
echo resting
```

This reads as: in 90 seconds, stand up. Meanwhile, sit and rest. `sit` goes out at once, "resting" appears when the game answers, and `stand` goes out 90 seconds after the `later` line was reached.

- **The seconds are a plain number from 0.1 to 600.** A value isn't allowed there.
- **The line after the seconds is one instant line,** the same kinds an action body takes: `send`, `set`, `keep`, `math`, `echo`, `speak`, `stop`, and the rest. No `if`, no wait, and no `later` inside a `later`.
- **The scheduled line's values are read when the `later` line is reached.** `later 10 echo $count` uses the count as it was then, not as it is 10 seconds later. A missing value is an error on the `later` line, and nothing is scheduled.
- **One scheduled line per `later` line.** If the script reaches the same `later` line again before it runs, the new one replaces the old one.
- **Pausing the script freezes the countdown.** A scheduled `send` that's waiting out roundtime when you pause waits for the resume too.
- **A scheduled `send` waits out roundtime** and takes its turn like any other command.
- **A script stays running until its scheduled lines run.** When the script's lines are done, the script stays running until its scheduled lines have run. The top of the Script Output panel shows what it still has to do. Stopping the script cancels them, and so does the script's own `stop` line, an action `stop`, a `stop` from another script, a restart, or a disconnect.
- **A trigger's script line runs a fresh copy each time it fires.** If it fires again while the last copy is still waiting on a scheduled line, the new firing waits its turn and runs once that copy ends, so both scheduled lines run. With `skipifbusy`, the new firing is skipped instead.

### waitfor (wait for specific text)

```
send open sack
waitfor You open the closed
```

`waitfor` holds the script until a line from the game contains the given words, then continues.

- **It watches only text that arrives after the `waitfor` starts.** Earlier lines don't count.
- **It matches whole words, with capitals exactly as the game prints them.** `waitfor rat` isn't woken by `You take a bite of your hard rations.`, and `waitfor you open the closed` isn't woken by `You open the closed purse.` The same goes for `match`, `until`, `stopon`, and `action … when`. To catch part of a word, or either capital, use `waitforre`, `matchre`, or `action … whenre`.
- **If the words never come, the script stops after 30 seconds** and tells you what it was waiting for.
- **For something that takes longer, put the number of seconds first:** `waitfor 120 You fall asleep` waits up to two minutes. For every wait that watches for words or a condition, the most is 600 and the least is 0.1; a number outside that range is clamped, and the script says so. `pause` and `delay` take any number from 0 up, with the same most of 600. Because the first word is read as the number, text that itself starts with a number needs a word in front of it: `waitfor up 2 silver coins`, not `waitfor 2 silver coins`.
- **A `$name` in the text that has no value ends the script and names the missing variable.** Otherwise, `waitfor $prey dies` with no `$prey` would wait for the literal words `$prey dies` and eventually time out. The same goes for `match` rows, an action line's `when`, and the text of an `until` or `stopon` line, read where that line is reached, so a loop that sets the value first is fine. A `send` with such a value says so and sends the words as typed.

`waitforre` is the pattern-matching version. See [Patterns](#patterns).

#### Wait for every answer

A game doesn't always say yes. When you open a sack, it might open, it might already be open, or there might be no sack. A `waitfor` that waits for only one answer doesn't notice when the game gives another. It keeps waiting, and after 30 seconds it stops the script. Put every answer you know in one `waitforre`, with a `|` between them:

```
send open sack
waitforre You open|already open|I see no
```

The script goes on, whichever answer comes. To find the answers, look in your logs for times you typed the command yourself. To do something different for each answer, use a match table; see [Multiple outcomes: match tables](#multiple-outcomes-match-tables).

### waiteval (wait for a condition)

```
waiteval $health > 80
```

`waiteval $health > 80` reads as "wait until my health is above 80." The script pauses at this line and waits for the condition to be true. Once it's true, the script moves to the next line. Use `waitfor` for events the game states once. Use `waiteval` for values Merlin tracks continuously, such as health. The default timeout is also 30 seconds; on timeout, it reports both the condition and the value it last saw. To wait longer than 30 seconds, put the number of seconds first: `waiteval 120 $health > 80` waits up to two minutes. **Important:** If nothing can ever make the condition true, the script waits until it times out. If a monster is still attacking you, your health won't rise while you stand there and wait. Use `if` to take action instead. See [Conditions](#conditions).

### waitreply (wait for the game to answer the last command)

```
send skills
waitfor Remain
waitreply
if $remain(Swimming) = 0 then echo Swimming is ready to train
```

This reads as: ask for the skills list, wait for its header line, wait until the game has finished answering, then read it. `$remain(Swimming)` holds the number from the list the game just sent, never the one from before. `waitreply` waits for the prompt that ends the reply to the last command this script sent. That's the moment every value the reply filled can be read: the skills list, your wounds after `health`, the room after `look`. A `waitfor`, `waitforre`, or `matchwait` that finds its line gives `waitreply` a new starting point. `waitreply` then waits for the first prompt after that line. That's the sure form. Nothing you type and nothing Merlin sends on its own can end the wait early, unless it prints that same line, such as a `skills` you typed yourself. Wait for a line only the reply you want can print, then use `waitreply`.

- **Use it when you read a game variable right after the command that fills it.** `pause 1` is a guess that the answer took less than a second. A reply can take two seconds, and longer if the command went out during a delay.
- **Use `waitfor` instead when you want a particular line,** and `waitreply` when you want the whole answer.
- **If this script has no unanswered command, or the answer has already come,** `waitreply` doesn't wait at all.
- **A command sent by an action line or by a scheduled `later` line counts, once it has gone out.** An action `send` still waiting out a delay isn't yet the last command. A script started with `run` sends its own commands, not this script's, and a `walkto` walk's steps are the map's.
- **Without a `waitfor` before it,** Merlin waits for the first answer after the command went out. If anything else was sent since the game's last answer (a command you typed, one Merlin sent on its own, or one Merlin sent anyway after the game went 5 seconds without answering), Merlin can't tell whose answer comes first. `waitreply` then goes on at the first answer and says so once, naming the sure form. A command sent before the last answer and still unanswered isn't counted, so the sure form is the one to trust.
- **Wait for a line of the reply you want, not for any line.** A line the game prints on its own, such as an attack, comes with no answer, and a `waitreply` after it waits for the next answer to anything, or until its patience runs out.
- **Pausing the script freezes it.**
- **If the game hasn't answered after 30 seconds,** the script stops and names the command. `waitreply 60` waits up to 60 seconds. The number is plain, up to 600.

## Moving

### move (take a step, and know it worked)

```
move north
if $moved = no then echo the way north is blocked
```

`move north` reads as "go north, and tell me whether I got there." `send north` sends the word and moves on at once. It can't tell whether you arrived. `move north` sends the same word, then waits for the map to say you're in a new room, and only then continues. Merlin stores the result in `$moved`: `yes` if the room changed, `no` if it didn't. A `no` isn't an error. It means something kept you where you were: a creature blocked the exit, the exit wasn't there, or the game refused the step. The script continues to the next line, where `if $moved = no` decides what to do about it.

- **It waits 10 seconds for the room to change.** For more, put the number of seconds first: `move 20 north`.
- **It reads the map, not the game's text,** so it needs no list of wordings to watch for.

### nextroom (wait for a move you sent yourself)

```
send swim east
nextroom
```

`nextroom` reads as "I've just sent a move; wait until I'm in the new room." Some moves aren't a direction word: swimming across water, `go path`, climbing a ladder. `move` sends those too. `move swim east` sends `swim east` and waits for the room to change. For a move you sent some other way, put `nextroom` on the next line to wait for the room to change. It fills `$moved` the same way `move` does, `yes` or `no`.

- **It waits 10 seconds for the room to change, like `move`,** and leaves the answer in `$moved`.
- **`nextroom 20` waits up to 20 seconds.**
- **Nothing is sent by `nextroom` itself.** It only waits.

### walkto (let the map take you to a room)

```
walkto 9460
```

`walkto 9460` reads as "take me to room 9460." The script gives the room to the map and waits while the map walks you there, then continues.

- **The number is the room's ID,** the one `$roomnum` shows when you stand in it.
- **The map plans the route from wherever you are.** The script doesn't need to know where it started.
- **A creature blocking the way doesn't stop it.** The map waits out the roundtime and tries the same step again, as many times as it takes, and stands you up if you're knocked down. **Important:** Don't add retries of your own around `walkto`. Two layers of retry would send the step twice.
- **If the map can't get you there, the script stops and says why,** with the room and the map's word for the reason, such as `walkto 9460: timeout`: `unseen` (the map doesn't know where you are), `unreachable` (no way there, or a room the map doesn't know), `closed`, `locked` or `stuck` (a door the walk couldn't get through, such as a locked door with no key; `closed` also when the map is closed during the walk), `timeout` (a step went unanswered), `mismatch` (a step took you somewhere the map didn't expect), `too tired` or `can't swim` (the game refused the step), `dead`, `superseded` (you moved yourself or clicked another room), `stopped` (you clicked the room you stand in), or `atlas` or `wiped` (you picked another atlas, or deleted the zone).
- **A `walkto` to the room you already stand in does nothing,** and the script goes on.
- **Pausing the script stops the walk where it is.** Resuming asks the map for the same room again, from wherever you stand, once the game has answered the step the pause interrupted. If no answer has come, it waits up to two seconds for one and then goes on.
- **A walk through creatures keeps retrying a blocked step, with no limit,** and a creature blocking the way can attack you while it does. Give the script an action that stops it when your health drops, such as `action stop when eval $health < 40`.
- **The map must be open.** A closed map answers "no map is open to walk" and the script stops there.

## Variables

A variable stores a value under a name. Some come from your game, such as your health (`$health`). Others you name yourself. Write `$health` anywhere in a script to use that value.

```
echo My health is $health.
```

The result, printed only on your screen:

```
Merlin Client: My health is 87.
```

Two variables can work in the same sentence. Here the script sets its own target first with `set`; see [Local variables](#local-variables):

```
set target a dark hound
echo My health is $health and I am fighting $target.
```

The result, printed only on your screen:

```
Merlin Client: My health is 87 and I am fighting a dark hound.
```

Each `$name` was replaced with its current value at the moment the line ran. There are three kinds of variables: game variables, saved variables, and local variables.

### Game variables

Game variables come with the game's module in Merlin and take their values from the game. When the game reports your room, your health, or what's in your hands, Merlin keeps the latest report, and the variable is how a script reads it.

- **You can read them anywhere a script accepts text or a condition.**
- **`keep` can't write them;** the game updates them when it reports the information they hold. `set` can give a game variable a different value for the rest of that run.
- **Until the game has spoken, a variable isn't set at all.** A condition that reads it stops the script and says why. A `waiteval` waits for it instead. Ask `is set` first if the game may not have spoken yet.

Which names exist depends on the game you're playing. To see the list for your game, with live values and what each one means, go to **Settings > Variables** and look under **Merlin managed variables**. For example, `$target` is the creature you last attacked, in the form the game takes back: `hound`, or `2nd hound` when there are two. A hunting script can attack it without knowing its name ahead of time:

```
send attack $target
```

It's set by your own attacks and spells only, never by someone else's fight. If the creature ahead of yours dies, the game renumbers yours, and `$target` gets the new number from your next attack line. A script that says `set target rat` keeps its own rat for that run.

#### Game variables that take a name

Some game variables take a name in parentheses. A skill's numbers work this way:

```
send skills
waitfor Remain
waitreply
if $remain(Thrown Weapons) = 0 then echo Thrown Weapons is ready to train
```

The script asks for the skills list, waits for the list's header line, waits for the game to finish answering, then says so when Thrown Weapons has no uses left before it can train.

- `$remain(Thrown Weapons)` is the uses left before the skill can train.
- `$level(Thrown Weapons)` is the skill's level.
- `$ready(Thrown Weapons)` is `yes` or `no`.

Write the skill's name the way the skills list shows it. Capitalization doesn't matter. The name in the parentheses can come from a variable. `$remain($s)` with `$s` holding Thrown Weapons reads `$remain(Thrown Weapons)`. That lets one loop read every skill:

```
set skills Sharp Weapons|Blunt Weapons|Brawling
foreach s $skills
if $remain($s) = 0 then echo $s is ready
endeach
```

This reads as: for each skill on my list, say so if it's ready. Parentheses can mean two different things. For a value of your own, `$list($n)` uses `$n` as the position of an item in the list. For a game value that takes a name, such as `$remain`, `$level`, or `$ready`, `$remain($skill)` uses `$skill` as the skill's name.

A value of your own with the same name wins when the parentheses hold a `$name`. If you have your own `$remain`, `$remain($skill)` is treated as a list lookup. Because a list position must be a number, a skill name there is refused as not a number. Written out, `$remain(Thrown Weapons)` still reads the game's value. If the name is wrong, Merlin says so and suggests the right one:

```
no skill called "Throw Weapons" — did you mean Thrown Weapons?
```

### Saved variables

Saved variables are stored on the computer where you created them. To create one by hand, go to **Settings > Variables**. A `keep` line in a script creates or updates one:

```
keep kills 12
```

**Settings > Variables** now shows a row `kills` with the value 12, and `$kills` reads 12 in any script, today or next week. Saved variables persist across sessions and across scripts on that computer: quit Merlin, reopen it next week, and the value is still there.

- **Game variables can't be written with `keep`;** Merlin reports an error if you try.
- **Merlin's own names can't be written either:** `args`, `arg1` to `arg9`, `match`, `moved`, `unixtime`, `date`, `time`, and any name starting with `timer_`. The same rule holds for `set` and every other line that names a variable.

### Local variables

Local variables are useful for holding a value you'll reuse a few lines later. Create a local variable with `set`:

```
set prey rat
send kill $prey
```

The result:

```
> kill rat
```

A local variable isn't stored between runs. A line such as `set prey rat` creates it fresh each time the script runs. It doesn't appear in **Settings > Variables**, and its value lasts only for that run. `set` keeps everything after the name, spaces and all. If `$gem` holds quartz gem, then `set found cracked $gem` stores cracked quartz gem. That's how you join words.

### Variable names

- **Variable names use letters, numbers, and underscores.**
- **`arg1` through `arg9` and `args` are reserved for script arguments;** Merlin rejects any attempt to use them as another kind of variable.
- **Variable names can't start with a number.**

## Arguments

Extra words after a script's name become its arguments: values the script can read. `.heal Marcus` runs the heal script with Marcus as an argument. `$arg1` holds the first word, Marcus. `$args` holds everything after the script's name. Inside a script, `run heal Marcus` does the same thing.

```
send cast heal $arg1
```

The result of running `.heal Marcus`:

```
> cast heal Marcus
```

Pattern captures can temporarily replace `$arg1`. A capture from `matches` or an `action … whenre` body lasts only for that line. A capture from a `matchre` row lasts for the rest of the run, or until the section that caught it returns. After the capture expires, `$arg1` goes back to the value it held before. A variable is one argument, even when its value has a space in it:

```
set patient Marcus Taath
run heal $patient
```

heal runs with `$arg1` holding Marcus Taath.

### Name the arguments

To give the words names, start the script with `args`. The names are taken in order. Write them without a `$`, the same as `set`: you name a variable here, and read it with `$` everywhere else.

```
args prey retreat
send attack $prey
```

`.fight hound south` gives `$prey` the word hound and `$retreat` the word south. `$arg1` and `$arg2` still work too.

- **If you type fewer words than there are names,** the extra names are empty.
- **A section you `gosub` to can start with its own `args` line** to name the words its `gosub` handed it.
- **A name Merlin already uses for the game, such as `health` or `roomnum`, is refused before the script starts,** with a message saying so. An argument with that name would hide the real value for the whole script. If you really do mean to replace a game value inside your script, use `set` instead.

## The clock

Three variables read the clock on your computer. They're the same in every game.

- `$time` reads the time of day, 24-hour, to the minute. Example: `14:05`.
- `$date` reads the date. Example: `2026-10-19`.
- `$unixtime` reads whole seconds since 1970, a number. Example: `1792612345`.

```
echo $time: killed $target
```

At five past two in the afternoon, with `$target` holding wraith, `14:05: killed wraith` appears on your screen. `$time` and `$date` are text. Compare them with `=` or `!=`, and write the other side in the same format: `if $time = 03:00 then send say it is three`. A condition such as `$time > 03:00` is refused before the script starts. To ask how long, use `$unixtime`, which is a number:

```
math until = $unixtime + 60
waiteval 120 $unixtime >= $until
send say a minute has passed
```

The script waits sixty seconds, then sends the line. The number after `waiteval` is how long it may wait. Without one, a wait gives up after 30 seconds, before the minute is up. **Note:** When your clock skips an hour in spring, 02:30 never comes that day. When it repeats an hour in fall, 02:30 comes twice. A clock set forward or back moves `$unixtime` by the same amount.

## Conditions

```
if $health < 50 then send flee
```

`if $health < 50 then send flee` reads as "if my health drops below 50, flee." The shape is: `if <condition> then <line>`. An `if` on one line controls one verb: the condition, then `then`, then the verb. To run several lines under one condition, use a block. See [Several lines under one condition](#several-lines-under-one-condition), below. With `$health` at 42, `> flee` goes out. At 70, nothing happens, and the script moves to the next line.

| Operator | Meaning |
|---|---|
| `=` | Equals (capitalization doesn't matter) |
| `!=` | Not equals |
| `<` `>` `<=` `>=` | Numeric comparison |
| `contains` `starts` `ends` | Part of a value (capitalization doesn't matter) |
| `matches` | A pattern test, with captures for that line |
| `is set` `is not set` | Whether a variable holds anything |
| `and` `or` `not` | Combine conditions |
| `( )` | Group |

`in` tests how a value ends against a list (see [Matching against a list](#matching-against-a-list-in)). To react to words inside a line the game prints, use a pattern: `waitforre`, `matchre`, or `action … whenre`.

### Combine conditions

```
if $hidden = yes and $health > 80 then send stalk
if not $target = rat then echo New prey!
```

`if $hidden = yes and $health > 80 then send stalk` reads as "if I'm hidden and my health is above 80, stalk." `if not $target = rat then echo New prey!` reads as "if my target isn't a rat, echo New prey!"

### Put words with spaces in quotes

Words you type with a space between them go in quotes. A variable needs none: it's one value, whatever it holds.

```
if $roomname = "The Yard" then send home
```

`if $roomname = "The Yard" then send home` reads as "if I'm in The Yard, go home." Without the quotes, Merlin sees two words where it expects one and refuses the script before it starts. Room names nearly always have spaces, so this is the shape for any test on `$roomname`. Quotes around a variable, `"$list"`, compare it as plain text. Without them, a value holding `|` is read as a list, and a list can't be compared with `=`.

### Empty variables

An empty variable compares as empty. `set target` with nothing after it leaves `$target` empty, and `if $target = none` is simply false. An empty variable counts as false in a condition by itself. If `$flag` is empty, `if $flag then …` does nothing.

### Test part of a value: contains, starts, and ends

`contains`, `starts`, and `ends` test part of a value. Capitalization doesn't matter.

```
if $prize contains dagger then send get $prize
if $roomname starts The then echo Still in town.
```

`if $prize contains dagger then send get $prize` reads as "if the thing I found has dagger anywhere in its name, get it." With `$prize` holding throwing dagger, `> get throwing dagger` goes out. With bronze battle-axe, nothing happens. `if $roomname starts The then echo Still in town.` reads as "if the room's name begins with The, echo Still in town." With `starts`, `contains` and `ends`, words with spaces don't require quotes: `if $roomname starts "The Yard"` works with or without them. The words `contains`, `starts`, `ends`, and `matches` are reserved, like `and`, `or`, `not`, and `in`. A value that's one of those words goes in quotes.

### Test a pattern: matches

`matches` tests a value against a pattern, the same kind `waitforre` takes; see [Patterns](#patterns). The pattern goes in quotes. What the pattern's parentheses catch is in `$arg1`, `$arg2`, and so on, for the rest of that line only.

```
set line You found 47 silver coins
if $line matches "(\d+) silver" then set coins $arg1
echo $coins
```

This reads as: if the line holds a number before the word silver, keep that number as coins. The result, printed only on your screen:

```
Merlin Client: 47
```

- **Capitals matter in a pattern.** `matches "Silver"` doesn't find silver. The other tests ignore capitals.
- **The captures last for that line's `then` part and no longer.** The next line reads the script's own `$arg1` again, and an `else` never sees them. To keep one, store it: `then set coins $arg1`. Only the pattern's own groups are filled: with two groups, `$arg3` and `$args` stay the script's own.
- **A block, `if … matches … {`, gets no captures.** A line inside it that reads `$arg1` is refused before the script starts. Store the capture on a `then` line first.
- **A `$name` inside the pattern is replaced with its value before matching.** The value is matched as plain text, so a `.` or `*` in it means only that character. A quote inside the pattern is written `\x22`.
- **A pattern Merlin can't read, or one with a named group, is an error before the script starts.** A pattern keeps at most nine groups.
- **One `matches` per line when its captures are used.** A line that reads `$arg1` after two of them, or after a `matches` joined by `or` or `not`, is refused before the script starts, because a true condition couldn't say which match filled them.
- **In `waiteval`, `until eval`, `stopon eval`, and `action … when eval`, `matches` is a plain yes or no and keeps nothing.** To capture from the game's text, use `whenre` or `matchre`.

### Ask whether a variable holds anything: is set

`is set` asks whether a variable holds anything at all:

```
if $target is set then send attack $target
```

`if $target is set then send attack $target` reads as "if I have a target, attack it." A variable never set, or holding nothing, isn't set. `is not set` is the reverse. **Important:** `if $food` on its own works with `yes` or `no`, `true` or `false`, and `on` or `off`. Numbers work too: 0 is no, and any other number is yes. An empty value is no. If it holds a word such as bread, the script stops. To ask whether a variable holds anything, use `is set`:

```
if $food is set then send eat $food
```

### When a condition can't be read

A condition that can't be read stops the script. `if $gone = yes` with `$gone` never set is neither true nor false, so the script stops on that line and says why, the way it stops on a walk that didn't arrive. This is on purpose. A skipped line would let the script carry on past a check it couldn't make. Give every variable a value before a condition reads it, or ask `is set` first.

- **`is set` reads a never-set variable as no.**
- **`and` and `or` stop reading at the first side that decides,** so `if $target is set and $health < 40` is safe when there's no target.

```
set gone no
action set gone yes when wanders away
```

This reads as: start with gone as no, and let the action line turn it to yes.

### else: do something when the condition is false

```
if $health > 50 then send attack hound
else send flee
```

`else` runs a line only when the `if` directly above did nothing. This reads as "if my health is above 50, attack; otherwise, flee." Exactly one of the two lines runs. With `$health` at 90, `> attack hound` goes out. At 42, `> flee` goes out instead. Chain them for more than two outcomes:

```
if $health > 80 then send attack hound
else if $health > 30 then send parry
else send flee
```

This reads as: above 80, attack; otherwise above 30, parry; otherwise flee. One line runs, never more. With `$health` at 55, `> parry` goes out. The first line did nothing, the second matched, and the third never ran.

- **`else` sits directly below its `if`.** Blank lines and comments in between are fine.
- **The line after `then` is one verb that does its work on that line,** such as `send`, `set`, `echo`, or `gosub`, which runs a whole section. A line that reaches past itself can't follow `then`: `repeat`, `until`, `stopon`, `foreach`, `endeach`, `waiteval`, the `match` lines, `action`, `include`, and `args`. Put those under a block instead, except `include`, which goes on a line of its own outside any block; see [Several lines under one condition](#several-lines-under-one-condition).

### Several lines under one condition

Put the lines between braces. The `{` is the last word of the `if` line, and the `}` sits alone on a line of its own. There's no `then`.

```
if $want = thrown {
  send unready shield
  waitfor You remove your
  pause 1
}
else {
  send ready $sharp
}
```

This reads as: if I want the thrown weapon, unready the shield, wait for the game to say so, and pause; otherwise, ready the sharp one. With `$want` holding thrown, the three lines in the first block run, one after another, and the `else` block is skipped. With anything else, only `ready` goes out. `else if $hp > 30 {` makes a ladder, the same way as the one-line form. Exactly one block runs. Blocks can sit inside blocks, and inside `repeat` and `foreach` loops.

- **The lines inside a block run one at a time,** so the game can change between them. The condition is read once, at the `{`. If a later line must check again, give it its own `if`.
- **Spaces at the start of a line mean nothing.** They're there for your eyes.
- **A `{` that isn't the whole end of an `if` line is ordinary text,** so `send emote {grin}` sends `emote {grin}`.

An `if … {` and its `else` go together as blocks: after a `}`, write `else {` or `else if … {`, not a one-line `else`. A section name (`Name:`) goes outside the braces, never inside. These mistakes are reported before the script starts.

## Loops

A loop runs the same lines more than once, so you don't write them out by hand for each repetition.

### repeat / until: run N times

```
repeat 5
send buy apple
pause 1
until
```

This runs the block 5 times. A bare `until` just closes the block.

- **`repeat` takes a whole number from 1 to 1000.** A number outside that range stops the script from starting.
- **A variable works too:** `repeat $times`. The variable must hold a whole number from 1 to 1000. Anything else ends the script and reports the value that caused the problem, including an empty or unset variable.
- **Loops can sit inside loops;** each `until` closes the nearest open `repeat`.
- **A `stopon` ends only the loop it's written in,** and it only looks at lines that arrived after that loop began.

### repeat with no number: run until told to stop

```
repeat
send drink canteen
pause 1
until You're no longer thirsty
```

This reads as "keep drinking until the game says I'm no longer thirsty." With no number, the loop has no end of its own. It ends when its `until` text arrives, when a `stopon` fires, or when you click Stop. Sips go out one a second until the game prints `You're no longer thirsty.`, then the script moves on. **Important:** A loop with no number must wait for something on every turn: a `pause`, a `waitfor`, a `waiteval`, or a command sent to the game. A loop that only sets variables runs continuously without waiting. Give every loop a wait. `repeat $times` never becomes a loop with no end. An empty `$times` ends the script instead.

### End a loop early with until

Give `until` matching text to end the loop early, once the game confirms completion:

```
repeat 20
send drink canteen
pause 1
until You're no longer thirsty
```

This reads as "repeat up to 20 times, but stop as soon as the game says 'You're no longer thirsty.'" The result of one run, where the game confirmed on the seventh sip:

```
> drink canteen
You take a drink from your small canteen.
   ...six more sips...
> drink canteen
You take a drink from your small canteen.
You're no longer thirsty.
```

The loop ended there, thirteen sips early. It stops at the `until` line once the text has appeared, or after 20 times, whichever comes first. **Tip:** Prefer ending on the game's own confirmation over counting turns.

### Abandon a loop with stopon

`stopon <words>` abandons the current loop when the script reaches the `stopon` line, if its text has appeared since the loop began. It doesn't cut a wait short. It's for the unusual case, not the loop's normal exit. Outside a loop, `stopon` ends the whole script, just like `stop`: any scheduled `later` line is canceled, and action lines are switched off. Both `until` and `stopon` can use a condition instead of matching text: `until eval $monstercount = 0`, `stopon eval $health < 30`. They use the same conditions as `if`, and check the condition at the same point in the loop where they would otherwise check for matching text.

```
repeat 20
send drink canteen
pause 1
stopon claws at you
until You're no longer thirsty
```

The result, attacked on the third sip:

```
> drink canteen
> drink canteen
> drink canteen
The dark hound claws at you!
```

The loop stopped there, seventeen sips short of the limit, without ever seeing "You're no longer thirsty." `stopon` fires on its own text, independent of `until`.

### foreach / endeach: run once per list item

A variable can hold a list. Separate each item with `|`, a vertical bar. It marks where one item ends and the next begins.

```
set snacks apple|bread|cheese
foreach snack $snacks
send eat $snack
pause 2
endeach
```

This reads as "for each item in `$snacks`, held one at a time as `$snack`, eat it and pause 2 seconds." The result:

```
> eat apple
> eat bread
> eat cheese
```

This runs the block once per item, with `$snack` set to the current one. Extending the list doesn't require changing the script.

#### List reference

- **`$snacks(2)` is the second item, bread.** Counting starts at 1, not 0, so `$snacks(1)` is apple.
- **`math n = items($snacks)` counts the items in `$snacks`** and stores the result in `$n`. For the snacks list above, `$n` becomes 3.
- **An item can contain multiple words:** `iron sword|healing potion` is two items.
- **Most list operations use up to 100 items.** If a list contains more, its full value is still kept, but `foreach`, `items()`, and `$list(n)` use only the first 100 and say so. `list remove` is different: on a list with more than 100 items, it keeps only the first 100 and drops the rest. `list add` won't add another item once the list has 100. The `in` test is different: it checks every item, even beyond the first 100.

### list: add an item, or remove the best-matching item

`list` changes a list the script holds. `add` puts an item on the end. `front` puts it at the front. `remove` takes one item off.

```
set seen
list seen add rat
list seen add dark hound
list seen front Tam
echo $seen
```

This reads as: start with an empty list, add rat, add dark hound, put Tam at the front. The result, printed only on your screen:

```
Merlin Client: Tam|rat|dark hound
```

An item is one value. It can be several words, keeps its capitals, and may appear twice. A value with a `|` in it is refused, because that would make two items. `remove` uses the same rule as `in`: the item you name is matched by its ending, and the most exact entry goes first.

```
set wanted ruby|gem|quartz gem
list wanted remove cracked quartz gem
echo $wanted
```

This reads as: remove the list entry that best matches the end of cracked quartz gem. Two entries match, gem and quartz gem, so the more exact one, quartz gem, is removed. The result, printed only on your screen:

```
Merlin Client: ListDemo, line 2: list wanted remove took "quartz gem" for "cracked quartz gem"
Merlin Client: ruby|gem
```

When the entry taken isn't the exact text you gave, Merlin says which one went, so a wider removal is never hidden. When nothing on the list fits, nothing changes, and a note says so in debug mode.

- **`list` changes the script's own copy, like `set`.** To change a list that comes from a saved variable, first copy it into the script with `set wanted $wanted`. Make your changes there, then use `keep` if you want to save the changed list.
- **A game variable can't be edited.** Copy it to a name of your own first.
- **To empty a list,** write `set wanted` with nothing after the name.
- **`list` works in an action body:** `action list seen add $arg1 whenre ^(\S+) wanders in`.

### Matching against a list: in

```
set wanted ruby|pearl|gem
if $item in $wanted then send get $item
```

This reads as "if my item is one of the wanted kinds (ruby, pearl, or something ending in gem), get it." `in` doesn't require the whole value to equal a list entry. It matches when the value ends with a list entry, word for word.

| Value | Ends with | Match |
|---|---|---|
| cracked quartz gem | gem | Yes |
| shining ruby | ruby | Yes |
| ruby ring | ring | No |
| pearl button | button | No |

Because item names put the category last, this gives you two things for free:

- **Adjectives don't need separate list entries.** "Cracked," "shining," and "flawless" all match, whatever word comes before the kind.
- **A bare category word, gem, matches anything ending in it,** including a kind you haven't found yet. quartz gem, sapphire gem, or any gem type you find later all match, since each ends in gem.

**Important:** When acting on a matched item, use the game's own full name for it, captured from its output (see [Patterns](#patterns)), not your list entry. Games require exact names for actions; decide with the list, act with the captured name.

## Sections

A section is a named block, called with `gosub` and ending in `return`:

```
send knock

gosub Greet
echo All done.
stop

Greet:
send bow
send say Good day!
return
```

This reads as: knock, then run the Greet section (bow, then say Good day!), then return to the line after `gosub` and continue: echo All done, then stop. The result:

```
> knock
> bow
> say Good day!
Merlin Client: All done.
```

`gosub Greet` runs the `Greet:` section, then returns to the line after the call, which is why "All done" prints last, after the section's two commands. **Important:** Put `stop` before the first section. Without it, the main script can continue straight into the section instead of waiting for a `gosub` to call it.

- **A section name is one word:** letters, numbers, and underscores, starting with a letter or an underscore. Capitalization doesn't matter: `gosub greet` finds `Greet:`.
- **A section can call another, up to 8 deep.** The ninth call is refused with a message, and the script goes on with the next line. A section that calls itself gets there fast. `run` has the same limit.

### Hand a section a value

`gosub` can hand a section a value. Words after the section name become that call's arguments, read inside the section as `$arg1` and `$args`; see [Arguments](#arguments):

```
gosub Heal Marcus
echo Done helping.
stop

Heal:
send cast heal $arg1
return
```

This reads as: run the Heal section with Marcus as its argument (cast heal on Marcus), then return and continue. The result:

```
> cast heal Marcus
Merlin Client: Done helping.
```

After `return`, `$arg1`, `$args`, and the script's other arguments return to the values they had before the `gosub`. A `gosub` with nothing after the section name works exactly as before. A `return` with no `gosub` to return to ends the script and reports the line. The same happens if a section reaches `return` while a `repeat` inside that section is still open because its `until` hasn't been reached.

### include: share sections across scripts

Copy the same section into several scripts, and each copy has to be fixed by hand if something changes. Fix one copy and forget another, and they no longer do the same thing. Instead, put shared sections in one script, a library, and reference it:

```
include MyToolbox
```

This lets the current script call any section in MyToolbox with `gosub`, as if that section were written directly in the current script.

- **A library can only contain sections, comments, `include` lines, and its group line.** Any other line outside a section is an error.
- **Nothing in a library runs on its own:** a section only runs when another script calls it with `gosub`.

## Math, text, random, timers

### math: arithmetic on a variable

`math` works out a sum and stores the answer. Write the name, an equals sign, and the sum.

```
set kills 0
math kills = $kills + 1
```

This reads as "kills becomes kills plus one." A sum can combine values:

```
math percent = ($hp * 100) / $hpMax
```

| In a sum | What it means |
|---|---|
| `+` `-` `*` `/` | Add, subtract, multiply, divide. |
| `mod` | The remainder after dividing. `$n mod 10` is 0 every tenth time. |
| `( )` | Work out the inside first. |
| `round($x)` | Round to a whole number. Halves go away from zero: 2.5 becomes 3, -2.5 becomes -3. |
| `abs($x)` | Make the number positive. `abs(-5)` is 5, and `abs(5)` stays 5. |
| `min($a, $b)` | The smaller. `min($hp, 100)` is never above 100. |
| `max($a, $b)` | The larger. `max($dmg, 0)` is never below 0. |
| `items($list)` | How many items a list holds. |
| `length($text)` | How many characters the text has, spaces included. |

Multiply, divide, and `mod` happen before add and subtract, and parentheses come first. Spaces around the signs mean nothing: `math n=$n+1` is the same line.

```
set snacks apple|bread|cheese
math n = items($snacks)
if $n = 0 then echo The snack list is empty!
```

This reads as: count the items in `$snacks` and store the count in `$n`; if `$n` is 0, echo that the snack list is empty. With `$snacks` set to apple|bread|cheese, `$n` is 3, and the `echo` stays silent.

- **A sum holds numbers only.** `+` never joins words; `set` does that.
- **Every name in the sum must have a value.** An unset one stops the sum and says which: `math kills = $kills + 1` with no kills yet says `set kills 0` first.
- **If a value in the sum isn't a number, the calculation fails** and Merlin reports the value it found. Dividing by zero does the same. The variable keeps its previous value.
- **Decimals work:** `math price = $price / 3` on 10 gives 3.333333333333. A result too large to count exactly, or smaller than a trillionth without being zero, is refused.
- **`%` is refused,** because it means percent in this client. Write `mod`.
- **A game variable can't be the name on the left.** Put the sum in a name of your own.
- **A sum with a mistake in its shape is an error before the script starts.**

### text: change words

`text` takes some text, changes it in one way, and stores the result in a variable.

| Operation | What it does |
|---|---|
| `shorten` | Drops the first word, keeps the rest. |
| `first` | Keeps only the first word. |
| `last` | Keeps only the last word. |
| `trim` | Takes away extra spaces: at the ends, and doubled ones inside. `a   b` becomes `a b`. |
| `upper` | Makes every letter uppercase. |
| `lower` | Makes every letter lowercase. |
| `replace "old" "new"` | Replaces every occurrence of the old text with the new text. |
| `ordinal` | Turns a number into 1st, 2nd, 3rd. |
| `opposite` | Gives the opposite direction. |

```
text short shorten $prize
```

`text short shorten $prize` sets `$short` to `$prize` with its first word removed. If `$prize` is throwing dagger, `$short` is dagger.

```
text kth ordinal $k
send disarm $kth target
```

`text kth ordinal $k` turns a number into the way the game counts: 1st, 2nd, 3rd, 4th, 11th, 21st. If `$k` is 2, `$kth` is 2nd, and the script sends `disarm 2nd target`.

```
text back opposite $dir
send $back
```

`text back opposite $dir` gives the opposite direction. If `$dir` is north, `$back` is south. A short direction gives a short answer: ne gives sw.

```
text kind last $prize
send get $kind
```

`text kind last $prize` sets `$kind` to the last word of `$prize`. If `$prize` is bronze battle-axe, `$kind` is battle-axe, and the script sends `get battle-axe`. `first` keeps the first word instead: bronze.

```
text loud upper $arg1
send shout $loud
```

`text loud upper $arg1` makes every letter a capital. If `$arg1` is help, `$loud` is HELP, and the script sends `shout HELP`. `lower` goes the other way: HELP becomes help.

```
text mine replace "the" "Belka's" $item
```

`text mine replace "the" "Belka's" $item` sets `$mine` to `$item` with every the turned into Belka's. If `$item` is the rusty dagger, `$mine` is Belka's rusty dagger.

- **Capitalization doesn't matter in the old part;** The is found too.
- **The new part goes in exactly as typed.** An empty new part, `""`, deletes the old part.
- **Both quoted parts are required,** and a `replace` missing one is refused before the script starts.

If the input doesn't work, such as 0 for `ordinal` or sideways for `opposite`, Merlin reports it and leaves the variable unchanged. A missing input variable is handled the same way: Merlin reports it, and the variable being set keeps its old value.

### random: a random integer

```
random d 1 6
```

`random d 1 6` sets `$d` to a random whole number between the two bounds you give it. Here that's 1 through 6, like a die roll. Use it to make repeated actions feel less mechanical, with different words and different pauses each time:

```
random r 1 4
if $r = 1 then send emote hums an old marching tune.
if $r = 2 then send emote stretches and cracks her knuckles.
random rest 3 8
pause $rest
```

This reads as: roll 1 through 4. On a 1, hum a tune; on a 2, crack her knuckles; on a 3 or 4, do nothing. Then roll a rest of 3 to 8 seconds and pause that long. The result of one run, where a 2 and a 6 came up:

```
> emote stretches and cracks her knuckles.
```

That's followed by a 6-second pause. On a 3 or 4, nothing is sent at all. Every run rolls fresh.

### timer: elapsed-time tracking

```
timer start errand
```

`timer start errand` starts a timer named errand. Merlin tracks its elapsed seconds in a variable built from that name: `timer_` plus whatever you called it, so `$timer_errand`.

```
if $timer_errand > 300 then echo five minutes on this errand
```

This reads as "if the errand timer has run past 300 seconds, say so." Five and a half minutes after the start, `$timer_errand` reads 330, the condition is true, and the message appears. `timer clear errand` removes the errand timer. Multiple named timers can run at once.

### turn on, turn off: switch a scheduled timer

A scheduled timer is one from **Settings > Timers**, which sends a command every so many seconds; see the [Timers Manual](MANUAL-TIMERS.md). A script can turn a scheduled timer off and back on by name.

```
turn off timer StandUp
send sit
pause 60
turn on timer StandUp
```

`turn off timer StandUp` reads as "turn off the timer named StandUp." `turn on timer StandUp` turns it back on. Capitalization doesn't matter in the name.

- **A timer that's already on is left alone.** Its countdown doesn't start over.
- **Switching one timer never restarts another timer's countdown.**
- **A name no timer has is reported,** and the script keeps going.

### hold, release: keep a trigger group off while the script runs

A script can temporarily turn off a trigger group while it handles that job itself. `hold trigger group` turns a group off for as long as the script runs. The group comes back the moment the script ends, however it ends.

```
hold trigger group Healer
walkto 184
send shop heal
waitfor restored to full vitality
```

This reads as: keep the Healer triggers off, walk to the healer, buy the heal, and wait for the game to confirm the healing. When the script ends, the Healer triggers are back on. The result:

```
> shop heal
You are healed and restored to full vitality!
```

`release trigger group Healer` turns the group back on before the script ends.

- **The name is the group's name as Settings > Triggers shows it.** Capitalization doesn't matter. A name no trigger carries is an error, and the script doesn't start. A name that comes from a value is checked when the line runs, and a bad one ends the script with a message.
- **A hold isn't saved.** It changes nothing in the triggers file, and your own Pause and Disable settings stay as they are.
- **While any running script holds a group, the group is off.** Two scripts can hold the same group, and it comes back when the last of them ends. A paused script keeps its hold.
- **A trigger that watches a value keeps watching while its group is held.** If the value crosses its trigger point during the hold, that crossing is not saved to fire later when the group comes back on.
- **Releasing a group the script doesn't hold does nothing.** A note says so in debug mode.
- **Both lines work in an action body.**

## Multiple outcomes: match tables

`waitfor` handles one expected response. Some commands can produce several different responses. A swing at a creature might land, or miss. A match table handles all of them:

```
match Hit The swing is a
match Miss The swing misses
send attack hound
matchwait

Hit:
echo A hit!
stop

Miss:
echo A miss.
stop
```

This reads as: register two possible outcomes. If the game says `The swing is a`, jump to Hit; if it says `The swing misses`, jump to Miss. Then attack, and `matchwait` holds until one of them appears. The result when the swing lands:

```
> attack hound
You swing your bronze battle-axe at the dark hound!
[4 sec. delay]
The swing is a strong blow to the head.
Merlin Client: A hit!
```

The result when it doesn't:

```
> attack hound
You swing your bronze battle-axe at the dark hound!
[4 sec. delay]
The swing misses its target.
Merlin Client: A miss.
```

The shape is: one `match <section> <words>` line per possible outcome, then the triggering command, then `matchwait`.

- **Matching starts the moment `match` runs, not when `matchwait` does.** A reply that arrives before the script even reaches `matchwait` still registers.
- **The first matching outcome wins;** the rest are canceled.

There are two ways to end `matchwait`:

- **`matchwait` alone** waits for one of the outcomes declared with `match` to appear. If none does within 30 seconds, it times out and stops the script.
- **`matchwait 10`** waits up to 10 seconds. If an outcome appears, it jumps to that section immediately. If none appears, the timeout is a normal outcome, not an error: the script continues with the line after `matchwait`, and none of the sections run.

`matchre` works like `match`, but with a pattern instead of plain text. Anything the pattern captures becomes `$arg1`, `$arg2`, and so on from the section it jumps to onward (see [Arguments](#arguments) for how long they last), which is useful for pulling a name or number out of the game's text. See [Patterns](#patterns) for a worked example.

## Patterns

Plain text in `waitfor` or `match` has to match exactly, word for word. A pattern, also called a regular expression, matches text that changes each time, such as a number. One rule catches "You pick up 2 silver coins" and "You pick up 15 silver coins" alike. The symbols:

- `text` matches itself.
- `|` means or.
- `\d` matches any digit.
- `+` matches one or more of the thing before it.
- `.` matches any character.
- `\.` matches a literal period.
- `\[` and `\]` match literal brackets.
- `( )` is a capture group.

```
waitforre opens|unlocks
waitforre You pick up \d+ silver coins
waitforre You're no longer thirsty\.
```

`waitforre opens|unlocks` reads as "wait for 'opens' or 'unlocks.'" `waitforre You pick up \d+ silver coins` reads as "wait for 'You pick up' followed by one or more digits, then 'silver coins.'" `waitforre You're no longer thirsty\.` reads as "wait for exactly 'You're no longer thirsty.'" The `\.` is a literal period, not "any character." Square brackets are special: `[Yy]` means any one of the letters inside. To match a bracket itself, put `\` before it: `\[\d+ sec\. delay\]` matches `[4 sec. delay]`.

### Capture text

In `matchre`, `action … whenre`, and `matches`, parentheses capture: the matched text becomes `$arg1`, `$arg2`, in order. `waitforre` only waits, and keeps nothing.

```
matchre Found You pick up (\d+) silver coins
send get coins
matchwait

Found:
echo I picked up $arg1 silver!
stop
```

This reads as: register a pattern named Found that matches "You pick up" followed by a number, capturing the number. Send `get coins`, then `matchwait` holds until it appears. When it does, jump to Found and echo the captured number. The result when the game replies `You pick up 2 silver coins.`:

```
Merlin Client: I picked up 2 silver!
```

The parentheses grabbed the 2; the section received it as `$arg1`. **Tip:** Build a pattern by starting from an actual line the game printed, then replacing only the parts that vary. Capital letters must match, the same as in `waitfor`. To accept either, put both letters in brackets: `[Yy]ou`. An invalid pattern is rejected at script start, with its line number, not silently at run time.

### What a pattern must obey in Merlin

A Merlin pattern is an ordinary regular expression, the same kind a web browser uses. Every symbol that works there works here, except that you can't add flags such as ignore-case. Use `[Yy]` instead. The rules below aren't about the symbols. They're about how Merlin uses a pattern, and a pattern that breaks one of them never matches, even when it's written correctly. They apply to `waitforre`, `whenre`, `matchre`, and a trigger set to matches a Regex.

1. **Merlin checks one line at a time.** Each line the game prints is tested by itself. `^` means the start of that one line, and `$` means its end. A pattern can't reach across two lines.
2. **Capital letters must match.** `opens` doesn't match "Opens". To match either, write `[Oo]pens`.
3. **Captured text is stored in `$arg1`, `$arg2`, and so on.** The first pair of parentheses is `$arg1`, the second is `$arg2`. Giving a group a name does nothing, except in `matches`, where it's an error. To group without saving, write `(?:` instead of `(`, and it takes no number. `waitforre` keeps nothing; to keep what a pattern finds, use `matchre` or `whenre`.
4. **`$name` in a script pattern is a variable.** Merlin swaps in its value before the pattern is used, so `$firstname` becomes the character's name. A `$` on its own at the very end still means end of line. A trigger's pattern is different: it's matched as written, so `$name` is never swapped for a value. There, `$` means the end of the line, as in any regular expression.
5. **Start from the real line.** Copy the text the game printed and change only the part that varies. A pattern written from memory can easily miss because the game's wording may not be exactly what you remember.
6. **Match the part that matters.** `just broke` is enough to catch `Your damaged short simple dagger just broke!`, and it still works when the weapon changes. Put `^` at the front when the same words could turn up in the middle of some other line.
7. **Avoid patterns that repeat something that can also repeat inside itself.** `(\w+\s?)+` looks harmless but is slow in many pattern engines. Merlin protects you from most of these, and switches off any pattern that still runs too long, for the rest of the session, and names it. A script using that pattern stops with a message.
8. **A pattern with a mistake in it is refused.** A script says so when it starts, with the line number. A trigger with one never fires. The trigger editor marks the pattern in red, and pointing at it shows what's wrong.

Regex tells you what `^` means. Rule 1 tells you which line it's the start of.

## Background triggers: action

Some events need a response no matter which line the script is on: getting knocked down, receiving a whisper. `action` creates a temporary trigger, a watcher that checks every game line while the script runs:

```
action send stand when You stumble and fall down
action flash when whispers
action beep when whispers
```

This reads as: whenever the game says something matching `You stumble and fall down`, send stand. Whenever it says something matching `whispers`, flash Merlin's icon and beep. Both actions respond to the same matching line. The result, mid-script, whatever line it was on:

```
    You stumble and fall down!
> stand
```

The script never left its place; the action answered on its own.

### Three trigger types

- **`action ... when <words>`** fires when a game line contains the text. `action send ready torch when too dark to see anything`
- **`action ... whenre <pattern>`** is the same as `when`, but matches a pattern instead of exact text. Anything the pattern captures becomes `$arg1`, `$arg2`, and so on, just like `matchre`. `action echo Picked up $arg1 silver! whenre You pick up (\d+) silver coins`
- **`action ... when eval <condition>`** fires once when the condition becomes true, and can't fire again until it goes false first. A condition that's already true when the action line runs doesn't fire until it goes false and then true again. `action send eat bread when eval $health < 30`

A trigger in **Settings > Triggers** can watch a value too, and keeps watching when no script is running (see [Watch a variable with If a value](MANUAL-TRIGGERS.md#watch-a-variable-with-if-a-value) in the Triggers Manual). If a script's action and a trigger both watch the same value, both act at the same moment, so pick one of them for each job.

### Group actions

Actions can share a group name, so several can be turned off, turned back on, or removed together instead of one at a time. Add the group name in parentheses right after `action`:

```
action (guards) send stand when You stumble and fall down
```

This tags the action with the group name guards. Any other action tagged `(guards)` joins the same group.

- `action (guards) off` disables every action in the guards group.
- `action (guards) on` re-enables them.
- `action (guards) clear` removes them completely.

Actions belong to the script that created them. They stop when the script stops and pause when it pauses.

### Keep an action to one instant effect

Keep an action's body to one instant effect: a `send`, an `echo`, a `beep`, a variable set, or starting a script with `run`.

- **A `send` in an action goes out after the line that fired it is on your screen,** and after any delay, the same as `pause 0` followed by `send`.
- **One command per action:** if the text matches again while the first command is still waiting to go out, the second match is dropped.
- **A command still waiting is dropped when the script stops, pauses, or ends.**

The one exception to "instant" is `stop`:

```
action stop when You are bleeding
```

This reads as: the instant the game says `You are bleeding`, end this script, wherever it is, even in the middle of a long wait. Getting away is then another script's job: an action `run` on the same text, placed before the `stop`, or a trigger that runs your Flee script. For anything more, have the action set a variable and check it from the main script:

```
set panic no
action set panic yes when eval $health < 30
// the rest of the script
if $panic = yes then echo health is low
```

This reads as: start with panic as no; the instant health drops below 30, set it to yes, no matter where the script is. Later, the main script checks `$panic` and says so if it's set.

### Start a script from an action

An action can start a script:

```
action run Flee when You are bleeding
action stop when You are bleeding
```

This reads as: the instant the game says `You are bleeding`, start the Flee script, then end this one. The `run` line comes first, because nothing acts after a `stop`.

- **Flee starts after the rest of that batch of text,** the same as a script a trigger starts, so its own first `waitfor` never sees the line that started it.
- **If Flee is already running, or paused,** the action leaves it alone, and a note says so in debug mode.
- **Values after the name are handed over as with any `run`, captures included:** `action run Loot $arg1 whenre ^(\w+) is dead`.
- **A name no script has, a `$` in the name, and a script starting itself are errors before the script starts.**
- **Thirty starts in ten seconds switch the action off for the run,** with a message.

## Validation

Before running, Merlin checks the whole script: section references, patterns, numbers, verb spelling, and matched pairs (`repeat`/`until`, `foreach`/`endeach`). It also refuses:

- A section defined twice. The message names both lines, and the library if one came from an `include`.
- A `waitfor` or `waitforre` with nothing after it.
- A `text … replace` without both its quoted parts.
- A `$` on the name side of `set`, `keep`, `math`, `text`, `random`, `timer`, or `foreach`. You name a variable without the `$` and read it with one.

A script with an error doesn't start. The error names:

- The line number
- The exact text that failed
- What was expected

For example:

```
Hunt, line 12: no section named "Recovr" — gosub needs a line reading "Recovr:" somewhere in this script
Hunt: not started — fix the line above
```

Validation doesn't check variable values. Those come from the game and change at runtime, so `pause $resttime` always passes validation, whatever `$resttime` holds later. Runtime behavior follows the same principle:

- Every wait has a timeout and reports what it was waiting for. `walkto` is the one exception: it keeps retrying a blocked step, and a creature blocking the way can attack you meanwhile, so give the script an action that stops it when your health drops.
- Invalid input to `math` produces a report, never a silent failure or a corrupted value. A list that was never set is reported too. An empty list is not an error: `foreach` runs zero times.

## Command reference

| Command | What it does |
|---|---|
| `send <text>` | send a command to the game |
| `echo <text>` | message visible only to you |
| `debug <text>` | message visible only in debug mode |
| `play <sound>` | play a sound file |
| `beep` | system alert sound |
| `speak <text>` | say it out loud; a trigger's spoken alert cuts it off |
| `flash` | flash Merlin's icon |
| `pause <n>` | wait n seconds, plus any roundtime |
| `delay <n>` | wait exactly n seconds |
| `later <n> <line>` | in n seconds do one instant line; the script carries on |
| `waitfor <words>` | wait for a game line containing the words |
| `waitforre <pattern>` | pattern version of waitfor; keeps nothing |
| `waiteval [n] <cond>` | wait until condition is true (n = timeout, seconds) |
| `waitreply [n]` | wait until the game has answered this script's last command |
| `move [n] <dir>` | send a step, wait for the room to change; `$moved` yes/no |
| `nextroom [n]` | the same wait, for a step you sent yourself |
| `walkto <room>` | the map walks you to a room by its id; stops the script if it cannot |
| `set <name> <value>` | local variable |
| `keep <name> <value>` | saved variable (Settings → Variables) |
| `math <name> = <sum>` | numbers, `$names`, `+ - * / mod`, `( )`, `round( ) abs( ) min( ) max( ) items( ) length( )` |
| `random <name> <lo> <hi>` | random integer, lo to hi, both included |
| `timer start <name>` | start a timer; read `$timer_<name>`; `timer clear <name>` |
| `text <name> shorten <x>` | drop the first word of x, keep the rest |
| `text <name> first <x>` | keep only the first word |
| `last` | keep only the last word |
| `text <name> trim <x>` | take away extra spaces |
| `text <name> upper <x>` | make every letter uppercase |
| `lower` | make every letter lowercase |
| `text <name> replace "old" "new" <x>` | every old becomes new (`""` deletes) |
| `text <name> ordinal <n>` | 2 becomes 2nd, 11 becomes 11th |
| `text <name> opposite <d>` | north becomes south, ne becomes sw |
| `turn on timer <name>` | turn on a Settings → Timers timer |
| `turn off timer <name>` | turn it off |
| `hold trigger group <name>` | keep a trigger group off while this script runs |
| `release trigger group <name>` | turn the trigger group back on before the script ends |
| `if <cond> then <line>` | conditional `( = != < > <= >= contains starts ends matches is set and or not in )` |
| `if <cond> { ... }` | several lines under one condition, with `else { ... }` and `else if <cond> { ... }` |
| `<value> in <list>` | true if value ends with a list entry |
| `repeat [n] ... until [words]` | loop n times, or until words appear, or with no number until told to stop |
| `until eval <cond>` | …or until a condition holds |
| `stopon <words>` | end the current loop if words appear |
| `stopon eval <cond>` | …or if a condition holds |
| `foreach <name> <list> ... endeach` | loop once per list item |
| `list <name> add <item>` | put an item on a list |
| `front` | put it in front |
| `remove` | remove the best-matching item |
| `$list(2)` | second item in a list (the first item is 1) |
| `$remain(<skill>)` | a game value that takes a name |
| `Name: ... return` | section definition |
| `gosub Name` | call it |
| `include <script>` | make a library's sections available to `gosub` |
| `run <script> [values]` | start another script, handing it `$arg1`, `$arg2` … and `$args`; a running one is left alone |
| `args <name> <name> …` | name this script's arguments, so `$name` reads what `$arg1` would |
| `stop` | end this script |
| `stop <script>` | end that script, if it is running |
| `stop all` | end every script |
| `match <sec> <words>` | declare a possible outcome |
| `matchre <sec> <pattern>` | pattern version; captures → `$arg1…` |
| `matchwait [n]` | wait for a declared outcome (bare = 30s timeout, stops; numbered = continues on timeout) |
| `action <line> when <words>` | temporary trigger while the script runs (also: `whenre`, `when eval`) |
| `action (<group>) on\|off\|clear` | manage the script's own action groups |

**Patterns:** `|` or · `\d` digit · `+` one or more · `.` any character (`\.` literal) · `\[` and `\]` literal brackets · `( )` capture → `$arg1…` (in `matchre`, `whenre`, `matches`)

## Glossary

- **script:** A saved sequence of instructions, run top to bottom.
- **verb:** The first word of a line; determines its action.
- **variable:** A named value, read with `$name`.
- **list:** A variable holding multiple values, separated by `|`.
- **condition:** A test that is either true or false, such as `$health > 50`.
- **section:** A named block within a script, run with `gosub`.
- **library:** A script containing only sections, for `include`.
- **pattern:** A regular expression, for matching variable text.
- **capture:** A parenthesized group in a pattern, delivered as `$arg1…` by `matchre`, `whenre`, and `matches`.
- **action:** A temporary trigger, a watcher that checks every game line while the script runs.
- **validation:** The check before a script starts that reads its structure and reports every error it finds.

## When a script stops with an error

A script that stops with an error reports two things in the Game panel:

1. **Where:** the script's name and what went wrong, such as a `waitfor` that timed out after 30 seconds. When the message names a line, that's the line. When it doesn't, the last line of the trace below is where it stopped.
2. **How it got there:** the last twenty lines it ran, oldest first, each with its line number.

Read the path that led to the error, not just the line where it stopped. A wait timing out inside the main loop means one thing; the same wait timing out inside a rescue section you didn't expect to be in means the problem started earlier. The Script Output panel shows a shorter trace: the last five lines that did something, and the sections the script passed through. **Show the full trace** opens all twenty. A script you stop yourself prints nothing. Neither does one that runs out of lines.

## Example scripts

Short scripts to start from. Change the words to fit your game.

### Eat and drink

```
send eat bread
waitreply
send drink canteen
```

You eat, Merlin waits for the game to answer, then you drink.

### Treat your wounds

```
send lie
waitreply
repeat 30
send treat $firstname
pause 2
until Target is not injured
send stand
```

You lie down and treat your wounds until the game says there's nothing left to treat, then you stand up. It stops then, or after 30 tries, whichever comes first.

### Practice on a target

```
send ready sword
waitreply
repeat
send attack target
pause 1
until I don't see
```

You ready your sword and attack the target over and over. When the target is gone, the game replies `I don't see what you are referring to.` That ends the loop and the script. To end it sooner, click Stop.

### Walk a short path

```
move north
move north
move east
```

Each step waits for the next room before the next one goes. If a step is blocked, `move` gives up after 10 seconds and the next step goes anyway. To stop instead, add `if $moved = no then stop` after each step.

### Greet a friend

```
send wave at $arg1
pause 1
send say Hello, $arg1!
```

Save it as Greet, then type `.greet Marcus`. You wave at Marcus and say `Hello, Marcus!` The name you type after the script's name is `$arg1`.

### Wave to all your friends

```
set friends Marcus|Rose|Taath
foreach friend $friends
send wave at $friend
pause 1
endeach
```

You wave at each friend on the list, one at a time.

### Rest until your fatigue is full

```
send sit
waitreply
repeat
pause 30
until eval $stamina >= 100
send stand
```

You sit and check every 30 seconds until your fatigue is full, then you stand up. It keeps checking as long as it takes.

### Treat your wounds only if you're hurt

```
if $health < 80 {
  send sit
  waitreply
  send treat $firstname
}
else {
  echo You're fine.
}
```

If your health is under 80 percent, you sit down and treat your wounds. If not, Merlin tells you that you're fine.

### Open a door

```
match Opened You open
match Opened already opened
match Locked It's locked
send open door
matchwait
Opened:
echo The door is open.
stop
Locked:
echo The door is locked.
```

You try the door, and the script tells you whether it opened or is locked. An open door counts as opened.

### Get back up while you practice

```
action send stand when fall down
repeat
send attack target
pause 1
until I don't see
```

You attack the target over and over. If you're knocked down, you stand back up and keep going.

### A reminder

```
delay 300
speak Time to eat.
```

Five minutes later, Merlin says `Time to eat.` out loud.

### Do something different each time

```
random n 1 3
if $n = 1 then send smile
if $n = 2 then send wave
if $n = 3 then send bow
```

You smile, wave, or bow. Merlin picks one at random.

### Count your sips

```
set sips 0
repeat 10
send drink canteen
math sips = $sips + 1
pause 1
until You're no longer thirsty
echo Sips: $sips
```

You drink until you're no longer thirsty, and Merlin tells you how many sips it took.

### Look in all your bags

```
set n 1
repeat 3
text which ordinal $n
send open $which bag
waitreply
send look in $which bag
waitreply
math n = $n + 1
until
```

You open each of your three bags and look inside, from the 1st to the 3rd. `text which ordinal $n` turns 2 into 2nd, the way the game counts. Change the 3 to the number of bags you carry.
