[PENNMUSH-ANNOUNCE] 1.7.7-patch21

dunemush at tala.mede.uic.edu dunemush at tala.mede.uic.edu
Mon Sep 29 16:56:18 CDT 2003


This is patch21 to PennMUSH 1.7.7. After applying this patch, you will
have version 1.7.7p21

To apply this patch, save it to a file in your top-level MUSH directory,
and do the following:
	patch -p1 < 1.7.7-patch21
	make install

If you use GNU patch 2.2, you probably want the above to be 'patch -b -p1',
not just 'patch -p1'.

Unix (or cygwin) users need not worry about failed hunks in src/switchinc.c,
hdrs/switches.h, hdrs/cmds.h, or hdrs/funs.h. These files are automatically
rebuilt on compile. On the off chance they appear not to be, simply
rm them and re-run make.

Then @shutdown and restart your MUSH.
    - Alan/Javelin

In this patch:

Major Changes:
  * Attribute trees. Attributes may now be organized hierarchically
    like files and directories in a filesystem, using the backtick (`)
    character as the branch separator. Attribute access restrictions
    propagate down trees. New wildcard ** is introduced to match
    all attributes at all tree levels. Suggested by Tabbifli. [TAP]
Locks:
  * New framework for performing lock failure activities in hardcode.
    As a proof-of-concept, the attributes FOLLOW_LOCK`FAILURE,
    FOLLOW_LOCK`OFAILURE, FOLLOW_LOCK`AFAILURE do what you'd expect
    when set on a potential leader.  Suggested by Sholevi at M*U*S*H.
Channels:
  * New per-channel flags NoTitles, NoNames, and NoCemit do what you'd
    expect. Set them with @chan/privs. Based on suggestion by 
    Saturn at M3.
  * @chan/recall/quiet omits timestamps. Suggested by Vadiv at M*U*S*H.
Commands:
  * 'help <wildcard-pattern>' now lists all help topics that match that 
    pattern.  By popular request. [MUX,SW] 
  * @flag/letter can be used to change or clear the one-letter alias for a 
    flag.  Suggested by Nymeria at M*U*S*H. [SW]
  * @flag/list by God notes disabled flags. Suggested by Nymeria at M*U*S*H. [SW]
Functions:
  * rand() now comes in a two-argument (low,high) flavor, and randword()
    selects a random word from a list. The latter is aliased to
    pickrand() to match Mux's name. Patch by Luke at M*U*S*H.
Minor Changes:
  * Although we're Pueblo 2.50 compliant, go back to sending Pueblo 1.10
    as the server version until everyone upgrades their clients so
    they can handle the 2.50 string. Suggested by Shirow.
  * The locate() function is no longer noisy (no longer notifies
    the executor in addition to returning a value). Suggested by
    Mystery8 at ST:AW. 
  * @lock/interact now has a higher priority than other interaction
    checks, so it will work for Wizards. Suggested by Viila at M*U*S*H.
  * Tweaks to facilitate a Debian package of PennMUSH. [EEH]
Fixes:
  * max descriptor could get stomped in some cases. [SW]
  * Removed extra struct def in hdrs/mushtype.h. Suggested by Kyle.
  * Help tweak by Kevin at M*U*S*H.
  * Fix to locks on players messing up their connection failure counts.
    Reported by Luke at M*U*S*H.
  * Fix to @entrances by Luke at M*U*S*H.
  * Fix to Win32 not handling a missing minimal.db by Luke at M*U*S*H.
  * The confirmation message for setting/clearing attribute flags would use
    the flag name given as an argument, not neccessarily the the full name of
    the flag. Reported by Vadiv at M*U*S*H.  [SW]
  * Fix a potential memory leak in ident.c [SW]

Prereq: 1.7.7p20
*** 1_7_7.624/Patchlevel Thu, 04 Sep 2003 17:36:31 -0500 dunemush (pennmush/5_Patchlevel 1.17.1.11.1.22 600)
--- 1_7_7.664(w)/Patchlevel Tue, 23 Sep 2003 12:12:35 -0500 dunemush (pennmush/5_Patchlevel 1.17.1.11.1.23 600)
***************
*** 1,2 ****
  Do not edit this file. It is maintained by the official PennMUSH patches.
! This is PennMUSH 1.7.7p20
--- 1,2 ----
  Do not edit this file. It is maintained by the official PennMUSH patches.
! This is PennMUSH 1.7.7p21
*** 1_7_7.624/CHANGES.176 Thu, 14 Aug 2003 09:59:50 -0500 dunemush (pennmush/g/17_CHANGES 1.10.1.6.1.2.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.9.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.2 600)
--- 1_7_7.664(w)/CHANGES.176 Wed, 24 Sep 2003 11:53:01 -0500 dunemush (pennmush/g/17_CHANGES 1.10.1.6.1.2.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.9.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.2.1.1.1.1.1.2.1.1.1.2 600)
***************
*** 13,18 ****
--- 13,29 ----
  
  ==========================================================================
  
+ Version 1.7.6 patchlevel 14                     September 23, 2003
+ 
+ Fixes:
+    * Fix to help @search2 by LeeLaLimaLLama at M*U*S*H.
+    * The max file descriptor could get stomped in some cases. [SW]
+    * Powers and toggles on destroyed objects are reset, as they 
+      caused anomalous lsearch/haspower behavior. Report by Mordie at M*U*S*H.
+    * Changing channel privs and loading channels with objects no longer
+      permitted could cause crashes. Report by Septimus at SW RP Forum.
+ 
+ 
  Version 1.7.6 patchlevel 13                     August 11, 2003
  
  Fixes:
*** 1_7_7.624/CHANGES.177 Fri, 05 Sep 2003 20:59:37 -0500 dunemush (pennmush/g/23_CHANGES 1.48.1.165 600)
--- 1_7_7.664(w)/CHANGES.177 Mon, 29 Sep 2003 16:35:25 -0500 dunemush (pennmush/g/23_CHANGES 1.48.1.184 600)
***************
*** 18,23 ****
--- 18,74 ----
  
  ==========================================================================
  
+ Version 1.7.7 patchlevel 21                     September 23, 2003
+ 
+ Major Changes:
+   * Attribute trees. Attributes may now be organized hierarchically
+     like files and directories in a filesystem, using the backtick (`)
+     character as the branch separator. Attribute access restrictions
+     propagate down trees. New wildcard ** is introduced to match
+     all attributes at all tree levels. Suggested by Tabbifli. [TAP]
+ Locks:
+   * New framework for performing lock failure activities in hardcode.
+     As a proof-of-concept, the attributes FOLLOW_LOCK`FAILURE,
+     FOLLOW_LOCK`OFAILURE, FOLLOW_LOCK`AFAILURE do what you'd expect
+     when set on a potential leader.  Suggested by Sholevi at M*U*S*H.
+ Channels:
+   * New per-channel flags NoTitles, NoNames, and NoCemit do what you'd
+     expect. Set them with @chan/privs. Based on suggestion by 
+     Saturn at M3.
+   * @chan/recall/quiet omits timestamps. Suggested by Vadiv at M*U*S*H.
+ Commands:
+   * 'help <wildcard-pattern>' now lists all help topics that match that 
+     pattern.  By popular request. [MUX,SW] 
+   * @flag/letter can be used to change or clear the one-letter alias for a 
+     flag.  Suggested by Nymeria at M*U*S*H. [SW]
+   * @flag/list by God notes disabled flags. Suggested by Nymeria at M*U*S*H. [SW]
+ Functions:
+   * rand() now comes in a two-argument (low,high) flavor, and randword()
+     selects a random word from a list. The latter is aliased to
+     pickrand() to match Mux's name. Patch by Luke at M*U*S*H.
+ Minor Changes:
+   * Although we're Pueblo 2.50 compliant, go back to sending Pueblo 1.10
+     as the server version until everyone upgrades their clients so
+     they can handle the 2.50 string. Suggested by Shirow.
+   * The locate() function is no longer noisy (no longer notifies
+     the executor in addition to returning a value). Suggested by
+     Mystery8 at ST:AW. 
+   * @lock/interact now has a higher priority than other interaction
+     checks, so it will work for Wizards. Suggested by Viila at M*U*S*H.
+   * Tweaks to facilitate a Debian package of PennMUSH. [EEH]
+ Fixes:
+   * max descriptor could get stomped in some cases. [SW]
+   * Removed extra struct def in hdrs/mushtype.h. Suggested by Kyle.
+   * Help tweak by Kevin at M*U*S*H.
+   * Fix to locks on players messing up their connection failure counts.
+     Reported by Luke at M*U*S*H.
+   * Fix to @entrances by Luke at M*U*S*H.
+   * Fix to Win32 not handling a missing minimal.db by Luke at M*U*S*H.
+   * The confirmation message for setting/clearing attribute flags would use
+     the flag name given as an argument, not neccessarily the the full name of
+     the flag. Reported by Vadiv at M*U*S*H.  [SW]
+   * Fix a potential memory leak in ident.c [SW]
+ 
  Version 1.7.7 patchlevel 20                     September 4, 2003
  
  Major Changes:
*** 1_7_7.624/game/txt/hlp/penntop.hlp Sat, 31 May 2003 16:32:59 -0500 dunemush (pennmush/13_penntop.hl 1.2.1.27.1.3.1.2.1.2.1.1.1.1.1.1.1.1.1.12.1.1.1.1.1.2 600)
--- 1_7_7.664(w)/game/txt/hlp/penntop.hlp Mon, 15 Sep 2003 11:38:53 -0500 dunemush (pennmush/13_penntop.hl 1.2.1.27.1.3.1.2.1.2.1.1.1.1.1.1.1.1.1.12.1.1.1.1.1.4 600)
***************
*** 6,11 ****
--- 6,12 ----
    For the list of MUSH commands, type:            help commands
    For the list of MUSH topics, type:              help topics
    For an alphabetical list of all help entries:   help entries 
+   For a list of entries that match a pattern:     help <wildcard>
    For information about PennMUSH:                 help code
    
    For a list of flags:                            help flag list
***************
*** 227,236 ****
    would.
  
    To see the attributes that are set on you or on any of the objects you own,
!   you should use the "examine" command. This will list all of the attributes
!   and their contents. As this can get very spammy for any large object, you
!   can also examine specific attributes by using this format:
!     examine <object>/<attribute>
    
  (continued in help attributes4)
  & ATTRIBUTES4
--- 228,234 ----
    would.
  
    To see the attributes that are set on you or on any of the objects you own,
!   you should use the "examine" command. See 'help examine'.
    
  (continued in help attributes4)
  & ATTRIBUTES4
***************
*** 250,256 ****
    to examine and work on objects.
  
    See also: ATTRIB-OWNERSHIP, @set, examine, @atrchown, @atrlock, hasattr()
!           get(), v(), NON-STANDARD ATTRIBUTES, SETTING-ATTRIBUTES
  & BEING KILLED
   
    Getting killed is no big deal. If you are killed, you return to
--- 248,255 ----
    to examine and work on objects.
  
    See also: ATTRIB-OWNERSHIP, @set, examine, @atrchown, @atrlock, hasattr()
!     get(), v(), NON-STANDARD ATTRIBUTES, SETTING-ATTRIBUTES, ATTRIBUTE TREES
! 
  & BEING KILLED
   
    Getting killed is no big deal. If you are killed, you return to
***************
*** 866,872 ****
    All attributes can be used in attribute locks and can be 'owned' 
    independent of object ownership. 
    
!   See also: ATTRIBUTES, ATTRIB-OWNERSHIP, Attribute Functions
  & PARENT
  & PARENTS
  & OBJECT PARENTS
--- 865,872 ----
    All attributes can be used in attribute locks and can be 'owned' 
    independent of object ownership. 
    
!   See also: ATTRIBUTES, ATTRIB-OWNERSHIP, Attribute Functions, 
!      ATTRIBUTE TREES
  & PARENT
  & PARENTS
  & OBJECT PARENTS
*** 1_7_7.624/game/txt/hlp/pennfunc.hlp Fri, 05 Sep 2003 20:59:37 -0500 dunemush (pennmush/16_pennfunc.h 1.2.1.50.1.1.1.1.1.2.1.7.1.8.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.9.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.10 600)
--- 1_7_7.664(w)/game/txt/hlp/pennfunc.hlp Tue, 23 Sep 2003 11:10:53 -0500 dunemush (pennmush/16_pennfunc.h 1.2.1.50.1.1.1.1.1.2.1.7.1.8.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.9.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.13 600)
***************
*** 1359,1365 ****
    
    These functions return a list of attributes on <object> containing
    <pattern> (or matching <regexp>).  <attrs> is a wildcard pattern for
!   attribute names to search; if you want to search all attributes, use "*".
  
    The list returned is similar to that returned by @grep/list
    <object>/<attrs>=<pattern>
--- 1359,1365 ----
    
    These functions return a list of attributes on <object> containing
    <pattern> (or matching <regexp>).  <attrs> is a wildcard pattern for
!   attribute names to search.
  
    The list returned is similar to that returned by @grep/list
    <object>/<attrs>=<pattern>
***************
*** 1690,1698 ****
    function on the object.
    
    If a wildcarded attribute pattern is provided, only attribute names
!   matching that pattern will be returned.
  
!   See also: nattr()
  & NATTR()
  & ATTRCNT()
    nattr(<object>)
--- 1690,1699 ----
    function on the object.
    
    If a wildcarded attribute pattern is provided, only attribute names
!   matching that pattern will be returned. lattr() uses the same
!   wildcards as examine (?, *, **).
  
!   See also: nattr(), examine
  & NATTR()
  & ATTRCNT()
    nattr(<object>)
***************
*** 2632,2641 ****
    See "help SETQ()" for details about registers.
  & RAND()
    rand(<num>)
  
-   Rand returns an integer between 0 and num-1, inclusive.
    If called with an invalid argument, rand() returns an error message
    beginning with #-1.
  & REGEDIT()
  & REGEDITALL()
  & REGEDITI()
--- 2633,2655 ----
    See "help SETQ()" for details about registers.
  & RAND()
    rand(<num>)
+   rand(<min>, <max>)
+   
+   Return a random number.
+   
+   The first form returns an integer between 0 and <num>-1, inclusive.
+   The second returns an integer between <min> and <max>, inclusive.
  
    If called with an invalid argument, rand() returns an error message
    beginning with #-1.
+ & RANDWORD()
+ & PICKRAND()
+   randword(<list>[, <delim>])
+   
+   Returns a randomly selected element from <list>. <delim> is the list
+   delimiter: if not specified, whitespace delimits the list.
+   
+   pickrand() may be an alias for randword() on some servers.
  & REGEDIT()
  & REGEDITALL()
  & REGEDITI()
*** 1_7_7.624/game/txt/hlp/penncmd.hlp Tue, 02 Sep 2003 10:07:23 -0500 dunemush (pennmush/18_penncmd.hl 1.2.1.1.1.47.1.1.1.1.1.3.1.4.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.10.1.1.1.1.1.1.1.1.1.11 600)
--- 1_7_7.664(w)/game/txt/hlp/penncmd.hlp Thu, 18 Sep 2003 09:00:30 -0500 dunemush (pennmush/18_penncmd.hl 1.2.1.1.1.47.1.1.1.1.1.3.1.4.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.2.1.10.1.1.1.1.1.1.1.1.1.1.1.3 600)
***************
*** 1291,1296 ****
--- 1291,1297 ----
    @flag/add <flag name>=[<letter>], [<type(s)>], [<setperms>], [<unsetperms>]
    @flag/delete <flag name>
    @flag/alias <flag name>=<alias>
+   @flag/letter <flag name>[=<letter>]
    @flag/restrict <flag name>=[<setperms>], [<unsetperms>]
    @flag/enable <flag name>
    @flag/disable <flagname>
***************
*** 1305,1310 ****
--- 1306,1312 ----
      /disable disables a flag, making it invisible and unusable
      /enable re-enables a disabled flag
      /alias adds a new alias for an existing flag
+     /letter changes or removes a single-letter alias for an existing flag.
      /restrict changes flag permissions (see help @flag2)
      /delete deletes a flag completely, removing it from all objects
        in the database and the removing it permanently from the 
***************
*** 2764,2769 ****
--- 2766,2772 ----
    prefixmatch       When a user attempts to set an attribute using @<attrib>,
                      this attribute will be matched down to its unique
                      prefixes. This flag is primarily used internally.
+   `                 This attribute is a branch. See: help ATTRIBUTE TREES
  & @sex
    @sex <player> = <gender>  
  
***************
*** 3456,3464 ****
    you do not own the object, or it is not visible, you will just see the 
    name of the object's owner.  May be abbreviated 'ex <object>'.  If the 
    attribute parameter is given, you will only see that attribute (good 
!   for looking at code). You can also wild-card match on attributes.  For 
!   example. to see all the attributes that began with a 'v' you could do 
!   ex <object>/v*
    
    The /brief switch is equivalent to the 'brief' command.
    The /debug switch is wizard-only and shows raw values for certa
--- 3459,3470 ----
    you do not own the object, or it is not visible, you will just see the 
    name of the object's owner.  May be abbreviated 'ex <object>'.  If the 
    attribute parameter is given, you will only see that attribute (good 
!   for looking at code). You can also wild-card match on attributes. 
!   The * wildcard matches any number of characters except a backtick (`).
!   The ? wildcard matches a single character except a backtick (`).
!   The ** wildcard matches any number of characters, including backticks.
!   For example. to see all the attributes that began with a 'v' you could do 
!   ex <object>/v**
    
    The /brief switch is equivalent to the 'brief' command.
    The /debug switch is wizard-only and shows raw values for certa
***************
*** 3467,3472 ****
--- 3473,3480 ----
      the object's owner and is primarily useful to admins. This switch
      ignores the object's VISUAL flag (but not its attribute flags)
    The /all switch shows the values of VEILED attributes.
+ 
+   See also: ATTRIBUTE TREES
  & follow
    follow <object>
  
*** 1_7_7.624/game/txt/hlp/pennchat.hlp Sun, 16 Mar 2003 16:55:02 -0600 dunemush (pennmush/19_pennchat.h 1.2.1.4.1.5 600)
--- 1_7_7.664(w)/game/txt/hlp/pennchat.hlp Mon, 15 Sep 2003 23:31:51 -0500 dunemush (pennmush/19_pennchat.h 1.2.1.4.1.6 600)
***************
*** 126,131 ****
--- 126,134 ----
    * "quiet" - channel will not show connection messages
    * "open" - you may speak even if you aren't listening to the channel
    * "hide_ok" - you may hide from the channel who list.
+   * "notitles" - chantitles are not displayed in channel messages.
+   * "nonames" - player names are not displayed in channel messages.
+   * "nocemit" - @cemit is prohibited on the channel.
    Specifications may be combined, space-separated. Default is "player"
  
    @channel/delete removes a channel. You must own it or be Wizard.
***************
*** 142,148 ****
    @channel/decompile[/brief] <channel>
    @channel/chown <channel> = <new owner>
  
!   The "priv" switch changes the channel's access privileges.
    The "quiet" switch turns the quiet status of a channel on and off.
    The "wipe" switch clears a channel of players without deleting it.
    The "buffer" switch sets the maximum number of full-length lines that
--- 145,152 ----
    @channel/decompile[/brief] <channel>
    @channel/chown <channel> = <new owner>
  
!   The "priv" switch changes the channel's access privileges. Use !<priv>
!     to reset a privilege.
    The "quiet" switch turns the quiet status of a channel on and off.
    The "wipe" switch clears a channel of players without deleting it.
    The "buffer" switch sets the maximum number of full-length lines that
*** 1_7_7.624/game/txt/index-files.pl Sat, 31 May 2003 16:07:52 -0500 dunemush (pennmush/28_index-file 1.9 600)
--- 1_7_7.664(w)/game/txt/index-files.pl Mon, 15 Sep 2003 11:35:05 -0500 dunemush (pennmush/28_index-file 1.10 600)
***************
*** 1,4 ****
! #!/usr/local/bin/perl
  #
  # index-files.pl - make an & index topic for events/news/help
  #
--- 1,4 ----
! #!/usr/bin/perl
  #
  # index-files.pl - make an & index topic for events/news/help
  #
*** 1_7_7.624/game/txt/compose.sh.SH Sat, 19 Jul 2003 09:31:25 -0500 dunemush (pennmush/33_compose.sh 1.5 700)
--- 1_7_7.664(w)/game/txt/compose.sh.SH Mon, 15 Sep 2003 11:35:05 -0500 dunemush (pennmush/33_compose.sh 1.5.1.1 700)
***************
*** 1,3 ****
--- 1,4 ----
+ #!/bin/sh
  case $CONFIG in
  '')
  	if test -f config.sh; then TOP=.;
*** 1_7_7.624/game/restart Tue, 02 Sep 2003 21:18:49 -0500 dunemush (pennmush/39_restart 1.1.1.1.1.1.1.2.1.1.1.1.1.2.1.2.2.1.2.2.1.1 700)
--- 1_7_7.664(w)/game/restart Mon, 15 Sep 2003 11:35:05 -0500 dunemush (pennmush/39_restart 1.1.1.1.1.1.1.2.1.1.1.1.1.2.1.2.2.1.2.2.1.1.1.1 700)
***************
*** 26,31 ****
--- 26,32 ----
  
  if [ ! -f $CONF_FILE ]; then
    echo "CONF_FILE doesn't exist. It's: $CONF_FILE"
+   echo "Create $CONF_FILE from $GAMEDIR/mushcnf.dst"
    exit 1
  fi
  
*** 1_7_7.624/game/mushcnf.dst Sun, 17 Aug 2003 08:30:54 -0500 dunemush (pennmush/41_mushcnf.ds 1.1.1.19.1.1.1.2.1.1.1.8.1.1.1.1.1.17 600)
--- 1_7_7.664(w)/game/mushcnf.dst Mon, 15 Sep 2003 22:20:00 -0500 dunemush (pennmush/41_mushcnf.ds 1.1.1.19.1.1.1.2.1.1.1.8.1.1.1.1.1.18 600)
***************
*** 76,84 ****
  #uncompress_program bunzip2
  #compress_suffix .bz2
  #
! compress_program compress
! uncompress_program uncompress
! compress_suffix .Z
  
  # Room where new players are created.
  player_start 0
--- 76,84 ----
  #uncompress_program bunzip2
  #compress_suffix .bz2
  #
! compress_program gzip
! uncompress_program gunzip
! compress_suffix .gz
  
  # Room where new players are created.
  player_start 0
*** 1_7_7.624/utils/mkcmds.sh.SH Wed, 02 Jul 2003 14:19:13 -0500 dunemush (pennmush/g/16_mkcmds.sh. 1.3.1.2 750)
--- 1_7_7.664(w)/utils/mkcmds.sh.SH Tue, 23 Sep 2003 12:07:40 -0500 dunemush (pennmush/g/16_mkcmds.sh. 1.3.1.3 750)
***************
*** 118,124 ****
  
  $echo "Rebuilding command prototype file"
  $echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.h
! for c in `grep "^COMMAND *(" ../src/*.c | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
    $echo >>../hdrs/temp.h "COMMAND_PROTO($c);"
    $echo $n "."
  done
--- 118,124 ----
  
  $echo "Rebuilding command prototype file"
  $echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.h
! for c in `grep "^COMMAND *(" ../src/*.c | grep -v cmd_local_silly | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
    $echo >>../hdrs/temp.h "COMMAND_PROTO($c);"
    $echo $n "."
  done
***************
*** 140,146 ****
  
  $echo "Rebuilding function prototype file"
  $echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.h
! for c in `grep "^FUNCTION *(" ../src/*.c | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
    $echo >>../hdrs/temp.h "FUNCTION_PROTO($c);"
    $echo $n "."
  done
--- 140,146 ----
  
  $echo "Rebuilding function prototype file"
  $echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.h
! for c in `grep "^FUNCTION *(" ../src/*.c | grep -v local_fun_silly | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
    $echo >>../hdrs/temp.h "FUNCTION_PROTO($c);"
    $echo $n "."
  done
*** 1_7_7.624/src/SWITCHES Mon, 01 Sep 2003 18:39:12 -0500 dunemush (pennmush/b/22_SWITCHES 1.12.1.3.1.7 600)
--- 1_7_7.664(w)/src/SWITCHES Mon, 29 Sep 2003 16:42:36 -0500 dunemush (pennmush/b/22_SWITCHES 1.12.1.3.1.8 600)
***************
*** 59,64 ****
--- 59,65 ----
  INVENTORY
  IPRINT
  JOIN
+ LETTER
  LIST
  LOWERCASE
  ME
*** 1_7_7.624/src/wiz.c Thu, 04 Sep 2003 17:27:46 -0500 dunemush (pennmush/b/23_wiz.c 1.44.1.1.1.1.1.2.1.7.1.1.1.1.1.1.1.1.1.1.1.8.1.2.2.2.1.2.1.1.1.1.1.1.1.2.1.1.1.2.2.17.1.1 660)
--- 1_7_7.664(w)/src/wiz.c Mon, 29 Sep 2003 16:42:35 -0500 dunemush (pennmush/b/23_wiz.c 1.44.1.1.1.1.1.2.1.7.1.1.1.1.1.1.1.1.1.1.1.8.1.2.2.2.1.2.1.1.1.1.1.1.1.2.1.1.1.2.2.17.1.2 660)
***************
*** 529,536 ****
  	return;
        }
        /* we can't do it */
!       did_it(player, destination, "EFAIL", T("Permission denied."),
! 	     "OEFAIL", NULL, "AEFAIL", Location(player));
        return;
      } else {
        /* attempted teleport to an exit */
--- 529,536 ----
  	return;
        }
        /* we can't do it */
!       fail_lock(player, destination, Enter_Lock, T("Permission denied."),
! 		Location(player));
        return;
      } else {
        /* attempted teleport to an exit */
*** 1_7_7.624/src/wild.c Mon, 14 Jul 2003 09:12:36 -0500 dunemush (pennmush/b/24_wild.c 1.11.1.1.1.8.1.1.1.4.1.2.1.4 660)
--- 1_7_7.664(w)/src/wild.c Mon, 29 Sep 2003 16:42:34 -0500 dunemush (pennmush/b/24_wild.c 1.11.1.1.1.8.1.1.1.4.1.2.1.5 660)
***************
*** 153,158 ****
--- 153,262 ----
    return 0;
  }
  
