MPI for the Technically Impaired
or Practical MPI Basics--What do I do with this?

an immense document written for your amusement and education by Ginger@FurToonia -- helpdocs with style!


MPI is a much underrated programming language that is hidden deep within the MUCK server. It is terribly powerful, and can be used to create all kinds of special effects. Properly used, it can move objects and players from place to place, run programs, change descriptions on rooms depending on the actions of the players in them, and even alter properties on players, rooms or objects. This is an invaluable aid to roleplaying and building.

However, MPI's help files aren't too verbose in just how to put the bloody language to use, and this is what this document aims to rectify. It will not define every MPI command, as this is already done by MPI, info mpidocs1 and info mpidocs2 on most MUCK servers. What it aims to do is introduce MPI and explain a few of its abilities in language that less closely resembles Japanese.



MPI Introduced

MPI can be called from the server anytime by inserting special code into properties on objects on the MUCK. (If you are not familiar with any of these concepts, start out a little more slowly with MUCK Basics.) Whenever you activate @desc, @succ/@osucc, @drop/@odrop, or @fail/@ofail properties on an object, room, exit or player, the MUCK server checks it to see if there's any MPI in it, and runs the MPI code if it finds any. That's it. Really. Anyone can use MPI--you don't need a MUCKer bit for it.

MPI looks like a whole bunch of funny words surrounded with {}s (curly brackets) and separated by commas. Some of the funny words are called commands, and others are called arguments. Basically, commands tell MPI what to do with the arguments. A typical MPI statement looks like this:

 

{<command>: <argument>,<argument>...}

When MPI runs this, it will run <comman d> on the arguments. For example, the command {prop:<propname>,<object>} retrieves the property <propname> off of the object <object>. So if you ran

{prop:species,me}

