MUSH Manual Version 2.008: Copyright 1993, 1994, 1995, Lydia Leong (lwl@netcom.com / Amberyl) Last revised 5/22/95. Section III: Programming Applications 5. Vehicle programming 5.1 Entering and leaving objects: @enter, @aenter, @oenter, @oxenter, @leave, @aleave, @oleave, @oxleave 5.2 Inside vehicles: @idesc, @listen, relays, and commands, @remit, AUDIBLE, @prefix, @filter, @forwardlist 5.3 Do it! -- Wagon controller 6. Complex programming 6.1 Useful functions: General use: NUM(), LOC(), NEARBY() List creation: LCON(), LEXITS(), LATTR(), LWHO() Parsing: POS(), FIRST(), REST(), STRLEN(), MID(), WORDS() Matching: EXTRACT(), MATCH(), MEMBER(), UCSTR(), LCSTR(), CAPSTR() 6.2 Non-standard attributes: & and attribute flags 6.3 Introduction to @dolist 6.4 Time synchronization: semaphores, complex @wait, @notify, @drain 6.5 Security problems in programming, INHERIT, ESCAPE(), SECURE(), @fsay, @femit, @fpose 6.6 Debugging: PUPPET and VERBOSE 6.7 On the New Programming Style 6.8 Hints and tips 7. MUSH Programming: QuickStart Guide 7.1 Basic syntax 7.2 Conditional and loop constructs 7.3 Functions 5. Vehicle programming 5.1 Entering and leaving objects Objects can be used as containers if they are set ENTER_OK. You can enter an object by typing "enter " and exit an object by typing "leave". You may enter any object that you own or is set ENTER_OK, and, in 1.50, that you pass the enter lock of. You may set EALIAS and LALIAS attributes on objects. The string in these two aliases may be used as a substitute for "enter " and "leave". Entering an object triggers the messages @enter, @oenter, @oxenter, and @aenter. The player who enters is shown the @enter message. The others inside the object see the @oenter message. People in the room that the player just left (which may not necessarily be the same room that the object is in, if the object is @tel'ed to) see the @oxenter message. @aenter is the action list to be executed. Leaving an object triggers the @leave, @oleave, @oxleave, and @aleave messages. @leave is shown to the leaving player, @oleave is shown to the things inside the object the player just left, @oxleave is shown to the things in the room the player goes to (not the room that the container object is in - if the player teleports or goes home, for example, the @oxleave message is shown at the player's destination). @aleave is the action list to be executed. ======================== [ Example 5.1: Wagon ] [ This creates the most basic of container objects ] > @create Wagon Wagon created as object #953 > @enter wagon=You climb into the wagon. Set. > @oenter wagon=climbs up into this wagon. Set. > @oxenter wagon=climbs up into the wagon. Set. > @leave wagon=You climb down from the wagon. Set. > @oleave wagon=climbs off this wagon. Set. > @oxleave wagon=climbs off the wagon. Set. > @set wagon=enter_ok Set. ======================== 5.2 Inside objects: internal descriptions, echoing, and relays An object's internal description - the description displayed to those inside it - can be set using @idesc =. This functions identically to @desc, except it is only visible by those inside. To enable those inside the object to hear what is going on outside, set the @listen of the container to *. Anything that is in the object's @listen match will be relayed to the occupants. Talking and posing within objects is identical to talking and posing in rooms. Unless there is some sort of relay set up (described below), only those inside the object can hear you. * * * * * It is frequently useful to be able to talk to the outside world, and perform other actions, like looking at the room that the object is in. This is best done with user-defined commands on an object which is placed inside the container object. It's a bad idea to define the commands directly on the container, since they can be used by those who are outside the object. The simplest way to handle looking outside is to do a user-defined command which forces the object to do a "look". This is extremely inelegant, though. On MUSHes which provide it (all 1.50 and most 2.0), the command "look/outside" will allow you to look outside the container you are in. Taking objects which are outside, however, generally requires a user-defined command which forces the object to do a 'get' or @teleport. * * * * * In the past: The simplest way of doing relays is to add special user-defined commands for say and pose which force the object to emit. When the object emits, both players inside and outside the object will hear the message. Another, more direct, way of talking to the outside world from the inside - actually, of showing a message to only those outside - is to use @emit/room. This outputs the message to the outermost container - the room that the wagon or similar object is in. * * * * * The 'best' way to relay information, however, is to use the AUDIBLE flag. This flag essentially outputs text from one place to another, without danger of looping. When set on an object, anything "said" (posed, emitted, etc.) inside the object will be broadcast to the object's location, prepended with the string "From ," The AUDIBLE object does not receive its own propagated messages, nor does it bounce back things it hears via "@listen *" from the outside world. AUDIBLE exits "funnel" noise from their source room to their destination room. In 1.50, the room itself must be set AUDIBLE, to activate AUDIBLE exits. The AUDIBLE exit propagates the message to the next room and no farther; if there are AUDIBLE exits in the next room, they do NOT pass the information on to their destinations. Messages channeled through AUDIBLE exits are prepended with, "From ," (1.50) or "From a distance," (2.0). 2.0 also provides an additional command, @forwardlist. This command, with the syntax "@forwardlist =", forwards all messages heard by to every object in the list of dbrefs. must have its AUDIBLE flag set. You can filter out messages by setting the @filter attribute with a (possibly wildcarded) pattern to suppress. For example, if you don't want " has left." messages, "@filter =* has left.", and messages matching that pattern will not be propagated. You can have more than one pattern; they should be separated by commas. (i.e., you can do: "@filter =* has left.,* has arrived." to suppress both arrival and departure messages). If the pattern contains a comma, the entire pattern should be enclosed in curly braces {} (just like in @switch). You can change the string prepended to propagated messages via the "@prefix" attribute on the AUDIBLE object. The game always puts a space between the prefix and the propagated message. Generally, prefixes are of the format, "From Mnedranth's back," or "In the gazebo," or the like -- something which indicates where the message is coming from. The game automatically prepends one if the @prefix attribute is not set. There are times when it is desirable to have no prefix prepended at all; in those cases, set the @prefix attribute to "\". There is an additional refinement: @infilter and @inprefix. @infilter suppresses text that is normally sent to the object via @listen (usually "@listen *"). @inprefix will prefix text forwarded from the outside with a string. Normally, text forwarded via @listen is not prepended with anything. 5.3 Do it! -- Wagon controller This section assumes that you have created a wagon as described in example 5.1. Below, <#wagon> refers to the dbref number of that wagon, and <#cont> refers to the dbref number of the controller. Enter the following commands: @idesc <#wagon>= @listen <#wagon>=* @create Wagon controller @lock control=me drop control Possibilities: This is the old way of doing things: @va <#cont>=$'*:@fo <#wagon>=@emit {On the wagon, %N says, "%0"} @vb <#cont>=$.*:@fo <#wagon>=@emit {On the wagon, %N %0} [ These two commands define ' and . as alternates to " and : inside the wagon. They relay to the outside world, as well as to those inside the vehicle. There is one major problem with this: because of the @listen, the players inside the wagon hear exactly what the wagon hears, which is the message "You emit: " This is ugly, but it's effective. ] @vc <#cont>=$view:@fo <#wagon>=l @vd <#cont>=$view *:@fo <#wagon>=l %0 [ These two commands substitute for look. ] This is the new way of doing things: @set <#wagon>=AUDIBLE @prefix <#wagon>=On the wagon, [ There is no need for the view command; "look/outside" will suffice. ] @ve <#cont>=$snag *:@fo <#wagon>=get %0 [ This command forces the wagon to get an object outside. ] 6. Complex programming 6.1 Useful functions Often, you will want to build machines that accept input from players only; the NUM() function is useful for checking whether or not something is a player. Simply evaluate: num(*%N), assuming that you are checking if the enactor is a player. The * forces %N to be evaluated as a player; if there is no player by that name, the function returns "#-1". The only case in which this trick with num(*%N) fails is if the enactor has the same name as a player. You can also use the TYPE() function to see if an object is a player. * * * * * The LOC() function is useful for several things. If it is used to locate a player, it will return the number of the player's location, assuming that the player is not set UNFINDABLE. If it is used to locate an object that you control, it will return the number of its location. It will return #-1 if you try to locate an object you don't control. Used on an exit, LOC() will return the dbref number of the exit's destination (to get an exit's source, use the HOME() function). The LOC() function returns the drop-to of a room; usually, this is #-1. Related to the loc function is the %L substitution. This returns the location of the enactor. Since it requires the enactor to trigger off a command of some sort, however, it cannot replace the LOC() function, unless you just want LOC(%#). * * * * * The NEARBY() function tests if one object is near another. It is called with: nearby(obj1,obj2). For this function to work, you must control at least one of the objects, or be near one of them. Object1 is considered to be nearby Object2 if it is in the same location, if it is being carried by Object2, or it is carrying Object2. If the two objects are nearby, the function returns a 1; otherwise, it returns 0. * * * * * The MUSH functions you will use most often will probably be those which return names and dbrefs. But almost all of MUSH programming involves the manipulation of lists - adding items, removing items, finding certain items within, etc. Lists, for MUSH purposes, are generally defined as a list of space-separated of words. The first word in a list is 1. Strings, on the other hand, are composed of arbitrary characters. The first character in a string is 0. Lists are basically special types of strings. Everything in MUSH is eventually treated as a string. Several functions create lists: LCON(), LEXITS(), LATTR(), and LWHO() are perhaps the most frequently used of these. LCON() gives a list of the dbref numbers of all objects in a room (including dark objects). LEXITS() gives a list of the dbref numbers of all non-dark exits out of a room. LATTR() gives a list of all attributes on an object. LWHO() gives a list of all non-dark connected players. * * * * * Several functions are useful for parsing lists. The POS() function, called with pos(string1,string2), returns the position number of string1 in string2. If string1 is not in string2, the function returns #-1. The first character is considered to be position 1. This function is particularly useful when checking flags on an object. For example, to check if an object is a puppet, check pos(p,flags(object)) If this returns anything but #-1, the object is a puppet. (An easier way to do this is to use the HASFLAG function: HASFLAG(object,puppet) will return 1 if the object is a puppet). For parsing, FIRST() and REST() are useful. They return the "head" and the "tail" of a list, respectively, and are basically identical to the LISP functions CAR() and CDR(). The "head" of a list is the first word in that list; the "tail" of the list is everything else. There's also the useful LAST() function in TinyMUSH 2.2, which returns the last word in a list. The WORDS() function counts the number of words in a list. This is useful for a loop counter ("repeat this action until the counter variable is greater than the number of words in the list"), and for finding the last word in a list, which is done as follows: Assuming that the list is in %0: extract(%0,words(%0),1) This counts the number of words in the list, then uses extract to take a one-word long list starting from that position - the last. (Of course, if you're programming in 2.2, you should just use the LAST() function instead of this somewhat-complicated extraction.) The STRLEN() function returns the length of an entire string (list). It is extremely useful when used in conjunction with the MID() function. The latter is called with: mid(string,position of first character,length) and returns a string of characters starting from . The first character in the string is numbered 0, not 1. Supposing you wish to return a string starting with its third character. You would use: mid(string,2,strlen(string)) * * * * * The EXTRACT() function, mentioned above, called with extract(list,first,length), is used to pull out words from the middle of a list. "Length" is the number of words to be extracted, _not_ the number of letters. MATCH(), called with match(list,pattern), returns the number of the word where first occurs. The first word in the list is numbered 1. If no match is found, the function will return 0. The pattern may be straight text, or it may contained the wildcards * and ?. The MEMBER() function is similar, except that it does not take wildcards, and is case-sensitive, so match(a B c d,b) will return 2, but member(a B c d,b) will return 0. The case-sensitivity of MEMBER() can be worked around using the UCSTR(), LCSTR(), and CAPSTR() functions. These functions return a list with all letters capitalized, all letters in lowercase, or with the first letter of the first word of the list capitalized, respectively. EXTRACT() and MATCH() functions can be combined to match two lists: Suppose you have two registers, va and vb. The first contains a list of first names, and the second contains a list of last names. Given a first name, you want to find the last name. Assume that the first name given as input is in %0, and that it is in the va. Then, the matching last name will be: extract(v(vb),match(%va,%0),1) It uses the match() function to find the position of the name in the first name list, then uses extract() to get the word in the corresponding position on the last name list. There's a function which combines an EXTRACT() and MATCH(), called GRAB(). It takes the syntax "grab(list, wildcard)"; it finds the first element of the list which matches the wildcard, and returns that element. This is useful if you want to partial-match something, and then want to get its entire name. For example, the function call "grab(banana:yellow apple:red pear:green plum:purple, pear:*)" will return "pear:green". * * * * * There are two functions related to EXTRACT() and MATCH(), which operate on multiple elements of lists. They're called ELEMENTS() and MATCHALL(). "matchall(list, wildcard)" will return the element numbers of all elements of the list which match. "extractall(list of words, list of numbers)" will return the elements of the list which correspond to the given list of numbers. These two functions can thus be combined to locate, and then grab, multiple elements of a list. The MATCHALL() function can be used in conjunction with WORDS() to determine how many times a given word appears in a list, via "words(matchall(list, word))" -- MATCHALL() returns the list of element numbers which match, and the WORDS() counts them up. 6.2 Non-standard attributes and attribute flags Non-standard attributes act much like V-attributes, except that they may be arbitrarily named, as long as the names do not conflict with the names of any existing attribute. Objects can have an unlimited number of attributes, thus freeing the programmer from the necessity of creating multiple objects to handle extremely complex programs. Non-standard attributes are set using one of two methods: & = @set =: 1.50 also allows: @_ = These attributes may be accessed via the get() and v-functions, %-substitutions of the form %attrib do not work for user-named attributes, as they do with @va-@vz. In all other respects, however, user-named attributes behave like @va-@vz. * * * * * Non-standard attributes may be locked (prevented from being changed by anyone other than the person doing the locking, or a wizard), or chowned separately of the object. The syntax for doing this is "@lock /"; "@unlock /" unlocks the attribute. Attributes can also have flags. These are set via the command "@set / = [!]". There are three flags which may be set. The VISUAL flag makes an attribute public; anyone can read it via the GET() function. The NO_COMMAND flag prevents an attribute from being checked for $-commands and ^-listens. The NO_INHERIT flag prevents an attribute from being inherited by the children of the object. When you examine something, a locked attribute will have a "+" sign next to it. Attributes such as LAST are owned and locked to God; mortals may not change them. VISUAL attributes will be marked with "v", NO_COMMAND attributes will be marked with "$", and NO_INHERIT attributes will be marked with "i". Certain 1.50 configurations always give the dbref of an attribute's owner next to the attribute name; other configurations, and 2.0, only show the attribute owner's dbref is it's different from the object's owner. In 1.50, the ANSI_DISPLAY flag, when set on a player, will cause attribute names to be highlighted in boldface (if the player's terminal supports ANSI control sequences). This is useful if you are examining a very large object, and need to be able to quickly and easily see where the breaks between attributes are. * * * * * Non-standard attributes can make MUSH code much easier to read; it is also good practice to do a &purpose or &program register on the object which explains a little bit of the programming, for future reference. One useful thing about non-standard attributes is that they are very useful when programming objects which contain numbered registers - mail objects, bulletin boards, etc. Instead of going through the clumsy method of putting get(me/va) get(me/vb) get(me/vc) etc. in a register and using extract() to force an evaluation, you can directly set registers, like: @va object=$foo *=*:&r%0 me=%1 @vb object=$bar *:@pemit %N=get(me/r%0) Note the use of the brackets within the get() to force register evaluation early. The two statements above allow you to set and retrieve numbered non-standard attributes. 6.3 Introduction to @dolist One very useful command is @dolist =. It performs for every item in the list, replacing the symbol "##" with a word from the list. For example, @dolist [lexits(here)]="[name(##)] will make you say the names of all exits in the room you are in. It can be used to multi-page: @dolist Culyn RosaLil Jellan = page ## = Hello! @dolist evaluates its arguments in order. So, for example, "@dolist Culyn RosaLil Jellan = page ## = Hello!" would page Culyn, then RosaLil, then Jellan. @dolist can also become rather confused by @switch statements and semi-colons. It is best to enclose the entire section of the @dolist with braces - @dolist = { } This will prevent most errors. @dolist should replace most list-based loops. In the past, the following was an efficient implementation of the problem listed above: VA: $name-exits: @tr me/vb=[extract(lexits(here), 1, 1)], 1 VB: @switch %1=>[words(lexits(here)],{},{"[name(%0)]; @tr me/vb=[extract(lexits(here), add(%1,1), 1)], [add(%1,1)]} (It might be more efficient, if there are a lot of exits, to set @vc me=lexits(here) in the VA, but this is a small point). Now, all that's needed is: VA: $name-exits: @dolist [lexits(here)]="[name(##)] Not only is this much cleaner, but it's considerably faster than the old method. Triggers take up quite a bit of time to execute, and @dolist is vastly more efficient. 6.4 Time synchronization Semaphores are a very flexible tool for the synchronization of objects. In practical terms, they make sure things occur in a specified order, and prevent simultaneous actions from conflicting with each other. The name "semaphore" comes from the name for signal flags used on ships; in modern computer systems, semaphores are used to prevent sections of memory from being concurrently read from or written to by different processes. Indeed, one of the main purposes of semaphores in MUSH is preventing two things from attempting to change the same attribute at the same time. More generally, in MUSH, semaphores are good for two major things: preventing an already triggered object from being used again until it has finished the action list associated with that trigger, and preventing a command from being run until a certain other command has executed. It is, fortunately, not necessary to completely understand how semaphores work in order to use them. "Synchronized" actions are those which are correctly performed in some specified order. Semaphores effectively delay the execution of commands by queueing them up in the correct order, and preventing the execution of the next command before the previous command has finished, thus synchronizing the objects and actions. Every semaphore is associated with a number, called the "semaphore count", which is the number of actions (or in the case of MUSH, action lists) it is trying to synchronizing. Such actions are also referred to as "pending", "blocking", or "waiting" on the semaphore. * * * * * In MUSH, this semaphore count is stored in the SEMAPHORE attribute. A semaphore which has no commands pending will have a count of 0. If the semaphore count is positive, the semaphore has that many actions waiting on it. A negative semaphore count indicates that actions will not block on it; for example, a count of -3 indicates that the next three actions to wait on the semaphore will be immediately executed. (Computer science types: please note that this is the REVERSE of the convention generally used in operating systems theory!) Two operations are used to control a semaphore: wait, and notify. "Waiting" an action list on a semaphore puts those actions at the end of that semaphore's queue. Each time a semaphore is notified, the first action list on its queue is executed. Thus, an action which waits on a semaphore is delayed until the semaphore is notified. A command which is waiting for its associated semaphore to be notified is referred to as "blocked"; when its semaphore allows it to be executed, the command is then "unblocked." Eexecuting a wait on a semaphore increases its semaphore count by one, and executing a notify on a semaphore decreases the count by one. To execute a wait on a semaphore, use "@wait ="; if is a list of commands, it must be enclosed in curly braces. To notify a semaphore, use "@notify ". Please note that the object that does the waiting executes the action; the semaphore is simply a timing device. Thus, in MUSH, you can use objects you do not own as semaphores, if they are LINK_OK. Objects waiting on a semaphore run their actions on a first-come, first-served basis, If #2, #3, and #4 execute waits, in that order, on semaphore object #5, the first notify the semaphore receives will begin #2's action list. The next notify will execute #3's action list, and so on. In other words, notification of a semaphore just releases the FIRST object which waited on it. It is possible to notify a semaphore repeatedly, by providing a numeric argument to @notify: "@notify =" If the number of times is not specified, the semaphore will be notified once. The semaphore executes the first commands that were @waiting on it. As a reminder, if the semaphore is notified more times than it has commands queued, the semaphore count will become negative and further @waits dependent on that semaphore will be immediately executed, until the semaphore count is zero. This normal @notify is shorthand for the default switch, "@notify/first". If you want all waits pending on the semaphore to execute, use "@notify/all ". This will cause all the actions waiting on the semaphore to be executed, and resets the semaphore count to 0. The other method of clearing a semaphore is "@drain ". This will reset the semaphore count to 0. Actions waiting on the semaphore are simply discarded. In 1.50, destroying an object automatically executes a @drain on it. MUSH also allows for semaphore waits to have a timeout expiration. An object can use "@wait /