+ /** Do an attribute name wildcard match.
+  *
+  * This probably crashes if fed NULLs instead of strings, too.
+  * The special thing about this one is that ` doesn't match normal
+  * wildcards; you have to use ** to match embedded `.  Also, patterns
+  * ending in ` are treated as patterns ending in `*, and empty patterns
+  * are treated as *.
+  *
+  * \param tstr pattern to match against.
+  * \param dstr string to check.
+  * \retval 1 dstr matches the tstr pattern.
+  * \retval 0 dstr does not match the tstr pattern.
+  */
+ int
+ atr_wild(const char *RESTRICT tstr, const char *RESTRICT dstr)
+ {
+   int starcount;
+ 
+   if (!*tstr)
+     return !strchr(dstr, '`');
+ 
+   while (*tstr != '*') {
+     switch (*tstr) {
+     case '?':
+       /* Single character match.  Return false if at
+        * end of data.
+        */
+       if (!*dstr || *dstr == '`')
+ 	return 0;
+       break;
+     case '`':
+       /* Delimiter match.  Special handling if at end of pattern. */
+       if (*dstr != '`')
+ 	return 0;
+       if (!tstr[1])
+ 	return !strchr(dstr + 1, '`');
+       break;
+     case '\\':
+       /* Escape character.  Move up, and force literal
+        * match of next character.
+        */
+       tstr++;
+       /* FALL THROUGH */
+     default:
+       /* Literal character.  Check for a match.
+        * If matching end of data, return true.
+        */
+       if (NOTEQUAL(0, *dstr, *tstr))
+ 	return 0;
+       if (!*dstr)
+ 	return 1;
+     }
+     tstr++;
+     dstr++;
+   }
+ 
+   /* Skip over '*'. */
+   tstr++;
+   starcount = 1;
+ 
+   /* Skip over wildcards. */
+   while (starcount < 2 && ((*tstr == '?') || (*tstr == '*'))) {
+     if (*tstr == '?') {
+       if (!*dstr || *dstr == '`')
+ 	return 0;
+       dstr++;
+       starcount = 0;
+     } else
+       starcount++;
+     tstr++;
+   }
+ 
+   /* Skip over long strings of '*'. */
+   while (*tstr == '*')
+     tstr++;
+ 
+   /* Return true on trailing '**'. */
+   if (!*tstr)
+     return starcount == 2 || !strchr(dstr, '`');
+ 
+   if (*tstr == '?') {
+     /* Scan for possible matches. */
+     while (*dstr) {
+       if (*dstr != '`' && atr_wild(tstr + 1, dstr + 1))
+ 	return 1;
+       dstr++;
+     }
+   } else {
+     /* Skip over a backslash in the pattern string if it is there. */
+     if (*tstr == '\\')
+       tstr++;
+ 
+     /* Scan for possible matches. */
+     while (*dstr) {
+       if (EQUAL(0, *dstr, *tstr) && atr_wild(tstr + 1, dstr + 1))
+ 	return 1;
+       if (starcount < 2 && *dstr == '`')
+ 	return 0;
+       dstr++;
+     }
+   }
+   return 0;
+ }
+ 
  /* ---------------------------------------------------------------------------
   * wild1: INTERNAL: do a wildcard match, remembering the wild data.
   *
*** 1_7_7.624/src/utils.c Mon, 01 Sep 2003 15:56:43 -0500 dunemush (pennmush/b/27_utils.c 1.30.1.1.1.14 660)
--- 1_7_7.664(w)/src/utils.c Mon, 29 Sep 2003 16:42:34 -0500 dunemush (pennmush/b/27_utils.c 1.30.1.1.1.15 660)
***************
*** 572,577 ****
--- 572,584 ----
  
    /* Standard checks */
  
+   /* If it's an audible message, it must pass your Interact_Lock
+    * (or be from a privileged speaker)
+    */
+   if ((type == INTERACT_HEAR) && !Pemit_All(from)
+       && !eval_lock(from, to, Interact_Lock))
+     return 0;
+ 
    /* You can interact with the object you are in or any objects
     * you're holding.
     * You can interact with objects you control, but not
***************
*** 580,592 ****
    if ((from == Location(to)) || (to == Location(from)) || controls(to, from))
      return 1;
  
-   /* If it's an audible message, it must pass your Interact_Lock
-    * (or be from a privileged speaker)
-    */
-   if ((type == INTERACT_HEAR) && !Pemit_All(from)
-       && !eval_lock(from, to, Interact_Lock))
-     return 0;
- 
  
    lci = local_can_interact_last(from, to, type);
    if (lci != NOTHING)
--- 587,592 ----
*** 1_7_7.624/src/help.c Sun, 10 Aug 2003 11:13:00 -0500 dunemush (pennmush/f/32_help.c 1.4.1.2.1.1.1.3.2.2.1.1.2.1.1.2.1.2.1.2.1.3.1.15 660)
--- 1_7_7.664(w)/src/help.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/f/32_help.c 1.4.1.2.1.1.1.3.2.2.1.1.2.1.1.2.1.2.1.2.1.3.1.17 660)
***************
*** 32,41 ****
  
  static int help_init = 0;
  
! static void do_new_spitfile(dbref player, char *arg1,
! 			    help_file *help_dat, int restricted);
  static const char *string_spitfile(help_file *help_dat, char *arg1);
  static help_indx *help_find_entry(help_file *help_dat, const char *the_topic);
  
  static void help_build_index(help_file *h, int restricted);
  
--- 32,41 ----
  
  static int help_init = 0;
  
! static void do_new_spitfile(dbref player, char *arg1, help_file *help_dat);
  static const char *string_spitfile(help_file *help_dat, char *arg1);
  static help_indx *help_find_entry(help_file *help_dat, const char *the_topic);
+ static char *list_matching_entries(const char *pattern, help_file *help_dat);
  
  static void help_build_index(help_file *h, int restricted);
  
***************
*** 66,72 ****
      return;
    }
  
