an immense document written for your amusement and education by Ginger@FurToonia -- helpdocs with style!
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 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:
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
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
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. |
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 |
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.
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:
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}"} |
|
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. |
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,
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! |
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. |
What the parser returns after all this nonsense is just this:
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.
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.
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").
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.
@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.
@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.