This is an old revision of the document!
Script Law
Otherwise known as the SpecProc handbook. Where immortals learn how to attach, edit, and reason about Beanshell scripts on rooms, objects, mobiles, and items in Nilgiri.
What scripting is here
Nilgiri does not ship a separate “dg_scripts” or MUDL interpreter for builders. Live world scripts are Beanshell (bsh) stored on SpecProc rows in the world H2 database and evaluated at event time. Hard-coded Java specials under net.nilgiri.mud.spec (for example MobileMayor, RoomPrayForItems, SpellSunbeam) remain available for behavior that belongs in compiled code rather than a DB script.
- See: Code Law — why Beanshell was chosen
- See: Creation Law — build the room / item / npc prototype before attaching a proc
Two immortal faces exist for scripts:
- '''
@proc''' — create/rename/edit/delete named SpecProcs for the world (item, npc, room, spell). Creator command set. - '''
script''' — list or rewrite the SpecProc whose name matches a player (usually your own). Immortals edit their own; implementors may name another player as a third token.
Who may script
@procis inCommand._creatorand gated byImmortality.creatorCheck(same as building).scriptis also listed under the creator set. Non-implementors may only edit the script named for themselves; implementors may pass another player name.- Scripts persist in world tables (
spec_mobile,spec_room,spec_item, spell procs) and reload on boot viaSpecProc*.init(). Errors during eval are reported to the owning immortal when the proc name matches a player CID (seeSpecProcMobile.func).
Creating and attaching a SpecProc
Named world procs (''@proc'')
> @proc npc new thanksgiving Okay. > @proc npc script thanksgiving (writer opens — paste Beanshell, then finish) > @proc npc examine thanksgiving …script text… > @proc npc matrix thanksgiving 0.0 Okay. > @proc npc rename thanksgiving turkey_mom Okay. > @proc npc delete turkey_mom Okay. >
Help text from code:
@proc item [delete|examine|new|matrix|rename|script] <name>@proc npc [delete|examine|new|matrix|rename|script] <name>@proc room [delete|examine|new|matrix|rename|script] <name>@proc spell [delete|examine|new|matrix|rename|script] <name>
Proc names are lowercased, alphabetic/underscore, length 3–16 (SpecProc.MIN_NAME_LEN / MAX_NAME_LEN).
List existing procs with the immortal index:
> index proc_npc thanksgiving > index proc_room > index proc_item >
Bind the proc to a prototype
While a room / item / npc workspace entry is open (see Creation Law):
> @load npc 9314 Okay. > @npc proc thanksgiving Okay. > @save npc Okay. > @load room 1200 > @room proc my_room_proc > @save room > @load item 3100 > @item proc my_item_proc > @save item >
Personal ''script'' writer
> script mobile list Listing the mobile script for Silk: … > script mobile write Writing a new mobile script for Silk. (writer opens) > script item list > script room write >
Usage patterns from Immortal.cmdScript: script (mobile|item|room) (list|write) [player].
Event triggers (STATE_*)
SpecProcs are invoked from room / mobile / item event paths with a Var context. The trigger kind is var.STATE (integers from Constant):
| Constant | Value | Typical meaning |
|---|---|---|
STATE_SCRIPT | 0 | Periodic / script pulse (mobile mobact, room roomact, …) |
STATE_INTERPRETER | -1 | Before / during command interpretation involving the entity |
STATE_COMBAT | -2 | Combat tick involvement |
STATE_POST_INTERPRETER | -3 | After a command (give, say, social reactions, …) |
STATE_ENTER | -4 | Entry into a room (room procs) |
STATE_MOVE_TO | -5 | Movement toward / into |
STATE_UPDATE | -6 | Periodic room update |
Scripts should branch on var.STATE (or the named constants on the Var, e.g. var.STATE_POST_INTERPRETER). Returning Boolean.TRUE can block the triggering command for interpreter-style calls (see javadoc on SpecProcMobile.func). Returning false / null allows normal processing.
Authors historically embed the literal tokens STATE_SCRIPT, STATE_INTERPRETER, … inside the script text so SpecProc.script() can set CPU filter flags. In current SpecProcMobile.func that early-return filter is commented out, so mobile scripts still run for every call until the optimization is re-enabled — keep scripts cheap, and still write the STATE branches for correctness.
Bindings available to Beanshell
Each func sets:
- '''
var''' — event context (MobileEvent.Var,RoomEvent.Var, orItemEvent.Var) - '''
util''' —ScriptToolhelpers (roll,chance,limit,max,min,getPosInt,tokenizerNoFill, …)
Fields on ''var'' (mobile / room family)
Common public fields include:
STATE,state— trigger kind and a free mutable script state integermobile/room— the SpecProc owner shell (MobileScript/RoomScript)victim,victims,item,itemscommand,args— command verb and argument string for interpreter hooks- Scratch aliases:
m,it,rm,s,n
Shell APIs (illustrative, from code)
Scripts call methods on shells rather than inventing new verbs. Real mobile scripts in the world DB use patterns such as:
var.mobile.echoRoom(…),cechoRoom(…),cechonRoom(…),say(…),social(…),interpretSocial(…)var.mobile.follow(…),teleportHome(),teleportToNC(…),createItem(…),createNPC(…),purge(),oldScript(String[] cmds)var.item.purge(),var.item.vid()util.roll(n),util.chance(die)
Room shells expose weather / door helpers (lockNorth, unlockEast, echo, …). Item shells expose type tests (isWeapon, isKey, …), purge, createMobile, fill helpers, and more.
Prefer reading an existing proc with @proc npc examine <name> or index proc_npc before inventing new call patterns — the shell surface is large and only what exists on MobileShell / RoomShell / ItemShell (and their script subclasses) is legal.
SpecProc matrix
@proc … matrix <name> <double> stores builder metadata on the SpecProc row. It is not combat power. Combat matrix scores come from Matrix.calculate / Mobile.matrix(). Live procs often leave matrix at 0.0.
Worked examples from the live world dump
These are shortened illustrations of patterns found in db.world.sql SpecProc mobile scripts — cite the real proc names when studying full text in-game.
Periodic chatter + post-interpret reaction (''babybunny'')
> @proc npc examine babybunny
Pattern: on STATE_SCRIPT (0), roll and echoRoom; on STATE_POST_INTERPRETER (-3), react to slap. Always end with return false; unless you intend to block.
Quest turn-in on give (''thanksgiving'')
On pulse (STATE == 0) the hen speaks via oldScript. On post-interpret, if var.command is give and var.item.vid() matches the quest item, thank the player, purge the item, and createItem a reward into the victim's inventory. Returning true consumes the interaction.
Follow / sayto control (''quest_cursor'')
Branches on var.STATE_POST_INTERPRETER for sayto orders (follow, make indian, …) and on STATE_SCRIPT to teleport home or stay with the master. Uses var.STATE_POST_INTERPRETER / var.STATE_SCRIPT named constants rather than raw integers.
Hard-coded Java specials
Not every interesting mobile is a Beanshell row. MobileSimpleScript dispatches compiled methods by VID for a large legacy set. Classes such as MobileMayor, MobileJanitor, RoomSkullDoor, ItemExcalibur implement SpecProc* in Java. Attach those through the SpecNPC / SpecItem / SpecRoom attribute on stats/rooms (via @npc_stats attrs / @item_stats attrs / @room attrs) when the special is an enum-backed compiled proc rather than a free-form script name.
Editing safety
- Immortal / creator only — mortals never pass
creatorCheck. @proc … scriptandscript … writeopen the standard multi-line writer; finishing the writer calls back intoImmortalityto store the text and update filter flags.@proc … deleteremoves the DB row; detach prototypes that still name the proc before deleting if you rely on clean references.- Eval errors: Beanshell
EvalErroris caught; when the proc name matches a player, that player receives the error text. Laggy scripts (>Eventable.MAX_LAG_TIME) are logged. - There is no separate “script reload” verb beyond saving the proc (
script()→update()) and ensuring the prototype points at the name. Boot reloads everything from H2. - Shared static Beanshell
Interpreterinstance onSpecProc— keep scripts free of leftover globals; the runtime unsetsvar/utilafter each eval.
Minimal starter script
A cautious mobile pulse stub that does nothing harmful:
if (var.STATE == var.STATE_SCRIPT) {
// occasional flavor only
if (util.chance(100)) {
var.mobile.echoRoom("You stretch.", "$n stretches.");
return true;
}
}
return false;
Attach after creating the proc:
> @proc npc new quiet_stretch Okay. > @proc npc script quiet_stretch (paste stub, finish writer) > @load npc 1210 > @npc proc quiet_stretch > @save npc > create npc 1210 > zone reset >
Related
- Creation Law — prototypes, zones, load/create/purge
- Code Law — Beanshell among coding resources
- Game Law — player-facing systems scripts may interact with