!   do_new_spitfile(player, arg_left, h, h->admin);
  }
  
  /** Initialize the helpfile hashtable, which contains the names of the
--- 66,81 ----
      return;
    }
  
!   if (h->admin && !Hasprivs(player)) {
!     notify(player, T("You don't look like an admin to me."));
!     return;
!   }
! 
!   if (wildcard(arg_left))
!     notify_format(player, T("Here are the entries which match '%s':\n%s"),
! 		  arg_left, list_matching_entries(arg_left, h));
!   else
!     do_new_spitfile(player, arg_left, h);
  }
  
  /** Initialize the helpfile hashtable, which contains the names of the
***************
*** 159,165 ****
  }
  
  static void
! do_new_spitfile(dbref player, char *arg1, help_file *help_dat, int restricted)
  {
    help_indx *entry = NULL;
    FILE *fp;
--- 168,174 ----
  }
  
  static void
! do_new_spitfile(dbref player, char *arg1, help_file *help_dat)
  {
    help_indx *entry = NULL;
    FILE *fp;
***************
*** 178,188 ****
    if (strlen(arg1) > LINE_SIZE)
      *(arg1 + LINE_SIZE) = '\0';
  
!   if (restricted) {
!     if (!Hasprivs(player)) {
!       notify(player, T("You don't look like an admin to me."));
!       return;
!     }
      sprintf(the_topic, "&%s", arg1);
    } else
      strcpy(the_topic, arg1);
--- 187,193 ----
    if (strlen(arg1) > LINE_SIZE)
      *(arg1 + LINE_SIZE) = '\0';
  
!   if (help_dat->admin) {
      sprintf(the_topic, "&%s", arg1);
    } else
      strcpy(the_topic, arg1);
***************
*** 195,201 ****
  
    entry = help_find_entry(help_dat, the_topic);
    if (!entry && default_topic)
!     entry = help_find_entry(help_dat, (restricted ? "&help" : "help"));
  
    if (!entry) {
      notify_format(player, T("No entry for '%s'."), arg1);
--- 200,206 ----
  
    entry = help_find_entry(help_dat, the_topic);
    if (!entry && default_topic)
!     entry = help_find_entry(help_dat, (help_dat->admin ? "&help" : "help"));
  
    if (!entry) {
      notify_format(player, T("No entry for '%s'."), arg1);
***************
*** 458,464 ****
      safe_str(T(e_perm), buff, bp);
      return;
    }
!   safe_str(string_spitfile(h, args[1]), buff, bp);
  }
  
  
--- 463,473 ----
      safe_str(T(e_perm), buff, bp);
      return;
    }
! 
!   if (wildcard(args[1]))
!     safe_str(list_matching_entries(args[1], h), buff, bp);
!   else
!     safe_str(string_spitfile(h, args[1]), buff, bp);
  }
  
  
***************
*** 488,537 ****
    if (!help_dat->indx || help_dat->entries == 0)
      return T("#-1 NO INDEX FOR FILE");
  
!   if (help_dat->entries < 10) {	/* Just do a linear search for small files */
!     for (n = 0; n < help_dat->entries; n++) {
!       if (string_prefix(help_dat->indx[n].topic, the_topic)) {
! 	entry = &help_dat->indx[n];
! 	break;
!       }
!     }
!   } else {			/* Binary search of the index */
!     int left = 0;
!     int cmp;
!     int right = help_dat->entries - 1;
! 
!     while (1) {
!       n = (left + right) / 2;
! 
!       if (left > right)
! 	break;
! 
!       cmp = strcasecmp(the_topic, help_dat->indx[n].topic);
! 
!       if (cmp == 0) {
! 	entry = &help_dat->indx[n];
! 	break;
!       } else if (cmp < 0) {
! 	/* We need to catch the first prefix */
! 	if (string_prefix(help_dat->indx[n].topic, the_topic)) {
! 	  int m;
! 	  for (m = n - 1; m >= 0; m--) {
! 	    if (!string_prefix(help_dat->indx[m].topic, the_topic))
! 	      break;
! 	  }
! 	  entry = &help_dat->indx[m + 1];
! 	  break;
! 	}
! 	if (left == right)
! 	  break;
! 	right = n - 1;
!       } else {			/* cmp > 0 */
! 	if (left == right)
! 	  break;
! 	left = n + 1;
!       }
!     }
!   }
  
    if (!entry) {
      return T("#-1 NO ENTRY");
--- 497,503 ----
    if (!help_dat->indx || help_dat->entries == 0)
      return T("#-1 NO INDEX FOR FILE");
  
!   entry = help_find_entry(help_dat, the_topic);
  
    if (!entry) {
      return T("#-1 NO ENTRY");
***************
*** 555,557 ****
--- 521,555 ----
    fclose(fp);
    return buff;
  }
+ 
+ /** Return a string with all help entries that match a pattern */
+ static char *
+ list_matching_entries(const char *pattern, help_file *help_dat)
+ {
+   static char buff[BUFFER_LEN];
+   int offset;
+   char *bp;
+   size_t n;
+ 
+   bp = buff;
+ 
+   if (help_dat->admin)
+     offset = 1;			/* To skip the leading & */
+   else
+     offset = 0;
+ 
+   for (n = 0; n < help_dat->entries; n++)
+     if (quick_wild(pattern, help_dat->indx[n].topic + offset)) {
+       safe_str(help_dat->indx[n].topic + offset, buff, &bp);
+       safe_strl(", ", 2, buff, &bp);
+     }
+ 
+   if (bp > buff)
+     *(bp - 2) = '\0';
+   else {
+     safe_str(T("No matching help topics."), buff, &bp);
+     *bp = '\0';
+   }
+ 
+   return buff;
+ }
*** 1_7_7.624/src/switchinc.c Mon, 01 Sep 2003 18:39:12 -0500 dunemush (pennmush/b/32_switchinc. 1.3.1.2.1.6.1.18.1.2.1.2.2.5.1.4.2.4.1.1.1.2.1.5.1.2.1.5.2.1.1.31.3.4.1.5.1.4.1.1.1.1.1.1.1.7.1.1 660)
--- 1_7_7.664(w)/src/switchinc.c Mon, 29 Sep 2003 16:42:41 -0500 dunemush (pennmush/b/32_switchinc. 1.3.1.2.1.6.1.18.1.2.1.2.2.5.1.4.2.4.1.1.1.2.1.5.1.2.1.5.2.1.1.31.3.4.1.5.1.4.1.1.1.1.1.1.1.7.1.1.1.1 660)
***************
*** 61,66 ****
--- 61,67 ----
    {"INVENTORY", SWITCH_INVENTORY},
    {"IPRINT", SWITCH_IPRINT},
    {"JOIN", SWITCH_JOIN},
+   {"LETTER", SWITCH_LETTER},
    {"LIST", SWITCH_LIST},
    {"LOWERCASE", SWITCH_LOWERCASE},
    {"ME", SWITCH_ME},
*** 1_7_7.624/src/set.c Wed, 03 Sep 2003 22:39:22 -0500 dunemush (pennmush/b/38_set.c 1.26.1.5.1.1.2.1.1.1.1.1.1.11.1.1.1.1.1.1.1.1.1.1.1.1.1.9 660)
--- 1_7_7.664(w)/src/set.c Mon, 29 Sep 2003 16:42:34 -0500 dunemush (pennmush/b/38_set.c 1.26.1.5.1.1.2.1.1.1.1.1.1.11.1.1.1.1.1.1.1.1.1.1.1.1.1.12 660)
***************
*** 538,546 ****
      notify(player, T("Unrecognized attribute flag."));
      return;
    }
!   af.flag = flag;
!   if (!atr_iter_get(player, thing, atrname, af_helper, &af))
      notify(player, T("No attribute found to change."));
  }
  
  
--- 538,547 ----
      notify(player, T("Unrecognized attribute flag."));
      return;
    }
!   af.flag = mush_strdup(atrflag_to_string(af.f), "af_flag list");
!   if (!atr_iter_get(player, thing, atrname, 0, af_helper, &af))
      notify(player, T("No attribute found to change."));
+   mush_free(af.flag, "af_flag list");
  }
  
  
***************
*** 869,875 ****
      notify(player, T("Nothing to do."));
      return;
    }
!   if (!atr_iter_get(player, thing, q, gedit_helper, argv))
      notify(player, T("No matching attributes."));
  }
  
--- 870,876 ----
      notify(player, T("Nothing to do."));
      return;
    }
!   if (!atr_iter_get(player, thing, q, 0, gedit_helper, argv))
      notify(player, T("No matching attributes."));
  }
  
***************
*** 939,946 ****
         noisy_match_result(player, what, TYPE_THING,
  			  MAT_NEAR_THINGS)) != NOTHING) {
      if (!eval_lock(player, thing, Use_Lock)) {
!       did_it(player, thing, "UFAIL", T("Pemission denied."), "OUFAIL", NULL,
! 	     "AUFAIL", NOTHING);
        return;
      } else
        did_it(player, thing, "USE", "Used.", "OUSE", NULL, "AUSE", NOTHING);
--- 940,946 ----
         noisy_match_result(player, what, TYPE_THING,
  			  MAT_NEAR_THINGS)) != NOTHING) {
      if (!eval_lock(player, thing, Use_Lock)) {
!       fail_lock(player, thing, Use_Lock, T("Permission denied."), NOTHING);
        return;
      } else
        did_it(player, thing, "USE", "Used.", "OUSE", NULL, "AUSE", NOTHING);
***************
*** 1058,1065 ****
      notify(player, T("That object is protected."));
      return;
    }
!   if (!atr_iter_get(player, thing, pattern, wipe_helper, NULL))
      notify(player, T("No matching attributes."));
    else
      notify(player, T("Attributes wiped."));
  }
--- 1058,1070 ----
      notify(player, T("That object is protected."));
      return;
    }
! 
!   we_are_wiping = 1;
! 
!   if (!atr_iter_get(player, thing, pattern, 0, wipe_helper, NULL))
      notify(player, T("No matching attributes."));
    else
      notify(player, T("Attributes wiped."));
+ 
+   we_are_wiping = 0;
  }
*** 1_7_7.624/src/predicat.c Wed, 03 Sep 2003 22:39:22 -0500 dunemush (pennmush/b/44_predicat.c 1.1.1.34.1.1.1.3.1.4.2.27 660)
--- 1_7_7.664(w)/src/predicat.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/b/44_predicat.c 1.1.1.34.1.1.1.3.1.4.2.28 660)
***************
*** 1105,1111 ****
    guh.lookfor = lookfor;
    guh.len = len;
    guh.insensitive = insensitive;
!   (void) atr_iter_get(player, thing, pattern, grep_util_helper, &guh);
    *guh.bp = '\0';
    return guh.buff;
  }
--- 1105,1111 ----
    guh.lookfor = lookfor;
    guh.len = len;
    guh.insensitive = insensitive;
!   (void) atr_iter_get(player, thing, pattern, 0, grep_util_helper, &guh);
    *guh.bp = '\0';
    return guh.buff;
  }
***************
*** 1201,1207 ****
      gh.lookfor = lookfor;
      gh.len = len;
      gh.insensitive = insensitive;
!     if (!atr_iter_get(player, thing, pattern, grep_helper, &gh))
        notify(player, T("No matching attributes."));
    } else {
      tp = grep_util(player, thing, pattern, lookfor, len, insensitive);
--- 1201,1207 ----
      gh.lookfor = lookfor;
      gh.len = len;
      gh.insensitive = insensitive;
!     if (!atr_iter_get(player, thing, pattern, 0, grep_helper, &gh))
        notify(player, T("No matching attributes."));
    } else {
      tp = grep_util(player, thing, pattern, lookfor, len, insensitive);
*** 1_7_7.624/src/move.c Tue, 06 May 2003 17:31:42 -0500 dunemush (pennmush/b/51_move.c 1.1.1.18.1.5.1.13.1.3.1.9.1.1.1.1.1.2.1.1.1.1.1.21 660)
--- 1_7_7.664(w)/src/move.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/b/51_move.c 1.1.1.18.1.5.1.13.1.3.1.9.1.1.1.1.1.2.1.1.1.1.1.22 660)
***************
*** 467,474 ****
  	  break;
  	}
        } else
! 	did_it(player, exit_m, "FAILURE", T("You can't go that way."),
! 	       "OFAILURE", NULL, "AFAILURE", NOTHING);
        break;
      }
    }
--- 467,474 ----
  	  break;
  	}
        } else
! 	fail_lock(player, exit_m, Basic_Lock, T("You can't go that way."),
! 		  NOTHING);
        break;
      }
    }
***************
*** 562,569 ****
  		    "ARECEIVE", NOTHING, myenv, NA_INTER_SEE);
  	mush_free(myenv[0], "dbref");
        } else
! 	did_it(player, thing, "FAILURE", T("You can't take that from there."),
! 	       "OFAILURE", NULL, "AFAILURE", NOTHING);
      } else {
        notify(player, T("I don't see that here."));
      }
--- 562,569 ----
  		    "ARECEIVE", NOTHING, myenv, NA_INTER_SEE);
  	mush_free(myenv[0], "dbref");
        } else
! 	fail_lock(player, thing, Basic_Lock,
! 		  T("You can't take that from there."), NOTHING);
      } else {
        notify(player, T("I don't see that here."));
      }
***************
*** 609,616 ****
  		      "ARECEIVE", NOTHING, myenv, NA_INTER_SEE);
  	  mush_free(myenv[0], "dbref");
  	} else
! 	  did_it(player, thing, "FAILURE", T("You can't pick that up."),
! 		 "OFAILURE", NULL, "AFAILURE", NOTHING);
  	break;
        case TYPE_EXIT:
  	notify(player, T("You can't pick up exits."));
--- 609,616 ----
  		      "ARECEIVE", NOTHING, myenv, NA_INTER_SEE);
  	  mush_free(myenv[0], "dbref");
  	} else
! 	  fail_lock(player, thing, Basic_Lock, T("You can't pick that up."),
! 		    NOTHING);
  	break;
        case TYPE_EXIT:
  	notify(player, T("You can't pick up exits."));
***************
*** 715,722 ****
      if (!((EnterOk(thing) || controls(player, thing)) &&
  	  (eval_lock(player, thing, Enter_Lock))
  	)) {
!       did_it(player, thing, "EFAIL", T("Permission denied."), "OEFAIL",
! 	     NULL, "AEFAIL", NOTHING);
        return;
      }
      if (thing == player) {
--- 715,721 ----
      if (!((EnterOk(thing) || controls(player, thing)) &&
  	  (eval_lock(player, thing, Enter_Lock))
  	)) {
!       fail_lock(player, thing, Enter_Lock, T("Permission denied."), NOTHING);
        return;
      }
      if (thing == player) {
***************
*** 744,751 ****
        || NoLeave(loc)
        || !eval_lock(player, loc, Leave_Lock)
      ) {
!     did_it(player, loc, "LFAIL", T("You can't leave."), "OLFAIL",
! 	   NULL, "ALFAIL", NOTHING);
      return;
    }
    enter_room(player, Location(loc), 0);
--- 743,749 ----
        || NoLeave(loc)
        || !eval_lock(player, loc, Leave_Lock)
      ) {
!     fail_lock(player, loc, Leave_Lock, T("You can't leave."), NOTHING);
      return;
    }
    enter_room(player, Location(loc), 0);
***************
*** 849,856 ****
      }
      /* Ok, are we allowed to follow them? */
      if (!eval_lock(player, leader, Follow_Lock)) {
!       notify_format(player,
! 		    T("You're not allowed to follow %s."), Name(leader));
        return;
      }
      /* Ok, looks good */
--- 847,854 ----
      }
      /* Ok, are we allowed to follow them? */
      if (!eval_lock(player, leader, Follow_Lock)) {
!       fail_lock(player, leader, Follow_Lock,
! 		T("You're not alllowed to follow."), Location(player));
        return;
      }
      /* Ok, looks good */
*** 1_7_7.624/src/look.c Mon, 25 Aug 2003 12:05:33 -0500 dunemush (pennmush/c/4_look.c 1.21.1.2.1.9.1.1.1.1.1.12 660)
--- 1_7_7.664(w)/src/look.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/c/4_look.c 1.21.1.2.1.9.1.1.1.1.1.17 660)
***************
*** 308,319 ****
  		   __attribute__ ((__unused__)))
  {
    char fbuf[BUFFER_LEN];
!   char *r;
  
    if (EX_PUBLIC_ATTRIBS &&
        !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
      return 0;
    strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
    if (AL_FLAGS(atr) & AF_VEILED) {
      if (ShowAnsi(player))
        notify_format(player,
--- 308,321 ----
  		   __attribute__ ((__unused__)))
  {
    char fbuf[BUFFER_LEN];
!   char const *r;
  
    if (EX_PUBLIC_ATTRIBS &&
        !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
      return 0;
    strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
+   if (atr_sub_branch(atr))
+     strcat(fbuf, "`");
    if (AL_FLAGS(atr) & AF_VEILED) {
      if (ShowAnsi(player))
        notify_format(player,
***************
*** 343,354 ****
  	    __attribute__ ((__unused__)))
  {
    char fbuf[BUFFER_LEN];
!   char *r;
  
    if (EX_PUBLIC_ATTRIBS &&
        !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
      return 0;
    strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
    r = safe_atr_value(atr);
    if (ShowAnsi(player))
      notify_format(player,
--- 345,358 ----
  	    __attribute__ ((__unused__)))
  {
    char fbuf[BUFFER_LEN];
!   char const *r;
  
    if (EX_PUBLIC_ATTRIBS &&
        !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
      return 0;
    strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
+   if (atr_sub_branch(atr))
+     strcat(fbuf, "`");
    r = safe_atr_value(atr);
    if (ShowAnsi(player))
      notify_format(player,
***************
*** 365,374 ****
  look_atrs(dbref player, dbref thing, const char *mstr, int all)
  {
    if (all || (mstr && *mstr && !wildcard(mstr))) {
!     if (!atr_iter_get(player, thing, mstr, look_helper, NULL) && mstr)
        notify(player, T("No matching attributes."));
    } else {
!     if (!atr_iter_get(player, thing, mstr, look_helper_veiled, NULL) && mstr)
        notify(player, T("No matching attributes."));
    }
  }
--- 369,378 ----
  look_atrs(dbref player, dbref thing, const char *mstr, int all)
  {
    if (all || (mstr && *mstr && !wildcard(mstr))) {
!     if (!atr_iter_get(player, thing, mstr, 0, look_helper, NULL) && mstr)
        notify(player, T("No matching attributes."));
    } else {
!     if (!atr_iter_get(player, thing, mstr, 0, look_helper_veiled, NULL) && mstr)
        notify(player, T("No matching attributes."));
    }
  }
***************
*** 377,386 ****
  mortal_look_atrs(dbref player, dbref thing, const char *mstr, int all)
  {
    if (all || (mstr && *mstr && !wildcard(mstr))) {
!     if (!atr_iter_get_visible(player, thing, mstr, look_helper, NULL) && mstr)
        notify(player, T("No matching attributes."));
    } else {
!     if (!atr_iter_get_visible(player, thing, mstr, look_helper_veiled, NULL)
  	&& mstr)
        notify(player, T("No matching attributes."));
    }
--- 381,390 ----
  mortal_look_atrs(dbref player, dbref thing, const char *mstr, int all)
  {
    if (all || (mstr && *mstr && !wildcard(mstr))) {
!     if (!atr_iter_get(player, thing, mstr, 1, look_helper, NULL) && mstr)
        notify(player, T("No matching attributes."));
    } else {
!     if (!atr_iter_get(player, thing, mstr, 1, look_helper_veiled, NULL)
  	&& mstr)
        notify(player, T("No matching attributes."));
    }
***************
*** 485,492 ****
        did_it(player, loc, "SUCCESS", NULL, "OSUCCESS", NULL, "ASUCCESS",
  	     NOTHING);
      else
!       did_it(player, loc, "FAILURE", NULL, "OFAILURE", NULL, "AFAILURE",
! 	     NOTHING);
    }
    /* tell him the contents */
    if (style != LOOK_CLOUDYTRANS)
--- 489,495 ----
        did_it(player, loc, "SUCCESS", NULL, "OSUCCESS", NULL, "ASUCCESS",
  	     NOTHING);
      else
!       fail_lock(player, loc, Basic_Lock, NULL, NOTHING);
    }
    /* tell him the contents */
    if (style != LOOK_CLOUDYTRANS)
***************
*** 1315,1321 ****
    dh.name = name;
    dh.skipdef = skipdef;
    /* Comment complaints if none are found */
!   if (!atr_iter_get(player, thing, pattern, decompile_helper, &dh))
      notify(player, T("@@ No attributes found. @@"));
  }
  
--- 1318,1324 ----
    dh.name = name;
    dh.skipdef = skipdef;
    /* Comment complaints if none are found */
!   if (!atr_iter_get(player, thing, pattern, 0, decompile_helper, &dh))
      notify(player, T("@@ No attributes found. @@"));
  }
  
***************
*** 1483,1488 ****
      decompile_powers(player, thing, object);
    }
    if (dbflag != DEC_FLAG) {
!     decompile_atrs(player, thing, object, "*", "", skipdef);
    }
  }
--- 1486,1491 ----
      decompile_powers(player, thing, object);
    }
    if (dbflag != DEC_FLAG) {
!     decompile_atrs(player, thing, object, "**", "", skipdef);
    }
  }
*** 1_7_7.624/src/lock.c Mon, 01 Sep 2003 15:52:02 -0500 dunemush (pennmush/c/6_lock.c 1.17.1.13.1.1.1.1.1.7 660)
--- 1_7_7.664(w)/src/lock.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/c/6_lock.c 1.17.1.13.1.1.1.1.1.11 660)
***************
*** 111,116 ****
--- 111,129 ----
    {NULL, TRUE_BOOLEXP, GOD, 0, NULL}
  };
  
+ /** Table of base attributes associated with success and failure of
+  * locks. These are the historical ones; we automatically generate
+  * such attribute names for those that aren't in this table using
+  * <lock>_LOCK`<message>
+  */
+ const LOCKMSGINFO lock_msgs[] = {
+   {"Basic", "SUCCESS", "FAILURE"},
+   {"Enter", "ENTER", "EFAIL"},
+   {"Use", "USE", "UFAIL"},
+   {"Leave", "LEAVE", "LFAIL"},
+   {NULL, NULL, NULL}
+ };
+ 
  /** Table of lock permissions */
  PRIV lock_privs[] = {
    {"visual", 'v', LF_VISUAL, LF_VISUAL},
***************
*** 618,624 ****
  	if (!AreQuiet(player, thing))
  	  notify_format(player, T("%s(%s) - %s unlocked."), Name(thing),
  			unparse_dbref(thing), real_type);
! 	ModTime(thing) = mudtime;
        } else
  	notify(player, T("Permission denied."));
      }
--- 631,638 ----
  	if (!AreQuiet(player, thing))
  	  notify_format(player, T("%s(%s) - %s unlocked."), Name(thing),
  			unparse_dbref(thing), real_type);
! 	if (!IsPlayer(thing))
! 	  ModTime(thing) = mudtime;
        } else
  	notify(player, T("Permission denied."));
      }
