MadROM Mud Script Authoring Guide
This guide documents the current MadROM .ms Mud Script language implemented in src/mudscript.c.
Source Of Truth
src/mudscript.ccontrols parsing, trigger dispatch, conditions, variables, response blocks, and dry-run simulation.src/db.cloads#MUDSCRIPTSarea mappings and assigns one.msfile to a mobile.area/MOBProgs/*.mscontains the active script files.MSTAT <mob>shows whether a mobile uses mudscript, has a broken mudscript mapping, or has no script.MUDSIMruns a dry-run trigger without executing live game commands.
What Mud Script Is
A Mud Script file is a behavior file for one NPC prototype. The area file maps a mobile vnum to a .ms file in a #MUDSCRIPTS section. The engine loads trigger blocks from that file and runs those blocks when the matching game event happens.
Each mobile prototype gets one Mud Script file. If an area tries to assign more than one, the later file is ignored and a bug is logged. Keep one coherent script file per mobile.
Area Mapping
Put mudscript mappings in the area file's #MUDSCRIPTS section. Files live under area/MOBProgs/.
#MUDSCRIPTS
M 3011 mid_hassan.ms
M 30600 chess_white.ms
S
This is the supported live path for NPC script mappings.
File Format
A .ms file contains one or more trigger blocks and normally ends with |.
>trigger_type arglist~
commands...
~
>trigger_type arglist~
commands...
~
|
- Each block starts with
>, then a trigger name, then an arglist ending in~. - The command body ends at the next
~. - The file normally ends with
|. - If the final
|is missing after a complete block, EOF is accepted and a repair note is logged. - Unknown trigger names or malformed block syntax fail the file load.
- Full-line comments starting with
#,*, or//are ignored by the loader and runner. Leading spaces are fine. Inline comments after commands are not stripped.
# File-level comment before the first trigger.
// Comment styles are intentionally simple.
>greet_prog p 100~
# Body comment. The script runner skips this line.
say Welcome, %player%.
~
* Another file-level comment between blocks.
|
Supported Triggers
| Trigger | Arglist | When It Runs |
|---|---|---|
speech_prog | word/phrase match | When a player or NPC speaks in the room. |
act_prog | word/phrase match | When the mob receives an act() message with an actor. |
entry_prog | percent chance | When the scripted mob enters a room. |
greet_prog | percent chance | When a player or NPC enters/is checked in the room, the mob is awake, not fighting, and can see the actor. |
all_greet_prog | percent chance | Like greet_prog, but without the visibility check. |
fight_prog | percent chance | During combat trigger checks. |
hitprcnt_prog | percent chance | During hit-percent combat trigger checks. |
death_prog | percent chance | When the scripted mob dies. The killer is available as the actor ($n) for player memory and targeted flavor. |
rand_prog | percent chance | On the normal random trigger tick. |
give_prog | exact object name string or all | When a player gives the mob an object. |
bribe_prog | minimum gold amount | When a player bribes the mob. |
time_prog | hour 0-23 | Once each game hour when the current hour exactly matches. |
exit_prog / leave_prog | percent chance | After a player successfully leaves the mob's room. |
arrive_prog | percent chance | After a player successfully enters the mob's room. |
open_prog, close_prog, lock_prog, unlock_prog | percent chance | After a player successfully changes an exit, portal, or container door state. |
get_prog, drop_prog, wear_prog, remove_prog | percent chance | After a player successfully moves an item or changes equipment state. |
function_prog | function name | Reusable block called with callfunc name; it does not fire by itself. |
Actor Filters
Speech, act, and percent triggers can gate on actor type with p or n.
- Bare arglist: accepts either player or NPC actors.
p phraseorp~: player actors only.n phraseorn~: NPC actors only.- For percent triggers,
p 25means player-only 25 percent;n 25means NPC-only 25 percent. - If the actor is missing, player-only and NPC-only gates do not pass.
>speech_prog p hello~
say Hello, %player%.
~
>speech_prog n~
say I heard another mobile.
~
>greet_prog p 50~
say Welcome, $n.
~
Word And Phrase Matching
speech_prog and act_prog support two matching styles.
- Without a prefix, the arglist is split into words. Any listed word can match on word boundaries.
- With
porn, the rest of the arglist is phrase mode for that actor type. - An empty phrase after
pornmatches every message from that actor type. - Matching is case-insensitive.
>speech_prog hammerbarn sting nits~
say That was one of the words I was waiting for.
~
>speech_prog p nice parking rita~
say Thanks for playing along.
~
Percent Triggers
Percent triggers clamp chance to 1..100 and run at most one matching block per trigger event. The engine walks the script blocks in file order and stops after the first block that passes its roll.
>rand_prog 15~
say The room feels a little less quiet.
~
>fight_prog 25~
say You picked the wrong fight.
~
>fight_prog n 40~
say Another NPC dragged me into this.
~
Command Execution
Ordinary command lines are expanded, then run as the scripted mobile through the normal command interpreter. That means most existing mobile commands work as long as the mob could legally issue them.
breakandreturnstop the current script run.- Stray
elseif,else,endif,case,default, andendswitchare ignored. - During
MUDSIM, command lines are printed as[would run]instead of executing. - Mud Script temporarily grants the mob a visibility bypass while script commands run, so private echoes/actions can target the trigger actor reliably.
- Normal
castrespects the mobile's level. Usempcastonly when a script intentionally needs to cast beyond that level gate.
Built-In Mudscript Commands
| Command | Use |
|---|---|
setvar name value | Set a run-scoped string variable. |
unsetvar name / clearvar name | Remove a run-scoped variable. |
setmem key value | Store numeric player memory for this mob vnum and key. |
addmem key amount | Add to numeric player memory. Memory is saved on the player file under this mob's vnum and key. |
unsetmem key / clearmem key | Remove numeric player memory. |
callfunc name | Run a function_prog name block from the same script file. |
delayfunc ticks name | Schedule a function_prog name block on the same NPC after that many mobile update pulses. Delayed functions run without a saved $n actor. |
mudlog message | Write a server log line prefixed with the mob vnum. |
mpcast spell [target] | Cast through the normal spell command path while bypassing the mobile level gate. |
response N% { ... } | Roll once, then choose one response option or one grouped response outcome. |
Movement, door, and item hooks are reaction-only mobile scripts. $n is the player. Item and object-door hooks also expose $o/$O. Percent variables %event%, %direction%, and %target% expose action context when present.
Variable Expansion
Variables expand before commands run.
| Token | Meaning |
|---|---|
$i | First keyword of the scripted mob. |
$I | Short description of the scripted mob, falling back to name. |
$n | First keyword/name of the actor; player names are capitalized. |
$N | Visible actor name/short description, or someone if hidden. |
$o | First keyword of the current object in give/bribe/object context. |
$O | Short description of the current object, falling back to object name. |
$e, $m, $s | Actor pronouns: he/she/they, him/her/them, his/her/their. Hidden actors become generic. |
$j, $k, $l | Scripted mob pronouns. |
$$ | Literal dollar sign. |
%name% | Run-scoped variable. |
%player% / %player_name% | Actor's first name/keyword. |
%mem:key% | Persistent numeric player memory for this mob and key. |
Unknown $ codes are preserved literally for forward compatibility.
Control Flow
Mudscript supports line-based conditionals and switches.
if ispc($n)
say You are a player.
elseif isnpc($n)
say You are another mobile.
else
say I do not know what you are.
endif
switch %mood%
case happy
say Good day.
case angry
say Not now.
default
say Hmm.
endswitch
if,elseif,else, andendifare supported.switch,case,default, andendswitchcompare strings after variable expansion.breakandreturnstop the script run, not just the current block.- Nested
ifand nestedswitchblocks are understood by the skip logic.
If-Check Operators
Conditions use function-style checks. Boolean checks can be negated with leading !.
if !isfemale($n)
say You are not female.
endif
String comparisons support the same operators as the engine helper: equality, inequality, and substring-style matching where supported by mudscript_seval(). Numeric comparisons support ==, !=, <, <=, >, and >=.
Character Checks
| Check | Meaning |
|---|---|
ispc($x) | Target is a player. |
isnpc($x) | Target is an NPC. |
isimmort($x) | Target is an immortal player. |
isgood($x) | Target is good aligned. |
isevil($x) | Target is evil aligned. |
isneutral($x) | Target is neutral aligned. |
ismale($x), isfemale($x), issexless($x) | Target sex checks. |
sex($x) == N | Numeric sex check for compatibility; prefer the named checks above. |
isfighting($x) | Target has a fighting pointer. |
ischarmed($x) | Target has charm affect. |
isfollowing($x) | Target has a master in the same room. |
isleader($x) | Target has no master. |
ispet($x) | Target is an NPC with ACT_PET. |
isnotpet($x) / isntpet($x) | Target is not an NPC pet. |
isactive($x) / isawake($x) | Target position is above sleeping. |
issleeping($x) | Target position is sleeping. |
isvisible($x) | The scripted mob can see the target. |
istarget($x) | The scripted mob is fighting the target. |
level($x) >= N | Target trust level comparison. |
hitprcnt($x) <= N | Target hit ratio comparison as currently implemented by the engine. |
inroom($x) == VNUM | Target room vnum comparison. |
questmob($n), questobj($n), questpoints($n) | Read the player's current quest target and quest point state. |
questactive($n), questdone($n), questgiver($n), questtime($n), nextquest($n) | Read active/completed quest state, local questmaster vnum, quest time remaining, and quest cooldown. |
align($x) < N | Target alignment comparison. |
position($x) == N | Target position number comparison. |
goldamt($x) >= N | Gold carried by $i or $n. |
tribe($x) == abbrev | Tribe abbreviation for $i or $n. |
Object And Room Checks
| Check | Meaning |
|---|---|
number($i) == N | For $i, compares the mob's gold value as currently implemented. |
number($n) == VNUM | For NPC actors, compares actor mobile vnum. |
number($o) == VNUM | Compares current object vnum. |
name($i) == word | String compare against mob keywords. |
name($n) == word | String compare against actor keywords/name. |
name($o) == word | String compare against object keywords. |
objtype($o) == N | Current object type number. |
objval3($o) == N | Current object's value slot 3. |
mobhere(VNUM) > 0 | Counts matching mobs in the current room. |
mobhere(VNUM) > ROOMVNUM | With a comparison value present, the engine checks the selected room. This is low-level; test with MUDSIM before relying on it. |
Affect Checks
hasaffect($x, flag) and isaffected($x, flag) look up named flags in affect_flags and affect2_flags.
if hasaffect($n, sanctuary)
say Sanctuary will not save you forever.
endif
Convenience aliases exist for common flags: isblind($x), isinvisible($x) / isinvis($x), issanctuary($x) / issanct($x), and isflying($x).
Variables And Functions
Run-scoped variables reset at the start of each trigger run. Functions share the caller's run-scoped variables and actor/object context.
>function_prog greet_player~
say Hello, %player%.
~
>greet_prog p 100~
setvar mood friendly
if var(mood) == friendly
callfunc greet_player
endif
~
|
Function recursion is capped at 16 calls to prevent runaway loops.
Delayed Functions
delayfunc lets a script split a staged action across update pulses without writing a custom special.
>greet_prog p 100~
say Give me a moment.
delayfunc 3 follow_up
~
>function_prog follow_up~
say There, that should do it.
~
Delayed functions run on the same live NPC if it still exists. They do not keep the original trigger actor as $n.
Persistent Player Memory
Mob memory is stored on the player file by mob vnum and key. It only works when the current actor is a player. For NPC actors or missing actors, memory commands safely do nothing.
>speech_prog p remember me~
addmem remembered 1
if mem(remembered) >= 3
say I know you, %player%.
else
say I will remember that.
endif
~
Response Blocks
response rolls once. If the roll passes, it picks one option. Plain options run one selected command. Grouped options run every command in the selected group.
response 50% {
say One possible line.
say Another possible line.
}
response 100% {
{
say First grouped outcome.
smile
}
{
say Second grouped outcome.
nod
}
}
- A leading
choiceword is ignored for old draft compatibility. - Malformed or empty response blocks log and skip safely.
- A missing closing brace can soft-close at a safe boundary, but do not rely on that for new scripts.
Give And Bribe Context
give_prog and bribe_prog carry object/money context into mudscript.
>give_prog all~
// Match all hand-ins, then decide by object context.
if name($o) / sigil
addmem sigils 1
say This sigil is real. I have counted %mem:sigils% from you.
elseif objtype($o) == 18
say A key is useful, but not the proof I asked for.
else
say I cannot use $O.
endif
~
>bribe_prog 1000~
say Fine. You have my attention, $n.
~
Use $o, $O, number($o), name($o), objtype($o), and objval3($o) only when an object context exists. For broad hand-in logic, use give_prog all and branch inside the block.
Testing With MUDSIM
MUDSIM is the staff safety tool for mudscript. It runs the trigger logic and prints what would happen without executing commands.
mudsim 3011 speech pc hello
mudsim 3011 speech npc hello
mudsim 3011 greet pc
mudsim 3011 bribe pc 1000
mudsim 3011 time none 19
- Syntax:
mudsim <mob vnum> <trigger> [pc|npc|none] [text|amount|hour]. - Supported trigger names:
speech,act,entry,greet,allgreet,fight,hitprcnt,death,rand,bribe, andtime. - It reports
[would run]lines for normal commands and special markers for variable/memory/function commands.
Debugging Live Behavior
MSTAT <mob>shows script source and loaded trigger count.- Boot/load errors go to the normal bug/log path and identify the mob vnum and file when possible.
mudlog messagewrites explicit script breadcrumbs to logs.- The engine has targeted debug support in
src/mudscript.c; use it when you need trigger-vs-skip evidence.
Migration Notes
This section is only for reading old history or converting outside material. It is not the preferred way to write new scripts.
- Old checks like
isfight,ischarm, andisfollowmap toisfighting,ischarmed, andisfollowing. The aliases still warn. - Prefer named sex checks like
ismale($n),isfemale($n), andissexless($n)over numericsex($n)tests. - Once a mob is migrated and validated, remove duplicate legacy execution paths. Do not leave old and new scripts active for the same behavior.
- Mud Script has grouped response blocks, function blocks, run variables, player memory, object context, and NPC actor filters.
Practical Script Examples
These examples are intentionally larger than syntax snippets. They show the patterns that make Mud Script worth using: comments, functions, response groups, player memory, actor filters, object context, combat phases, and safe dry-run behavior.
Stateful Greeter With Player Memory
# A guide who remembers repeat visitors and shares flavor without a pile of duplicate blocks.
>function_prog first_visit~
response 100% {
{
say First time through? Keep your eyes open and your hands off anything humming.
emote points toward the safest-looking hallway.
}
{
say New faces get one free warning: if the floor clicks, stop moving.
emote taps a chalk mark beside the door.
}
}
~
>function_prog returning_visit~
response 100% {
{
say Welcome back, %player%. You are starting to learn the rhythm of this place.
emote checks a mark beside your name.
}
{
say %player%, again? Either you are brave or the exit signs are terrible.
smile
}
}
~
>greet_prog p 100~
# Player-only greet. NPC traffic will not burn memory counters.
if isimmort($n)
say Staff inspection? I will pretend this desk is organized.
return
endif
addmem visits 1
if mem(visits) == 1
callfunc first_visit
elseif mem(visits) < 4
callfunc returning_visit
else
say You know the route now, %player%. Show the next lost soul where to stand.
endif
~
>speech_prog p lost help direction~
if mem(visits) < 2
say Start with the quiet door. Loud doors want attention.
else
say You have been here before. Trust the chalk marks, not the shiny ones.
endif
~
|
Quest Hand-In And Bribe Negotiation
# One file handles clue gathering, item hand-ins, and a paid shortcut.
>function_prog ask_for_proof~
say Bring me the stamped sigil, not a story about where you saw it.
~
>speech_prog p proof sigil job~
if mem(approved) >= 1
say You already proved yourself, %player%. Do not make me do paperwork twice.
else
callfunc ask_for_proof
endif
~
>give_prog all~
# The player has already given the item to this mob. Inspect $o before rewarding.
if mem(approved) >= 1
say I already cleared you. This extra $O goes in the evidence pile.
return
endif
if name($o) / sigil
setmem approved 1
addmem sigils 1
response 100% {
{
say This seal is genuine. You may pass, %player%.
emote records the sigil in a battered ledger.
}
{
say Finally. A real sigil, real ink, real permission.
nod
}
}
elseif objtype($o) == 18
say A key proves you can open doors. It does not prove you belong beyond mine.
else
say This is not proof. This is $O.
endif
~
>bribe_prog 500~
if mem(approved) >= 1
say Keep your gold. You are already cleared.
else
addmem bribes 1
if mem(bribes) >= 3
say You keep trying to buy a stamp. That tells me enough.
mudlog repeated bribe attempt from %player%
else
say That buys advice, not permission: find the sigil.
endif
endif
~
|
Combat Phase Script With Clean Exit Paths
# A compact boss pattern: early taunts, mid-fight pressure, panic behavior, death cleanup.
>function_prog taunt~
response 100% {
say You mistake persistence for progress.
say I have ended better plans than yours before breakfast.
emote adjusts position and watches your footing.
}
~
>fight_prog 35~
if hitprcnt($i) <= 25
return
endif
if rand(20)
callfunc taunt
endif
if rand(30)
cast 'dispel magic' $n
return
endif
if rand(25)
cast 'blindness' $n
endif
~
>hitprcnt_prog 100~
if hitprcnt($i) > 25
return
endif
response 100% {
{
say Enough. Now I stop being polite.
cast heal self
}
{
emote slams a hand against the wall and steadies.
cast sanctuary self
}
}
~
>death_prog 100~
say Remember this room. It remembers you.
mudlog boss defeated by %player%
~
|
Timed Atmosphere With Staff-Friendly Breadcrumbs
# Time triggers do not have an actor. Avoid $n checks unless a trigger supplies one.
>time_prog 6~
say The first bell rings, and the room begins pretending it was awake already.
mudlog dawn line fired
~
>time_prog 19~
say The evening lamps catch one by one, each a little less confident than the last.
~
>rand_prog 8~
response 100% {
{
emote straightens a stack of notes nobody admits to moving.
say If anyone asks, this was always alphabetized.
}
{
emote pauses, listening to the walls.
say Old rooms have old opinions.
}
}
~
|
Authoring Rules
- Use Mud Script for new NPC behavior.
- Keep one behavior concept per block where practical.
- Use
function_progfor repeated command groups. - Use
responsefor random flavor instead of deep random ladders. - Use
MUDSIMbefore deploying risky logic. - Document weird script intent in comments inside the
.msfile. - Do not teach builders old scripting habits unless the job is conversion.