.-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-.  
 |                                                                       |  
 |                                                                       |  
 |                                                                       |  
 |                           MPREDIT COMMAND                             |  
 |                                                                       |  
 !                                                                       !  
 :                         A Snippet written by                          :  
 :                              Valcados                                 :  
 .                           for SMAUG MUDs                              .  
 .                                                                       .  
 :                                                                       :  
 :                                                                       :  
 !                                                                       !  
 |                                                                       |  
 |                                                                       |  
 |                                                                       |  
 |                                                                       |  
 |                                                                       |  
 `-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-'

NOTE: This snippet was written for SMAUGFUSS 1.7.  It should be fairly
      straightforward to install it into other versions of SMAUG.

DESCRIPTION:
   Creates a new mobprog command, MPREDIT.  This allows mobs to edit
   rooms, similar to MPMSET and MPOSET.  It is set up so that if a
   builder saves an area, changes done by MPREDIT will not be saved.

TESTING:
   This has been fairly rigorously tested, but has changed slightly in the
   conversion to stock SMAUG.

KNOWN PROBLEMS/BUGS:
   Stock SMAUG's room affect code is buggy.  One of the things mpredit can
   do is manipulate room affects.  I've posted snippets at mudbytes.net and
   fussproject.org which fix the room affect bugs.  If you don't want to
   install them, then it's probably a good idea not to use mpredit to manipulate
   room affects (nor manipulate them with regular redit either).
   If you don't have any idea what room affects are, and don't plan on making
   mobs mess with them, then this is a non-issue.

IN-GAME MECHANICS:
   The syntax of the mpcommand are as follows.

   mpredit affect (affect):  add a room affect
   mpredit rmaffect #:  remove a room affect
   mpredit bexit (dir) [destination]:  create (or remove) an exit and its reverse
   mpredit description (text):  set the room description
   mpredit description +(text):  add to the room description
   mpredit description clear:  clear the room description
   mpredit ed (keyword) (text):  add or modify an extradescription
   mpredit ed (keyword) +(text):  add text to an extradescription
   mpredit rmed (keyword):  remove an extradescription
   mpredit exkey (dir) (key):  set an exit's key
   mpredit exname (dir) (name):  set an exit's name
   mpredit exdesc (dir) (text):  set an exit's description
   mpredit exflags (dir) (flags):  toggle exit flags
   mpredit exit (dir) [destination]:  create (or remove) an exit.
   mpredit flags (roomflags):  toggle room flags
   mpredit name (name):  change room's name
   mpredit pull (dir) #:  set the strength of an exit's pull
   mpredit push (dir) #:  set the strength of an exit's push
   mpredit revert:  return room to its original state before any mpredits took place
   mpredit sector #:  change the room's sector type (Water traps!!!:)
   mpredit tunnel #:  change the room's tunnel size
   mpredit teledelay #:  change the room's teleport delay
   mpredit televnum #:  change the room's teleport destination

   The snippet also installs an imm command "unmpredit" which is the same as
   "mpredit revert".  Also it adds support for the "rstat" command to tell an
   imm when a room is under the effects of mpredit.  An imm can type "rstat mpredit"
   to see a list of exactly what those changes are.

INSTALLATION INSTRUCTIONS:


1. In act_move.c, function generate_exit, make these changes:

   if( !found )
   {
      CREATE( room, ROOM_INDEX_DATA, 1 );
      room->area = in_room->area;
      room->vnum = serial;
      room->tele_vnum = roomnum;
      room->sector_type = in_room->sector_type;
      room->room_flags = in_room->room_flags;
+     room->redecorations = 0;
+     room->first_redecoration = NULL;
+     room->last_redecoration = NULL;


2. In act_wiz.c, function do_rstat, make these changes:

-  if( !str_cmp( arg, "ex" ) || !str_cmp( arg, "exits" ) )
+  if( !str_cmp( arg, "ex" ) || !str_cmp( arg, "exits" ) || !str_cmp( arg, "ex_mpredit" ) )
   {
      location = ch->in_room;

+     if ( str_cmp( arg, "ex_mpredit" ) )
      ch_printf_color( ch, "&cExits for room '&W%s&c'  Vnum &W%d\r\n", location->name, location->vnum );
      for( cnt = 0, pexit = location->first_exit; pexit; pexit = pexit->next )
         ch_printf_color( ch,
                          "&W%2d) &w%2s to %-5d  &cKey: &w%d  &cFlags: &w%d  &cKeywords: '&w%s&c'\r\n     Exdesc: &w%s    &cBack link: &w%d  &cVnum: &w%d  &cDistance: &w%d  &cPulltype: &w%s  &cPull: &w%d\r\n",
                          ++cnt,
                          dir_text[pexit->vdir],
                          pexit->to_room ? pexit->to_room->vnum : 0,
                          pexit->key,
                          pexit->exit_info,
                          pexit->keyword,
                          pexit->description[0] != '\0'
                          ? pexit->description : "(none).\r\n",
                          pexit->rexit ? pexit->rexit->vnum : 0,
                          pexit->rvnum, pexit->distance, pull_type_name( pexit->pulltype ), pexit->pull );
      return;
   }
   location = ( arg[0] == '\0' ) ? ch->in_room : find_location( ch, arg );
+  if ( is_name( arg, "mpredit mpredits mp_redit mp_redits redecoration redecorations" ) )
+  {
+    display_mpredits( ch, ch->in_room );
+    return;
+  }
   if( !location )
   {
      send_to_char( "No such location.\r\n", ch );
      return;
   }


3. Further down in do_rstat, make the following changes:

   for( obj = location->first_content; obj; obj = obj->next_content )
   {
      send_to_char( " ", ch );
      one_argument( obj->name, buf );
      send_to_char( buf, ch );
   }
   send_to_char( "\r\n", ch );

+  if ( location->first_redecoration )
+  {
+    mpredit_decor *decor;
+    int count = 1;
+
+    for ( decor = location->first_redecoration->next; decor; decor = decor->next )
+      count++;
+
+    send_to_char_color( "&c----------------- &wMPREDITS &c------------------\n\r", ch );
+    ch_printf( ch, " This room is currently affected by %d mpredit change%s.\n\r", count, count == 1 ? "" : "s" );
+    ch_printf( ch, " To view %s, type \"rstat mpredit\".\n\r", count == 1 ? "it" : "them" );
+   }

   if( location->first_exit )
      send_to_char_color( "&c------------------- &wEXITS &c-------------------\r\n", ch );


4. In build.c, function do_redit, make these changes:

   location = ch->in_room;

+  if ( location->first_redecoration )
+  {
+    set_char_color( AT_PLAIN, ch );
+    send_to_char( "This room has been modified by a mudprogram.\n\r", ch );
+    send_to_char( "You can't alter a room which has been modified by a mudprogram.\n\r", ch );
+    send_to_char( "To see a list of mudprogram changes, type \"rstat mpredit\".\n\r", ch );
+    send_to_char( "To change the room back to its original form, type \"unmpredit\".\n\r", ch );
+    return;
+  }

   smash_tilde( argument );
   argument = one_argument( argument, arg );
   if( ch->substate == SUB_REPEATCMD )


5. In build.c, above function SetRExtra, make following changes:

    ch_printf( ch, "Assigning you: %s\r\n", tarea->name );
    return;
 }

+EXTRA_DESCR_DATA *GetRExtra( ROOM_INDEX_DATA *room, char *keywords )
+{
+  EXTRA_DESCR_DATA *ed;
+
+  for ( ed = room->first_extradesc; ed; ed = ed->next )
+  {
+    if ( is_name( keywords, ed->keyword ) )
+      return ed;
+  }
+  return SetRExtra( room, keywords );
+}

 EXTRA_DESCR_DATA *SetRExtra( ROOM_INDEX_DATA * room, char *keywords )


6. In build.c, change function do_savearea as follows:

void do_savearea( CHAR_DATA * ch, char *argument )
{
   AREA_DATA *tarea;
   char filename[256];
+  bool mpredit_changes;

   set_char_color( AT_IMMORT, ch );

   if( IS_NPC( ch ) || get_trust( ch ) < LEVEL_CREATOR || !ch->pcdata || ( argument[0] == '\0' && !ch->pcdata->area ) )
   {
      send_to_char( "You don't have an assigned area to save.\r\n", ch );
      return;
   }

   if( argument[0] == '\0' )
      tarea = ch->pcdata->area;
   else
   {
      bool found;

      if( get_trust( ch ) < LEVEL_GOD )
      {
         send_to_char( "You can only save your own area.\r\n", ch );
         return;
      }
      for( found = FALSE, tarea = first_build; tarea; tarea = tarea->next )
         if( !str_cmp( tarea->filename, argument ) )
         {
            found = TRUE;
            break;
         }
      if( !found )
      {
         send_to_char( "Area not found.\r\n", ch );
         return;
      }
   }

   if( !tarea )
   {
      send_to_char( "No area to save.\r\n", ch );
      return;
   }

   /*
    * Ensure not wiping out their area with save before load - Scryn 8/11
    */
   if( !IS_SET( tarea->status, AREA_LOADED ) )
   {
      send_to_char( "Your area is not loaded!\r\n", ch );
      return;
   }

   if( !IS_SET( tarea->flags, AFLAG_PROTOTYPE ) )
   {
      ch_printf( ch, "Cannot savearea %s, use foldarea instead.\r\n", tarea->filename );
      return;
   }
   snprintf( filename, 256, "%s%s", BUILD_DIR, tarea->filename );
+  if ( (mpredit_changes = mpredit_modify_area( tarea, TRUE )) == TRUE )
+   send_to_char( "Reverting changes made by mpredit...\n\r", ch );
   send_to_char( "Saving area...\r\n", ch );
   fold_area( tarea, filename, FALSE );
+  if ( mpredit_changes )
+  {
+    set_char_color( AT_IMMORT, ch );
+    send_to_char( "Restoring changes made by mpredit...\n\r", ch );
+    mpredit_modify_area( tarea, FALSE );
+  }
   set_char_color( AT_IMMORT, ch );
   send_to_char( "Done.\r\n", ch );
}


7. In build.c, change function do_foldarea as follows:

void do_foldarea( CHAR_DATA * ch, char *argument )
{
   AREA_DATA *tarea;
+  bool mpredit_changes;

   set_char_color( AT_IMMORT, ch );

   if( !argument || argument[0] == '\0' )
   {
      send_to_char( "Fold what?\r\n", ch );
      return;
   }

   if( !str_cmp( argument, "all" ) )
   {
      for( tarea = first_area; tarea; tarea = tarea->next )
         fold_area( tarea, tarea->filename, FALSE );
      send_to_char( "All installed areas have been folded.\r\n", ch );
      return;
   }

   for( tarea = first_area; tarea; tarea = tarea->next )
   {
      if( !str_cmp( tarea->filename, argument ) )
      {
+        if ( (mpredit_changes = mpredit_modify_area( tarea, TRUE )) == TRUE )
+          send_to_char( "Reverting changes made by mpredit...\n\r", ch );
         send_to_char( "Folding area...\r\n", ch );
         fold_area( tarea, tarea->filename, FALSE );
+        if ( mpredit_changes )
+        {
+          set_char_color( AT_IMMORT, ch );
+          send_to_char( "Restoring changes made by mpredit...\n\r", ch );
+          mpredit_modify_area( tarea, FALSE );
+          return;
+        }
         set_char_color( AT_IMMORT, ch );
         send_to_char( "Done.\r\n", ch );
         return;
      }
   }
   send_to_char( "No such area exists.\r\n", ch );
   return;
}


8. In db.c, function load_rooms, do this:

      fBootDb = tmpBootDb;
      pRoomIndex->area = tarea;
      pRoomIndex->vnum = vnum;
      pRoomIndex->first_extradesc = NULL;
      pRoomIndex->last_extradesc = NULL;
+     pRoomIndex->redecorations = 0;
+     pRoomIndex->first_redecoration = NULL;
+     pRoomIndex->last_redecoration = NULL;

      if( fBootDb )
      {
         if( !tarea->low_r_vnum )


9. In db.c, function make_room, do this:

   pRoomIndex->light = 0;
   pRoomIndex->first_exit = NULL;
   pRoomIndex->last_exit = NULL;
+  pRoomIndex->redecorations = 0;
+  pRoomIndex->first_redecoration = NULL;
+  pRoomIndex->last_redecoration = NULL;
   LINK( pRoomIndex, area->first_room, area->last_room, next_aroom, prev_aroom );

   iHash = vnum % MAX_KEY_HASH;


10. Replace mpxset.c with the new mpxset.c included with this snippet.


11. In mud.h, do this:

 typedef struct lcnv_data LCNV_DATA;
 typedef struct lang_data LANG_DATA;
 typedef struct specfun_list SPEC_LIST;
+typedef struct MPREDIT_DECOR mpredit_decor;

 /*
  * Function types.
  */


12. In mud.h, do this:

 struct plane_data
 {
    PLANE_DATA *next;
    PLANE_DATA *prev;
    char *name;
 };

+struct MPREDIT_DECOR
+{
+  mpredit_decor *next;
+  mpredit_decor *prev;
+  void *v1;
+  void *v2;
+  sh_int type;
+};

 /*
  * Room type.
  */


13. In mud.h, do this:

 typedef enum
 {
    SECT_INSIDE, SECT_CITY, SECT_FIELD, SECT_FOREST, SECT_HILLS, SECT_MOUNTAIN,
    SECT_WATER_SWIM, SECT_WATER_NOSWIM, SECT_UNDERWATER, SECT_AIR, SECT_DESERT,
    SECT_DUNNO, SECT_OCEANFLOOR, SECT_UNDERGROUND, SECT_LAVA, SECT_SWAMP,
    SECT_MAX
 } sector_types;

+typedef enum
+{
+  REDECOR_DESC, REDECOR_FLAGS,
+  REDECOR_NAME, REDECOR_SECTOR, REDECOR_TUNNEL, REDECOR_TELEDELAY,
+  REDECOR_TELEVNUM, REDECOR_AFFECTS, REDECOR_EXTRAS, REDECOR_EXITS
+} redecoration_types;

 /*
  * Equpiment wear locations.
  * Used in #RESETS.
  */


14. In mud.h, in structure room_index_data, do this:

   EXT_BV progtypes; /* mudprogs */
   short light;   /* amount of light in the room */
   short sector_type;
+  int redecorations;
+  mpredit_decor *first_redecoration;
+  mpredit_decor *last_redecoration;
   int tele_vnum;
   short tele_delay;
   short tunnel;  /* max people that will fit */
};

/*
 * Delayed teleport type.
 */


15. In mud.h, do this:

 DECLARE_DO_FUN( do_mpmusicaround );
 DECLARE_DO_FUN( do_mpmusicat );
+DECLARE_DO_FUN( do_mpredit );
+DECLARE_DO_FUN( do_unmpredit );

 /*
  * Spell functions.
  * Defined in magic.c.
  */


16. In mud.h, do this:

 void assign_area args( ( CHAR_DATA * ch ) );
+EXTRA_DESCR_DATA *GetRExtra args(( ROOM_INDEX_DATA *room, char *keywords ));
 EDD *SetRExtra args( ( ROOM_INDEX_DATA * room, char *keywords ) );


17. In mud.h, do this:

 /* mud_comm.c */
 char *mprog_type_to_name args( ( int type ) );

+/* mpxset.c */
+void recalculate_teleports args(( ROOM_INDEX_DATA *room ));
+void add_redecoration args(( ROOM_INDEX_DATA *room, int decor_type, void *v1, void *v2 ));
+bool has_original_extras args(( ROOM_INDEX_DATA *room ));
+void memorize_original_extras args(( ROOM_INDEX_DATA *room ));
+void forget_original_extras args(( ROOM_INDEX_DATA *room ));
+bool has_original_affects args(( ROOM_INDEX_DATA *room ));
+void memorize_original_affects args(( ROOM_INDEX_DATA *room ));
+void forget_original_affects args(( ROOM_INDEX_DATA *room ));
+bool has_original_exits args(( ROOM_INDEX_DATA *room ));
+void memorize_original_exits args(( ROOM_INDEX_DATA *room ));
+void forget_original_exits args(( ROOM_INDEX_DATA *room ));
+void display_mpredits args(( CHAR_DATA *ch, ROOM_INDEX_DATA *room ));
+void delete_mpredits args(( ROOM_INDEX_DATA *room ));
+bool mpredit_modify args(( ROOM_INDEX_DATA *room, bool fRevert ));
+bool room_being_edited args(( ROOM_INDEX_DATA *room ));
+char *one_exit args(( CHAR_DATA *ch, char *argument, EXIT_DATA **p, char *buf ));
+bool mpredit_modify_area args(( AREA_DATA *area, bool fRevert ));

 /* mud_prog.c */


18. In mud.h, do this:

 void progbug args( ( char *str, CHAR_DATA * mob ) );
+void progbugf  args(( CHAR_DATA *mob, char *str, ... ));
 void rset_supermob args( ( ROOM_INDEX_DATA * room ) );


19. In mud_prog.c, near the top with the other #includes, add, #include <stdarg.h>


20. In mud_prog.c, do this:

 void rprog_hour_trigger( CHAR_DATA * ch )
 { 
    if( HAS_PROG( ch->in_room, HOUR_PROG ) )
    {
       rset_supermob( ch->in_room );
       rprog_time_check( supermob, NULL, NULL, ch->in_room, HOUR_PROG );
       release_supermob(  );
    }
 }

+void progbugf( CHAR_DATA *mob, char *str, ... )
+{
+  char buf[MAX_STRING_LENGTH];
+  va_list args;
+
+  va_start( args, str );
+  vsprintf( buf, str, args );
+  va_end( args );
+
+  progbug( buf, mob );
+  return;
+}

 /* Written by Jenny, Nov 29/95 */
 void progbug( char *str, CHAR_DATA * mob )


22. In mud.h, do this:

 /* double-linked list handling macros -Thoric */
 /* Updated by Scion 8/6/1999 */
 #define LINK(link, first, last, next, prev) \
 do                                          \
 {                                           \
    if ( !(first) )                          \
    {                                        \
       (first) = (link);                     \
       (last) = (link);                      \
    }                                        \
    else                                     \
       (last)->next = (link);                \
    (link)->next = NULL;                     \
      if ((first) == (link))                 \
       (link)->prev = NULL;                  \
    else                                     \
       (link)->prev = (last);                \
    (last) = (link);                         \
 } while(0)

+/*
+ * Special macro for lists whose head/tail are void *'s
+ */
+#define VOIDLINK(link, first, last, next, prev, type) \
+do                                                    \
+{                                                     \
+  if ( !(first) )                                     \
+  {                                                   \
+    (first) = (link);                                 \
+    (last) = (link);                                  \
+  }                                                   \
+  else                                                \
+    ((type)(last))->next = (link);                    \
+  (link)->next = NULL;                                \
+  if ((first) == (link))                              \
+      (link)->prev = NULL;                            \
+  else                                                \
+      (link)->prev = (last);                          \
+  (last) = (link);                                    \
+} while(0)

#define INSERT(link, insert, first, next, prev) \


21. In mpxset.c, do this:

 extern int top_affect;

+char * const decor_names[] = {
+    "description", "flags", "name", "sector",
+    "tunnel", "teledelay", "televnum" };

 void do_mpmset( CHAR_DATA * ch, char *argument )


22. What follows is a large block of code, to be added at the bottom of mpxset.c.
    This is the heart of mpredit.  After it's been added, if necessary, update
    tables.c to add the commands do_mpredit and do_unmpredit.  Compile.  Reboot 
    the MUD and create the new commands using cedit.  Make sure to make mpredit
    level 1 (so mobs can use it) and set its flags similar to other mpcommands
    (to prevent people from ordering charmed mobs to do it).  Enjoy!!
    If there are any any questions or problems compiling, the snippet is supported
    at mudbytes.net, go there and find the snippet in the code repository and
    post a comment.
    

========== CODE TO ADD AT THE BOTTOM OF MPXSET.C ==========

/*
 * The main mpredit code begins here
 */

int get_rflag args(( char *flag ));
int get_exflag args(( char *flag ));

void do_mpredit( CHAR_DATA *ch, char *argument )
{
  ROOM_INDEX_DATA *room = ch->in_room, *dest;
  char arg1[MAX_INPUT_LENGTH * 2];
  char arg2[MAX_INPUT_LENGTH];
  char arg3[MAX_INPUT_LENGTH];
  char *ptr;
  int *value;
  int loc, i, j;
  AFFECT_DATA *paf;
  EXIT_DATA *xit, *xit_next;

  if ( !IS_NPC(ch) )
  {
    send_to_char( "Huh?\n\r", ch );
    return;
  }

  if ( !*argument )
  {
    progbug( "do_mpredit: no argument", ch );
    return;
  }

  /*
   *  Avoid conflicts with immortals editing the room
   */
  if ( room_being_edited( room ) )
  {
    progbugf( ch, "Trying to mpredit room #%d, which is already being edited in someone's writing buffer", ch->in_room->vnum );
    return;
  }

  argument = one_argument( argument, arg1 );

  switch( LOWER( *arg1 ) )
  {
    case 'a':
      if ( !str_cmp( arg1, "affect" )
      ||   !str_cmp( arg1, "aff" ) )
      {
        CHAR_DATA *vch;
        int bitv = 0;

        if ( !*argument )
        {
          progbug( "do_mpredit affect:  no argument", ch );
          return;
        }
        argument = one_argument( argument, arg2 );
        loc = get_atype( arg2 );
        if ( loc < 1 )
        {
          progbugf( ch, "do_mpredit affect:  '%s' is not a known affect type", arg2 );
          return;
        }
        if ( loc >= APPLY_AFFECT && loc < APPLY_WEAPONSPELL )  /* This includes: "affected", "resistant", "immune", and "susceptible" */
        {
          i=0;

          if ( !*argument )
          {
            progbugf( ch, "do_mpredit affect %s:  no flags specified", arg2 );
            return;
          }

          /*
           * Determine the flags.
           * If there are any invalid flags, count how many (store the number as "i").
           */
          while ( *argument )
          {
            argument = one_argument( argument, arg3 );
            j = (loc == APPLY_AFFECT) ? get_aflag( arg3 ) : get_risflag( arg3 );
            if ( j < 0 || j > 31 )
              i++;
            else
              SET_BIT( bitv, 1 << j );
          }
            
          if ( i )
            progbugf( ch, "do_mpredit affect %s:  flag list contained %d bad flag(s)%s", arg2, i, bitv ? "" : " (and no good flags!)" );
          if ( bitv )
            i = bitv;
          else
            return;
        }
        else
        {
          one_argument( argument, arg3 );
          /*
           * For certain affect locations, the next argument can be either an SN or the name of a skill/spell.
           * In the latter case, we must look up the skill/spell...
           * In the "stripsn" case, search the entire skill list.  In all other cases, only search the spells.
           */
          if ( ( loc == APPLY_WEARSPELL || loc == APPLY_REMOVESPELL || loc == APPLY_WEAPONSPELL
          ||     loc == APPLY_STRIPSN || loc == APPLY_RECURRINGSPELL ) && !is_number( arg3 ) )
          {
            i = bsearch_skill_exact( arg3, loc == APPLY_STRIPSN ? 1 : gsn_first_spell, loc == APPLY_STRIPSN ? top_sn - 1 : gsn_first_skill - 1 );
            if ( i == -1 )
            {
              progbugf( ch, "do_mpredit affect %s: '%s' is a bad %s", arg2, arg3, loc == APPLY_STRIPSN ? "skill/spell/etc" : "spell" );
              return;
            }
          }
          else
            i = atoi( arg3 );
        }
        /*
         * Create the affect and add it to the room!
         * (But first, memorize the room's original affects)
         */
        CREATE( paf, AFFECT_DATA, 1 );
        paf->type = -1;
        paf->duration = -1;
        paf->location = loc;
        paf->modifier = i;
        xCLEAR_BITS( paf->bitvector );
        memorize_original_affects( room );
        LINK( paf, room->first_affect, room->last_affect, next, prev );
        ++top_affect;
        /*
         *  To prevent character corruption, now APPLY this affect to anyone in the room.
         *  (Here we're assuming Valcados's "Fix room affects" snippet has been installed)
         */
        if ( paf->location != APPLY_WEARSPELL && paf->location != APPLY_WEAPONSPELL && paf->location != APPLY_STRIPSN )
        {
          for ( vch = room->first_person; vch; vch = vch->next_in_room )
            affect_modify( vch, paf, TRUE );
        }
        /*
         * If this change causes the room to return to how it originally was, then act accordingly.
         */
        if ( has_original_affects( room ) )
          forget_original_affects( room );
        return;
      }        
    break;

    case 'b':
      if ( !str_cmp( arg1, "bexit" )
      ||   !str_cmp( arg1, "bexits" ) )
      {
        /*
         * Get the exit, simultaneously chopping it off of the input tape (and storing its name as "arg2").
         */
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          /*
           * If there's no exit there, then our job is to try and create one.
           * Special treatment for SMAUG's multi-exit support.
           */
          i = get_dir( (*arg2 == '+')? arg2+1 : arg2 );
          argument = one_argument( argument, arg3 );
          if ( !(dest=get_room_index(atoi(arg3))) )
          {
            progbugf( ch, "do_mpredit %s %s %s:  no such destination exists.", arg1, arg2, arg3 );
            return;
          }
          /*
           * "bexit" creates the reverse exit as well, and we don't want to conflict with immortal edits.
           */
          if ( room_being_edited( dest ) )
          {
             progbugf( ch, "do_mpredit %s %s %s:  destination room is currently being edited", arg1, arg2, arg3 );
             return;
          }
          /*
           * This is cheap and hacky, but the alternative gets really messy and repetitive really fast.
           */
          sprintf( arg1, "exit %s %d", arg2, dest->vnum );
          do_mpredit( ch, arg1 );
          if ( !get_exit(dest, rev_dir[i] ) )
          {
            sprintf( arg1, "%d mpredit exit %s %d", dest->vnum, dir_name[rev_dir[i]], room->vnum );
            do_at( ch, arg1 );
          }
          return;
        }
        /*
         * If there *IS* an exit already, then remove it (and attempt to remove the reverse exit too, if there is one)
         */
        sprintf( arg1, "exit %s remlink", arg2 );
        do_mpredit( ch, arg1 );
        return;
      }
      break;

    case 'd':
      if ( !str_cmp( arg1, "desc" )
      ||   !str_cmp( arg1, "description" ) )
      {
        if ( !*argument )
        {
          progbug( "do_mpredit desc:  no argument", ch );
          return;
        }
        if ( !str_cmp( argument, "none" )
        ||   !str_cmp( argument, "clear" )
        ||   !str_cmp( argument, "blank" ) )
        {
          add_redecoration( room, REDECOR_DESC, room->description, (ptr = str_alloc( "" )) );
          room->description = ptr;
          /*
           * This is NOT a memory leak.  By setting room->description = ptr, we compensate for
           * the fact that ptr is about to be lost.
           * This trick is repeated several times below, and we'll omit this comment in the future...
           */
          return;
        }
        /*
         * Since mobs don't have edit buffers, we'll give them the following tool instead.
         * By starting the argument with a +, the mob can append it to the existing room desc,
         * vs. overwriting the existing room desc.
         */
        if ( *argument == '+' )  /* start "argument == '+'" case */
        {
          int len;
          char *tmp;

          argument++;
          len = strlen( room->description ) + strlen( argument ) + 2;
          if ( len >= MAX_STRING_LENGTH - 1 )
          {
            progbugf( ch, "do_mpredit desc:  trying to make roomdesc too large, room %d", room->vnum );
            return;
          }
          CREATE( tmp, char, len + 1 );
          sprintf( tmp, "%s%s\n\r", room->description, argument );
          add_redecoration( room, REDECOR_DESC, room->description, (ptr = str_alloc(tmp)) );
          room->description = ptr;
          free( tmp );
          return;
        } /* end "argument == '+'" case*/
        if ( strlen( argument ) > MAX_INPUT_LENGTH - 3 )
          argument[MAX_INPUT_LENGTH - 3] = '\0';
        sprintf( argument, "%s\n\r", argument );
        add_redecoration( room, REDECOR_DESC, room->description, (ptr = str_alloc( argument )) );
        room->description = ptr;
        return;
      }
    break;

    case 'e':
      if ( !str_cmp( arg1, "ed" ) )
      {
        EXTRA_DESCR_DATA *ed;

        if ( !*argument )
        {
          progbug( "mpredit ed:  no keyword specified", ch );
          return;
        }
        argument = one_argument( argument, arg2 );
        if ( !*argument )
        {
          progbugf( ch, "mpredit ed:  no description specified for keyword(s) '%s'", arg2 );
          return;
        }
        /*
         * Since mobs have no edit buffers, allow them to append to extra descs using "+"
         */
        if ( *argument == '+' )
        {
          int len;
          char *tmp;

          argument++;
          /*
           * If the extradesc doesn't already exist, GetRExtra will create it.
           */
          ed = GetRExtra( room, arg2 );
          len = strlen( ed->description ) + strlen( argument ) + 2;
          if ( len >= MAX_STRING_LENGTH - 3 )
          {
            progbugf( ch, "do_mpredit desc:  trying to make extradesc %s too large, room %d", ed->keyword, room->vnum );
            /*
             * The astute reader will ask:  "What if the extradesc was just created?
             * Won't the MUD now fail to mark down that the room has been altered?"
             * Fortunately this scenario shouldn't occur, because in order to get the length so big,
             * the mob would have to be appending to an existing extradesc.
             */
            return;
          }
          /*
           * Change the extradesc as the mob specifies!  But first, memorize the room's original extradescs...
           */
          CREATE( tmp, char, len + 1 );
          sprintf( tmp, "%s%s\n\r", ed->description, argument );
          memorize_original_extras(room);
          str_free( ed->description );
          ed->description = str_alloc( tmp );
          free( tmp );
          /*
           * If this change causes the room to be as it was to start with, then act accordingly
           */
          if ( has_original_extras( room ) )
            forget_original_extras( room );
          return;
        }
        /*
         * The mob is overwriting the extradesc's content (by not using "+").
         * Similar to the + case, so see comments above.
         */
        memorize_original_extras( room );
        ed = SetRExtra( room, arg2 );
        str_free( ed->description );
        if ( strlen( argument ) > MAX_INPUT_LENGTH - 3 )
          argument[MAX_INPUT_LENGTH - 3] = '\0';
        sprintf( argument, "%s\n\r", argument );
        ed->description = str_alloc( argument );
        if ( has_original_extras( room ) )
          forget_original_extras( room );
        return;
      }

      if ( !str_cmp( arg1, "exkey" ) )
      {
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit exkey: no '%s' exit in room %d", arg2, room->vnum );
          return;
        }
        if ( !is_number(argument) || atoi(argument) < 1 )
        {
          progbugf( ch, "do_mpredit exkey %s: bad key vnum '%s'", arg2, argument );
          return;
        }
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits(room);
        xit->key = atoi(argument);
        if ( has_original_exits(room) )
          forget_original_exits(room);
        return;
      }
      if ( !str_cmp( arg1, "exname" ) )
      {
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit exname: no '%s' exit in room %d", arg2, room->vnum );
          return;
        }
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits(room);
        str_free(xit->keyword);
        xit->keyword = str_alloc( argument );
        if ( has_original_exits(room) )
          forget_original_exits(room);
        return;
      }

      if ( !str_cmp( arg1, "exdesc" ) )
      {
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit exdesc: no exit '%s' in room %d", arg2, room->vnum );
          return;
        }
        if ( !*argument )
        {
          progbugf( ch, "do_mpredit exdesc %s: no exdesc specified!", arg2 );
          return;
        }
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits( room );
        str_free( xit->description );
        xit->description = str_alloc( argument );
        if ( has_original_exits( room ) )
          forget_original_exits( room );
        return;
      } 

      if ( !str_cmp( arg1, "exflag" )
      ||   !str_cmp( arg1, "exflags" ) )
      {
        int flags=0;

        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit %s: no '%s' exit in room %d", arg1, arg2, room->vnum );
          return;
        }
        if ( !*argument )
          return;
        /*
         * Get the exit flags
         */
        while ( *argument )
        {
          argument = one_argument( argument, arg3 );
          i = get_exflag( arg3 );
          if ( i < 0 || i > MAX_EXFLAG )
            progbugf( ch, "do_mpredit %s %s: bad flag '%s'", arg1, arg2, arg3 );
          else
            TOGGLE_BIT( flags, 1 << i );
        }
        if ( !flags )
          return;
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits(room);
        TOGGLE_BIT( xit->exit_info, flags );
        if ( has_original_exits(room) )
          forget_original_exits(room);
        return;
      }
      if ( !str_cmp( arg1, "exit" ) )
      {
        int vnum;

        argument = one_exit( ch, argument, &xit, arg2 );
        /*
         * Blank argument means "delete exit".
         * An argument of "remlink" means "delete exit, and delete its reverse exit if there is one".
         */
        if ( !*argument || !str_cmp( argument, "remlink" ) )
        {
          if ( xit )
          {
            if ( *argument )
            {
              /*
               * Delete reverse exit, if possible.
               */
              xit_next = get_exit( xit->to_room, rev_dir[xit->vdir] );
              if ( xit_next != xit && xit_next->to_room == ch->in_room )
              {
                /*
                 * Avoid conflict with imms.
                 */
                if ( room_being_edited( xit->to_room ) )
                {
                  progbug( "do_mpredit bexit: reverse exit can't be removed because an imm is editing that room... aborting", ch );
                  return;
                }
                memorize_original_exits(xit->to_room);
                extract_exit(xit->to_room,xit_next);
                if ( has_original_exits(xit->to_room))
                  forget_original_exits(xit->to_room);
              }
            }
            memorize_original_exits(room);
            extract_exit(room,xit);
            if ( has_original_exits(room) )
              forget_original_exits(room);
            return;
          }
          progbugf( ch, "do_mpredit exit %s: no such exit", arg2 );
          return;
        }
        /*
         * The argument was _not_ blank, so now our job is to _create_ an exit if possible.
         */
        if ( !is_number( argument ) )
        {
          progbugf( ch, "do_mpredit exit %s:  target must be a room vnum", arg2 );
          return;
        }
        argument = one_argument( argument, arg1 );
        vnum = atoi(arg1);
        if ( vnum < 1 )
        {
          progbugf( ch, "do_mpredit exit %s:  invalid vnum %d", arg2, vnum );
          return;
        }
        if ( (dest = get_room_index(vnum)) == NULL )
        {
          progbugf( ch, "do_mpredit exit %s:  no room exists with vnum %d", arg2, vnum );
          return;
        }
        if ( room_being_edited(dest) )
        {
          progbugf( ch, "do_mpredit exit %s %d:  destination room is currently being edited", arg2, vnum );
          return;
        }
        if ( xit && *arg2 != '+' )
        {
          /*
           * The exit already exists.  We'll just change the destination.
           */
          if ( !xit->to_room )
          {
            progbugf( ch, "do_mpredit exit %s %d:  exit has NULL to_room", arg2, vnum );
            return;
          }
          if ( xit->to_room == dest )
            return;
          memorize_original_exits(room);
          xit->to_room = dest;
          i = xit->vdir;
          goto fix_up_exit_ptrs;
        }
        /*
         * Support for SMAUG's multi-exit code
         */
        i = get_dir( (*arg2 == '+') ? arg2+1 : arg2 );
        if ( get_exit_to( room, i, vnum ) )
        {
          progbugf( ch, "do_mpredit exit %s %d:  such an exit already exists", arg2, vnum );
          return;
        }
        memorize_original_exits(room);
        xit = make_exit( room, dest, i );
        xit->keyword = str_alloc( "" );
        xit->description = str_alloc( "" );
        xit->key = -1;
        xit->exit_info = 0;
fix_up_exit_ptrs:
        /*
         * Some annoying paperwork to keep "rexit" pointers pointing to the right things.
         */
        xit->vnum = vnum;
        xit_next = get_exit_to( dest, rev_dir[i], room->vnum );
        if ( xit_next )
        {
          xit->rexit = xit_next;
          xit_next->rexit = xit;
        }
        argument = one_argument( argument, arg1 );
        if ( *arg1 )
        {
          xit->exit_info = atoi(arg1);
          argument = one_argument( argument, arg1 );
          if ( *arg1 )
          {
            xit->key = atoi(arg1);
            if ( *argument )
            {
              str_free( xit->keyword );
              xit->keyword = str_alloc( argument );
            }
          }
        }
        if ( has_original_exits(room) )
          forget_original_exits(room);
        return;
      } 
    break;

    case 'f':
      if ( !str_cmp( arg1, "flags" ) )
      {
        int original = room->room_flags;

        if ( !*argument )
        {
          progbug( "do_mpredit flags:  no flags specified", ch );
          return;
        }

        while ( *argument )
        {
          argument = one_argument( argument, arg2 );
          i = get_rflag( arg2 );
          if ( i < 0 || i > 31 )
            progbugf( ch, "do_mpredit flags:  unknown flag '%s'", arg2 );
          else if ( ((1<<i) == ROOM_PROTOTYPE) || ((1<<i) == ROOM_CLANSTOREROOM) )
            progbugf( ch, "do_mpredit flags:  mobs can't toggle the %s flag", arg2 );
          else
            TOGGLE_BIT( room->room_flags, 1 << i );
        }
        
        if ( original != room->room_flags )
        {
          /*
           * Take care to carefully remember what the original room flags were.
           */
          CREATE( value, int, 2 );
          value[0] = original;
          value[1] = room->room_flags;
          add_redecoration( room, REDECOR_FLAGS, value, &value[1] );
          /*
           *  If the mob toggled the "teleport" flag, then act appropriately.
           *  (This could be fun, the players better hold onto their hats!)
           */
          if ( (room->room_flags & ROOM_TELEPORT) != (original & ROOM_TELEPORT) )
            recalculate_teleports(room);
        }
        return;
      }
    break;        

    case 'n':
      if ( !str_cmp( arg1, "name" ) )
      {
        if ( !*argument )
        {
          progbugf( ch, "do_mpredit name:  trying to give room %d a blank name", room->vnum );
          return;
        }
        add_redecoration( room, REDECOR_NAME, room->name, (ptr = str_alloc( argument )) );
        room->name = ptr;
        return;
      }
    break;

    case 'p':
      if ( !str_cmp( arg1, "pull" ) )
      {
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit pull: no '%s' exit in room %d", arg2, room->vnum );
          return;
        }
        if ( !is_number(argument) )
        {
          progbugf( ch, "do_mpredit pull %s: invalid pull strength '%s'", arg2, argument );
          return;
        }
        if ( atoi(argument) < -100 || atoi(argument) > 100 )
        {
          progbugf( ch, "do_mpredit pull %s: pull strength must be between -100 and 100", arg2 );
          return;
        }
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits( room );
        xit->pull = atoi(argument);
        if ( has_original_exits( room ) )
          forget_original_exits( room );
        return;
      }
      if ( !str_cmp( arg1, "push" ) )
      {
        argument = one_exit( ch, argument, &xit, arg2 );
        if ( !xit )
        {
          progbugf( ch, "do_mpredit push: no '%s' exit in room %d", arg2, room->vnum );
          return;
        }
        if ( !is_number(argument) )
        {
          progbugf( ch, "do_mpredit push %s: invalid push strength '%s'", arg2, argument );
          return;
        }
        if ( atoi(argument) < -100 || atoi(argument) > 100 )
        {
          progbugf( ch, "do_mpredit push %s: push strength must be between -100 and 100", arg2 );
          return;
        }
        /*
         *  Make sure we remember how the room's exits were originally.
         *  If, after the change, the room's exits are as they originally were, act accordingly.
         */
        memorize_original_exits( room );
        xit->pull = -(atoi(argument));
        if ( has_original_exits( room ) )
          forget_original_exits( room );
        return;
      }

    case 'r':
      if ( !str_cmp( arg1, "revert" ) )
      {
        /*
         * Return room to its original state, undoing all mpredited changes.
         */
        mpredit_modify( room, TRUE );
        delete_mpredits( room );
        return;
      }
        
      if ( !str_cmp( arg1, "rmaffect" )
      ||   !str_cmp( arg1, "rmaff" ) )
      {
        CHAR_DATA *vch;

        if ( !*argument )
        {
          progbug( "do_mpredit rmaffect:  no number specified", ch );
          return;
        }
        if ( !is_number( argument ) )
        {
          progbugf( ch, "do_mpredit rmaffect:  '%s' is not a number", argument );
          return;
        }
        loc = atoi( argument );
        if ( loc < 1 )
        {
          progbugf( ch, "do_mpredit rmaffect:  %d is an invalid affect number", loc );
          return;
        }
        i=0;
        for ( paf = room->first_affect; paf; paf = paf->next )
        {
          if ( ++i == loc )
          {
            memorize_original_affects(room);
            UNLINK( paf, room->first_affect, room->last_affect, next, prev );
            /*
             * To prevent character corruption, now actually REMOVE the affect from anyone in the room.
             * (This is assuming you've installed my "Fix room affect bugs" snippet...)
             */
            if ( paf->location != APPLY_WEARSPELL && paf->location != APPLY_REMOVESPELL && paf->location != APPLY_STRIPSN )
            {
              for ( vch = room->first_person; vch; vch = vch->next_in_room )
                affect_modify( vch, paf, FALSE );
            }
            free( paf );
            if ( has_original_affects( room ) )
              forget_original_affects( room );
            return;
          }
        }
        progbugf( ch, "do_mpredit rmaffect:  room has fewer than %d affects!", loc );
        return;
      }

      if ( !str_cmp( arg1, "rmed" ) )
      {
        bool found;

        if ( !*argument )
        {
          progbug( "do_mpredit rmed:  no keyword specified", ch );
          return;
        }
        /*
         * Delete the extradesc, but first make sure we remember what the room's extradescs originally were.
         * Then if, after the deletion, the room's extradescs are as they originally were, act accordingly.
         */
        memorize_original_extras( room );
        found = DelRExtra( room, argument );
        if ( has_original_extras( room ) )
          forget_original_extras( room );
        if ( !found )
          progbugf( ch, "do_mpredit rmed:  ed '%s' not found", argument );
        return;
      }         
    break;

    case 's':
      /*
       * Oh boy, you know what this next argument means, right??
       * Water traps!!!  :-D
       */
      if ( !str_cmp( arg1, "sector" ) )
      {
        if ( !*argument )
        {
          progbug( "do_mpredit sector:  no sector specified", ch );
          return;
        }
        loc = atoi( argument );
        if ( loc < 0 || loc > SECT_MAX )
        {
          progbugf( ch, "do_mpredit sector:  %s is an invalid sector", argument );
          return;
        }
        /*
         * Take care to remember what the sector type originally was.
         */
        CREATE( value, int, 2 );
        value[0] = room->sector_type;
        value[1] = loc;
        add_redecoration( room, REDECOR_SECTOR, value, &value[1] );
        room->sector_type = loc;
        return;
      }
    break;
      
    case 't':
      if ( !str_cmp( arg1, "tunnel" ) )
      {
        if ( !*argument )
        {
          progbug( "do_mpredit tunnel:  no tunnel specified", ch );
          return;
        }
        if ( !is_number( argument ) )
        {
          progbugf( ch, "do_mpredit tunnel:  argument '%s' is not a number", argument );
          return;
        }
        if ( atoi(argument) < 1 )
        {
          progbugf( ch, "do_mpredit tunnel: argument '%s' an invalid tunnel", argument );
          return;
        }
        /*
         * Take care to remember the original tunnel size.
         */
        CREATE( value, int, 2 );
        value[0] = room->tunnel;
        value[1] = atoi( argument );
        room->tunnel = value[1];
        add_redecoration( room, REDECOR_TUNNEL, value, &value[1] );
        return;
      }

      if ( !str_cmp( arg1, "teledelay" )
      ||   !str_cmp( arg1, "tele_delay" ) )
      {
        if ( !*argument )
        {
          progbug( "do_mpredit teledelay:  no teledelay specified", ch );
          return;
        }
        if ( !is_number( argument ) )
        {
          progbugf( ch, "do_mpredit teledelay:  '%s' is not a number", argument );
          return;
        }
        i = atoi( argument );
        if ( i < 0 )
        {
          progbugf( ch, "do_mpredit teledelay:  %d is an invalid teledelay", i );
          return;
        }
        /*
         * Take care to remember the original teledelay value.
         */
        CREATE( value, int, 2 );
        value[0] = room->tele_delay;
        value[1] = i;
        add_redecoration( room, REDECOR_TELEDELAY, value, &value[1] );
        room->tele_delay = i;
        /*
         * Since the teleport data has been changed, recalculate whether anyone is pending teleport, etc.
         */
        recalculate_teleports( room );
        return;
      }

      if ( !str_cmp( arg1, "televnum" )
      ||   !str_cmp( arg1, "tele_vnum" ) )
      {
        if ( !*argument )
        {
          progbug( "do_mpredit televnum:  no televnum specified", ch );
          return;
        }
        if ( !is_number( argument ) )
        {
          progbugf( ch, "do_mpredit televnum:  '%s' is not a number", argument );
          return;
        }
        i = atoi( argument );
        if ( i < 0 )
        {
          progbugf( ch, "do_mpredit televnum:  %d is an invalid teledelay", i );
          return;
        }
        /*
         * Take care to remember the original televnum.
         */
        CREATE( value, int, 2 );
        value[0] = room->tele_vnum;
        value[1] = i;
        add_redecoration( room, REDECOR_TELEVNUM, value, &value[1] );
        room->tele_vnum = i;
        /*
         * Make sure the teleportation queue keeps up with the change.
         */
        recalculate_teleports( room );
        return;
      }
    break;

    default: break;

  }

  progbugf( ch, "do_mpredit:  bad argument '%s'", arg1 );
  return;
}

/*
 * If a mob changes a room's teleport data while players are standing in that room,
 * then we must make sure the future teleportation (or lack thereof) of those players
 * is handled correctly.
 */
void recalculate_teleports( ROOM_INDEX_DATA *room )
{
  TELEPORT_DATA *tele;

  if ( IS_SET( room->room_flags, ROOM_TELEPORT )
  &&   room->tele_delay > 0 )
  {
    for ( tele = first_teleport; tele; tele = tele->next )
      if ( tele->room == room )
      {
        tele->timer = room->tele_delay;
        return;
      }
    CREATE( tele, TELEPORT_DATA, 1 );
    LINK( tele, first_teleport, last_teleport, next, prev );
    tele->room = room;
    tele->timer = room->tele_delay;
    return;
  }
  else
  {
    for ( tele = first_teleport; tele; tele = tele->next )
    {
      if ( tele->room == room )
      {
        UNLINK( tele, first_teleport, last_teleport, next, prev );
        free( tele );
        return;
      }
    }
  }

  return;
}

/*
 * This next function allows the MUD to memorize small changes to rooms.
 * (Bigger changes, like exits, are handled elsewhere)
 */
void add_redecoration( ROOM_INDEX_DATA *room, int decor_type, void *v1, void *v2 )
{
  mpredit_decor *d;

  switch( decor_type )
  {
    case REDECOR_DESC:
    case REDECOR_NAME:
      if ( v1 == v2 )
      {
        /*
         * No change actually occurred.  Nothing to memorize.
         * Memory handling is a bit tricky though.
         * ONE of the pointers needs to be free'd, since it was originally going to be kept track of by this memory.
         * The other pointer needn't be free'd because the room's description or name still points to it.
         */
        str_free( (char *)v1 );
        return;
      }
      /*
       * Check whether the room already has been changed in this way.
       */
      if ( IS_SET( room->redecorations, 1 << decor_type ) )
      {
        for ( d = room->first_redecoration; d; d = d->next )
        {
          if ( d->type == decor_type )
          {
            if ( d->v1 == v2 )  /* Mob has set field back to original */
            {
              /*
               * This is probably the subtlest part of MPRedit, the very tricky memory handling here.
               * The name/desc WAS changed from the original, and the MOB has changed it back to the original.
               * We WERE remembering the original, as well as the change-- we can forget both those.
               * As for the two passed chars, one is what the room name WAS, just before this mpredit.
               * We no longer need it, and can free it.  As for the 2nd passed char, we CAN'T free it, because
               * the room is now pointing to that block of memory.
               */
              str_free( (char *) d->v1 );
              str_free( (char *) d->v2 );
              str_free( (char *) v1 );
              UNLINK( d, room->first_redecoration, room->last_redecoration, next, prev );
              free( d );
              REMOVE_BIT( room->redecorations, 1 << decor_type );
              return;
            }
            /* Mob is re-changing an already changed field */
            str_free( (char *) d->v2 );
            str_free( (char *) v1 );
            d->v2 = (void *) quick_link( v2 );
            return;
          }
        }
      }
      SET_BIT( room->redecorations, 1 << decor_type );
      CREATE( d, mpredit_decor, 1 );
      d->v1 = v1;
      d->v2 = quick_link((char *)v2);
      d->type = decor_type;
      LINK( d, room->first_redecoration, room->last_redecoration, next, prev );
      return;
      
    case REDECOR_FLAGS:
    case REDECOR_SECTOR:
    case REDECOR_TUNNEL:
    case REDECOR_TELEDELAY:
    case REDECOR_TELEVNUM:
      if ( *((int *)v1) == *((int *)v2) )
      {
        /*
         * Nothing actually changed.  Do nothing (except memory cleanup).
         */
        free( v1 );
        return;
      }
      if ( IS_SET( room->redecorations, 1 << decor_type ) )
      {
        for ( d = room->first_redecoration; d; d = d->next )
        {
          if ( d->type == decor_type )
          {
            if ( *((int *)d->v1) == *((int *)v2) )  /* Mob is returning field to original state */
            {
              free( d->v1 );
              free( v1 );
              UNLINK( d, room->first_redecoration, room->last_redecoration, next, prev );
              free( d );
              REMOVE_BIT( room->redecorations, 1 << decor_type );
              return;
            }
            /* Re-changing an already changed field */
            ((int *) d->v1)[1] = ((int *) v1)[1];
            free( v1 );
            return;
          }
        }
      }
      SET_BIT( room->redecorations, 1 << decor_type );
      CREATE( d, mpredit_decor, 1 );
      d->v1 = v1;
      d->v2 = v2;
      d->type = decor_type;
      LINK( d, room->first_redecoration, room->last_redecoration, next, prev );
      return;
    
  }

  return;
}

/*
 * Check whether the room's current extra desc's are the same as the room's original extra desc's were, before any MPRedits.
 */
bool has_original_extras( ROOM_INDEX_DATA *room )
{
  mpredit_decor *d;
  EXTRA_DESCR_DATA *ed1, *ed2; 

  if ( !IS_SET( room->redecorations, 1 << REDECOR_EXTRAS ) )
    return TRUE;

  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_EXTRAS )
    {
      for ( ed1 = (EXTRA_DESCR_DATA *) d->v1, ed2 = room->first_extradesc; ed1 && ed2; ed1 = ed1->next, ed2 = ed2->next )
      {
        if ( ed1->keyword != ed2->keyword || ed1->description != ed2->description )
          return FALSE;
      }
      return ed1 == ed2;
    }
  }
  return TRUE;
}

/*
 * Make sure we know what the room's extra desc's originally looked like.
 * (In particular, if we already know what they looked like, then do nothing)
 */
void memorize_original_extras( ROOM_INDEX_DATA *room )
{
  EXTRA_DESCR_DATA *ed1, *ed2;
  mpredit_decor *d;

  if ( IS_SET( room->redecorations, 1 << REDECOR_EXTRAS ) )
    return;

  SET_BIT( room->redecorations, 1 << REDECOR_EXTRAS );

  CREATE( d, mpredit_decor, 1 );
  d->v1 = NULL;
  d->v2 = NULL;
  d->type = REDECOR_EXTRAS;
  LINK( d, room->first_redecoration, room->last_redecoration, next, prev );

  /*
   * Copy the room's extra desc's, making a new linked list.
   * This new list's head and tail are the void pointers of the mpredit_decor structure.
   */
  for ( ed1 = room->first_extradesc; ed1; ed1 = ed1->next )
  {
    CREATE( ed2, EXTRA_DESCR_DATA, 1 );
    ed2->keyword = quick_link( ed1->keyword );
    ed2->description = quick_link( ed1->description );
    VOIDLINK( ed2, d->v1, d->v2, next, prev, EXTRA_DESCR_DATA * );
  }
  return;
}

/*
 * When the room's extra desc's somehow return to their original configuration, then forget they ever changed.
 */
void forget_original_extras( ROOM_INDEX_DATA *room )
{
  mpredit_decor *d;
  EXTRA_DESCR_DATA *ed, *ed_next;

  if ( !IS_SET( room->redecorations, 1 << REDECOR_EXTRAS ) )
    return;

  REMOVE_BIT( room->redecorations, 1 << REDECOR_EXTRAS );
  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_EXTRAS )
    {
      for ( ed = (EXTRA_DESCR_DATA *)d->v1; ed; ed = ed_next )
      {
        ed_next = ed->next;
        str_free( ed->keyword );
        str_free( ed->description );
        free( ed );
      }
      UNLINK( d, room->first_redecoration, room->last_redecoration, next, prev );
      free( d );
      return;
    }
  }
}

/*
 * Check if the room's current exits agree with the original exits before any MPRedits occurred.
 */
bool has_original_exits( ROOM_INDEX_DATA *room )
{
  EXIT_DATA *p, *q;
  mpredit_decor *d;

  if ( !IS_SET( room->redecorations, 1 << REDECOR_EXITS ) )
    return TRUE;

  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_EXITS )
    {
      for ( p = room->first_exit, q = (EXIT_DATA *)d->v1; p && q; p = p->next, q = q->next )
      {
        if ( p->to_room != q->to_room
        ||   p->keyword != q->keyword
        ||   p->description != q->description
        ||   p->exit_info != q->exit_info
        ||   p->key != q->key
        ||   p->vdir != q->vdir
        ||   p->distance != q->distance
        ||   p->pull != q->pull
        ||   p->pulltype != q->pulltype )
        {
          return FALSE;
        }
      }
      return p==q;
    }
  }

  return TRUE;
}

/*
 * If the room's exits are as they originally were before any MPRedits, then forget they ever changed.
 */
void forget_original_exits( ROOM_INDEX_DATA *room )
{
  mpredit_decor *d;
  EXIT_DATA *p, *p_next;

  if ( !IS_SET( room->redecorations, 1 << REDECOR_EXITS ) )
    return;

  REMOVE_BIT( room->redecorations, 1 << REDECOR_EXITS );

  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_EXITS )
    {
      /*
       * The void pointers of the REDECOR_EXITS mpredit_decor are the head and tail of a linked
       * list containing copies of the original exits.  Delete this list to clean up memory.
       */
      for ( p = (EXIT_DATA*)d->v1; p; p = p_next )
      {
        p_next = p->next;
        str_free( p->keyword );
        str_free( p->description );
        free( p );
      }
      UNLINK( d, room->first_redecoration, room->last_redecoration, next, prev );
      free( d );
      return;
    }
  }

  return;
}

/*
 * Make sure we know what the room's original exits were.
 * (In particular, if we already know this, then do nothing)
 */
void memorize_original_exits( ROOM_INDEX_DATA *room )
{
  EXIT_DATA *p, *q;
  mpredit_decor *d;

  if ( IS_SET( room->redecorations, 1 << REDECOR_EXITS ) )
    return;

  SET_BIT( room->redecorations, 1 << REDECOR_EXITS );

  CREATE( d, mpredit_decor, 1 );
  d->type = REDECOR_EXITS;
  d->v1 = NULL;
  d->v2 = NULL;

  /*
   * Copy the exits, making a new linked list whose head and tail are the void pointers of the mpredit_decor structure.
   */
  for ( p = room->first_exit; p; p = p->next )
  {
    CREATE( q, EXIT_DATA, 1 );
    q->rexit = p->rexit;
    q->to_room = p->to_room;
    q->keyword = quick_link( p->keyword );
    q->description = quick_link( p->description );
    q->vnum = p->vnum;
    q->rvnum = p->rvnum;
    q->exit_info = p->exit_info;
    q->key = p->key;
    q->vdir = p->vdir;
    q->distance = p->distance;
    q->pull = p->pull;
    q->pulltype = p->pulltype;
    VOIDLINK( q, d->v1, d->v2, next, prev, EXIT_DATA * );
  }
  LINK( d, room->first_redecoration, room->last_redecoration, next, prev );

  return;
}

/*
 * Check whether the room has the same affects it originally had before any MPRedits took place.
 */
bool has_original_affects( ROOM_INDEX_DATA *room )
{
  AFFECT_DATA *paf1, *paf2;
  mpredit_decor *d;

  if ( !IS_SET( room->redecorations, 1 << REDECOR_AFFECTS ) )
    return TRUE;

  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_AFFECTS )
    {
      for ( paf1 = (AFFECT_DATA *) d->v1, paf2 = room->first_affect; paf1 && paf2; paf1 = paf1->next, paf2 = paf2->next )
      {
        if ( paf1->location != paf2->location || paf1->modifier != paf2->modifier )
          return FALSE;
      }
      return paf1 == paf2;
    }
  }
  return TRUE;
}

/*
 * Make sure we remember the room's original, pre-MPRedit, affects.
 */
void memorize_original_affects( ROOM_INDEX_DATA *room )
{
  AFFECT_DATA *paf1, *paf2;
  mpredit_decor *d;

  if ( IS_SET( room->redecorations, 1 << REDECOR_AFFECTS ) )
    return;

  SET_BIT( room->redecorations, 1 << REDECOR_AFFECTS );

  CREATE( d, mpredit_decor, 1 );
  d->v1 = NULL;
  d->v2 = NULL;
  d->type = REDECOR_AFFECTS;
  LINK( d, room->first_redecoration, room->last_redecoration, next, prev );

  /*
   * Copy the affects into a new linked list whose head and tail are the void pointers of the mpredit_decor struct.
   */
  for ( paf1 = room->first_affect; paf1; paf1 = paf1->next )
  {
    CREATE( paf2, AFFECT_DATA, 1 );
    paf2->location = paf1->location;
    paf2->modifier = paf1->modifier;
    paf2->duration = -1;
    paf2->type = -1;
    xCLEAR_BITS( paf2->bitvector );
    VOIDLINK( paf2, d->v1, d->v2, next, prev, AFFECT_DATA * );
  }
  return;
}

/*
 * If the room's affects are as they originally were, then forget any change ever took place.
 */
void forget_original_affects( ROOM_INDEX_DATA *room )
{
  AFFECT_DATA *paf, *paf_next;
  mpredit_decor *d;

  if ( !IS_SET( room->redecorations, 1 << REDECOR_AFFECTS ) )
    return;

  REMOVE_BIT( room->redecorations, 1 << REDECOR_AFFECTS );

  /*
   * The void pointers of the mpredit_decor are the head and tail of a linked list containing
   * copies of the affects.  Delete this list to clean up memory.
   */
  for ( d = room->first_redecoration; d; d = d->next )
  {
    if ( d->type == REDECOR_AFFECTS )
    {
      for ( paf = (AFFECT_DATA *) d->v1; paf; paf = paf_next )
      {
        paf_next = paf->next;
        free( paf );
      }
      UNLINK( d, room->first_redecoration, room->last_redecoration, next, prev );
      free( d );
      return;
    }
  }
}

/*
 * Show a list of mpredits to an immortal.
 */
void display_mpredits( CHAR_DATA *ch, ROOM_INDEX_DATA *room )
{
  mpredit_decor *d;
  int count = 1;
  int type;
  void *tmp1, *tmp2;
  AFFECT_DATA *paf;
  EXTRA_DESCR_DATA *ext1, *ext2;

  if ( !room || !room->first_redecoration )
  {
    send_to_char( "This room is not presently suffering any mpredits.\n\r", ch );
    return;
  }

  for ( d = room->first_redecoration; d; d = d->next )
  {
    ch_printf_color( ch, "&c[%-3d]  ", count++ );
    type = d->type;
    switch( type )
    {
      case REDECOR_DESC:
        ch_printf_color( ch, "&wChanged room's description.  Original roomdesc:\n\r&Y%s", (char *) d->v1 );
        break;
      case REDECOR_FLAGS:
        ch_printf_color( ch, "&wChanged room's flags.  Original flags:\n\r     &W%s\n\r", flag_string( *((int*)d->v1), r_flags ) );
        break;
      case REDECOR_NAME:
        ch_printf_color( ch, "&wChanged room's %s.  Original %s:  &W%s\n\r", decor_names[type], decor_names[type], (char *) d->v1 );
        break;
      case REDECOR_SECTOR:
      case REDECOR_TUNNEL:
      case REDECOR_TELEDELAY:
      case REDECOR_TELEVNUM:
        ch_printf_color( ch, "&wChanged room's %s.  Original %s:  &W%d\n\r", decor_names[type], decor_names[type], *((int*)d->v1) );
        break;
      case REDECOR_EXITS:
        ch_printf_color( ch, "&wChanged room's exits.  The original exits were:\n\r" );
        if ( !d->v1 )
        {
          send_to_char( "(None)\n\r", ch );
        }
        else
        {
          tmp1 = (void*)room->first_exit;
          tmp2 = (void*)room->last_exit;
          room->first_exit = (EXIT_DATA*) d->v1;
          room->last_exit = (EXIT_DATA*) d->v2;
          do_rstat( ch, "ex_mpredit" );
          room->first_exit = (EXIT_DATA*) tmp1;
          room->last_exit = (EXIT_DATA*) tmp2;
        }
      break;
      case REDECOR_AFFECTS:
        ch_printf_color( ch, "&wChanged room's affects.  The original affects were:\n\r" );
        for( paf = (AFFECT_DATA*) d->v1; paf; paf = paf->next )
          ch_printf_color( ch, "  &cAffect: &w%s &cby &w%d.\n\r",
            affect_loc_name( paf->location ), paf->modifier );
        break;

      case REDECOR_EXTRAS:
      {
        /*
         * This takes O(n^2) time in number of roomdescs, but that
         * should be fine since rooms typically don't contain
         * hundreds of extradescs and this function is only called
         * by do_rstat anyway!
         */
       ch_printf_color( ch, "Made the following extra_description changes:\n\r" );

       for ( ext1 = room->first_extradesc; ext1; ext1 = ext1->next )
       {
         for ( ext2 = (EXTRA_DESCR_DATA*) d->v1; ext2; ext2=ext2->next )
           if ( ext2->keyword == ext1->keyword )
             break;
         if ( ext2 )
         {
           if ( ext2->description != ext1->description )
             ch_printf_color( ch, "  Changed text of extradesc '%s'\n\r", ext1->keyword );
         }
         else
           ch_printf_color( ch, "  Added extradesc '%s'\n\r", ext1->keyword );
       }
       for ( ext2 = (EXTRA_DESCR_DATA*) d->v1; ext2; ext2 = ext2->next )
       {
         for ( ext1 = room->first_extradesc; ext1; ext1 = ext1->next )
           if ( ext2->keyword == ext1->keyword )
             break;
         if ( !ext1 )
           ch_printf_color( ch, "  Deleted extradesc '%s'\n\r", ext2->keyword );
       }
     }
     break;
        
      default: break;
    }
  }
  return;
}

ROOM_INDEX_DATA *room_index_hash                [MAX_KEY_HASH];

/*
 * Revert all mpredits in an area... or revert the revert.  Depending on the value of fRevert.
 * Returns TRUE if this actually causes any change, FALSE otherwise.
 */
bool mpredit_modify_area( AREA_DATA *area, bool fRevert )
{
  ROOM_INDEX_DATA *room;
  int hash;
  bool fModified = FALSE;

  for ( hash = 0; hash < MAX_KEY_HASH; hash++ )
  for ( room = room_index_hash[hash]; room; room = room->next )
  {
    if ( room->area == area )
      fModified = mpredit_modify( room, fRevert ) || fModified;
  }
  return fModified;
}

/*
 * Revert all mpredits in a room... or revert the revert.  Depending on the value of fRevert.
 * Returns TRUE if this actually causes any change, FALSE otherwise.
 * This is why for minor redecorations like roomname, we stored not just the original value,
 * but the new value too.
 */
bool mpredit_modify( ROOM_INDEX_DATA *room, bool fRevert )
{
  mpredit_decor *d;
  void *src, *head, *tail;
  EXIT_DATA *xit;

  if ( !room->first_redecoration )
    return FALSE;

  for ( d = room->first_redecoration; d; d = d->next )
  {
    src = fRevert ? d->v1 : d->v2;

    switch( d->type )
    {
      case REDECOR_DESC:
        str_free( room->description );
        room->description = quick_link((char *)src );
        continue;
      case REDECOR_FLAGS:
        room->room_flags = *((int*)src);
        continue;
      case REDECOR_NAME:
        str_free( room->name );
        room->name = quick_link((char *)src);
        continue;
      case REDECOR_SECTOR:
        room->sector_type = *((int*)src);
        continue;
      case REDECOR_TUNNEL:
        room->tunnel = *((int*)src);
        continue;
      case REDECOR_TELEDELAY:
        room->tele_delay = *((int*)src);
        continue;
      case REDECOR_TELEVNUM:
        room->tele_vnum = *((int*)src);
        continue;
      /*
       * In the case of affects, extradescs, and exits:
       * the void pointers of the mpredit_decor struct are the head and tail of a linked list
       * of affects/extradescs/exits.  Swap this list with the room's list of same.
       */
      case REDECOR_AFFECTS:
        head = (void*) room->first_affect;
        tail = (void*) room->last_affect;
        room->first_affect = (AFFECT_DATA *)d->v1;
        room->last_affect = (AFFECT_DATA *)d->v2;
        d->v1 = head;
        d->v2 = tail;
        continue;
      case REDECOR_EXTRAS:
        head = (void*) room->first_extradesc;
        tail = (void*) room->last_extradesc;
        room->first_extradesc = (EXTRA_DESCR_DATA *)d->v1;
        room->last_extradesc = (EXTRA_DESCR_DATA *)d->v2;
        d->v1 = head;
        d->v2 = tail;
        continue;
      case REDECOR_EXITS:
        head = (void*) room->first_exit;
        tail = (void*) room->last_exit;
        room->first_exit = (EXIT_DATA *)d->v1;
        room->last_exit = (EXIT_DATA *)d->v2;
        d->v1 = head;
        d->v2 = tail;
        for ( xit = room->first_exit; xit; xit = xit->next )
          if ( xit->rexit )
            xit->rexit->rexit = xit;
        continue;
      default:      
        continue;
    }
  }
  return TRUE;
}

/*
 * The next function deletes all mpredit overhead data in a room-- WITHOUT doing any reverting of changes.
 * So before calling it, use mpredit_modify to ensure the original stats are on the room.
 */
void delete_mpredits( ROOM_INDEX_DATA *room )
{
  mpredit_decor *d, *d_next;
  AFFECT_DATA *paf, *paf_next;
  EXTRA_DESCR_DATA *ed, *ed_next;
  EXIT_DATA *xit, *xit_next;
  void *x, *y;
  CHAR_DATA *vch;

  for ( d = room->first_redecoration; d; d = d_next )
  {
    x = d->v1, y = d->v2;
    d_next = d->next;

    switch( d->type )
    {
      case REDECOR_NAME:
      case REDECOR_DESC:
        str_free((char*)x);
        str_free((char*)y);
        break;
      case REDECOR_FLAGS:
      case REDECOR_SECTOR:
      case REDECOR_TUNNEL:
      case REDECOR_TELEDELAY:
      case REDECOR_TELEVNUM:
        free((int*)x);
        break;
      case REDECOR_AFFECTS:
        /*
         * The only time delete_mpredits gets called is when a mob does "mpredit revert"
         * (or an imm uses "unmpredit").
         * Conveniently, this is also the only time when ALL the affect paperwork needs to be
         * maintained (to prevent character corruption).  (Hopefully you've already installed
         * my "fix room affect bugs" snippet to fix the room affect character corruption that
         * occurs elsewhere)
         */
        for ( paf = (AFFECT_DATA*)x; paf; paf = paf_next )
        {
          paf_next = paf->next;
          if ( paf->location != APPLY_WEARSPELL && paf->location != APPLY_REMOVESPELL && paf->location != APPLY_STRIPSN )
          {
            for ( vch = room->first_person; vch; vch = vch->next )
              affect_modify( vch, paf, FALSE );
          }
          free( paf );
        }
        for ( paf = room->first_affect; paf; paf = paf->next )
        {
          if ( paf->location != APPLY_WEARSPELL && paf->location != APPLY_REMOVESPELL && paf->location != APPLY_STRIPSN )
          {
            for ( vch = room->first_person; vch; vch = vch->next )
              affect_modify( vch, paf, TRUE );
          }
        }
        break;
      case REDECOR_EXTRAS:
        for ( ed = (EXTRA_DESCR_DATA*)x; ed; ed = ed_next )
        {
          ed_next = ed->next;
          str_free( ed->keyword );
          str_free( ed->description );
          free( ed );
        }
        break;        
      case REDECOR_EXITS:
        for ( xit = (EXIT_DATA*)x; xit; xit = xit_next )
        {
          xit_next = xit->next;
          str_free( xit->keyword );
          str_free( xit->description );
          free( xit );
        }
        break;
      default:
        break;
    }
    free(d);
  }
  room->first_redecoration = NULL;
  room->last_redecoration = NULL;
  room->redecorations = 0;
  recalculate_teleports(room);
  return;
}

/*
 * Check whether someone is editing the room with a writing buffer.
 */
bool room_being_edited( ROOM_INDEX_DATA *room )
{
  DESCRIPTOR_DATA *d;
  EXTRA_DESCR_DATA *extra;

  for ( d = first_descriptor; d; d = d->next )
  {
    if ( d->character )
    {
      if ( d->character->substate == SUB_ROOM_DESC && d->character->dest_buf == room )
        return TRUE;
      if ( d->character->substate == SUB_ROOM_EXTRA )
      {
        for ( extra = room->first_extradesc; extra; extra = extra->next )
          if ( d->character->dest_buf == extra )
            return TRUE;
      }
    }
  }

  return FALSE;
}

/*
 * This next function is like "one_argument".  It chops the leftmost word from "argument",
 * puts that in "buf", but then it also attempts to identify that word with an exit in the char's
 * room.  The exit (or NULL) is then placed in the exit pointer pointed to by p.
 */
char *one_exit( CHAR_DATA *ch, char *argument, EXIT_DATA **p, char *buf )
{
  argument = one_argument( argument, buf );
  if ( *buf == '#' )
    *p = get_exit_num( ch->in_room, atoi( buf+1 ) );
  else
    *p = get_exit( ch->in_room, get_dir(buf) );

  return argument;
}

/*
 * Immortal command to clear the mpredits from a room.
 */
void do_unmpredit( CHAR_DATA *ch, char *argument )
{
  if ( !ch->in_room )
    return;

  if ( !ch->in_room->first_redecoration )
  {
    send_to_char( "This room has not been mpredit'ed.\n\r", ch );
    return;
  }

  mpredit_modify( ch->in_room, TRUE );
  delete_mpredits( ch->in_room );

  send_to_char( "Done.  The room has been restored to its original state.\n\r", ch );
  return;
}