***************
*** 683,689 ****
  	if (!AreQuiet(player, thing))
  	  notify_format(player, T("%s(%s) - %s locked."), Name(thing),
  			unparse_dbref(thing), real_type);
! 	ModTime(thing) = mudtime;
        } else
  	notify(player, T("Permission denied."));
      }
--- 697,704 ----
  	if (!AreQuiet(player, thing))
  	  notify_format(player, T("%s(%s) - %s locked."), Name(thing),
  			unparse_dbref(thing), real_type);
! 	if (!IsPlayer(thing))
! 	  ModTime(thing) = mudtime;
        } else
  	notify(player, T("Permission denied."));
      }
***************
*** 720,725 ****
--- 735,791 ----
    return eval_boolexp(player, getlock(thing, ltype), thing);
  }
  
+ /** Active a lock's failure attributes.
+  * \param player dbref failing to pass the lock.
+  * \param thing object containing the lock.
+  * \param ltype type of lock failed.
+  * \param def default message if there is no appropriate failure attribute.
+  * \param loc location in which action is taking place.
+  */
+ void
+ fail_lock(dbref player, dbref thing, lock_type ltype, const char *def,
+ 	  dbref loc)
+ {
+   const LOCKMSGINFO *lm;
+   char atr[BUFFER_LEN];
+   char oatr[BUFFER_LEN];
+   char aatr[BUFFER_LEN];
+   char *bp;
+ 
+   /* Find the lock's failure attribute, if it's there */
+   for (lm = lock_msgs; lm->type; lm++) {
+     if (!strcmp(lm->type, ltype))
+       break;
+   }
+   if (lm->type) {
+     strcpy(atr, lm->failbase);
+     bp = oatr;
+     safe_format(oatr, &bp, "O%s", lm->failbase);
+     *bp = '\0';
+     strcpy(aatr, oatr);
+     aatr[0] = 'A';
+   } else {
+     /* Oops, it's not in the table. So we construct them on these lines:
+      * <LOCKNAME>_LOCK`<type>FAILURE
+      */
+     bp = atr;
+     safe_format(atr, &bp, "%s_LOCK`FAILURE", ltype);
+     *bp = '\0';
+     bp = oatr;
+     safe_format(oatr, &bp, "%s_LOCK`OFAILURE", ltype);
+     *bp = '\0';
+     bp = aatr;
+     safe_format(aatr, &bp, "%s_LOCK`AFAILURE", ltype);
+     *bp = '\0';
+   }
+   /* Now do the work */
+   upcasestr(atr);
+   upcasestr(oatr);
+   upcasestr(aatr);
+   did_it(player, thing, atr, def, oatr, NULL, aatr, loc);
+ }
+ 
+ 
  /** Determine if a lock is visual.
   * \param thing object containing the lock.
   * \param ltype type of lock to check.
***************
*** 791,795 ****
    if (!Quiet(player) && !(Quiet(thing) && (Owner(thing) == player)))
      notify_format(player, "%s/%s - %s.", Name(thing), L_TYPE(l),
  		  unset ? T("lock flags unset") : T("lock flags set"));
!   ModTime(thing) = mudtime;
  }
--- 857,862 ----
    if (!Quiet(player) && !(Quiet(thing) && (Owner(thing) == player)))
      notify_format(player, "%s/%s - %s.", Name(thing), L_TYPE(l),
  		  unset ? T("lock flags unset") : T("lock flags set"));
!   if (!IsPlayer(thing))
!     ModTime(thing) = mudtime;
  }
*** 1_7_7.624/src/ident.c Sat, 01 Mar 2003 23:51:32 -0600 dunemush (pennmush/c/8_ident.c 1.19.1.4.1.13 660)
--- 1_7_7.664(w)/src/ident.c Mon, 29 Sep 2003 16:42:32 -0500 dunemush (pennmush/c/8_ident.c 1.19.1.4.1.14 660)
***************
*** 228,235 ****
    memset(id, 0, sizeof(ident_t));
  
    if (getnameinfo(faddr, flen, host, sizeof(host), NULL, 0,
! 		  NI_NUMERICHOST | NI_NUMERICSERV) != 0)
      return 0;
  
    /* Make sure we connect from the right interface. Changing the pointer
       directly doesn't seem to work. So... */
--- 228,237 ----
    memset(id, 0, sizeof(ident_t));
  
    if (getnameinfo(faddr, flen, host, sizeof(host), NULL, 0,
! 		  NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
!     free(id);
      return 0;
+   }
  
    /* Make sure we connect from the right interface. Changing the pointer
       directly doesn't seem to work. So... */
*** 1_7_7.624/src/funmisc.c Wed, 03 Sep 2003 22:39:22 -0500 dunemush (pennmush/c/14_funmisc.c 1.30.1.1.1.22 660)
--- 1_7_7.664(w)/src/funmisc.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/14_funmisc.c 1.30.1.1.1.25 660)
***************
*** 181,193 ****
     * Uses Sh'dow's random number generator, found in utils.c.  Better
     * distribution than original, w/ minimal speed losses.
     */
!   int i;
    if (!is_integer(args[0])) {
      safe_str(T(e_int), buff, bp);
      return;
    }
!   i = get_random_long(0, parse_integer(args[0]) - 1);
!   safe_integer(i, buff, bp);
  }
  
  /* ARGSUSED */
--- 181,209 ----
     * Uses Sh'dow's random number generator, found in utils.c.  Better
     * distribution than original, w/ minimal speed losses.
     */
!   int low, high;
    if (!is_integer(args[0])) {
      safe_str(T(e_int), buff, bp);
      return;
    }
!   if (nargs == 1) {
!     low = 0;
!     high = parse_integer(args[0]) - 1;
!   } else {
!     if (!is_integer(args[1])) {
!       safe_str(T(e_ints), buff, bp);
!       return;
!     }
!     low = parse_integer(args[0]);
!     high = parse_integer(args[1]);
!   }
! 
!   if (low > high) {
!     safe_str(T(e_range), buff, bp);
!     return;
!   }
! 
!   safe_integer(get_random_long(low, high), buff, bp);
  }
  
  /* ARGSUSED */
*** 1_7_7.624/src/funlist.c Mon, 14 Jul 2003 09:07:25 -0500 dunemush (pennmush/c/16_funlist.c 1.3.1.1.1.5.1.2.1.1.1.1.1.4.1.2.1.2.1.19.1.2.1.1.1.2.1.5.1.1.1.1.1.1.1.1.1.1.2.1.1.3.1.2.1.1.1.1.1.1.1.1.1.1.1.12.1.14 660)
--- 1_7_7.664(w)/src/funlist.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/16_funlist.c 1.3.1.1.1.5.1.2.1.1.1.1.1.4.1.2.1.2.1.19.1.2.1.1.1.2.1.5.1.1.1.1.1.1.1.1.1.1.2.1.1.3.1.2.1.1.1.1.1.1.1.1.1.1.1.12.1.14.1.2 660)
***************
*** 1481,1486 ****
--- 1481,1519 ----
  }
  
  /* ARGSUSED */
+ FUNCTION(fun_randword)
+ {
+   char *s, *r;
+   char sep;
+   int word_count, word_index;
+ 
+   if (!*args[0])
+     return;
+ 
+   if (!delim_check(buff, bp, nargs, args, 2, &sep))
+     return;
+ 
+   s = trim_space_sep(args[0], sep);
+   word_count = do_wordcount(s, sep);
+   word_index = get_random_long(0, word_count - 1);
+ 
+   /* Go to the start of the token we're interested in. */
+   while (word_index && s) {
+     s = next_token(s, sep);
+     word_index--;
+   }
+ 
+   if (!s || !*s)		/* ran off the end of the string */
+     return;
+ 
+   /* Chop off the end, and copy. No length checking needed. */
+   r = s;
+   if (s && *s)
+     (void) split_token(&s, sep);
+   safe_str(r, buff, bp);
+ }
+ 
+ /* ARGSUSED */
  FUNCTION(fun_rest)
  {
    char *p;
***************
*** 2859,2865 ****
    reharg.buff = buff;
    reharg.bp = bp;
  
!   atr_iter_get(executor, it, args[1], regrep_helper, (void *) &reharg);
    mush_free(reharg.re, "pcre");
    if (reharg.study)
      mush_free(reharg.study, "pcre.extra");
--- 2892,2898 ----
    reharg.buff = buff;
    reharg.bp = bp;
  
!   atr_iter_get(executor, it, args[1], 0, regrep_helper, (void *) &reharg);
    mush_free(reharg.re, "pcre");
    if (reharg.study)
      mush_free(reharg.study, "pcre.extra");
*** 1_7_7.624/src/fundb.c Fri, 05 Sep 2003 20:59:37 -0500 dunemush (pennmush/c/17_fundb.c 1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.7.1.3.1.3.1.3.1.2.1.2.1.3.2.1.2.1.2.1.1.1.1.4.1.1.1.1.1.1.1.1.1.1.1.3.1.1.2.2.2.1.1.1.1.1.1.19 660)
--- 1_7_7.664(w)/src/fundb.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/17_fundb.c 1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.7.1.3.1.3.1.3.1.2.1.2.1.3.2.1.2.1.2.1.1.1.1.4.1.1.1.1.1.1.1.1.1.1.1.3.1.1.2.2.2.1.1.1.1.1.1.22 660)
***************
*** 125,131 ****
    lh.first = 1;
    lh.buff = buff;
    lh.bp = bp;
!   (void) atr_iter_get(executor, thing, pattern, lattr_helper, &lh);
  }
  
  /* ARGSUSED */
--- 125,131 ----
    lh.first = 1;
    lh.buff = buff;
    lh.bp = bp;
!   (void) atr_iter_get(executor, thing, pattern, 0, lattr_helper, &lh);
  }
  
  /* ARGSUSED */
***************
*** 437,442 ****
--- 437,444 ----
        return;
      }
      safe_str(privs_to_letters(attr_privs, AL_FLAGS(a)), buff, bp);
+     if (atr_sub_branch(a))
+       safe_chr('`', buff, bp);
    } else {
      /* Object flags, visible to all */
      safe_str(unparse_flags(thing, executor), buff, bp);
***************
*** 1501,1512 ****
    /* find out what we're matching in relation to */
    looker = match_thing(executor, args[0]);
    if (!GoodObject(looker)) {
-     notify(executor, T("I don't see that here."));
      safe_str("#-1", buff, bp);
      return;
    }
    if (!See_All(executor) && !controls(executor, looker)) {
-     notify(executor, T("Permission denied."));
      safe_str("#-1", buff, bp);
      return;
    }
--- 1503,1512 ----
***************
*** 1587,1596 ****
    else
      item = last_match_result(looker, args[1], pref_type, match_flags);
  
-   if (item == NOTHING)
-     notify(executor, T("Nothing found."));
-   else if (item == AMBIGUOUS)
-     notify(executor, T("More than one match found."));
    if (GoodObject(item) && (force_type && pref_type != NOTYPE
  			   && !(Typeof(item) == pref_type)))
      safe_dbref(NOTHING, buff, bp);
--- 1587,1592 ----
*** 1_7_7.624/src/function.c Mon, 11 Aug 2003 16:30:36 -0500 dunemush (pennmush/c/18_function.c 1.29.1.14.1.3.1.6.1.1.1.1.1.14.1.2.1.1.1.7.1.22 660)
--- 1_7_7.664(w)/src/function.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/18_function.c 1.29.1.14.1.3.1.6.1.1.1.1.1.14.1.2.1.1.1.7.1.22.1.1 660)
***************
*** 409,415 ****
    {"QUOTA", fun_quota, 1, 1, FN_REG},
  #endif
    {"R", fun_r, 1, 1, FN_REG},
!   {"RAND", fun_rand, 1, 1, FN_REG},
    {"REGEDIT", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
    {"REGEDITALL", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
    {"REGEDITALLI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
--- 409,416 ----
    {"QUOTA", fun_quota, 1, 1, FN_REG},
  #endif
    {"R", fun_r, 1, 1, FN_REG},
!   {"RAND", fun_rand, 1, 2, FN_REG},
!   {"RANDWORD", fun_randword, 1, 2, FN_REG},
    {"REGEDIT", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
    {"REGEDITALL", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
    {"REGEDITALLI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
*** 1_7_7.624/src/flags.c Wed, 27 Aug 2003 23:03:24 -0500 dunemush (pennmush/c/20_flags.c 1.1.1.1.1.1.1.1.1.1.1.1.1.6.1.2.1.1.1.1.1.2.2.2.2.1.2.1.1.3.1.2.1.1.1.1.1.1.1.1.1.3.1.9.1.2.2.1.1.2.1.56 660)
--- 1_7_7.664(w)/src/flags.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/20_flags.c 1.1.1.1.1.1.1.1.1.1.1.1.1.6.1.2.1.1.1.1.1.2.2.2.2.1.2.1.1.3.1.2.1.1.1.1.1.1.1.1.1.3.1.9.1.2.2.1.1.2.1.56.1.2 660)
***************
*** 1911,1916 ****
--- 1911,1961 ----
      notify(player, T("Unknown failure adding alias."));
  }
  
+ /** Change a flag's alias. 
+  * \param player the enactor.
+  * \param name name of the flag.
+  * \param letter The new alias, or an empty string to remove the alias.
+  */
+ void
+ do_flag_letter(dbref player, const char *name, const char *letter)
+ {
+   FLAG *f;
+   FLAGSPACE *n;
+ 
+   if (!God(player)) {
+     notify(player, T("You don't look like God."));
+     return;
+   }
+   Flagspace_Lookup(n, "FLAG");
+   f = match_flag_ns(n, name);
+   if (!f) {
+     notify(player, T("I don't know that flag."));
+     return;
+   }
+   if (letter && *letter) {
+     FLAG *other;
+ 
+     if (strlen(letter) > 1) {
+       notify(player, T("Flag characters must be single characters."));
+       return;
+     }
+ 
+     if ((other = letter_to_flagptr(n, *letter, NOTYPE))) {
+       notify_format(player, T("Letter conflicts with the %s flag."),
+ 		    other->name);
+       return;
+     }
+ 
+     f->letter = *letter;
+     notify_format(player, T("Letter for flag %s set to '%c'."),
+ 		  f->name, *letter);
+   } else {			/* Clear a flag */
+     f->letter = '\0';
+     notify_format(player, T("Letter for flag %s cleared."), f->name);
+   }
+ }
+ 
+ 
  /** Disable a flag. 
   * \verbatim
   * This function implements @flag/disable.
***************
*** 2065,2071 ****
  /** Return a list of all flags.
   * \param name wildcard to match against flag names, or NULL for all.
   * \param privs the looker, for permission checking.
!  * \param which a bitmask of 0x1 (flag chars) and 0x2 (flag names)u
   */
  char *
  list_all_flags(const char *ns, const char *name, dbref privs, int which)
--- 2110,2116 ----
  /** Return a list of all flags.
   * \param name wildcard to match against flag names, or NULL for all.
   * \param privs the looker, for permission checking.
!  * \param which a bitmask of 0x1 (flag chars) and 0x2 (flag names).
   */
  char *
  list_all_flags(const char *ns, const char *name, dbref privs, int which)
***************
*** 2093,2103 ****
      switch (which) {
      case 0x3:
        if (i)
! 	safe_chr(' ', buf, &bp);
        safe_str(ptrs[i], buf, &bp);
        f = match_flag_ns(n, ptrs[i]);
!       if (f && (f->letter != '\0'))
  	safe_format(buf, &bp, " (%c)", f->letter);
        break;
      case 0x2:
        if (i)
--- 2138,2152 ----
      switch (which) {
      case 0x3:
        if (i)
! 	safe_strl(", ", 2, buf, &bp);
        safe_str(ptrs[i], buf, &bp);
        f = match_flag_ns(n, ptrs[i]);
!       if (!f)
! 	break;
!       if (f->letter != '\0')
  	safe_format(buf, &bp, " (%c)", f->letter);
+       if (f->perms & F_DISABLED)
+ 	safe_str(T(" (disabled)"), buf, &bp);
        break;
      case 0x2:
        if (i)
*** 1_7_7.624/src/filecopy.c Thu, 28 Aug 2003 11:10:52 -0500 dunemush (pennmush/c/21_filecopy.c 1.80 660)
--- 1_7_7.664(w)/src/filecopy.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/21_filecopy.c 1.81 660)
***************
*** 256,262 ****
        
if (panicdb_OK) {	/* panicdb */
  	
ConcatenateFiles(options.crash_db, options.input_db);
        
} else {			/* NOTHING */
! 	
exit(-1);
        
}
      
}
    
}
--- 256,262 ----
        
if (panicdb_OK) {	/* panicdb */
  	
ConcatenateFiles(options.crash_db, options.input_db);
        
} else {			/* NOTHING */
! 	
return;
        
}
      
}
    
}
*** 1_7_7.624/src/extchat.c Mon, 01 Sep 2003 15:54:38 -0500 dunemush (pennmush/c/23_extchat.c 1.1.1.1.1.1.1.1.1.2.1.1.1.3.1.1.1.5.1.1.1.1.1.5.1.2.1.3.1.3.1.1.1.4.1.2.1.6.1.2.1.1.2.4.2.9.1.2.1.2.1.3.1.2.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.2.1.1.2.1.1.2.1.1.1.1.1.37.1.2 660)
--- 1_7_7.664(w)/src/extchat.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/23_extchat.c 1.1.1.1.1.1.1.1.1.2.1.1.1.3.1.1.1.5.1.1.1.1.1.5.1.2.1.3.1.3.1.1.1.4.1.2.1.6.1.2.1.1.2.4.2.9.1.2.1.2.1.3.1.2.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.2.1.1.2.1.1.2.1.1.1.1.1.1.1.3 660)
***************
*** 80,85 ****
--- 80,88 ----
  static void channel_push_buffer(CHAN *c, char *tbuf1);
  static void channel_shift_buffer(CHAN *c, int space_needed);
  static int channel_buffer_lines(CHAN *c);