through the MPI parser (the part of the server that "parses", or translates, MPI), you would get back the value of your species property (help properties online if you aren't clear about properties and values). If you ran

 

{prop:sex,#1580}

through the parser, you would get the value of the "sex" prop off of object #1580.

@set <object>=<property>:<value>. Assigns the value <value> to a property <property> on <object>. Example: @set me=sex:female assigns the value "female" to the the "sex" property on the player typing the command.


 

Using MPI

Okay, now you know what MPI looks like. Here's how to actually use it in some of your building.

As stated before, whenever any of the @messages (@succ/@osucc, @fail/@ofail, etc) on an object in the database are activated (someone looks at you, tries to pick up an object, or goes through an exit), the server automatically checks to see if there's any MPI in that message. If there is MPI there, then the server parses (translates, remember) the MPI before it tells the players the message.

@success <object> [=<message>]. Sets the success message for <object>. The message is displayed when a player successfully uses <object>. Without a message argument, it clears the message. It can be abbreviated @succ. <object> can be specified as <name> or #<number>, or as 'me' or 'here'. This is the same as '@set =_/sc:[text]' See also @OSUCCESS, @DROP, @ODROP, @FAIL, @OFAIL.

Basically, using MPI is as simple as stuffing it into any one of these messages and letting the MUCK server do the rest of the work. Observe:

@desc here={prop:roomdesc,here}
@set here=roomdesc:You see a chair, some tables, and a bed, and a really, really huge television set. There is a cat in the corner of the room, eating the drapes.
> look here
You see a chair, some tables, and a bed, and a really, really huge television set. There is a cat in the corner of the room, eating the drapes.

When a player types "look here" the MUCK server checks the room's @description property for MPI. It finds some: {prop:roomdesc,here}, and evaluates it. It gets the property called "roomdesc", and gives its value as the description of the room. This same principle works for @descs, @succs, and all those other messages.


 

Writing MPI

There are two lovely programs on FurToonia--@mpi and MPIedit--which will help you in writing and debugging (fixing problems in) MPI.

@MPI will simply evaluate any MPI string you put into it. For example, @MPI {name:me} will return your name, and @MPI {loc:me} will return the database number of your location.

MPIedit is a slightly more complex program which will allow you to edit long strings of MPI for complicated maneuvers, such as altering the descriptions of rooms depending on what the players in them are carrying, or altering the @succs of exits depending on the description of the room or the time of day.

Both of these programs are invaluable when writing MPI, because when MPI breaks, it comes up with lovely cryptic messages like "@Succ) IF: Too many arguments. (ARG 2)". People with my amount of technical expertise will want to type mpi debug and become familiar with the {debug} command.

MPI is probably written just like a whole lot of programming languages but I don't know any except MUF so that's not much help. Basically, MPI is a "nesting" language: those curly brackets nest around each other from the inside to the outside. The stuff on the inside is evaluated first, and the stuff on the outside is done last. For example:

 

{name:{loc:me}}

Evaluating this (by putting it in the @succ of an exit, the @desc of a room, or running it through @MPI) will return the name of your location. The MPI parser starts with {loc:me} and returns the dbref (not the name in words) of the room you are standing in; then it evaluates the {name:} part, with the dbref of your location as the argument. Here is the MUCK server's evaluation of that:

(@Succ) {NAME:"{loc:me}"}
(@Succ) {LOC:"me"}
(@Succ) "me"
(@Succ) {LOC:"me"} = "#2265"
(@Succ) {NAME:"#2265"} = "Inside the Hamster"
Inside the Hamster

  • The parser looks at the whole MPI command.
  • It goes to the middle command: {loc:me}.
  • It evaluates the argument of {loc}, which is "me".
  • It finds the location of "me", and returns its dbref.
  • It runs the dbref through {name} and gets the room's name.
  • The parser returns the name of the room to the player.
Note: To get a listing like the one above, surround all of your MPI code with the {debug} command. For example, to get the evaluation above, I ran @MPI {debug:{name:{loc:me}}}

Commands generally get a lot more convoluted than that, however, which is why MPI programmers are always so bitter about everything. Let's say you want to have a false exit (one that fakes a "sit down" motion, generally) which allows most people to sit down but will do something else if the sitter is a fox (has the word "fox" or "vixen" in their species). This is nasty and complicated (though not as bad as it gets), and looks like this:

A fake exit is an exit that isn't linked to a player, an object, or a program, and doesn't do anything when triggered except emit a message to the triggering player or to the room. The "sit down" example involves an exit named sit down, which, when triggered, tells the triggering player "You sit down." and the rest of the room "<player's name> sits down.". For the "official" explanation, type help bogus.

{if:{or:{not:{eq:{instr:{prop:species,me},fox},0}},{not:{eq:{instr:{prop:species,me},vixen},0}},A beautiful feline comes up behind you and massages your shoulders\, just because you're vulpine.,You sit comfortably on the chair.}

Okay, that was a bad one. Let's take it apart:

It starts out with an {if} command. {if}'s syntax is like this: {if:<conditions>,<commands if true>,<commands if false>}.
The server thus looks at the <conditions> part first, and sees:

{or:{not:{eq:{instr:{prop:species,me},fox},0}},{not:{eq:{instr:{prop:species,me},vixen},0}}

(We're going further into the "nest".) {or} looks at two arguments to see if either one of them evaluates to be true: it's looking at {not:{eq:{instr:{prop:species,me},fox},0}} and {not:{eq:{instr:{prop:species,me},vixen},0}} (See the comma between them?). {not} changes a true value to a false, and vice versa. {eq} takes two arguments and checks to see if their (numerical) value is equal.

{instr} is the nasty one. It looks to see if its second argument is "in" the first. For example,

 

{instr:supercalifragilisticexpialidocious,calif}

This checks to see if the word "calif" is contained in the string "supercalifragilisticexpialidocious". It is, and so the server will return 6, the position of the first letter "calif" in the longer string. If we'd typed {instr:wobbegong,calif}, however, we would get 0 returned, because the word "calif" is not in the word "wobbegong".

So the MUCK server looks at the first {instr} command and sees that {instr} wants to know about {prop:species,me} and the word "fox". Let's say our species is "vixen"...so when {instr} checks to see if "fox" is contained in our species property the MUCK server says "No.", and returns a 0.

Now the {eq} command comes in. Look closely, and you'll see that {eq} is asking if whatever {instr} returned is equal to zero. It is, because your species doesn't contain the word "fox".

Thankfully, there is online MPI help in the server. Just type "MPI" for the list of available MPI commands. The syntax and permissions checking for each command can be listed with "MPI <command>". If all else fails, ask a wiz or the helpstaff!

So the server checks the other argument for the {or} statement to see if it's true. This one's really close to the first one: it wants to see if your species property contains the word "vixen". Lo and behold, it does! and the parser returns a 1, because that's where it found the word "vixen" in your species property.

The parser returns that 1 to the {or} statement, which tells the {or} statement that one of its arguments is true. The {or} tells that to the {if} statement--the statement evaluates out as true. Now the server looks at the <commands if true> section of the big {if} statement. That bit reads A beautiful feline comes up behind you and massages your shoulders\, just because you're vulpine.

In case you're lost at this point, here's a simplified view of that {if} statement.
{if:<conditions (that {or} stuff)>,A beautiful feline comes up behind you and massages your shoulders\, just because you're vulpine.(Show this if it's true),You sit comfortably in the chair.(Show this if it's false)}

Now, because the MPI parser evaluates everything in the {if} statement for MPI, it runs this through the MPI parser, too, even though there isn't any MPI in it. MPI interprets all commas as separators between arguments, so when it hits that comma just after the word "shoulders", it will think it's a separator between the arguments of the {if} statment and will return an error. That's why there's a little \ in front of the comma--that tells the MPI parser to treat that comma like plain text, and to ignore it.

What the parser returns after all this nonsense is just this:

 

A beautiful feline comes up behind you and massages your shoulders, just because you're vulpine.

All that work for just one sentence! Notice that if your species property had read "Bear" (or even "vulpine"!), the {instr} would have returned zeros for both "fox" and "vixen"--would not have found either string in your species property--and it would have returned a "No" to the {or} statement, telling {if} to use the second sentence, You sit comfortably in the chair.


 

MPI's common problems
(and suggestions on how to fix them)

That last example may have given you a clue as to the most common problem with MPI--keeping the arguments straight and the brackets in order and in the right places. Just looking at that example makes stomachs turn; imagine trying to debug this if it was returning something nice and cryptic like IF: Too many arguments:

{if:{and:{awake:{ref:this}},{eq:{prop:species,this},White Siberian Tiger}}, {eval:{list:{prop:_curspecies,this}}} {nl} {prop:{prop:clothing,this}} {nl} {if:{eq:{prop:_naked?,this},yes}, {null:{tell:You aren't wearing any pants!,this}}, {null:{tell:You're clothed!,this}}}, {eval:{list:{prop:_curspecies,this}}} {nl} {prop:{prop:asleep,this}} {null:{store:seen/{name:me},{name:me} looked at you while you were asleep at {time} on {date}.,this}}}

<< This nasty little thing is actually a very selective description property. If you are awake, and your species property is set to "White Siberian Tiger", the server will evaluate any MPI code in the list whose name is stored in your _curspecies prop, then return the string stored in the property whose name is stored in your clothing prop, and will tell you whether or not you are set _naked?:yes. If you are asleep and someone looks at you, it will list a different description, and store in a property on your character the name of the person that looked at you and the date and time. The spaces between the {commands} are to help format this more clearly on your screen, and aren't used in the real MPI command.

Ghastly, isn't it? FurToonia's MPIedit program works to help you fix this sort of problem by letting you work things out on multiple lines before you actually store the MPI on an object. You can also debug the MPI as you work through it, which is quite helpful. If you use TinyFugue and have an appropriate ASCII text editor, you can do the same sort of thing on your own and then /quote, or import, the file into the MUCK. This sounds terribly complicated, but things look a lot simpler if you can see your MPI commands bit by bit, instead of all mushed together. Here's the above example, in that format:

{if:
   {and:
   {awake:{ref:this}}, 
          {eq:{prop:species,this},White Siberian Tiger}
   },
   {eval:{list:{prop:_curspecies,this}}} {nl}
     {prop:{prop:clothing,this}} {nl}
     {if:
        {eq:{prop:_naked?,this},yes
        },
        {null:{tell:You aren't wearing any pants!,this}},
        {null:{tell:You're clothed!,this}}
     },
   {eval:{list:{prop:_curspecies,this}}} {nl}
     {prop:{prop:asleep,this}}
     {null:{store:seen/{name:me},{name:me} looked at you while you were
         asleep at {time} on {date}.,this}}
}

Not that you have to use this format; it's just that I hate trying to do MPI all in a bunch and this format is so much easier to read! Remember the example from earlier? Here's what it looks like in line-by-line. Notice that if you divide each {command} by its arguments, it's easier to keep the {s and }s straight.

 

{if:
   {or:
       {not:
          {eq:
             {instr:{prop:species,me},fox},
             0}
          },
      {not:
          {eq:
             {instr:{prop:species,me},vixen},
             0}
          },
   A beautiful feline comes up behind you and massages your shoulders\,
          just because you're vulpine.,
   You sit comfortably on the chair.}

Another big problem with MPI is permissions checking, which you will become intimately familiar with, and which you will understand well if you know MUF. MPI is basically like having a MUCKER1 bit, which means that you can do very basic things, and not much else. For example, {prop} won't get properties off of objects you don't control (read: own) if they begin with a _, a . or a @, and {store} won't store properties on anything you don't own. You can't get properties off of objects which aren't nearby. You can't {otell} to players in other rooms. The limitations of each command are usually given in the MPI <command> online help, and if you do something you shouldn't, you get a big fat Permission Denied. error. Wizards can override this, but players can't. Sometimes it is easier just to write a MUF program.

Note: You cannot run $desc and MPI on the same property ($desc tries to parse the MPI code). You can run $desc via MPI--see discussion below.

You also cannot run MPI via a _listen property. This is a function of the server, and is there for security reasons.


 

Cool Things MPI Does Do
You can run MUF programs via MPI.
The {muf:#<program number>,<argument>} command will take the dbref of a MUF program, and an argument to pass to that program, and return whatever the program does. For example, if you wanted to have most of your description in MPI but needed to have a list run through the $desc program (I do this when I need to fudge time-dependent descriptions), you can have a call like this in the MPI code: {muf:$desc,%time[<listname>]}.

 

You can make exits with multiple functions.
If you have a lot of fake exits attached to a room or yourself (exits which lead to $nothing, and just emit something to you or to the room when you run them), you can combine them all into one exit, saving @quota and dbspace. Here's how:

Say you have three exits attached to you: one is called info, and its @succ message is {list:info,me}. One is called wave, and its @osucc message is waves to everybody in the room!, and one is called go home; its @succ is {force:@tel me=#1234,*{name:me}} (that would force you to @tel yourself to a particular room whenever you typed "go home").

 

  • Create one exit called info;wave;go home, and @link it to $nothing. @recycle the old info exit, so that you can refer to the new exit by a short name.
  • Type @succ info={eval:{prop:_{&cmd}}
    This makes the MPI parser evaluate a property with the same name as the command you typed but with _ in front of it. For instance, if you typed info, the parser wo uld evaluate the property "_info" on the exit for MPI code and return the result.
  • Type @set info=_info:{list:info,me}
    Notice that this sets the property _info to the same as the @succ of the old info exit. When you type info now, the server will do the same thing it always h as.
  • Type @set info=_wave:{force::waves to everyone in the room!,me} and @set info=_go home:{force:@tel me=#1234,me}, just like you did for the info action.
  • @recycle the old wave and go home exits. Now you have one exit that does exactly what three exits did!

You can combine an infinite number of exits this way. Note that you can run MUF programs this way, by @set <exitname>=_<command name>:{muf:#<:program db#>,<arguments to pass to program>} Note that if the arguments you want to give the MUF program are going to change, use {&arg} as the <arguments to program> part. {&arg} just stands for whatever stuff you typed after the exit name. If you typed hug Ginger, the exit name would be hug, and {&arg} would be Ginger.

 

You can @lock exits to strings of MPI code, in a roundabout way.
(Thanks go to Revar for telling me about this!) Basically, you have to set the MPI code on the exit or object in a property, and then @lock the exit to that property.
  • @set <exit>=_<propname>:yes (replace <propname> with anything you so desire)
  • @set <exit>=_<propname>:{MPI code which returns "yes" if the player can go through the exit and any other answer if s/he cannot}

 

You can run things through _connect, _disconnect, _arrive and _depart propdirs.
A "propdir" is just a directory full of properties. The _connect, etc., propdirs above are all full of stuff that's run whenever you connect, disconnect, or arrive or depart from a room. Just

@set me=_connect/<propname>:&<MPI code to run when you connect>
(Yes, that ampersand (&) is there for a reason!)

and, whenever you connect, the server will run that MPI code. The same goes for the other propdirs. Note that <propname> can be anything you like, but if you have multiple properties in your _connect, etc., directory, the MPI code is run in the alphabetical order of the properties (_connect/Aardvark is run before _connect/Garbanzo). Note that you cannot run MPI via a _listen: property.

 

You can make MPI "macros" to shorten some of that code.
If you have a piece of MPI code that you use a lot--let's say {name:{loc:{owner:<argument>}}}, you can write a macro for it. You need to set the macro as a property on some object--an environment room is favourite (on FurToonia, msgmacs lists the MPI macros on #0), or you can set the macro on the same object where you put your MPI code. The format for setting macros is:

@set <object>=_msgmacs/<macroname>:<macro commands>

If you wanted to write a macro for {name:{loc:{owner:<argument>}}}, called ownerloc, and put it on your environment room so that everyone in the rooms you own can use it, you would go to the environment room and type:

@set here=_msgmacs/ownerloc:{name:{loc:{owner:{:1}}}

You would then call the macro in any MPI code you wrote in your rooms by using {ownerloc:<argument>} just like any other macro. That's what that {:1} stands for: it's the first argument that {ownerloc} takes. If you wanted {ownerloc} to take two arguments, you'd use {:2} for the next argument, and {:3} for the next, and so on. If this has terminally confused you, get online and type MPI macros.


Ginger knows she is terribly verbose and a little scatterbrained and welcomes suggestions for improvement to this document.


return to the FT home page  return to the MUCK help page