TinyMUSH Manual Version 2.007: Last revised 8/29/93. Amberyl. lwl@eniac.seas.upenn.edu 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(), 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 Applications of @dolist 6.5 Time synchronization: semaphores, complex @wait, @notify, @drain 6.6 Security problems in programming, INHERIT, ESCAPE(), SECURE(), @fsay, @femit, @fpose 6.7 Debugging: PUPPET and VERBOSE 6.8 On the New Programming Style 7. What to do next 7.1 Hints and tips 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(%N). 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(), 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. 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. 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. 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. 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: @_ = 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. 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 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. One useful application of @dolist is multi-page. The following code has been formatted for easy reading: &PAGECOM multi-pager = $mp *=*:@emit Paging.; @switch pos(:,%1) = 1, {@dolist %0 = @switch num(*##)=#-1, {@emit Error: ## is not a player.}, {@pemit *##={(To: %0) % %[v(player)] [mid(%1,1,strlen(%1))]}}; @emit {(Sent to: %0) % %[v(player)] [mid(%1,1,strlen(%1))]}}, {@dolist %0=@switch num(*##)=#-1, {@emit Error: ## is not a player.}, {@pemit *##={(To: %0) % % [v(player)] says, "%1"}}; @emit {(You told: %0) %1}} &PLAYER multi-pager = To page multiple people, use "mp =" If you begin the message with a ":", it is done as a page-pose. The command checks to see if the person you are paging is a player; if not, it emits an error message. Otherwise, it sends the person the message as a @pemit. It uses the MID()/STRLEN() combination to extract out the ":" from poses. This code should be, for the most part, self-explanatory. 6.4 Applications of @dolist This entire section is outdated, dating from the era when @dolist evaluated its arguments backwards. The discussion has been left intact, for use as a general example of how to get around odd evaluation problems. Rewriting this code to use a forward-evaluated @dolist is left as an exercise for the reader. If you are not already comfortable with reading MUSHcode, you will probably want to skip this section. Also, this code doesn't demonstrate the best or the easiest ways to do things; if you wish to create an object like the one described below, you are best off using other methods. Several interesting MUSHcoding principles are illustrated here, nonetheless. The (old) backwards evaluation of @dolist arguments presented some minor programming problems. Often, loops in MUSH programming involve EXTRACT()'ing some element from a list. The list is often pre-set, created at an earlier time by another command on the object. There are two obvious ways to handle this problem. The first is to create the list backwards, and manipulate functions to do the correct thing. The second is to reverse the list at execution time. Reversing a list is not difficult, but it isn't the most efficient of things to do. The code for this is quite simple. Assuming that the original list is in OLDLIST and the reversed list should be in NEWLIST, &REVERSE object = $reverse: @dolist [v(oldlist)] = &NEWLIST me=[v(newlist)] ## (Under the newer versions of MUSH code, there's an even easier way to do this: use the REVWORDS() function, which reverses the elements in a list). The first method is explained below, using the example of a notepad. The notepad is a fairly simple device, intended for use by one person. It keeps track of notes, complete with time-stamp and subject line. The notepad uses a variable called "TOP-ID", the total number of messages written. Each note that is written is assigned the value of TOP-ID and that variable is incremented, ensuring that the note has a unique message ID. The list of ID numbers is kept in a list called MESS-LIST. The EXTRACT() function is used to get the correct messages, in chronological order, so the user never needs to see message IDs. &TOP-ID Notepad=0 &NUM-MESS Notepad=0 &NOTE Notepad = $note *=*: &TOP-ID me=add(v(top-id),1); &NUM-MESS me=add(v(num-mess),1); @tr me/note2={%0},{%1} &NOTE2 Notepad = &S[v(top-id)] me=[time()]: %0; &M[v(top-id)] me=Text: %1; &MESS-LIST me=[v(top-id)] [v(mess-list)]; @emit You stuff a note into your belt pouch. These two commands are used for writing the actual notes. Note that the MESS-LIST is created backwards, with the newest message at the head of the list. &QUICKSCAN Notepad=$scan:@switch v(num-mess)=0,{@emit You have no notes.},1,{@emit You have one note.},{@emit You have [v(num-mess)] notes.} This command is simple: by typing "scan" the user is shown the total number of messages on the notepad. A more complex command follows: "scan ". This will show the subject lines of notes. &SCAN Notepad = $scan *: @switch v(num-mess) = 0, {@emit You have no notes.}, {@switch %0 = a*, {@tr me/scan-all;@emit You have [v(num-mess)] notes.}, {@switch %0 = >[v(num-mess)], {@emit Note #%0 does not exist.}, 0, {@emit Note #%0 does not exist.}, {@tr me/scan-one = %0, [extract(v(mess-list),sub(words(v(mess-list) xx),%0),1)]}} That truly horrible last line is what's required to extract out a specific message, since MESS-LIST is backwards. The first and last arguments to the EXTRACT() are simple - they say, "take from the variable MESS-LIST one word." The second function means: subtract from (one plus the total length of the MESS-LIST) the (number of the message the user wants to read). The "xx" is there because it's simpler than adding one to the WORDS() count. Adding is one is necessary, or the subtract function will return 0 for the last message, for example. &SCAN-ONE Notepad = @emit Note #%0:; @emit get(me/s%1) Once the ID number is found, though, the actual printing out of the subject line is simple. &SCAN-ALL Notepad = @dolist xx [v(mess-list)] = {@switch ##=xx, {@emit End of notes.}, {@emit Note #[sub(words(v(mess-list) xx),member(v(mess-list),##))]: [get(me/s##)]}} The reason for the subtract function in this command is similar to the reasons given above - it's needed because the list is backwards. Once again, xx is used in the function because it's more efficient than using ADD(). Note that "xx" is used in the @dolist as a delimiter - a signal to stop. Because @dolist evaluates the list backwards, the delimiter must be the first item in the list. The "read" functions are similar to the "scan" functions, except they show messages as well as subjects. These functions and the "erase" functions below have been left unformatted, since the code is similar. &READ Notepad=$rn *:@switch v(num=mess)=0,{@emit You have no notes.},{@switch %0=a*,{@tr me/read-all;@emit You have [v(num-mess)] notes.;@emit --------------------------------},{@switch %0=>[v(num-mess)],{@emit Note #%0 does not exist.},0,{@emit Note #%0 does not exist.},{@tr me/read-one=%0,[extract(v(mess-list),sub(words(v(mess-list) xx),%0),1)]}}} &READ-ONE Notepad=@emit Note #%0:;@emit get(me/s%1);@emit get(me/m%1) @VA Notepad=@dolist [v(vb)]=:[extract(##,wordpos(v(vb),##),1)] &READ-ALL Notepad=@dolist xx [v(mess-list)]={@switch ##=xx,{@emit End of notes.},{@emit Note #[sub(words(v(mess-list) xx),member(v(mess-list),##))]:;@emit get(me/s##);@emit get(me/m##);@emit --------------------------------}} The "erase" functions also follow the same programming logic. Here, it becomes obvious why a message ID is used instead of straight message numbers. It is much easier to extract out an ID than it is to recursively copy each message up (as would be the case if you had messages 1 through 5, and removed message 1 - you would have to move each message up one number. Using the message ID, you merely have to remove the ID from the list and reduce the counter of total messages). &ERASE Notepad=$junk *:@switch v(num-mess)=0,{@emit You have no notes to junk.},{@switch %0=a*,{@emit Junking notes. One moment please...;@tr me/erase-all},{@switch %0=>[v(num-mess)],{@emit Note #%0 does not exist.},0,{@emit Note #%0 does not exist.},{@tr me/erase-one=%0,[extract(v(mess-list),sub(words(v(mess-list) xx),%0),1)]}}} &ERASE-ONE Notepad=&S%1 me= ;&M%1 me= ;&MESS-LIST me=remove(v(mess-list),%1);&NUM-MESS me=sub(v(num-mess),1);@emit Note #%0 junked. &ERASE-ALL Notepad=@dolist xx [v(mess-list)]={@switch ##=xx,{&MESS-LIST me= ;&NUM-MESS me=0;@emit You junk all your notes.},{&S## me= ;&M## me= }} This should now be a working notepad. In 1.50 and the present versions of 2.0, this can be done in a much cleaner manner by using LNUM(). Not having to worry about a reversed list is also a bit of a plus. 6.5 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 /