+ static void format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim,
+ 				     int flags, const char *msg,
+ 				     const char *extra);
  
  const char *chan_speak_lock = "ChanSpeakLock";	/**< Name of speak lock */
  const char *chan_join_lock = "ChanJoinLock";	/**< Name of join lock */
***************
*** 111,116 ****
--- 114,122 ----
    {"Quiet", 'Q', CHANNEL_QUIET, CHANNEL_QUIET},
    {"Open", 'o', CHANNEL_OPEN, CHANNEL_OPEN},
    {"Hide_Ok", 'H', CHANNEL_CANHIDE, CHANNEL_CANHIDE},
+   {"NoTitles", 'T', CHANNEL_NOTITLES, CHANNEL_NOTITLES},
+   {"NoNames", 'N', CHANNEL_NONAMES, CHANNEL_NONAMES},
+   {"NoCemit", 'C', CHANNEL_NOCEMIT, CHANNEL_NOCEMIT},
    {NULL, '\0', 0, 0}
  };
  
***************
*** 287,292 ****
--- 293,299 ----
  static int
  load_channel(FILE * fp, CHAN *ch)
  {
+   int ret = 1;
    strcpy(ChanName(ch), getstring_noalloc(fp));
    if (feof(fp))
      return 0;
***************
*** 303,310 ****
    ChanNumUsers(ch) = getref(fp);
    ChanMaxUsers(ch) = ChanNumUsers(ch);
    ChanUsers(ch) = NULL;
!   if (ChanNumUsers(ch) > 0)
!     return (ChanNumUsers(ch) = load_chanusers(fp, ch));
    return 1;
  }
  
--- 310,321 ----
    ChanNumUsers(ch) = getref(fp);
    ChanMaxUsers(ch) = ChanNumUsers(ch);
    ChanUsers(ch) = NULL;
!   if (ChanNumUsers(ch) > 0) {
!     ret = load_chanusers(fp, ch);
!     if (ret < 0)
!       return 0;
!     ChanNumUsers(ch) = ret;
!   }
    return 1;
  }
  
***************
*** 321,327 ****
      if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
        user = new_user(player);
        if (!user)
! 	return 0;
        CUtype(user) = getref(fp);
        strcpy(CUtitle(user), getstring_noalloc(fp));
        CUnext(user) = NULL;
--- 332,338 ----
      if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
        user = new_user(player);
        if (!user)
! 	return -1;
        CUtype(user) = getref(fp);
        strcpy(CUtitle(user), getstring_noalloc(fp));
        CUnext(user) = NULL;
***************
*** 876,881 ****
--- 887,893 ----
  do_channel(dbref player, const char *name, const char *target, const char *com)
  {
    CHAN *chan = NULL;
+   CHANUSER *u;
    dbref victim;
  
    if (!name && !*name) {
***************
*** 973,990 ****
        notify_format(player,
  		    T("CHAT: You join %s to channel <%s>."), Name(victim),
  		    ChanName(chan));
!       if (!Channel_Quiet(chan) && !DarkLegal(victim))
! 	channel_broadcast(chan, victim, CB_CHECKQUIET | CB_PRESENCE,
! 			  T("<%s> %s has joined this channel."), ChanName(chan),
! 			  Name(victim));
!       ChanNumUsers(chan)++;
!     } else {
!       notify_format(player,
! 		    T("%s is already on channel <%s>."), Name(victim),
! 		    ChanName(chan));
      }
-     return;
    } else if (!strcasecmp("off", com) || !strcasecmp("leave", com)) {
      /* You must control either the victim or the channel */
      if (!controls(player, victim) && !Chan_Can_Modify(chan, player)) {
        notify(player, T("Invalid target."));
--- 985,1004 ----
        notify_format(player,
  		    T("CHAT: You join %s to channel <%s>."), Name(victim),
  		    ChanName(chan));
!       u = onchannel(victim, chan);
!       if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
! 	format_channel_broadcast(chan, u, victim, CB_CHECKQUIET | CB_PRESENCE,
! 				 T("<%s> %s has joined this channel."), NULL);
! 	ChanNumUsers(chan)++;
!       } else {
! 	notify_format(player,
! 		      T("%s is already on channel <%s>."), Name(victim),
! 		      ChanName(chan));
!       }
!       return;
      }
    } else if (!strcasecmp("off", com) || !strcasecmp("leave", com)) {
+     char title[CU_TITLE_LEN];
      /* You must control either the victim or the channel */
      if (!controls(player, victim) && !Chan_Can_Modify(chan, player)) {
        notify(player, T("Invalid target."));
***************
*** 994,1004 ****
        notify(player, T("Guests may not leave channels."));
        return;
      }
!     if (remove_user_by_dbref(victim, chan)) {
!       if (!Channel_Quiet(chan) && !DarkLegal(victim))
! 	channel_broadcast(chan, victim, CB_CHECKQUIET | CB_PRESENCE,
! 			  T("<%s> %s has left this channel."), ChanName(chan),
! 			  Name(victim));
        notify_format(victim,
  		    T("CHAT: %s removes you from channel <%s>."),
  		    Name(player), ChanName(chan));
--- 1008,1021 ----
        notify(player, T("Guests may not leave channels."));
        return;
      }
!     u = onchannel(victim, chan);
!     strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
!     if (remove_user(u, chan)) {
!       if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
! 	format_channel_broadcast(chan, NULL, victim,
! 				 CB_CHECKQUIET | CB_PRESENCE,
! 				 T("<%s> %s has left this channel."), title);
!       }
        notify_format(victim,
  		    T("CHAT: %s removes you from channel <%s>."),
  		    Name(player), ChanName(chan));
***************
*** 1020,1031 ****
  channel_join_self(dbref player, const char *name)
  {
    CHAN *chan = NULL;
    if (Guest(player)) {
      notify(player, T("Guests are not allowed to join channels."));
      return;
    }
  
- 
    switch (find_channel_partial_off(name, &chan, player)) {
    case CMATCH_NONE:
      if (find_channel_partial_on(name, &chan, player))
--- 1037,1049 ----
  channel_join_self(dbref player, const char *name)
  {
    CHAN *chan = NULL;
+   CHANUSER *u;
+ 
    if (Guest(player)) {
      notify(player, T("Guests are not allowed to join channels."));
      return;
    }
  
    switch (find_channel_partial_off(name, &chan, player)) {
    case CMATCH_NONE:
      if (find_channel_partial_on(name, &chan, player))
***************
*** 1062,1071 ****
    }
    if (insert_user_by_dbref(player, chan)) {
      notify_format(player, T("CHAT: You join channel <%s>."), ChanName(chan));
      if (!Channel_Quiet(chan) && !DarkLegal(player))
!       channel_broadcast(chan, player, CB_CHECKQUIET | CB_PRESENCE,
! 			T("<%s> %s has joined this channel."), ChanName(chan),
! 			Name(player));
      ChanNumUsers(chan)++;
    } else {
      /* Should never happen */
--- 1080,1089 ----
    }
    if (insert_user_by_dbref(player, chan)) {
      notify_format(player, T("CHAT: You join channel <%s>."), ChanName(chan));
+     u = onchannel(player, chan);
      if (!Channel_Quiet(chan) && !DarkLegal(player))
!       format_channel_broadcast(chan, u, player, CB_CHECKQUIET | CB_PRESENCE,
! 			       T("<%s> %s has joined this channel."), NULL);
      ChanNumUsers(chan)++;
    } else {
      /* Should never happen */
***************
*** 1079,1084 ****
--- 1097,1105 ----
  channel_leave_self(dbref player, const char *name)
  {
    CHAN *chan = NULL;
+   CHANUSER *u;
+   char title[CU_TITLE_LEN];
+ 
    if (Guest(player)) {
      notify(player, T("Guests are not allowed to leave channels."));
      return;
***************
*** 1096,1106 ****
      notify(player, T("CHAT: I don't know which channel you mean."));
      return;
    }
!   if (remove_user_by_dbref(player, chan)) {
      if (!Channel_Quiet(chan) && !DarkLegal(player))
!       channel_broadcast(chan, player, CB_CHECKQUIET | CB_PRESENCE,
! 			T("<%s> %s has left this channel."), ChanName(chan),
! 			Name(player));
      notify_format(player, T("CHAT: You leave channel <%s>."), ChanName(chan));
    } else {
      /* Should never happen */
--- 1117,1128 ----
      notify(player, T("CHAT: I don't know which channel you mean."));
      return;
    }
!   u = onchannel(player, chan);
!   strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
!   if (remove_user(u, chan)) {
      if (!Channel_Quiet(chan) && !DarkLegal(player))
!       format_channel_broadcast(chan, NULL, player, CB_CHECKQUIET | CB_PRESENCE,
! 			       T("<%s> %s has left this channel."), title);
      notify_format(player, T("CHAT: You leave channel <%s>."), ChanName(chan));
    } else {
      /* Should never happen */
***************
*** 1196,1207 ****
  {
    CHANUSER *u;
    const char *gap;
    char *title;
    int canhear;
  
    if (!Chan_Ok_Type(chan, player)) {
      notify_format(player,
! 		  T("Sorry, you're not the right type to be on channel <%s>."),
  		  ChanName(chan));
      return;
    }
--- 1218,1232 ----
  {
    CHANUSER *u;
    const char *gap;
+   const char *someone = "Someone";
    char *title;
+   const char *name;
    int canhear;
  
    if (!Chan_Ok_Type(chan, player)) {
      notify_format(player,
! 		  T
! 		  ("Sorry, you're not the right type to be on channel <%s>."),
  		  ChanName(chan));
      return;
    }
***************
*** 1232,1241 ****
      return;
    }
  
!   if (u &&CUtitle(u) && *CUtitle(u))
       title = CUtitle(u);
    else
      title = NULL;
  
    /* figure out what kind of message we have */
    gap = " ";
--- 1257,1272 ----
      return;
    }
  
!   if (!Channel_NoTitles(chan) && u &&CUtitle(u) && *CUtitle(u))
       title = CUtitle(u);
    else
      title = NULL;
+   if (Channel_NoNames(chan))
+     name = NULL;
+   else
+     name = accented_name(player);
+   if (!title && !name)
+     name = someone;
  
    /* figure out what kind of message we have */
    gap = " ";
***************
*** 1247,1268 ****
      arg1 = arg1 + 1;
      channel_broadcast(chan, player, 0, "<%s> %s%s%s%s%s%s", ChanName(chan),
  		      title ? title : "", title ? ANSI_NORMAL : "",
! 		      title ? " " : "", accented_name(player), gap, arg1);
      if (!canhear)
!       notify_format(player, T("To channel %s: %s%s%s"), ChanName(chan),
! 		    accented_name(player), gap, arg1);
      break;
    default:
      if (CHAT_STRIP_QUOTE && (*arg1 == SAY_TOKEN))
        arg1 = arg1 + 1;
      channel_broadcast(chan, player, 0, T("<%s> %s%s%s%s says, \"%s\""),
  		      ChanName(chan), title ? title : "",
! 		      title ? ANSI_NORMAL : "", title ? " " : "",
! 		      accented_name(player), arg1);
      if (!canhear)
        notify_format(player,
! 		    T("To channel %s: %s says, \"%s\""), ChanName(chan),
! 		    accented_name(player), arg1);
      break;
    }
  
--- 1278,1302 ----
      arg1 = arg1 + 1;
      channel_broadcast(chan, player, 0, "<%s> %s%s%s%s%s%s", ChanName(chan),
  		      title ? title : "", title ? ANSI_NORMAL : "",
! 		      (title && name) ? " " : "", name ? name : "", gap, arg1);
      if (!canhear)
!       notify_format(player, T("To channel %s: %s%s%s%s%s%s"), ChanName(chan),
! 		    title ? title : "", title ? ANSI_NORMAL : "",
! 		    (title && name) ? " " : "", name ? name : "", gap, arg1);
      break;
    default:
      if (CHAT_STRIP_QUOTE && (*arg1 == SAY_TOKEN))
        arg1 = arg1 + 1;
      channel_broadcast(chan, player, 0, T("<%s> %s%s%s%s says, \"%s\""),
  		      ChanName(chan), title ? title : "",
! 		      title ? ANSI_NORMAL : "", (title && name) ? " " : "",
! 		      name ? name : "", arg1);
      if (!canhear)
        notify_format(player,
! 		    T("To channel %s: %s%s%s%s says, \"%s\""),
! 		    ChanName(chan), title ? title : "",
! 		    title ? ANSI_NORMAL : "", (title && name) ? " " : "",
! 		    name ? name : "", arg1);
      break;
    }
  
***************
*** 1303,1315 ****
    }
    if (!Chan_Ok_Type(chan, player)) {
      notify_format(player,
! 		  T("Sorry, you're not the right type to be on channel <%s>."),
  		  ChanName(chan));
      return;
    }
!   if (!Chan_Can_Speak(chan, player)) {
      notify_format(player,
! 		  T("Sorry, you're not allowed to speak on channel <%s>."),
  		  ChanName(chan));
      return;
    }
--- 1337,1350 ----
    }
    if (!Chan_Ok_Type(chan, player)) {
      notify_format(player,
! 		  T
! 		  ("Sorry, you're not the right type to be on channel <%s>."),
  		  ChanName(chan));
      return;
    }
!   if (!Chan_Can_Cemit(chan, player)) {
      notify_format(player,
! 		  T("Sorry, you're not allowed to @cemit on channel <%s>."),
  		  ChanName(chan));
      return;
    }
***************
*** 1343,1354 ****
  /** Administrative channel commands.
   * \verbatim
   * This is one of top-level functions for @channel. This one handles
!  * the /add, /delete, /rename, /priv, and /quiet switches.
   * \endverbatim
   * \param player the enactor.
   * \param name the name of the channel.
   * \param perms the permissions to set on an added/priv'd channel, the newname for a renamed channel, or on/off for a quieted channel.
!  * \param flag switch indicator: 0=add, 1=delete, 2=rename, 3=priv, 4=quiet
   */
  void
  do_chan_admin(dbref player, char *name, const char *perms, int flag)
--- 1378,1389 ----
  /** Administrative channel commands.
   * \verbatim
   * This is one of top-level functions for @channel. This one handles
!  * the /add, /delete, /rename, and /priv switches.
   * \endverbatim
   * \param player the enactor.
   * \param name the name of the channel.
   * \param perms the permissions to set on an added/priv'd channel, the newname for a renamed channel, or on/off for a quieted channel.
!  * \param flag switch indicator: 0=add, 1=delete, 2=rename, 3=priv
   */
  void
  do_chan_admin(dbref player, char *name, const char *perms, int flag)
***************
*** 1509,1527 ****
  		    T("Permissions on channel <%s> changed."), ChanName(chan));
      }
      break;
-   case 4:
-     /* Quiet a channel */
-     if (!Chan_Can_Modify(chan, player)) {
-       notify(player, T("Permission denied. Use @channel/mute <chan>=<y/n>"));
-       return;
-     }
-     if (abs(yesno(perms)))
-       ChanType(chan) |= CHANNEL_QUIET;
-     else
-       ChanType(chan) &= ~CHANNEL_QUIET;
-     notify_format(player,
- 		  T("Quiet status on channel <%s> changed."), ChanName(chan));
-     break;
    }
  }
  
--- 1544,1549 ----
***************
*** 1653,1659 ****
  	  CUtype(u) |= CU_HIDE;
  	  if (!silent)
  	    notify_format(player,
! 			  T("You no longer appear on channel <%s>'s who list."),
  			  ChanName(c));
  	}
        } else {
--- 1675,1682 ----
  	  CUtype(u) |= CU_HIDE;
  	  if (!silent)
  	    notify_format(player,
! 			  T
! 			  ("You no longer appear on channel <%s>'s who list."),
  			  ChanName(c));
  	}
        } else {
***************
*** 1670,1676 ****
  	CUtype(u) |= CU_GAG;
  	if (!silent)
  	  notify_format(player,
! 			T("You will no longer hear messages on channel <%s>."),
  			ChanName(c));
        } else {
  	CUtype(u) &= ~CU_GAG;
--- 1693,1700 ----
  	CUtype(u) |= CU_GAG;
  	if (!silent)
  	  notify_format(player,
! 			T
! 			("You will no longer hear messages on channel <%s>."),
  			ChanName(c));
        } else {
  	CUtype(u) &= ~CU_GAG;
***************
*** 2447,2460 ****
  {
    CHAN *c;
    CHANUSER *u;
  
    for (c = channels; c; c = c->next) {
      u = onchannel(player, c);
      if (u) {
        if (!Channel_Quiet(c) && (Channel_Admin(c) || Channel_Wizard(c)
! 				|| (!Chanuser_Hide(u) && !Dark(player))))
! 	 channel_broadcast(c, player, CB_CHECKQUIET | CB_PRESENCE, "<%s> %s",
! 			   ChanName(c), msg);
        CUtype(u) &= ~CU_GAG;
      }
    }
--- 2471,2490 ----
  {
    CHAN *c;
    CHANUSER *u;
+   char buff[BUFFER_LEN], *bp;
  
    for (c = channels; c; c = c->next) {
      u = onchannel(player, c);
      if (u) {
        if (!Channel_Quiet(c) && (Channel_Admin(c) || Channel_Wizard(c)
! 				|| (!Chanuser_Hide(u) && !Dark(player)))) {
! 	bp = buff;
! 
! 	safe_format(buff, &bp, "<%s> %s", "%s", msg);
! 	*bp = '\0';
! 	format_channel_broadcast(c, u, player, CB_CHECKQUIET | CB_PRESENCE,
! 				 buff, NULL);
!       }
        CUtype(u) &= ~CU_GAG;
      }
    }
***************
*** 2639,2648 ****
      do_chan_admin(player, arg_left, arg_right, 2);
    else if (SW_ISSET(sw, SWITCH_PRIVS))
      do_chan_admin(player, arg_left, arg_right, 3);
    else if (SW_ISSET(sw, SWITCH_QUIET))
!     do_chan_admin(player, arg_left, arg_right, 4);
    else if (SW_ISSET(sw, SWITCH_NOISY))
!     do_chan_admin(player, arg_left, "n", 4);
    else if (SW_ISSET(sw, SWITCH_DECOMPILE))
      do_chan_decompile(player, arg_left, SW_ISSET(sw, SWITCH_BRIEF));
    else if (SW_ISSET(sw, SWITCH_DESCRIBE))
--- 2669,2680 ----
      do_chan_admin(player, arg_left, arg_right, 2);
    else if (SW_ISSET(sw, SWITCH_PRIVS))
      do_chan_admin(player, arg_left, arg_right, 3);
+   else if (SW_ISSET(sw, SWITCH_RECALL))
+     do_chan_recall(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_QUIET));
    else if (SW_ISSET(sw, SWITCH_QUIET))
!     do_chan_admin(player, arg_left, "quiet", 3);
    else if (SW_ISSET(sw, SWITCH_NOISY))
!     do_chan_admin(player, arg_left, "!quiet", 3);
    else if (SW_ISSET(sw, SWITCH_DECOMPILE))
      do_chan_decompile(player, arg_left, SW_ISSET(sw, SWITCH_BRIEF));
    else if (SW_ISSET(sw, SWITCH_DESCRIBE))
***************
*** 2667,2674 ****
      do_chan_user_flags(player, arg_left, "n", 2, 0);
    else if (SW_ISSET(sw, SWITCH_WHAT))
      do_chan_what(player, arg_left);
-   else if (SW_ISSET(sw, SWITCH_RECALL))
-     do_chan_recall(player, arg_left, arg_right);
    else if (SW_ISSET(sw, SWITCH_BUFFER))
      do_chan_buffer(player, arg_left, arg_right);
    else
--- 2699,2704 ----
***************
*** 2896,2904 ****
   * \param player the enactor.
   * \param name the name of the channel.
   * \param lines a string given the number of lines to recall (default all).
   */
  void
! do_chan_recall(dbref player, const char *name, const char *lines)
  {
    CHAN *chan;
    CHANUSER *u;
--- 2926,2935 ----
   * \param player the enactor.
   * \param name the name of the channel.
   * \param lines a string given the number of lines to recall (default all).
+  * \param quiet if true, don't show timestamps.
   */
  void
! do_chan_recall(dbref player, const char *name, const char *lines, int quiet)
  {
    CHAN *chan;
    CHANUSER *u;
***************
*** 2967,2982 ****
      memcpy(&size, p, sizeof size);
      p += sizeof size;
      memcpy(&timestamp, p, sizeof timestamp);
-     stamp = show_time(timestamp, 0);
      p += sizeof timestamp;
      memcpy(tbuf1, p, size + 1);
!     notify_format(player, "[%s] %s", stamp, tbuf1);
      p += size + 1;
    }
    notify(player, T("CHAT: End recall"));
    if (!lines || !*lines)
      notify_format(player,
! 		  T("CHAT: To recall the entire buffer, use @chan/recall %s=0"),
  		  ChanName(chan));
  }
  
--- 2998,3018 ----
      memcpy(&size, p, sizeof size);
      p += sizeof size;
      memcpy(&timestamp, p, sizeof timestamp);
      p += sizeof timestamp;
      memcpy(tbuf1, p, size + 1);
!     if (quiet) {
!       notify(player, tbuf1);
!     } else {
!       stamp = show_time(timestamp, 0);
!       notify_format(player, "[%s] %s", stamp, tbuf1);
!     }
      p += size + 1;
    }
    notify(player, T("CHAT: End recall"));
    if (!lines || !*lines)
      notify_format(player,
! 		  T
! 		  ("CHAT: To recall the entire buffer, use @chan/recall %s=0"),
  		  ChanName(chan));
  }
  
***************
*** 3041,3046 ****
--- 3077,3100 ----
    }
  }
  
+ static void
+ format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim, int flags,
+ 			 const char *msg, const char *extra)
+ {
+   const char *title = NULL;
+   if (extra && *extra)
+     title = extra;
+   else if (u &&CUtitle(u))
+      title = CUtitle(u);
+ 
+   if (Channel_NoNames(chan)) {
+     if (Channel_NoTitles(chan) || !title)
+       channel_broadcast(chan, victim, flags, msg, ChanName(chan), "Someone");
+     else
+       channel_broadcast(chan, victim, flags, msg, ChanName(chan), title);
+   } else
+     channel_broadcast(chan, victim, flags, msg, ChanName(chan), Name(victim));
+ }
  
  
  #endif				/* CHAT_SYSTEM */
*** 1_7_7.624/src/destroy.c Tue, 13 May 2003 12:53:40 -0500 dunemush (pennmush/c/24_destroy.c 1.24.2.2.1.3.1.1.1.1.1.1.1.4.1.1.1.2 660)
--- 1_7_7.664(w)/src/destroy.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/24_destroy.c 1.24.2.2.1.3.1.1.1.1.1.1.1.4.1.1.1.1.1.3 660)
***************
*** 726,731 ****
--- 726,732 ----
    Type(thing) = TYPE_GARBAGE;
    destroy_flag_bitmask(Flags(thing));
    Flags(thing) = NULL;
+   Powers(thing) = 0;
    Location(thing) = NOTHING;
    set_name(thing, "Garbage");
    Exits(thing) = NOTHING;
***************
*** 1112,1118 ****
         * an invalid dbref, change its ownership to God.
         */
        if (!IsGarbage(thing))
! 	atr_iter_get(GOD, thing, NULL, attribute_owner_helper, NULL);
      }
    }
  }
--- 1113,1119 ----
         * an invalid dbref, change its ownership to God.
         */
        if (!IsGarbage(thing))
! 	atr_iter_get(GOD, thing, "**", 0, attribute_owner_helper, NULL);
      }
    }
  }
*** 1_7_7.624/src/cque.c Mon, 28 Apr 2003 22:37:00 -0500 dunemush (pennmush/c/28_cque.c 1.36.1.5.1.1.1.1.1.1.1.2.1.6.1.9 660)
--- 1_7_7.664(w)/src/cque.c Mon, 29 Sep 2003 16:42:31 -0500 dunemush (pennmush/c/28_cque.c 1.36.1.5.1.1.1.1.1.1.1.2.1.6.1.10 660)
***************
*** 667,673 ****
      if (aname)
        (void) atr_clr(thing, aname, GOD);
      else
!       atr_iter_get(GOD, thing, "*", drain_helper, NULL);
    }
  
    /* If @notify and count was higher than the number of queue entries,
--- 667,673 ----
      if (aname)
        (void) atr_clr(thing, aname, GOD);
      else
!       atr_iter_get(GOD, thing, "**", 0, drain_helper, NULL);
    }
  
    /* If @notify and count was higher than the number of queue entries,
*** 1_7_7.624/src/command.c Mon, 01 Sep 2003 23:41:11 -0500 dunemush (pennmush/c/36_command.c 1.56.1.1.1.1.1.1.1.2.1.1.1.1.1.5.1.2.1.1.1.1.1.2.1.3.1.10.1.1.3.20 660)
--- 1_7_7.664(w)/src/command.c Mon, 29 Sep 2003 16:42:30 -0500 dunemush (pennmush/c/36_command.c 1.56.1.1.1.1.1.1.1.2.1.1.1.1.1.5.1.2.1.1.1.1.1.2.1.3.1.10.1.1.3.22 660)
***************
*** 79,85 ****
    {"@CEMIT", "NOEVAL NOISY", cmd_cemit,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
    {"@CHANNEL",
!    "LIST ADD DELETE RENAME NAME PRIVS QUIET NOISY DECOMPILE DESCRIBE CHOWN WIPE MUTE UNMUTE GAG UNGAG HIDE UNHIDE WHAT TITLE BRIEF RECALL BUFFER",
     cmd_channel,
     CMD_T_ANY | CMD_T_SWITCHES | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
    {"@CHAT", NULL, cmd_chat, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
--- 79,85 ----
    {"@CEMIT", "NOEVAL NOISY", cmd_cemit,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
    {"@CHANNEL",
!    "LIST ADD DELETE RENAME NAME PRIVS QUIET NOISY DECOMPILE DESCRIBE CHOWN WIPE MUTE UNMUTE GAG UNGAG HIDE UNHIDE WHAT TITLE BRIEF RECALL BUFFER SET",
     cmd_channel,
     CMD_T_ANY | CMD_T_SWITCHES | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
    {"@CHAT", NULL, cmd_chat, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0},
***************
*** 139,145 ****
    {"@FIND", NULL, cmd_find,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
    {"@FIRSTEXIT", NULL, cmd_firstexit, CMD_T_ANY, 0, 0},
!   {"@FLAG", "ADD LIST RESTRICT DELETE ALIAS DISABLE ENABLE", cmd_flag,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  
    {"@FORCE", "NOEVAL", cmd_force, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
--- 139,145 ----
    {"@FIND", NULL, cmd_find,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
    {"@FIRSTEXIT", NULL, cmd_firstexit, CMD_T_ANY, 0, 0},
!   {"@FLAG", "ADD LETTER LIST RESTRICT DELETE ALIAS DISABLE ENABLE", cmd_flag,
     CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0},
  
    {"@FORCE", "NOEVAL", cmd_force, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
*** 1_7_7.624/src/cmds.c Mon, 01 Sep 2003 22:38:06 -0500 dunemush (pennmush/c/37_cmds.c 1.33.1.1.1.2.1.2.2.3.1.1.1.2.1.1.1.3.1.8.1.1.2.2.2.20.1.3 660)
--- 1_7_7.664(w)/src/cmds.c Mon, 29 Sep 2003 16:42:30 -0500 dunemush (pennmush/c/37_cmds.c 1.33.1.1.1.2.1.2.2.3.1.1.1.2.1.1.1.3.1.8.1.1.2.2.2.20.1.3.1.2 660)
***************
*** 249,263 ****
  
  COMMAND (cmd_entrances) {
    if (SW_ISSET(sw, SWITCH_EXITS))
!     do_entrances(player, arg_left, args_right, 1);
    else if (SW_ISSET(sw, SWITCH_THINGS))
!     do_entrances(player, arg_left, args_right, 2);
    else if (SW_ISSET(sw, SWITCH_PLAYERS))
!     do_entrances(player, arg_left, args_right, 3);
    else if (SW_ISSET(sw, SWITCH_ROOMS))
!     do_entrances(player, arg_left, args_right, 4);
    else
!     do_entrances(player, arg_left, args_right, 0);
  }
  
  COMMAND (cmd_eunlock) {
--- 249,263 ----
  
  COMMAND (cmd_entrances) {
    if (SW_ISSET(sw, SWITCH_EXITS))
!     do_entrances(player, arg_left, args_right, ENT_EXITS);
    else if (SW_ISSET(sw, SWITCH_THINGS))
!     do_entrances(player, arg_left, args_right, ENT_THINGS);
    else if (SW_ISSET(sw, SWITCH_PLAYERS))
!     do_entrances(player, arg_left, args_right, ENT_PLAYERS);
    else if (SW_ISSET(sw, SWITCH_ROOMS))
!     do_entrances(player, arg_left, args_right, ENT_ROOMS);
    else
!     do_entrances(player, arg_left, args_right, ENT_ALL);
  }
  
  COMMAND (cmd_eunlock) {
***************
*** 287,292 ****
--- 287,294 ----
      do_flag_disable(player, arg_left);
    else if (SW_ISSET(sw, SWITCH_ENABLE))
      do_flag_enable(player, arg_left);
+   else if (SW_ISSET(sw, SWITCH_LETTER))
+     do_flag_letter(player, arg_left, args_right[1]);
    else
      do_flag_info("FLAG", player, arg_left);
  }
*** 1_7_7.624/src/bsd.c Thu, 04 Sep 2003 17:27:46 -0500 dunemush (pennmush/c/38_bsd.c 1.58.1.11.1.2.1.5.1.7.1.14.1.13.1.9.1.4.1.2.1.12.1.1.1.1.1.2.1.1.1.13.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.8.2.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.24.1.8.1.2 660)
--- 1_7_7.664(w)/src/bsd.c Mon, 29 Sep 2003 16:42:30 -0500 dunemush (pennmush/c/38_bsd.c 1.58.1.11.1.2.1.5.1.7.1.14.1.13.1.9.1.4.1.2.1.12.1.1.1.1.1.2.1.1.1.13.1.1.1.1.1.1.1.1.1.1.1.3.1.1.1.1.1.1.1.1.1.1.1.1.1.1.1.3.1.8.2.1.1.1.1.1.1.1.1.2.1.1.1.1.1.1.1.1.1.1.1.1.1.2 660)
***************
*** 1014,1025 ****
  #endif
      if (!restarting) {
        sock = make_socket(port, NULL, NULL, MUSH_IP_ADDR);
!       maxd = sock + 1;
  #ifdef HAS_OPENSSL
        if (sslport) {
  	sslsock = make_socket(sslport, NULL, NULL, MUSH_IP_ADDR);
  	ssl_master_socket = ssl_setup_socket(sslsock);
! 	maxd = sslsock + 1;
        }
  #endif
      }
--- 1014,1027 ----
  #endif
      if (!restarting) {
        sock = make_socket(port, NULL, NULL, MUSH_IP_ADDR);
!       if (sock >= maxd)
! 	maxd = sock + 1;
  #ifdef HAS_OPENSSL
        if (sslport) {
  	sslsock = make_socket(sslport, NULL, NULL, MUSH_IP_ADDR);
  	ssl_master_socket = ssl_setup_socket(sslsock);
! 	if (sslsock >= maxd)
! 	  maxd = sslsock + 1;
        }
  #endif
      }
***************
*** 4697,4703 ****
    }
  
    sock = getref(f);
!   maxd = getref(f);
    while ((val = getref(f)) != 0) {
      ndescriptors++;
      d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
--- 4699,4708 ----
    }
  
    sock = getref(f);
!   val = getref(f);
!   if (val > maxd)
!     maxd = val;
! 
    while ((val = getref(f)) != 0) {
      ndescriptors++;
      d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
*** 1_7_7.624/src/attrib.c Wed, 03 Sep 2003 22:39:22 -0500 dunemush (pennmush/c/40_attrib.c 1.15.1.2.1.5.1.1.1.3.1.3.1.2.1.2.1.2.2.1.1.2.1.2.1.2.1.1.1.3.1.1.1.1.1.1.3.29 660)
--- 1_7_7.664(w)/src/attrib.c Mon, 29 Sep 2003 16:42:30 -0500 dunemush (pennmush/c/40_attrib.c 1.15.1.2.1.5.1.1.1.3.1.3.1.2.1.2.1.2.2.1.1.2.1.2.1.2.1.1.1.3.1.1.1.1.1.1.3.36 660)
***************
*** 46,51 ****
--- 46,64 ----
  /** Table of attribute flags. */
  extern PRIV attr_privs[];
  
+ /** A flag to show if we're in the middle of a @wipe (this changes
+  * behaviour for atr_clr()).  Yes, this is gross and ugly, but it
+  * seemed like a better idea than propogating a signature changes
+  * for atr_clr() and do_set_atr() through the entire codebase.  If
+  * you come up with a better way, PLEASE fix this...
+  */
+ int we_are_wiping;
+ 
+ /** A string to hold the name of a missing prefix branch, set by
+  * can_write_attr_internal.  Again, gross and ugly.  Please fix.
+  */
+ static char missing_name[ATTRIBUTE_NAME_LIMIT + 1];
+ 
  /*======================================================================*/
  
  /** How many attributes go in a "page" of attribute memory? */
***************
*** 79,85 ****
  /** Decide if a name is valid for an attribute.
   * A good attribute name is at least one character long, no more than
   * ATTRIBUTE_NAME_LIMIT characters long, and every character is a 
!  * valid character.
   * \param s a string to test for validity as an attribute name.
   */
  int
--- 92,99 ----
  /** Decide if a name is valid for an attribute.
   * A good attribute name is at least one character long, no more than
   * ATTRIBUTE_NAME_LIMIT characters long, and every character is a 
!  * valid character. An attribute name may not start or end with a backtick.
!  * An attribute name may not contain multiple consecutive backticks.
   * \param s a string to test for validity as an attribute name.
   */
  int
***************
*** 89,97 ****
--- 103,117 ----
    int len = 0;
    if (!s || !*s)
      return 0;
+   if (*s == '`')
+     return 0;
+   if (strstr(s, "``"))
+     return 0;
    for (a = (const unsigned char *) s; *a; a++, len++)
      if (!atr_name_table[*a])
        return 0;
+   if (*(s + len - 1) == '`')
+     return 0;
    return len <= ATTRIBUTE_NAME_LIMIT;
  }
  
***************
*** 105,110 ****
--- 125,155 ----
    return aname_hash_lookup(string);
  }
  
+ /** Find the first attribute branching off the specified attribute.
+  * \param branch the attribute to look under
+  */
+ ATTR *
+ atr_sub_branch(ATTR *branch)
+ {
+   char const *name, *n2;
+   size_t len;
+ 
+   name = AL_NAME(branch);
+   len = strlen(name);
+   for (branch = AL_NEXT(branch); branch; branch = AL_NEXT(branch)) {
+     n2 = AL_NAME(branch);
+     if (strlen(n2) <= len)
+       return NULL;
+     if (n2[len] == '`') {
+       if (!strncmp(n2, name, len))
+ 	return branch;
+       else
+ 	return NULL;
+     }
+   }
+   return NULL;
+ }
+ 
  /** Convert a string of attribute flags to a bitmask.
   * Given a space-separated string of attribute flags, look them up
   * and return a bitmask of them if player is permitted to use
***************
*** 127,132 ****
--- 172,188 ----
    return f;
  }
  
+ /** Convert an attribute flag bitmask into a list of the full
+  * names of the flags.
+  * \param flags the attribute flags to display.
+  * \return a pointer to a static buffer with the full names of the flags.
+  */
+ const char *
+ atrflag_to_string(int mask)
+ {
+   return privs_to_string(attr_privs, mask);
+ }
+ 
  /** Add an attribute to an object, dangerously.
   * This is a stripped down version of atr_add, without duplicate checking,
   * permissions checking, attribute count checking, or auto-ODARKing.  
***************
*** 333,338 ****
--- 389,395 ----
  
  /** Remove an attribute from an object.
   * This function clears an attribute from an object. 
+  * Permission is denied if the attribute is a branch, not a leaf.
   * \param thing object to clear attribute from.
   * \param atr name of attribute to remove.
   * \param player enactor attempting to remove attribute.
***************
*** 340,347 ****
  int
  atr_clr(dbref thing, char const *atr, dbref player)
  {
!   ATTR *ptr, **prev;
    int comp = 0;
  
    prev = &List(thing);
    ptr = *prev;
--- 397,405 ----
  int
  atr_clr(dbref thing, char const *atr, dbref player)
  {
!   ATTR *ptr, **prev, *sub;
    int comp = 0;
+   size_t len;
  
    prev = &List(thing);
    ptr = *prev;
***************
*** 358,363 ****
--- 416,426 ----
    if (!Can_Write_Attr(player, thing, ptr))
      return -1;
  
+   len = strlen(AL_NAME(ptr));
+   sub = atr_sub_branch(ptr);
+   if (!we_are_wiping && sub)
+     return -1;
+ 
    if (!IsPlayer(thing) && !(AL_FLAGS(ptr) & AF_NODUMP))
      ModTime(thing) = mudtime;
  
***************
*** 371,376 ****
--- 434,459 ----
    AL_FLAGS(ptr) = 0;
    atr_free_list = ptr;
    AttrCount(thing)--;
+ 
+   if (we_are_wiping && sub) {
+     while (*prev != sub)
+       prev = &AL_NEXT(*prev);
+     ptr = *prev;
+     while (ptr && strlen(AL_NAME(ptr)) > len && AL_NAME(ptr)[len] == '`') {
+       *prev = AL_NEXT(ptr);
+ 
+       if (ptr->data)
+ 	chunk_delete(ptr->data);
+       st_delete(AL_NAME(ptr), &atr_names);
+ 
+       AL_NEXT(ptr) = atr_free_list;
+       AL_FLAGS(ptr) = 0;
+       atr_free_list = ptr;
+       AttrCount(thing)--;
+ 
+       ptr = *prev;
+     }
+   }
    return 1;
  }
  
***************
*** 380,479 ****
   * matches or NULL. This is a pointer to an attribute structure, not
   * to the value of the attribute, so the value is usually accessed
   * through atr_value() or safe_atr_value().
!  * \param thing the object containing the attribute.
!  * \param atr the name of the attribute.
   * \return pointer to the attribute structure retrieved, or NULL.
   */
  ATTR *
! atr_get(dbref thing, char const *atr)
  {
!   ATTR *ptr;
    int parent_depth;
!   dbref temp;
    dbref ancestor;
    int comp;
  
!   if (thing == NOTHING || !good_atr_name(atr))
      return NULL;
  
!   ancestor = Ancestor_Parent(thing);
!   for (parent_depth = 0, temp = thing;
!        parent_depth < MAX_PARENTS && temp != NOTHING;
!        parent_depth++, temp = Parent(temp)) {
!     /* If the ancestor of the object is in its explict parent chain,
!      * we use it there, and don't check the ancestor later
!      */
!     if (temp == ancestor)
!       ancestor = NOTHING;
!     for (ptr = List(temp); ptr; ptr = AL_NEXT(ptr)) {
!       comp = strcoll(atr, AL_NAME(ptr));
!       if (comp < 0)
! 	break;
!       if (comp == 0) {
! 	if (temp == thing || !(AL_FLAGS(ptr) & AF_PRIVATE))
! 	  return ptr;
! 	else
! 	  break;
!       }
!     }
!   }
!   /* If we have a valid ancestor, search it as well */
!   if (GoodObject(ancestor)) {
!     for (parent_depth = 0, temp = ancestor;
! 	 parent_depth < MAX_PARENTS && temp != NOTHING;
! 	 parent_depth++, temp = Parent(temp)) {
!       for (ptr = List(temp); ptr; ptr = AL_NEXT(ptr)) {
! 	comp = strcoll(atr, AL_NAME(ptr));
! 	if (comp < 0)
! 	  break;
! 	if (comp == 0) {
! 	  if (temp == thing || !(AL_FLAGS(ptr) & AF_PRIVATE))
! 	    return ptr;
! 	  else
! 	    break;
  	}
        }
-     }
-   }
  
!   /* Perhaps we should try the alias */
!   ptr = atr_match(atr);
!   if (!ptr || !strcmp(atr, AL_NAME(ptr)))
!     return NULL;
!   atr = AL_NAME(ptr);
! 
!   for (parent_depth = 0, temp = thing;
!        parent_depth < MAX_PARENTS && temp != NOTHING;
!        parent_depth++, temp = Parent(temp)) {
!     for (ptr = List(temp); ptr; ptr = AL_NEXT(ptr)) {
!       comp = strcoll(atr, AL_NAME(ptr));
!       if (comp < 0)
! 	break;
!       if (comp == 0) {
! 	if (temp == thing || !(AL_FLAGS(ptr) & AF_PRIVATE))
! 	  return ptr;
! 	else
! 	  break;
!       }
!     }
!   }
!   /* If we have a valid ancestor, search it as well */
!   if (GoodObject(ancestor)) {
!     for (parent_depth = 0, temp = ancestor;
! 	 parent_depth < MAX_PARENTS && temp != NOTHING;
! 	 parent_depth++, temp = Parent(temp)) {
!       for (ptr = List(temp); ptr; ptr = AL_NEXT(ptr)) {
! 	comp = strcoll(atr, AL_NAME(ptr));
  	if (comp < 0)
  	  break;
  	if (comp == 0) {
! 	  if (temp == thing || !(AL_FLAGS(ptr) & AF_PRIVATE))
! 	    return ptr;
  	  else
  	    break;
  	}
        }
      }
    }
  
    return NULL;
--- 463,553 ----
   * matches or NULL. This is a pointer to an attribute structure, not
   * to the value of the attribute, so the value is usually accessed
   * through atr_value() or safe_atr_value().
!  * \param obj the object containing the attribute.
!  * \param atrname the name of the attribute.
   * \return pointer to the attribute structure retrieved, or NULL.
   */
  ATTR *
! atr_get(dbref obj, char const *atrname)
  {
!   static char name[ATTRIBUTE_NAME_LIMIT + 1];
!   char *p;
!   ATTR *atr;
    int parent_depth;
!   dbref target;
    dbref ancestor;
    int comp;
  
!   if (obj == NOTHING || !good_atr_name(atrname))
      return NULL;
  
!   /* First try given name, then try alias match. */
!   strcpy(name, atrname);
!   for (;;) {
!     /* Hunt through the parents/ancestor chain... */
!     ancestor = Ancestor_Parent(obj);
!     target = obj;
!     parent_depth = 0;
!     while (parent_depth < MAX_PARENTS && GoodObject(target)) {
!       /* If the ancestor of the object is in its explict parent chain,
!        * we use it there, and don't check the ancestor later.
!        */
!       if (target == ancestor)
! 	ancestor = NOTHING;
!       atr = List(target);
! 
!       /* If we're looking at a parent/ancestor, then we
!        * need to check the branch path for privacy... */
!       if (target != obj) {
! 	for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
! 	  *p = '\0';
! 	  while (atr) {
! 	    comp = strcoll(name, AL_NAME(atr));
! 	    if (comp < 0) {
! 	      *p = '`';
! 	      goto continue_target;
! 	    }
! 	    if (comp == 0)
! 	      break;
! 	    atr = AL_NEXT(atr);
! 	  }
! 	  if (!atr || (AL_FLAGS(atr) & AF_PRIVATE)) {
! 	    *p = '`';
! 	    goto continue_target;
! 	  }
! 	  *p = '`';
  	}
        }
  
!       /* Now actually find the attribute. */
!       while (atr) {
! 	comp = strcoll(name, AL_NAME(atr));
  	if (comp < 0)
  	  break;
  	if (comp == 0) {
! 	  if (target == obj || !(AL_FLAGS(atr) & AF_PRIVATE))
! 	    return atr;
  	  else
  	    break;
  	}
+ 	atr = AL_NEXT(atr);
+       }
+ 
+     continue_target:
+       /* Attribute wasn't on this object.  Check a parent or ancestor. */
+       parent_depth++;
+       target = Parent(target);
+       if (!GoodObject(target)) {
+ 	parent_depth = 0;
+ 	target = ancestor;
        }
      }
+ 
+     /* Try the alias, too... */
+     atr = atr_match(atrname);
+     if (!atr || !strcmp(name, AL_NAME(atr)))
+       break;
+     strcpy(name, AL_NAME(atr));
    }
  
    return NULL;
***************
*** 533,616 ****
   * \param player the enactor.
   * \param thing the object containing the attribute.
   * \param name the pattern to match against the attribute name.
   * \param func the function to call for each matching attribute.
   * \param args additional arguments to pass to the function.
   * \return the sum of the return values of the functions called.
   */
  int
! atr_iter_get(dbref player, dbref thing, const char *name, aig_func func,
! 	     void *args)
! {
!   ATTR *ptr, *next;
!   int result;
! 
!   result = 0;
!   if (!name || !*name)
!     name = "*";
! 
!   if (!wildcard(name)) {
!     ptr = atr_get_noparent(thing, strupper(name));
!     if (ptr && Can_Read_Attr(player, thing, ptr))
!       result = func(player, thing, name, ptr, args);
!   } else {
!     for (ptr = List(thing); ptr; ptr = next) {
!       next = AL_NEXT(ptr);
!       if (Can_Read_Attr(player, thing, ptr) &&
! 	  local_wild_match(name, AL_NAME(ptr)))
! 	result += func(player, thing, name, ptr, args);
!     }
!   }
! 
!   return result;
! }
! 
! /** Apply a function to a set of visual attributes.
!  * This function applies another function to a set of attributes on an
!  * object specified by a (wildcarded) pattern to match against the
!  * attribute name.
!  * \param player the enactor.
!  * \param thing the object containing the attribute.
!  * \param name the pattern to match against the attribute name.
!  * \param func the function to call for each matching attribute.
!  * \param args additional arguments to pass to the function.
!  * \return the sum of the return values of the functions called.
!  */
! int
! atr_iter_get_visible(dbref player, dbref thing, const char *name, aig_func func,
! 		     void *args)
  {
!   ATTR *ptr, *next;
    int result;
!   int object_visual;
  
    result = 0;
    if (!name || !*name)
      name = "*";
  
!   object_visual = (Visual(thing) &&
! 		   (getlock(thing, Examine_Lock) == TRUE_BOOLEXP ||
! 		    (eval_lock(PLAYER_START, thing, Examine_Lock) &&
! 		     eval_lock(MASTER_ROOM, thing, Examine_Lock))));
! 
!   if (!wildcard(name)) {
      ptr = atr_get_noparent(thing, strupper(name));
!     if (ptr && (object_visual ? Can_Read_Attr(player, thing, ptr)
! 		: Is_Visible_Attr(thing, ptr)))
        result = func(player, thing, name, ptr, args);
    } else {
!     for (ptr = List(thing); ptr; ptr = next) {
!       next = AL_NEXT(ptr);
!       if ((object_visual ? Can_Read_Attr(player, thing, ptr)
! 	   : Is_Visible_Attr(thing, ptr))
! 	  && local_wild_match(name, AL_NAME(ptr)))
  	result += func(player, thing, name, ptr, args);
      }
    }
  
    return result;
  }
  
- 
  /** Free the memory associated with all attributes of an object.
   * This function frees all of an object's attribute memory.
   * This includes the memory allocated to hold the attribute's value,
--- 607,650 ----
   * \param player the enactor.
   * \param thing the object containing the attribute.
   * \param name the pattern to match against the attribute name.
+  * \param mortal only fetch mortal-visible attributes?
   * \param func the function to call for each matching attribute.
   * \param args additional arguments to pass to the function.
   * \return the sum of the return values of the functions called.
   */
  int
! atr_iter_get(dbref player, dbref thing, const char *name, int mortal,
! 	     aig_func func, void *args)
  {
!   ATTR *ptr, **indirect;
    int result;
!   int len;
  
    result = 0;
    if (!name || !*name)
      name = "*";
+   len = strlen(name);
  
!   if (!wildcard(name) && name[len - 1] != '`') {
      ptr = atr_get_noparent(thing, strupper(name));
!     if (ptr && (mortal ? Is_Visible_Attr(thing, ptr)
! 		: Can_Read_Attr(player, thing, ptr)))
        result = func(player, thing, name, ptr, args);
    } else {
!     indirect = &List(thing);
!     while (*indirect) {
!       ptr = *indirect;
!       if ((mortal ? Is_Visible_Attr(thing, ptr)
! 	   : Can_Read_Attr(player, thing, ptr)) && atr_wild(name, AL_NAME(ptr)))
  	result += func(player, thing, name, ptr, args);
+       if (ptr == *indirect)
+ 	indirect = &AL_NEXT(ptr);
      }
    }
  
    return result;
  }
  
  /** Free the memory associated with all attributes of an object.
   * This function frees all of an object's attribute memory.
   * This includes the memory allocated to hold the attribute's value,
***************
*** 664,669 ****
--- 698,771 ----
      }
  }
  
+ /** Structure for keeping track of which attributes have appeared
+  * on children when doing command matching. */
+ typedef struct used_attr {
+   struct used_attr *next;	/**< Next attribute in list */
+   char const *name;		/**< The name of the attribute */
+   int no_prog;			/**< Was it AF_NOPROG */
+ } UsedAttr;
+ 
+ /** Find an attribute in the list of seen attributes.
+  * Since attributes are checked in collation order, the pointer to the
+  * list is updated to reflect the current search position.
+  * For efficiency of insertions, the pointer used is a trailing pointer,
+  * pointing at the pointer to the next used struct.
+  * To allow a useful return code, the pointer used is actually a pointer
+  * to the pointer mentioned above.  Yes, I know three-star coding is bad,
+  * but I have good reason, here.
+  * \param prev the pointer to the pointer to the pointer to the next
+  *             used attribute.
+  * \param name the name of the attribute to look for.
+  * \retval 0 the attribute was not in the list,
+  *           **prev now points to the next atfer.
+  * \retval 1 the attribute was in the list,
+  *           **prev now points to the entry for it.
+  */
+ static int
+ find_attr(UsedAttr *** prev, char const *name)
+ {
+   int comp;
+ 
+   comp = 1;
+   while (**prev) {
+     comp = strcoll(name, prev[0][0]->name);
+     if (comp <= 0)
+       break;
+     *prev = &prev[0][0]->next;
+   }
+   return comp == 0;
+ }
+ 
+ /** Insert an attribute in the list of seen attributes.
+  * Since attributes are inserted in collation order, an updated insertion
+  * point is returned (so subsequent calls don't have to go hunting as far).
+  * \param prev the pointer to the pointer to the attribute list.
+  * \param name the name of the attribute to insert.
+  * \param no_prog the AF_NOPROG value from the attribute.
+  * \return the pointer to the pointer to the next attribute after
+  *         the one inserted.
+  */
+ static UsedAttr **
+ use_attr(UsedAttr ** prev, char const *name, int no_prog)
+ {
+   int found;
+   UsedAttr *used;
+ 
+   found = find_attr(&prev, name);
+   if (!found) {
+     used = mush_malloc(sizeof *used, "used_attr");
+     used->next = *prev;
+     used->name = name;
+     used->no_prog = 0;
+     *prev = used;
+   }
+   prev[0]->no_prog |= no_prog;
+   /* do_rawlog(LT_TRACE, "Recorded %s: %d -> %d", name,
+      no_prog, prev[0]->no_prog); */
+   return &prev[0]->next;
+ }
+ 
  /** Match input against a $command or ^listen attribute.
   * This function attempts to match a string against either the $commands
   * or ^listens on an object. Matches may be glob or regex matches, 
***************
*** 694,699 ****
--- 796,804 ----
    char *s;
    int match, match_found;
    dbref parent;
+   UsedAttr *used_list, **prev;
+   ATTR *skip[ATTRIBUTE_NAME_LIMIT / 2];
+   int skipcount;
  
    /* check for lots of easy ways out */
    if ((type != '$' && type != '^') || !GoodObject(thing) || Halted(thing)
***************
*** 713,724 ****
    }
  
    match = 0;
!   st_init(&temp_attrib);
  
    for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
      if (parent_depth)
!       st_insert(AL_NAME(ptr), &temp_attrib);
!     if (AL_FLAGS(ptr) & AF_NOPROG || !(AL_FLAGS(ptr) & flag_mask))
        continue;
      strcpy(tbuf1, atr_value(ptr));
      s = tbuf1;
--- 818,848 ----
    }
  
    match = 0;
!   used_list = NULL;
!   prev = &used_list;
  
+   skipcount = 0;
+   /* do_rawlog(LT_TRACE, "Searching %s:", Name(thing)); */
    for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
+     if (skipcount && ptr == skip[skipcount - 1]) {
+       size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
+       while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
+ 	     AL_NAME(AL_NEXT(ptr))[len] == '`') {
+ 	ptr = AL_NEXT(ptr);
+ 	/* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
+       }
+       skipcount--;
+       continue;
+     }
      if (parent_depth)
!       prev = use_attr(prev, AL_NAME(ptr), AL_FLAGS(ptr) & AF_NOPROG);
!     if (AL_FLAGS(ptr) & AF_NOPROG) {
!       skip[skipcount] = atr_sub_branch(ptr);
!       if (skip[skipcount])
! 	skipcount++;
!       continue;
!     }
!     if (!(AL_FLAGS(ptr) & flag_mask))
        continue;
      strcpy(tbuf1, atr_value(ptr));
      s = tbuf1;
***************
*** 777,789 ****
  
    for (parent_depth = MAX_PARENTS, parent = Parent(thing);
         parent_depth-- && parent != NOTHING; parent = Parent(parent)) {
      for (ptr = List(parent); ptr; ptr = AL_NEXT(ptr)) {
!       if (st_find(AL_NAME(ptr), &temp_attrib))
  	continue;
!       if (Parent(parent) != NOTHING)
! 	st_insert(AL_NAME(ptr), &temp_attrib);
!       if (AL_FLAGS(ptr) & (AF_NOPROG | AF_PRIVATE)
! 	  || !(AL_FLAGS(ptr) & flag_mask))
  	continue;
        strcpy(tbuf1, atr_value(ptr));
        s = tbuf1;
--- 901,947 ----
  
    for (parent_depth = MAX_PARENTS, parent = Parent(thing);
         parent_depth-- && parent != NOTHING; parent = Parent(parent)) {
+     /* do_rawlog(LT_TRACE, "Searching %s:", Name(parent)); */
+     skipcount = 0;
+     prev = &used_list;
      for (ptr = List(parent); ptr; ptr = AL_NEXT(ptr)) {
!       if (skipcount && ptr == skip[skipcount - 1]) {
! 	size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
! 	while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
! 	       AL_NAME(AL_NEXT(ptr))[len] == '`') {
! 	  ptr = AL_NEXT(ptr);
! 	  /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
! 	}
! 	skipcount--;
! 	continue;
!       }
!       if (AL_FLAGS(ptr) & AF_PRIVATE) {
! 	/* do_rawlog(LT_TRACE, "Private %s:", AL_NAME(ptr)); */
! 	skip[skipcount] = atr_sub_branch(ptr);
! 	if (skip[skipcount])
! 	  skipcount++;
  	continue;
!       }
!       if (find_attr(&prev, AL_NAME(ptr))) {
! 	/* do_rawlog(LT_TRACE, "Found %s:", AL_NAME(ptr)); */
! 	if (prev[0]->no_prog || (AL_FLAGS(ptr) & AF_NOPROG)) {
! 	  skip[skipcount] = atr_sub_branch(ptr);
! 	  if (skip[skipcount])
! 	    skipcount++;
! 	  prev[0]->no_prog = AF_NOPROG;
! 	}
! 	continue;
!       }
!       if (GoodObject(Parent(parent)))
! 	prev = use_attr(prev, AL_NAME(ptr), AL_FLAGS(ptr) & AF_NOPROG);
!       if (AL_FLAGS(ptr) & AF_NOPROG) {
! 	/* do_rawlog(LT_TRACE, "NoProg %s:", AL_NAME(ptr)); */
! 	skip[skipcount] = atr_sub_branch(ptr);
! 	if (skip[skipcount])
! 	  skipcount++;
! 	continue;
!       }
!       if (!(AL_FLAGS(ptr) & flag_mask))
  	continue;
        strcpy(tbuf1, atr_value(ptr));
        s = tbuf1;
***************
*** 832,843 ****
  	  safe_chr('/', atrname, abp);
  	  safe_str(AL_NAME(ptr), atrname, abp);
  	}
! 	if (!just_match)
  	  parse_que(thing, s, player);
        }
      }
    }
!   st_flush(&temp_attrib);
    return match;
  }
  
--- 990,1007 ----
  	  safe_chr('/', atrname, abp);
  	  safe_str(AL_NAME(ptr), atrname, abp);
  	}
! 	if (!just_match) {
! 	  /* do_rawlog(LT_TRACE, "MATCHED %s:", AL_NAME(ptr)); */
  	  parse_que(thing, s, player);
+ 	}
        }
      }
    }
!   while (used_list) {
!     UsedAttr *temp = used_list->next;
!     mush_free(used_list, "used_attr");
!     used_list = temp;
!   }
    return match;
  }
  
***************
*** 1005,1011 ****
      notify(player, T("That's not a very good name for an attribute."));
      return 0;
    } else if (res == AE_ERROR) {
!     notify(player, T("That attribute cannot be changed by you."));
      return 0;
    } else if (!res) {
      notify(player, T("No such attribute to reset."));
--- 1169,1178 ----
      notify(player, T("That's not a very good name for an attribute."));
      return 0;
    } else if (res == AE_ERROR) {
!     if (*missing_name)
!       notify_format(player, T("You must set %s first."), missing_name);
!     else
!       notify(player, T("That attribute cannot be changed by you."));
      return 0;
    } else if (!res) {
      notify(player, T("No such attribute to reset."));
***************
*** 1208,1213 ****
--- 1375,1545 ----
    return atr_free_list;
  }
  
+ /** Traversal routine for Can_Read_Attr.
+  * This function determines if an attribute can be read by examining
+  * the tree path to the attribute.  This is not the full Can_Read_Attr
+  * check; only the stuff after See_All (just to avoid function calls
+  * when the answer is trivialized by special powers).  If the specified
+  * player is NOTHING, then we're doing a generic mortal visibility check.
+  * \param player the player trying to do the read.
+  * \param obj the object targetted for the read (may be a child of a parent!).
+  * \param attr the attribute being interrogated.
+  * \retval 0 if the player cannot read the attribute.
+  * \retval 1 if the player can read the attribute.
+  */
+ int
+ can_read_attr_internal(dbref player, dbref obj, ATTR *atr)
+ {
+   static char name[ATTRIBUTE_NAME_LIMIT + 1];
+   char *p;
+   int control;
+   int exam;
+   int comp;
+   dbref target;
+   dbref ancestor;
+   int visible;
+   int parent_depth;
+ 
+   visible = (player == NOTHING);
+   if (visible) {
+     control = 0;
+     exam = (Visual(obj) &&
+ 	    eval_lock(PLAYER_START, obj, Examine_Lock) &&
+ 	    eval_lock(MASTER_ROOM, obj, Examine_Lock));
+   } else {
+     control = controls(player, obj);
+     exam = (Visual(obj) && eval_lock(player, obj, Examine_Lock));
+   }
+ 
+   /* Take an easy out if there is one... */
+   /* If we can't see the attribute itself, then that's easy. */
+   if ((AL_FLAGS(atr) & AF_MDARK) ||
+       !((AL_FLAGS(atr) & AF_VISUAL) || control || exam ||
+ 	(!visible && !Mistrust(player) &&
+ 	 (Owner(AL_CREATOR(atr)) == Owner(player)))))
+     return 0;
+ 
+   /* If the attribute isn't on a branch, then that's also easy. */
+   if (!strchr(AL_NAME(atr), '`'))
+     return 1;
+ 
+   /* Nope, we actually have to go looking for the attribute in a tree. */
+   strcpy(name, AL_NAME(atr));
+   ancestor = Ancestor_Parent(obj);
+   target = obj;
+   parent_depth = 0;
+   while (parent_depth < MAX_PARENTS && GoodObject(target)) {
+     /* If the ancestor of the object is in its explict parent chain,
+      * we use it there, and don't check the ancestor later.
+      */
+     if (target == ancestor)
+       ancestor = NOTHING;
+     atr = List(target);
+ 
+     /* Check along the branch for permissions... */
+     for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
+       *p = '\0';
+       while (atr) {
+ 	comp = strcoll(name, AL_NAME(atr));
+ 	if (comp < 0)
+ 	  goto continue_target;
+ 	if (comp == 0)
+ 	  break;
+ 	atr = AL_NEXT(atr);
+       }
+       if (!atr || (target != obj && (AL_FLAGS(atr) & AF_PRIVATE)))
+ 	goto continue_target;
+       if ((AL_FLAGS(atr) & AF_INTERNAL) || (AL_FLAGS(atr) & AF_MDARK) ||
+ 	  !((AL_FLAGS(atr) & AF_VISUAL) || control || exam ||
+ 	    (!visible && !Mistrust(player) &&
+ 	     (Owner(AL_CREATOR(atr)) == Owner(player)))))
+ 	return 0;
+       *p = '`';
+     }
+ 
+     /* Now actually find the attribute. */
+     while (atr) {
+       comp = strcoll(name, AL_NAME(atr));
+       if (comp < 0)
+ 	break;
+       if (comp == 0)
+ 	return 1;
+       atr = AL_NEXT(atr);
+     }
+ 
+   continue_target:
+     *p = '`';
+ 
+     /* Attribute wasn't on this object.  Check a parent or ancestor. */
+     parent_depth++;
+     target = Parent(target);
+     if (!GoodObject(target)) {
+       parent_depth = 0;
+       target = ancestor;
+     }
+   }
+ 
+   return 0;
+ }
+ 
+ /** Traversal routine for Can_Write_Attr.
+  * This function determines if an attribute can be written by examining
+  * the tree path to the attribute.  As a side effect, missing_name is
+  * set to the name of a missing prefix branch, if any.  Yes, side effects
+  * are evil.  Please fix if you can.
+  * \param player the player trying to do the write.
+  * \param obj the object targetted for the write.
+  * \param atr the attribute being interrogated.
+  * \param safe whether to check the safe attribute flag.
+  * \retval 0 if the player cannot write the attribute.
+  * \retval 1 if the player can write the attribute.
+  */
+ int
+ can_write_attr_internal(dbref player, dbref obj, ATTR *atr, int safe)
+ {
+   char *p;
+   int comp;
+ 
+   missing_name[0] = '\0';
+ 
+   if (!God(player) &&
+       ((AL_FLAGS(atr) & AF_INTERNAL) ||
+        (safe && (AL_FLAGS(atr) & AF_SAFE)) ||
+        !(Wizard(player) ||
+ 	 (!(AL_FLAGS(atr) & AF_WIZARD) &&
+ 	  (!(AL_FLAGS(atr) & AF_LOCKED) ||
+ 	   (AL_CREATOR(atr) == Owner(player)))))))
+     return 0;
+ 
+   strcpy(missing_name, AL_NAME(atr));
+   atr = List(obj);
+   for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
+     *p = '\0';
+     while (atr) {
+       comp = strcoll(missing_name, AL_NAME(atr));
+       if (comp < 0)
+ 	return 0;
+       if (comp == 0)
+ 	break;
+       atr = AL_NEXT(atr);
+     }
+     if (!atr ||
+ 	(!God(player) &&
+ 	 ((AL_FLAGS(atr) & AF_INTERNAL) ||
+ 	  (safe && (AL_FLAGS(atr) & AF_SAFE)) ||
+ 	  !(Wizard(player) ||
+ 	    (!(AL_FLAGS(atr) & AF_WIZARD) &&
+ 	     (!(AL_FLAGS(atr) & AF_LOCKED) ||
+ 	      (AL_CREATOR(atr) == Owner(player)))))))) {
+       missing_name[0] = '\0';
+       return 0;
+     }
+     *p = '`';
+   }
+ 
+   return 1;
+ }
+ 
  /** Return the compressed data for an attribute.
   * This is a chokepoint function for accessing the chunk data.
   * \param atr the attribute struct from which to get the data reference.
*** 1_7_7.624/hdrs/version.h Thu, 04 Sep 2003 17:36:31 -0500 dunemush (pennmush/c/47_version.h 1.32.1.2.1.7.1.9.1.1.1.17.1.24 660)
--- 1_7_7.664(w)/hdrs/version.h Mon, 29 Sep 2003 16:42:35 -0500 dunemush (pennmush/c/47_version.h 1.32.1.2.1.7.1.9.1.1.1.17.1.25 660)
***************
*** 1,3 ****
! #define VERSION "PennMUSH version 1.7.7 patchlevel 20 [09/04/2003]"
! #define SHORTVN "PennMUSH 1.7.7p20"
! #define NUMVERSION 001007007020
--- 1,3 ----
! #define VERSION "PennMUSH version 1.7.7 patchlevel 21 [09/23/2003]"
! #define SHORTVN "PennMUSH 1.7.7p21"
! #define NUMVERSION 001007007021
*** 1_7_7.624/hdrs/mushdb.h Sat, 30 Aug 2003 14:51:01 -0500 dunemush (pennmush/d/2_mushdb.h 1.1.1.9.1.1.1.16 660)
--- 1_7_7.664(w)/hdrs/mushdb.h Mon, 29 Sep 2003 16:42:35 -0500 dunemush (pennmush/d/2_mushdb.h 1.1.1.9.1.1.1.17 660)
***************
*** 85,101 ****
  /* can p access attribute a on object x? */
  #define Can_Read_Attr(p,x,a)   \
     (!((a)->flags & AF_INTERNAL) && \
!     (See_All(p) || \
!      (!((a)->flags & AF_MDARK) && \
!       (controls(p,x) || ((a)->flags & AF_VISUAL) || \
!         (Visual(x) && eval_lock(p,x,Examine_Lock)) || \
!        (!Mistrust(p) && (Owner((a)->creator) == Owner(p)))))))
  
  /* can anyone access attribute a on object x? */
  #define Is_Visible_Attr(x,a)   \
     (!((a)->flags & AF_INTERNAL) && \
!      !((a)->flags & AF_MDARK) && \
!       ((a)->flags & AF_VISUAL))
  
  /* can p write attribute a on object x, assuming p may modify x? 
   * Must be (1) God, or (2) a non-internal, non-safe flag and
--- 85,96 ----
  /* can p access attribute a on object x? */
  #define Can_Read_Attr(p,x,a)   \
     (!((a)->flags & AF_INTERNAL) && \
!     (See_All(p) || can_read_attr_internal((p),(x),(a))))
  
  /* can anyone access attribute a on object x? */
  #define Is_Visible_Attr(x,a)   \
     (!((a)->flags & AF_INTERNAL) && \
!      can_read_attr_internal(NOTHING,(x),(a)))
  
  /* can p write attribute a on object x, assuming p may modify x? 
   * Must be (1) God, or (2) a non-internal, non-safe flag and
***************
*** 103,122 ****
   * the attrib or (2b2) it's not atrlocked.
   */
  #define Can_Write_Attr(p,x,a)  \
!    (God(p) || \
!     (!((a)->flags & AF_INTERNAL) && \
!      !((a)->flags & AF_SAFE) && \
!      (Wizard(p) || \
!       (!((a)->flags & AF_WIZARD) && \
!        (((a)->creator == Owner(p)) || !((a)->flags & AF_LOCKED)) \
!    ))))
  #define Can_Write_Attr_Ignore_Safe(p,x,a)  \
!    (God(p) || \
!     (!((a)->flags & AF_INTERNAL) && \
!      (Wizard(p) || \
!       (!((a)->flags & AF_WIZARD) && \
!        (((a)->creator == Owner(p)) || !((a)->flags & AF_LOCKED)) \
!    ))))
  
  
  /* Can p forward a message to x (via @forwardlist)? */
--- 98,106 ----
   * the attrib o