LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 *			   Player movement module			     *
 *****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "h/mud.h"

bool sneaking_char;
bool stop_fishing( CHAR_DATA *ch );

void handle_stat( CHAR_DATA *ch, short stat, bool silence );

const short movement_loss[SECT_MAX] =
{
   1, 2, 2, 3, 4, 6, 4, 1, 6, 10, 6, 5, 7, 4
};

char *const dir_name[] =
{
   "north",     "east",      "south",     "west",
   "up",        "down",      "northeast", "northwest",
   "southeast", "southwest", "somewhere"
};

const int trap_door[] =
{
   TRAP_N,  TRAP_E, TRAP_S,  TRAP_W,
   TRAP_U,  TRAP_D, TRAP_NE, TRAP_NW,
   TRAP_SE, TRAP_SW
};

const short rev_dir[] =
{
   2, 3, 0, 1, 5, 4, 9, 8, 7, 6, 10
};

/* Local functions. */
OBJ_DATA *has_key( CHAR_DATA *ch, int key );

char *rev_exit( short vdir )
{
   switch( vdir )
   {
      default:
         return "somewhere";
      case 0:
         return "the south";
      case 1:
         return "the west";
      case 2:
         return "the north";
      case 3:
         return "the east";
      case 4:
         return "below";
      case 5:
         return "above";
      case 6:
         return "the southwest";
      case 7:
         return "the southeast";
      case 8:
         return "the northwest";
      case 9:
         return "the northeast";
   }
}

/*
 * Function to get the equivelant exit of DIR 0-MAXDIR out of linked list.
 * Made to allow old-style diku-merc exit functions to work.	-Thoric
 */
EXIT_DATA *get_exit( ROOM_INDEX_DATA *room, short dir )
{
   EXIT_DATA *xit;

   if( !room )
   {
      bug( "%s: NULL room", __FUNCTION__ );
      return NULL;
   }

   for( xit = room->first_exit; xit; xit = xit->next )
      if( xit->vdir == dir )
         return xit;
   return NULL;
}

/* Function to get an exit, leading the the specified room */
EXIT_DATA *get_exit_to( ROOM_INDEX_DATA *room, short dir, int vnum )
{
   EXIT_DATA *xit;

   if( !room )
   {
      bug( "%s: NULL room", __FUNCTION__ );
      return NULL;
   }

   for( xit = room->first_exit; xit; xit = xit->next )
      if( xit->vdir == dir && xit->vnum == vnum )
         return xit;
   return NULL;
}

/* Function to get the nth exit of a room - Thoric */
EXIT_DATA *get_exit_num( ROOM_INDEX_DATA *room, short count )
{
   EXIT_DATA *xit;
   int cnt;

   if( !room )
   {
      bug( "%s: NULL room", __FUNCTION__ );
      return NULL;
   }

   for( cnt = 0, xit = room->first_exit; xit; xit = xit->next )
      if( ++cnt == count )
         return xit;
   return NULL;
}

/* Modify movement due to encumbrance - Thoric */
short encumbrance( CHAR_DATA *ch, short move )
{
   int cur, max;

   max = can_carry_w( ch );
   cur = ch->carry_weight;
   if( cur >= max )
      return move * 4;
   else if( cur >= max * 0.95 )
      return ( short )( move * 3.5 );
   else if( cur >= max * 0.90 )
      return move * 3;
   else if( cur >= max * 0.85 )
      return ( short )( move * 2.5 );
   else if( cur >= max * 0.80 )
      return move * 2;
   else if( cur >= max * 0.75 )
      return ( short )( move * 1.5 );
   else
      return move;
}

/* Check to see if a character can fall down, checks for looping - Thoric */
bool will_fall( CHAR_DATA *ch, int fall )
{
   if( xIS_SET( ch->in_room->room_flags, ROOM_NOFLOOR )
   && can_go( ch->in_room, DIR_DOWN )
   && ( !IS_AFFECTED( ch, AFF_FLYING ) || ( ch->mount && !IS_AFFECTED( ch->mount, AFF_FLYING ) ) ) )
   {
      if( fall > 80 )
      {
         bug( "Falling (in a loop?) more than 80 rooms: vnum %d", ch->in_room->vnum );
         char_from_room( ch );
         char_to_room( ch, get_room_index( sysdata.room_temple ) );
         fall = 0;
         return true;
      }
      set_char_color( AT_FALLING, ch );
      send_to_char( "You're falling down...\r\n", ch );
      move_char( ch, get_exit( ch->in_room, DIR_DOWN ), ++fall );
      return true;
   }
   return false;
}

ch_ret move_char( CHAR_DATA *ch, EXIT_DATA *pexit, int fall )
{
   ROOM_INDEX_DATA *in_room, *to_room, *from_room;
   OBJ_DATA *boat;
   char buf[MSL], *txt, *dtxt;
   ch_ret retcode;
   short door;
   bool drunk = false, brief = false;

   if( !is_npc( ch ) )
   {
      if( is_drunk( ch, 2 ) && ( ch->position != POS_SHOVE ) && ( ch->position != POS_DRAG ) )
         drunk = true;
   }

   if( drunk && !fall )
   {
      door = number_door( );
      pexit = get_exit( ch->in_room, door );
   }

   retcode = rNONE;
   txt = NULL;

   if( is_npc( ch ) && xIS_SET( ch->act, ACT_MOUNTED ) )
      return retcode;

   in_room = ch->in_room;
   from_room = in_room;
   if( !pexit || !( to_room = pexit->to_room ) )
   {
      if( drunk && ch->position != POS_MOUNTED
      && ch->in_room->sector_type != SECT_WATER_SWIM
      && ch->in_room->sector_type != SECT_WATER_NOSWIM
      && ch->in_room->sector_type != SECT_UNDERWATER
      && ch->in_room->sector_type != SECT_OCEANFLOOR )
      {
         switch( number_bits( 4 ) )
         {
            default:
               act( AT_ACTION, "You drunkenly stumble into some obstacle.", ch, NULL, NULL, TO_CHAR );
               act( AT_ACTION, "$n drunkenly stumbles into a nearby obstacle.", ch, NULL, NULL, TO_ROOM );
               break;

            case 3:
               act( AT_ACTION, "In your drunken stupor you trip over your own feet and tumble to the ground.", ch, NULL, NULL, TO_CHAR );
               act( AT_ACTION, "$n stumbles drunkenly, trips and tumbles to the ground.", ch, NULL, NULL, TO_ROOM );
               ch->position = POS_RESTING;
               break;

            case 4:
               act( AT_SOCIAL, "You utter a string of slurred obscenities.", ch, NULL, NULL, TO_CHAR );
               act( AT_ACTION, "Something blurry and immovable has intercepted you as you stagger along.", ch, NULL, NULL, TO_CHAR );
               act( AT_HURT, "Oh geez... THAT really hurt.  Everything slowly goes dark and numb...", ch, NULL, NULL, TO_CHAR );

               act( AT_ACTION, "$n drunkenly staggers into something.", ch, NULL, NULL, TO_ROOM );
               act( AT_SOCIAL, "$n utters a string of slurred obscenities: @*&^%@*&!", ch, NULL, NULL, TO_ROOM );
               act( AT_ACTION, "$n topples to the ground with a thud.", ch, NULL, NULL, TO_ROOM );
               ch->position = POS_INCAP;
               break;
         }
      }
      else if( drunk )
         act( AT_ACTION, "You stare around trying to make sense of things through your drunken stupor.", ch, NULL, NULL, TO_CHAR );
      else
         send_to_char( "Alas, you can't go that way.\r\n", ch );
      return rNONE;
   }

   door = pexit->vdir;

   /*
    * Exit is only a "window", there is no way to travel in that direction
    * unless it's a door with a window in it      -Thoric
    */
   if( xIS_SET( pexit->exit_info, EX_WINDOW ) && !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
   {
      send_to_char( "Alas, you can't go that way.\r\n", ch );
      return rNONE;
   }

   if( is_npc( ch ) )
   {
      if( xIS_SET( pexit->exit_info, EX_PORTAL ) )
      {
         act( AT_PLAIN, "Mobs can't use portals.", ch, NULL, NULL, TO_CHAR );
         return rNONE;
      }
      if( xIS_SET( pexit->exit_info, EX_NOMOB ) || xIS_SET( to_room->room_flags, ROOM_NO_MOB ) )
      {
         act( AT_PLAIN, "Mobs can't enter there.", ch, NULL, NULL, TO_CHAR );
         return rNONE;
      }
   }

   if( xIS_SET( pexit->exit_info, EX_CLOSED ) && ( !IS_AFFECTED( ch, AFF_PASS_DOOR ) || xIS_SET( pexit->exit_info, EX_NOPASSDOOR ) ) )
   {
      if( !xIS_SET( pexit->exit_info, EX_SECRET ) && !xIS_SET( pexit->exit_info, EX_DIG ) )
      {
         if( drunk )
         {
            act( AT_PLAIN, "$n runs into the $d in $s drunken state.", ch, NULL, pexit->keyword, TO_ROOM );
            act( AT_PLAIN, "You run into the $d in your drunken state.", ch, NULL, pexit->keyword, TO_CHAR );
         }
         else
            act( AT_PLAIN, "The $d is closed.", ch, NULL, pexit->keyword, TO_CHAR );
      }
      else
      {
         if( drunk )
            send_to_char( "You stagger around in your drunken state.\r\n", ch );
         else
            send_to_char( "Alas, you can't go that way.\r\n", ch );
      }

      return rNONE;
   }

   if( !fall && IS_AFFECTED( ch, AFF_CHARM ) && ch->master && in_room == ch->master->in_room )
   {
      send_to_char( "What?  And leave your beloved master?\r\n", ch );
      return rNONE;
   }

   if( room_is_private( to_room ) )
   {
      send_to_char( "That room is private right now.\r\n", ch );
      return rNONE;
   }

   if( room_is_dnd( ch, to_room ) )
   {
      send_to_char( "That room is \"do not disturb\" right now.\r\n", ch );
      return rNONE;
   }

   if( !is_immortal( ch ) && !is_npc( ch ) && ch->in_room->area != to_room->area )
   {
      if( ch->level < to_room->area->low_hard_range )
      {
         set_char_color( AT_TELL, ch );
         switch( to_room->area->low_hard_range - ch->level )
         {
            case 1:
               send_to_char( "A voice in your mind says, 'You are nearly ready to go that way...'\r\n", ch );
               break;

            case 2:
               send_to_char( "A voice in your mind says, 'Soon you shall be ready to travel down this path... soon.'\r\n", ch );
               break;

            case 3:
               send_to_char( "A voice in your mind says, 'You aren't ready to go down that path... yet.'\r\n", ch );
               break;

            default:
               send_to_char( "A voice in your mind says, 'You aren't ready to go down that path.'\r\n", ch );
               break;
         }
         return rNONE;
      }
      else if( ch->level > to_room->area->hi_hard_range )
      {
         set_char_color( AT_TELL, ch );
         send_to_char( "A voice in your mind says, 'There is nothing more for you down that path.'\r\n", ch );
         return rNONE;
      }
   }

   if( !fall && !is_npc( ch ) )
   {
      int move;

      if( xIS_SET( to_room->area->flags, AFLAG_NOPKILL )
      && !xIS_SET( ch->in_room->area->flags, AFLAG_NOPKILL )
      && ( is_pkill( ch ) && !is_immortal( ch ) ) )
      {
         set_char_color( AT_MAGIC, ch );
         send_to_char( "\r\nA godly force forbids deadly characters from entering that area...\r\n", ch );
         return rNONE;
      }

      if( in_room->sector_type == SECT_AIR || to_room->sector_type == SECT_AIR
      || xIS_SET( pexit->exit_info, EX_FLY ) )
      {
         if( ch->mount && !IS_AFFECTED( ch->mount, AFF_FLYING ) )
         {
            send_to_char( "Your mount can't fly.\r\n", ch );
            return rNONE;
         }
         if( !ch->mount && !IS_AFFECTED( ch, AFF_FLYING ) )
         {
            send_to_char( "You'd need to fly to go there.\r\n", ch );
            return rNONE;
         }
      }

      if( in_room->sector_type == SECT_WATER_NOSWIM || to_room->sector_type == SECT_WATER_NOSWIM )
      {
         if( ( ch->mount && !is_floating( ch->mount ) ) || !is_floating( ch ) )
         {
            /*
             * Look for a boat.
             * We can use the boat obj for a more detailed description.
             */
            if( ( boat = get_objtype( ch, ITEM_BOAT ) ) )
            {
               if( drunk )
                  txt = "paddles unevenly";
               else
                  txt = "paddles";
            }
            else
            {
               if( ch->mount )
                  send_to_char( "Your mount would drown!\r\n", ch );
               else
                  send_to_char( "You'd need a boat to go there.\r\n", ch );
               return rNONE;
            }
         }
      }

      if( xIS_SET( pexit->exit_info, EX_CLIMB ) )
      {
         bool found;

         found = false;
         if( ch->mount && IS_AFFECTED( ch->mount, AFF_FLYING ) )
            found = true;
         else if( IS_AFFECTED( ch, AFF_FLYING ) )
            found = true;

         if( !found && !ch->mount )
         {
            if( ( !is_npc( ch ) && number_percent( ) > LEARNED( ch, gsn_climb ) ) || drunk || ch->mental_state < -90 )
            {
               send_to_char( "You start to climb... but lose your grip and fall!\r\n", ch );
               learn_from_failure( ch, gsn_climb );
               if( pexit->vdir == DIR_DOWN )
               {
                  retcode = move_char( ch, pexit, 1 );
                  return retcode;
               }
               set_char_color( AT_HURT, ch );
               send_to_char( "OUCH! You hit the ground!\r\n", ch );
               wait_state( ch, 20 );
               retcode = damage( ch, ch, ( pexit->vdir == DIR_UP ? 10 : 5 ), TYPE_UNDEFINED );
               return retcode;
            }
            found = true;
            learn_from_success( ch, gsn_climb );
            wait_state( ch, skill_table[gsn_climb]->beats );
            txt = "climbs";
         }

         if( !found )
         {
            send_to_char( "You can't climb.\r\n", ch );
            return rNONE;
         }
      }

      if( ch->mount )
      {
         if( is_npc( ch->mount ) )
         {
            if( xIS_SET( pexit->exit_info, EX_PORTAL ) )
            {
               act( AT_PLAIN, "Your mount can't use portals.", ch, NULL, NULL, TO_CHAR );
               return rNONE;
            }
            if( xIS_SET( pexit->exit_info, EX_NOMOB ) || xIS_SET( to_room->room_flags, ROOM_NO_MOB ) )
            {
               act( AT_PLAIN, "Your mount can't enter there.", ch, NULL, NULL, TO_CHAR );
               return rNONE;
            }
         }

         switch( ch->mount->position )
         {
            case POS_DEAD:
               send_to_char( "Your mount is dead!\r\n", ch );
               return rNONE;
               break;

            case POS_MORTAL:
            case POS_INCAP:
               send_to_char( "Your mount is hurt far too badly to move.\r\n", ch );
               return rNONE;
               break;

            case POS_STUNNED:
               send_to_char( "Your mount is too stunned to do that.\r\n", ch );
               return rNONE;
               break;

            case POS_SLEEPING:
               send_to_char( "Your mount is sleeping.\r\n", ch );
               return rNONE;
               break;

            case POS_RESTING:
               send_to_char( "Your mount is resting.\r\n", ch );
               return rNONE;
               break;

            case POS_SITTING:
               send_to_char( "Your mount is sitting down.\r\n", ch );
               return rNONE;
               break;

            default:
               break;
         }

         if( !is_floating( ch->mount ) )
            move = movement_loss[ URANGE( 0, in_room->sector_type, SECT_MAX - 1 ) ];
         else
            move = 1;
         if( ch->mount->move < move )
         {
            send_to_char( "Your mount is too exhausted.\r\n", ch );
            return rNONE;
         }
      }
      else
      {
         if( !is_floating( ch ) )
            move = encumbrance( ch, movement_loss[ URANGE( 0, in_room->sector_type, SECT_MAX - 1 ) ] );
         else
            move = 1;
         if( ch->move < move )
         {
            send_to_char( "You are too exhausted.\r\n", ch );
            return rNONE;
         }
      }

      wait_state( ch, move );
      if( ch->mount )
         ch->mount->move -= move;
      else
         ch->move -= move;
   }

   /* Check if player can fit in the room */
   if( to_room->tunnel > 0 )
   {
      CHAR_DATA *ctmp;
      int count = ch->mount ? 1 : 0;

      for( ctmp = to_room->first_person; ctmp; ctmp = ctmp->next_in_room )
      {
         if( ++count >= to_room->tunnel )
         {
            if( ch->mount && count == to_room->tunnel )
               send_to_char( "There is no room for both you and your mount there.\r\n", ch );
            else
               send_to_char( "There is no room for you there.\r\n", ch );
            return rNONE;
         }
      }
   }

   if( stop_fishing( ch ) )
      send_to_char( "You pull your line out of the water.\r\n", ch );

   if( is_npc( ch ) || !xIS_SET( ch->act, PLR_WIZINVIS ) )
   {
      if( fall )
         txt = "falls";
      else if( !txt )
      {
         if( ch->mount )
         {
            if( IS_AFFECTED( ch->mount, AFF_FLOATING ) )
               txt = "floats";
            else if( IS_AFFECTED( ch->mount, AFF_FLYING ) )
               txt = "flies";
            else
               txt = "rides";
         }
         else
         {
            if( IS_AFFECTED( ch, AFF_FLOATING ) )
            {
               if( drunk )
                  txt = "floats unsteadily";
               else
                  txt = "floats";
            }
            else if( IS_AFFECTED( ch, AFF_FLYING ) )
            {
               if( drunk )
                  txt = "flies shakily";
               else
                  txt = "flies";
            }
            else if( ch->position == POS_SHOVE )
               txt = "is shoved";
            else if( ch->position == POS_DRAG )
               txt = "is dragged";
            else
            {
               if( drunk )
                  txt = "stumbles drunkenly";
               else
                  txt = "leaves";
            }
         }
      }
      if( ch->mount )
      {
         snprintf( buf, sizeof( buf ), "$n %s %s upon $N.", txt, dir_name[door] );
         if( IS_AFFECTED( ch->mount, AFF_SNEAK ) )
            sneaking_char = true;
         act( AT_ACTION, buf, ch, NULL, ch->mount, TO_NOTVICT );
         sneaking_char = false;
      }
      else
      {
         snprintf( buf, sizeof( buf ), "$n %s $T.", txt );
         if( IS_AFFECTED( ch, AFF_SNEAK ) )
            sneaking_char = true;
         act( AT_ACTION, buf, ch, NULL, dir_name[door], TO_ROOM );
         sneaking_char = false;
      }
   }

   rprog_leave_trigger( ch );
   if( char_died( ch ) )
      return global_retcode;

   if( ch->in_room->first_content )
      retcode = check_room_for_traps( ch, TRAP_LEAVE_ROOM );
   if( retcode != rNONE )
      return retcode;

   char_from_room( ch );
   char_to_room( ch, to_room );
   if( ch->mount )
   {
      rprog_leave_trigger( ch->mount );

      if( char_died( ch->mount ) )
         return global_retcode;

      if( ch->mount )
      {
         char_from_room( ch->mount );
         char_to_room( ch->mount, to_room );
      }
   }

   if( is_npc( ch ) || !xIS_SET( ch->act, PLR_WIZINVIS ) )
   {
      if( fall )
         txt = "falls";
      else if( ch->mount )
      {
         if( IS_AFFECTED( ch->mount, AFF_FLOATING ) )
            txt = "floats in";
         else if( IS_AFFECTED( ch->mount, AFF_FLYING ) )
            txt = "flies in";
         else
            txt = "rides in";
      }
      else
      {
         if( IS_AFFECTED( ch, AFF_FLOATING ) )
         {
            if( drunk )
               txt = "floats in unsteadily";
            else
               txt = "floats in";
         }
         else if( IS_AFFECTED( ch, AFF_FLYING ) )
         {
            if( drunk )
               txt = "flies in shakily";
            else
               txt = "flies in";
         }
         else if( ch->position == POS_SHOVE )
            txt = "is shoved in";
         else if( ch->position == POS_DRAG )
            txt = "is dragged in";
         else
         {
            if( drunk )
               txt = "stumbles drunkenly in";
            else
               txt = "arrives";
         }
      }
      dtxt = rev_exit( door );
      if( ch->mount )
      {
         snprintf( buf, sizeof( buf ), "$n %s from %s upon $N.", txt, dtxt );
         if( IS_AFFECTED( ch->mount, AFF_SNEAK ) )
            sneaking_char = true;
         act( AT_ACTION, buf, ch, NULL, ch->mount, TO_ROOM );
         sneaking_char = false;
      }
      else
      {
         snprintf( buf, sizeof( buf ), "$n %s from %s.", txt, dtxt );
         if( IS_AFFECTED( ch, AFF_SNEAK ) )
            sneaking_char = true;
         act( AT_ACTION, buf, ch, NULL, NULL, TO_ROOM );
         sneaking_char = false;
      }
   }

   if( !is_immortal( ch ) && !is_npc( ch ) && ch->in_room->area != to_room->area )
   {
      if( ch->level < to_room->area->low_soft_range )
      {
         set_char_color( AT_MAGIC, ch );
         send_to_char( "You feel uncomfortable being in this strange land...\r\n", ch );
      }
      else if( ch->level > to_room->area->hi_soft_range )
      {
         set_char_color( AT_MAGIC, ch );
         send_to_char( "You feel there is not much to gain visiting this place...\r\n", ch );
      }
   }

   /* Make sure everyone sees the room description of death traps. */
   if( xIS_SET( ch->in_room->room_flags, ROOM_DEATH ) && !is_immortal( ch ) )
   {
      if( xIS_SET( ch->act, PLR_BRIEF ) )
         brief = true;
      xREMOVE_BIT( ch->act, PLR_BRIEF );
   }

   do_look( ch, "auto" );
   if( brief )
      xSET_BIT( ch->act, PLR_BRIEF );

   /* Put good-old EQ-munching death traps back in! - Thoric */
   if( xIS_SET( ch->in_room->room_flags, ROOM_DEATH ) && !is_immortal( ch ) )
   {
      act( AT_DEAD, "$n falls prey to a terrible death!", ch, NULL, NULL, TO_ROOM );
      act( AT_DEAD, "Oopsie... you're dead!\r\n", ch, NULL, NULL, TO_CHAR );
      snprintf( buf, sizeof( buf ), "%s hit a DEATH TRAP in room %d!", ch->name, ch->in_room->vnum );
      log_string( buf );
      to_channel( buf, "monitor", PERM_IMM );
      if( is_npc( ch ) )
         extract_char( ch, true );
      else
         extract_char( ch, false );
      return rCHAR_DIED;
   }

   /*
    * BIG ugly looping problem here when the character is mptransed back
    * to the starting room.  To avoid this, check how many chars are in 
    * the room at the start and stop processing followers after doing
    * the right number of them.  -- Narn
    */
   if( !fall )
   {
      CHAR_DATA *fch, *nextinroom;
      int chars = 0, count = 0;

      for( fch = from_room->first_person; fch; fch = fch->next_in_room )
         chars++;

      for( fch = from_room->first_person; fch && ( count < ( chars + 5 ) ); fch = nextinroom )
      {
         nextinroom = fch->next_in_room;
         count++;
         if( fch != ch && fch->master == ch
         && ( fch->position == POS_STANDING || fch->position == POS_MOUNTED )
         && !xIS_SET( ch->act, PLR_SOLO ) && !xIS_SET( fch->act, PLR_SOLO ) )
         {
            act( AT_ACTION, "You follow $N.", fch, NULL, ch, TO_CHAR );
            move_char( fch, get_exit( from_room, door ), 0 );
         }
      }
   }


   if( ch->in_room->first_content )
      retcode = check_room_for_traps( ch, TRAP_ENTER_ROOM );
   if( retcode != rNONE || char_died( ch ) )
      return retcode;

   mprog_entry_trigger( ch );
   if( char_died( ch ) )
      return retcode;

   rprog_enter_trigger( ch );
   if( char_died( ch ) )
      return retcode;

   mprog_greet_trigger( ch );
   if( char_died( ch ) )
      return retcode;

   oprog_greet_trigger( ch );
   if( char_died( ch ) )
      return retcode;

   if( !will_fall( ch, fall ) && fall > 0 )
   {
      if( !IS_AFFECTED( ch, AFF_FLOATING ) || ( ch->mount && !IS_AFFECTED( ch->mount, AFF_FLOATING ) ) )
      {
         if( ch->mount )
         {
            set_char_color( AT_HURT, ch->mount );
            send_to_char( "OUCH! You hit the ground!\r\n", ch->mount );
            wait_state( ch->mount, 20 );
            retcode = damage( ch->mount, ch->mount, 20 * fall, TYPE_UNDEFINED );
         }
         else
         {
            set_char_color( AT_HURT, ch );
            send_to_char( "OUCH! You hit the ground!\r\n", ch );
            wait_state( ch, 20 );
            retcode = damage( ch, ch, 20 * fall, TYPE_UNDEFINED );
         }
      }
      else
      {
         set_char_color( AT_MAGIC, ch );
         if( ch->mount )
            send_to_char( "You and your mount lightly float down to the ground.\r\n", ch );
         else
            send_to_char( "You lightly float down to the ground.\r\n", ch );
      }
   }

   /* Very small chance of getting a dex increase through moving around */
   if( number_percent( ) < 2 )
      handle_stat( ch, STAT_DEX, true );

   return retcode;
}

CMDF( do_north )
{
   move_char( ch, get_exit( ch->in_room, DIR_NORTH ), 0 );
}

CMDF( do_east )
{
   move_char( ch, get_exit( ch->in_room, DIR_EAST ), 0 );
}

CMDF( do_south )
{
   move_char( ch, get_exit( ch->in_room, DIR_SOUTH ), 0 );
}

CMDF( do_west )
{
   move_char( ch, get_exit( ch->in_room, DIR_WEST ), 0 );
}

CMDF( do_up )
{
   move_char( ch, get_exit( ch->in_room, DIR_UP ), 0 );
}

CMDF( do_down )
{
   move_char( ch, get_exit( ch->in_room, DIR_DOWN ), 0 );
}

CMDF( do_northeast )
{
   move_char( ch, get_exit( ch->in_room, DIR_NORTHEAST ), 0 );
}

CMDF( do_northwest )
{
   move_char( ch, get_exit( ch->in_room, DIR_NORTHWEST ), 0 );
}

CMDF( do_southeast )
{
   move_char( ch, get_exit( ch->in_room, DIR_SOUTHEAST ), 0 );
}

CMDF( do_southwest )
{
   move_char( ch, get_exit( ch->in_room, DIR_SOUTHWEST ), 0 );
}

EXIT_DATA *find_door( CHAR_DATA *ch, char *arg, bool quiet )
{
   EXIT_DATA *pexit;
   int door;

   if( !arg || !str_cmp( arg, "" ) )
      return NULL;

   pexit = NULL;
   if( !str_cmp( arg, "n" ) || !str_cmp( arg, "north" ) )
      door = 0;
   else if( !str_cmp( arg, "e" ) || !str_cmp( arg, "east" ) )
      door = 1;
   else if( !str_cmp( arg, "s" ) || !str_cmp( arg, "south" ) )
      door = 2;
   else if( !str_cmp( arg, "w" ) || !str_cmp( arg, "west" ) )
      door = 3;
   else if( !str_cmp( arg, "u" ) || !str_cmp( arg, "up" ) )
      door = 4;
   else if( !str_cmp( arg, "d" ) || !str_cmp( arg, "down" ) )
      door = 5;
   else if( !str_cmp( arg, "ne" ) || !str_cmp( arg, "northeast" ) )
      door = 6;
   else if( !str_cmp( arg, "nw" ) || !str_cmp( arg, "northwest" ) )
      door = 7;
   else if( !str_cmp( arg, "se" ) || !str_cmp( arg, "southeast" ) )
      door = 8;
   else if( !str_cmp( arg, "sw" ) || !str_cmp( arg, "southwest" ) )
      door = 9;
   else
   {
      for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
      {
         if( ( quiet || xIS_SET( pexit->exit_info, EX_ISDOOR ) ) && pexit->keyword && nifty_is_name( arg, pexit->keyword ) )
            return pexit;
      }
      if( !quiet )
         act( AT_PLAIN, "You see no $T here.", ch, NULL, arg, TO_CHAR );
      return NULL;
   }

   if( !( pexit = get_exit( ch->in_room, door ) ) )
   {
      if( !quiet )
         act( AT_PLAIN, "You see no $T here.", ch, NULL, arg, TO_CHAR );
      return NULL;
   }

   if( quiet )
      return pexit;

   if( xIS_SET( pexit->exit_info, EX_SECRET ) )
   {
      act( AT_PLAIN, "You see no $T here.", ch, NULL, arg, TO_CHAR );
      return NULL;
   }

   if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
   {
      send_to_char( "You can't do that.\r\n", ch );
      return NULL;
   }

   return pexit;
}

void set_bexit_flag( EXIT_DATA *pexit, int flag )
{
   EXIT_DATA *pexit_rev;

   xSET_BIT( pexit->exit_info, flag );
   if( ( pexit_rev = pexit->rexit ) && pexit_rev != pexit )
      xSET_BIT( pexit_rev->exit_info, flag );
}

void remove_bexit_flag( EXIT_DATA *pexit, int flag )
{
   EXIT_DATA *pexit_rev;

   xREMOVE_BIT( pexit->exit_info, flag );
   if( ( pexit_rev = pexit->rexit ) && pexit_rev != pexit )
      xREMOVE_BIT( pexit_rev->exit_info, flag );
}

void toggle_bexit_flag( EXIT_DATA *pexit, int flag )
{
   EXIT_DATA *pexit_rev;

   xTOGGLE_BIT( pexit->exit_info, flag );
   if( ( pexit_rev = pexit->rexit ) && pexit_rev != pexit )
      xTOGGLE_BIT( pexit_rev->exit_info, flag );
}

void do_open( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj;
   EXIT_DATA *pexit;
   int door;

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

   if( ( pexit = find_door( ch, arg, true ) ) )
   {
      /* 'open door' */
      EXIT_DATA *pexit_rev;

      if( xIS_SET( pexit->exit_info, EX_SECRET ) && pexit->keyword && !nifty_is_name( arg, pexit->keyword ) )
      {
         ch_printf( ch, "You see no %s here.\r\n", arg );
         return;
      }
      if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
      {
         send_to_char( "You can't do that.\r\n", ch );
         return;
      }
      if( !xIS_SET( pexit->exit_info, EX_CLOSED ) )
      {
         send_to_char( "It's already open.\r\n", ch );
         return;
      }
      if( xIS_SET( pexit->exit_info, EX_LOCKED ) && xIS_SET( pexit->exit_info, EX_BOLTED ) )
      {
         send_to_char( "The bolts locked.\r\n", ch );
         return;
      }
      if( xIS_SET( pexit->exit_info, EX_BOLTED ) )
      {
         send_to_char( "It's bolted.\r\n", ch );
         return;
      }
      if( xIS_SET( pexit->exit_info, EX_LOCKED ) )
      {
         send_to_char( "It's locked.\r\n", ch );
         return;
      }

      if( !xIS_SET( pexit->exit_info, EX_SECRET ) || ( pexit->keyword && nifty_is_name( arg, pexit->keyword ) ) )
      {
         act( AT_ACTION, "$n opens the $d.", ch, NULL, pexit->keyword, TO_ROOM );
         act( AT_ACTION, "You open the $d.", ch, NULL, pexit->keyword, TO_CHAR );
         if( ( pexit_rev = pexit->rexit ) && pexit_rev->to_room == ch->in_room )
         {
            CHAR_DATA *rch;

            for( rch = pexit->to_room->first_person; rch; rch = rch->next_in_room )
               act( AT_ACTION, "The $d opens.", rch, NULL, pexit_rev->keyword, TO_CHAR );
         }
         remove_bexit_flag( pexit, EX_CLOSED );
         if( ( door = pexit->vdir ) >= 0 && door < 10 )
            check_room_for_traps( ch, trap_door[door] );
         return;
      }
   }

   if( ( obj = get_obj_here( ch, arg ) ) )
   {
      /* 'open object' */
      if( obj->item_type != ITEM_CONTAINER )
      {
         ch_printf( ch, "%s is not a container.\r\n", capitalize( obj->short_descr ) );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_CLOSED ) )
      {
         ch_printf( ch, "%s is already open.\r\n", capitalize( obj->short_descr ) );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_CLOSEABLE ) )
      {
         ch_printf( ch, "%s can't be opened or closed.\r\n", capitalize( obj->short_descr ) );
         return;
      }
      if( IS_SET( obj->value[1], CONT_LOCKED ) )
      {
         ch_printf( ch, "%s is locked.\r\n", capitalize( obj->short_descr ) );
         return;
      }

      REMOVE_BIT( obj->value[1], CONT_CLOSED );
      act( AT_ACTION, "You open $p.", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n opens $p.", ch, obj, NULL, TO_ROOM );
      oprog_open_trigger( ch, obj );
      if( !char_died( ch ) )
         check_for_trap( ch, obj, TRAP_OPEN );
      return;
   }

   ch_printf( ch, "You see no %s here.\r\n", arg );
}

void do_close( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj;
   EXIT_DATA *pexit;
   int door;

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

   if( ( pexit = find_door( ch, arg, true ) ) )
   {
      /* 'close door' */
      EXIT_DATA *pexit_rev;

      if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
      {
         send_to_char( "You can't do that.\r\n", ch );
         return;
      }
      if( xIS_SET( pexit->exit_info, EX_CLOSED ) )
      {
         send_to_char( "It's already closed.\r\n", ch );
         return;
      }

      act( AT_ACTION, "$n closes the $d.", ch, NULL, pexit->keyword, TO_ROOM );
      act( AT_ACTION, "You close the $d.", ch, NULL, pexit->keyword, TO_CHAR );

      /* close the other side */
      if( ( pexit_rev = pexit->rexit ) && pexit_rev->to_room == ch->in_room )
      {
         CHAR_DATA *rch;

         xSET_BIT( pexit_rev->exit_info, EX_CLOSED );
         for( rch = pexit->to_room->first_person; rch; rch = rch->next_in_room )
            act( AT_ACTION, "The $d closes.", rch, NULL, pexit_rev->keyword, TO_CHAR );
      }
      set_bexit_flag( pexit, EX_CLOSED );
      if( ( door = pexit->vdir ) >= 0 && door < 10 )
         check_room_for_traps( ch, trap_door[door] );
      return;
   }

   if( ( obj = get_obj_here( ch, arg ) ) )
   {
      /* 'close object' */
      if( obj->item_type != ITEM_CONTAINER )
      {
         ch_printf( ch, "%s is not a container.\r\n", capitalize( obj->short_descr ) );
         return;
      }
      if( IS_SET( obj->value[1], CONT_CLOSED ) )
      {
         ch_printf( ch, "%s is already closed.\r\n", capitalize( obj->short_descr ) );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_CLOSEABLE ) )
      {
         ch_printf( ch, "%s can't be opened or closed.\r\n", capitalize( obj->short_descr ) );
         return;
      }

      SET_BIT( obj->value[1], CONT_CLOSED );
      act( AT_ACTION, "You close $p.", ch, obj, NULL, TO_CHAR );
      act( AT_ACTION, "$n closes $p.", ch, obj, NULL, TO_ROOM );
      oprog_close_trigger( ch, obj );
      if( !char_died( ch ) )
         check_for_trap( ch, obj, TRAP_CLOSE );
      return;
   }

   ch_printf( ch, "You see no %s here.\r\n", arg );
}

/*
 * Keyring support added by Thoric
 * Idea suggested by Onyx <MtRicmer@worldnet.att.net> of Eldarion
 *
 * New: returns pointer to key/NULL instead of true/false
 *
 * If you want a feature like having immortals always have a key... you'll
 * need to code in a generic key, and make sure extract_obj doesn't extract it
 */
OBJ_DATA *has_key( CHAR_DATA *ch, int key )
{
   OBJ_DATA *obj, *obj2;

   for( obj = ch->first_carrying; obj; obj = obj->next_content )
   {
      if( obj->pIndexData->vnum == key || ( obj->item_type == ITEM_KEY && obj->value[0] == key ) )
         return obj;
      else if( obj->item_type == ITEM_KEYRING )
      {
         for( obj2 = obj->first_content; obj2; obj2 = obj2->next_content )
            if( obj2->pIndexData->vnum == key || ( obj2->item_type == ITEM_KEY && obj2->value[0] == key ) )
               return obj2;
      }
   }

   return NULL;
}

void do_lock( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj, *key;
   EXIT_DATA *pexit;
   int count;

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

   if( ( pexit = find_door( ch, arg, true ) ) )
   {
      /* 'lock door' */
      if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
      {
         send_to_char( "You can't do that.\r\n", ch );
         return;
      }
      if( !xIS_SET( pexit->exit_info, EX_CLOSED ) )
      {
         send_to_char( "It's not closed.\r\n", ch );
         return;
      }
      if( pexit->key < 0 )
      {
         send_to_char( "It can't be locked.\r\n", ch );
         return;
      }
      if( !( key = has_key( ch, pexit->key ) ) )
      {
         send_to_char( "You lack the key.\r\n", ch );
         return;
      }
      if( xIS_SET( pexit->exit_info, EX_LOCKED ) )
      {
         send_to_char( "It's already locked.\r\n", ch );
         return;
      }

      if( !xIS_SET( pexit->exit_info, EX_SECRET ) || ( pexit->keyword && nifty_is_name( arg, pexit->keyword ) ) )
      {
         send_to_char( "*Click*\r\n", ch );
         count = key->count;
         key->count = 1;
         act( AT_ACTION, "$n locks the $d with $p.", ch, key, pexit->keyword, TO_ROOM );
         key->count = count;
         set_bexit_flag( pexit, EX_LOCKED );
         return;
      }
   }

   if( ( obj = get_obj_here( ch, arg ) ) )
   {
      /* 'lock object' */
      if( obj->item_type != ITEM_CONTAINER )
      {
         send_to_char( "That's not a container.\r\n", ch );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_CLOSED ) )
      {
         send_to_char( "It's not closed.\r\n", ch );
         return;
      }
      if( obj->value[2] < 0 )
      {
         send_to_char( "It can't be locked.\r\n", ch );
         return;
      }
      if( !( key = has_key( ch, obj->value[2] ) ) )
      {
         send_to_char( "You lack the key.\r\n", ch );
         return;
      }
      if( IS_SET( obj->value[1], CONT_LOCKED ) )
      {
         send_to_char( "It's already locked.\r\n", ch );
         return;
      }

      SET_BIT( obj->value[1], CONT_LOCKED );
      send_to_char( "*Click*\r\n", ch );
      count = key->count;
      key->count = 1;
      act( AT_ACTION, "$n locks $p with $P.", ch, obj, key, TO_ROOM );
      key->count = count;
      return;
   }

   ch_printf( ch, "You see no %s here.\r\n", arg );
}

void do_unlock( CHAR_DATA *ch, char *argument )
{
   char arg[MIL];
   OBJ_DATA *obj, *key;
   EXIT_DATA *pexit;
   int count;

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

   if( ( pexit = find_door( ch, arg, true ) ) )
   {
      /* 'unlock door' */
      if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
      {
         send_to_char( "You can't do that.\r\n", ch );
         return;
      }
      if( !xIS_SET( pexit->exit_info, EX_CLOSED ) )
      {
         send_to_char( "It's not closed.\r\n", ch );
         return;
      }
      if( pexit->key < 0 )
      {
         send_to_char( "It can't be unlocked.\r\n", ch );
         return;
      }
      if( !( key = has_key( ch, pexit->key ) ) )
      {
         send_to_char( "You lack the key.\r\n", ch );
         return;
      }
      if( !xIS_SET( pexit->exit_info, EX_LOCKED ) )
      {
         send_to_char( "It's already unlocked.\r\n", ch );
         return;
      }

      if( !xIS_SET( pexit->exit_info, EX_SECRET ) || ( pexit->keyword && nifty_is_name( arg, pexit->keyword ) ) )
      {
         send_to_char( "*Click*\r\n", ch );
         count = key->count;
         key->count = 1;
         act( AT_ACTION, "$n unlocks the $d with $p.", ch, key, pexit->keyword, TO_ROOM );
         key->count = count;
         if( xIS_SET( pexit->exit_info, EX_EATKEY ) )
         {
            separate_obj( key );
            extract_obj( key );
         }
         remove_bexit_flag( pexit, EX_LOCKED );
         return;
      }
   }

   if( ( obj = get_obj_here( ch, arg ) ) )
   {
      /* 'unlock object' */
      if( obj->item_type != ITEM_CONTAINER )
      {
         send_to_char( "That's not a container.\r\n", ch );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_CLOSED ) )
      {
         send_to_char( "It's not closed.\r\n", ch );
         return;
      }
      if( obj->value[2] < 0 )
      {
         send_to_char( "It can't be unlocked.\r\n", ch );
         return;
      }
      if( !( key = has_key( ch, obj->value[2] ) ) )
      {
         send_to_char( "You lack the key.\r\n", ch );
         return;
      }
      if( !IS_SET( obj->value[1], CONT_LOCKED ) )
      {
         send_to_char( "It's already unlocked.\r\n", ch );
         return;
      }

      REMOVE_BIT( obj->value[1], CONT_LOCKED );
      send_to_char( "*Click*\r\n", ch );
      count = key->count;
      key->count = 1;
      act( AT_ACTION, "$n unlocks $p with $P.", ch, obj, key, TO_ROOM );
      key->count = count;
      if( IS_SET( obj->value[1], CONT_EATKEY ) )
      {
         separate_obj( key );
         extract_obj( key );
      }
      return;
   }

   ch_printf( ch, "You see no %s here.\r\n", arg );
}

CMDF( do_bashdoor )
{
   EXIT_DATA *pexit;
   char arg[MIL];
   char *keyword = "wall";

   if( !is_npc( ch ) && ch->level < skill_table[gsn_bashdoor]->skill_level[ch->Class] )
   {
      send_to_char( "You're not enough of a warrior to bash doors!\r\n", ch );
      return;
   }

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

   if( ch->fighting )
   {
      send_to_char( "You can't break off your fight.\r\n", ch );
      return;
   }

   if( ( pexit = find_door( ch, arg, true ) ) )
   {
      ROOM_INDEX_DATA *to_room;
      EXIT_DATA *pexit_rev;
      int schance;

      if( !xIS_SET( pexit->exit_info, EX_CLOSED ) )
      {
         send_to_char( "Calm down.  It is already open.\r\n", ch );
         return;
      }

      wait_state( ch, skill_table[gsn_bashdoor]->beats );

      if( !xIS_SET( pexit->exit_info, EX_SECRET ) )
         keyword = pexit->keyword;
      if( !is_npc( ch ) )
         schance = LEARNED( ch, gsn_bashdoor ) / 2;
      else
         schance = 90;
      if( xIS_SET( pexit->exit_info, EX_LOCKED ) )
         schance /= 3;

      if( !xIS_SET( pexit->exit_info, EX_BASHPROOF )
      && ch->move >= 15 && number_percent( ) < ( schance + 4 * ( get_curr_str( ch ) - 19 ) ) )
      {
         xREMOVE_BIT( pexit->exit_info, EX_CLOSED );
         if( xIS_SET( pexit->exit_info, EX_LOCKED ) )
            xREMOVE_BIT( pexit->exit_info, EX_LOCKED );
         xSET_BIT( pexit->exit_info, EX_BASHED );

         act( AT_SKILL, "Crash!  You bashed open the $d!", ch, NULL, keyword, TO_CHAR );
         act( AT_SKILL, "$n bashes open the $d!", ch, NULL, keyword, TO_ROOM );
         learn_from_success( ch, gsn_bashdoor );

         if( ( to_room = pexit->to_room ) && ( pexit_rev = pexit->rexit ) && pexit_rev->to_room == ch->in_room )
         {
            CHAR_DATA *rch;

            xREMOVE_BIT( pexit_rev->exit_info, EX_CLOSED );
            if( xIS_SET( pexit_rev->exit_info, EX_LOCKED ) )
               xREMOVE_BIT( pexit_rev->exit_info, EX_LOCKED );
            xSET_BIT( pexit_rev->exit_info, EX_BASHED );

            for( rch = to_room->first_person; rch; rch = rch->next_in_room )
               act( AT_SKILL, "The $d crashes open!", rch, NULL, pexit_rev->keyword, TO_CHAR );
         }
         damage( ch, ch, ( ch->max_hit / 20 ), gsn_bashdoor );
         return;
      }
   }
   act( AT_SKILL, "WHAAAAM!!!  You bash against the $d, but it doesn't budge.", ch, NULL, keyword, TO_CHAR );
   act( AT_SKILL, "WHAAAAM!!!  $n bashes against the $d, but it holds strong.", ch, NULL, keyword, TO_ROOM );
   damage( ch, ch, ( ch->max_hit / 20 ) + 10, gsn_bashdoor );
   learn_from_failure( ch, gsn_bashdoor );
}

void do_stand( CHAR_DATA *ch, char *argument )
{
   switch( ch->position )
   {
      case POS_SLEEPING:
         if( IS_AFFECTED( ch, AFF_SLEEP ) )
         {
            send_to_char( "You can't seem to wake up!\r\n", ch );
            return;
         }

         send_to_char( "You wake and climb quickly to your feet.\r\n", ch );
         act( AT_ACTION, "$n arises from $s slumber.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_STANDING;
         break;

      case POS_RESTING:
         send_to_char( "You gather yourself and stand up.\r\n", ch );
         act( AT_ACTION, "$n rises from $s rest.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_STANDING;
         break;

      case POS_SITTING:
         send_to_char( "You move quickly to your feet.\r\n", ch );
         act( AT_ACTION, "$n rises up.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_STANDING;
         break;

      case POS_STANDING:
         send_to_char( "You are already standing.\r\n", ch );
         break;

      case POS_FIGHTING:
      case POS_EVASIVE:
      case POS_DEFENSIVE:
      case POS_AGGRESSIVE:
      case POS_BERSERK:
         send_to_char( "You are already fighting!\r\n", ch );
         break;
   }
}

void do_sit( CHAR_DATA *ch, char *argument )
{
   switch( ch->position )
   {
      case POS_SLEEPING:
         if( IS_AFFECTED( ch, AFF_SLEEP ) )
         {
            send_to_char( "You can't seem to wake up!\r\n", ch );
            return;
         }

         send_to_char( "You wake and sit up.\r\n", ch );
         act( AT_ACTION, "$n wakes and sits up.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SITTING;
         break;

      case POS_RESTING:
         send_to_char( "You stop resting and sit up.\r\n", ch );
         act( AT_ACTION, "$n stops resting and sits up.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SITTING;
         break;

      case POS_STANDING:
         send_to_char( "You sit down.\r\n", ch );
         act( AT_ACTION, "$n sits down.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SITTING;
         break;

      case POS_SITTING:
         send_to_char( "You are already sitting.\r\n", ch );
         return;

      case POS_FIGHTING:
      case POS_EVASIVE:
      case POS_DEFENSIVE:
      case POS_AGGRESSIVE:
      case POS_BERSERK:
         send_to_char( "You are busy fighting!\r\n", ch );
         return;

      case POS_MOUNTED:
         send_to_char( "You are already sitting - on your mount.\r\n", ch );
         return;
   }
}

CMDF( do_rest )
{
   switch( ch->position )
   {
      case POS_SLEEPING:
         if( IS_AFFECTED( ch, AFF_SLEEP ) )
         {
            send_to_char( "You can't seem to wake up!\r\n", ch );
            return;
         }

         send_to_char( "You rouse from your slumber.\r\n", ch );
         act( AT_ACTION, "$n rouses from $s slumber.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_RESTING;
         break;

      case POS_RESTING:
         send_to_char( "You are already resting.\r\n", ch );
         return;

      case POS_STANDING:
         send_to_char( "You sprawl out haphazardly.\r\n", ch );
         act( AT_ACTION, "$n sprawls out haphazardly.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_RESTING;
         break;

      case POS_SITTING:
         send_to_char( "You lie back and sprawl out to rest.\r\n", ch );
         act( AT_ACTION, "$n lies back and sprawls out to rest.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_RESTING;
         break;

      case POS_FIGHTING:
      case POS_EVASIVE:
      case POS_DEFENSIVE:
      case POS_AGGRESSIVE:
      case POS_BERSERK:
         send_to_char( "You are busy fighting!\r\n", ch );
         return;

      case POS_MOUNTED:
         send_to_char( "You'd better dismount first.\r\n", ch );
         return;
   }

   rprog_rest_trigger( ch );
}

void do_sleep( CHAR_DATA *ch, char *argument )
{
   switch( ch->position )
   {
      case POS_SLEEPING:
         send_to_char( "You are already sleeping.\r\n", ch );
         return;

      case POS_RESTING:
         if( ch->mental_state > 30 && ( number_percent( ) + 10 ) < ch->mental_state )
         {
            send_to_char( "You just can't seem to calm yourself down enough to sleep.\r\n", ch );
            act( AT_ACTION, "$n closes $s eyes for a few moments, but just can't seem to go to sleep.", ch, NULL, NULL,
                 TO_ROOM );
            return;
         }
         send_to_char( "You close your eyes and drift into slumber.\r\n", ch );
         act( AT_ACTION, "$n closes $s eyes and drifts into a deep slumber.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SLEEPING;
         break;

      case POS_SITTING:
         if( ch->mental_state > 30 && ( number_percent( ) + 5 ) < ch->mental_state )
         {
            send_to_char( "You just can't seem to calm yourself down enough to sleep.\r\n", ch );
            act( AT_ACTION, "$n closes $s eyes for a few moments, but just can't seem to go to sleep.", ch, NULL, NULL,
                 TO_ROOM );
            return;
         }
         send_to_char( "You slump over and fall dead asleep.\r\n", ch );
         act( AT_ACTION, "$n nods off and slowly slumps over, dead asleep.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SLEEPING;
         break;

      case POS_STANDING:
         if( ch->mental_state > 30 && number_percent( ) < ch->mental_state )
         {
            send_to_char( "You just can't seem to calm yourself down enough to sleep.\r\n", ch );
            act( AT_ACTION, "$n closes $s eyes for a few moments, but just can't seem to go to sleep.", ch, NULL, NULL,
                 TO_ROOM );
            return;
         }
         send_to_char( "You collapse into a deep sleep.\r\n", ch );
         act( AT_ACTION, "$n collapses into a deep sleep.", ch, NULL, NULL, TO_ROOM );
         ch->position = POS_SLEEPING;
         break;

      case POS_FIGHTING:
      case POS_EVASIVE:
      case POS_DEFENSIVE:
      case POS_AGGRESSIVE:
      case POS_BERSERK:
         send_to_char( "You are busy fighting!\r\n", ch );
         return;

      case POS_MOUNTED:
         send_to_char( "You really should dismount first.\r\n", ch );
         return;
   }

   rprog_sleep_trigger( ch );
}

CMDF( do_wake )
{
   CHAR_DATA *victim;

   if( !argument || argument[0] == '\0' )
   {
      do_stand( ch, "" );
      return;
   }

   if( !is_awake( ch ) )
   {
      send_to_char( "You are asleep yourself!\r\n", ch );
      return;
   }

   if( !( victim = get_char_room( ch, argument ) ) )
   {
      send_to_char( "They aren't here.\r\n", ch );
      return;
   }

   if( is_awake( victim ) )
   {
      act( AT_PLAIN, "$N is already awake.", ch, NULL, victim, TO_CHAR );
      return;
   }

   if( IS_AFFECTED( victim, AFF_SLEEP ) || victim->position < POS_SLEEPING )
   {
      act( AT_PLAIN, "You can't seem to wake $M!", ch, NULL, victim, TO_CHAR );
      return;
   }

   act( AT_ACTION, "You wake $M.", ch, NULL, victim, TO_CHAR );
   victim->position = POS_STANDING;
   act( AT_ACTION, "$n wakes you.", ch, NULL, victim, TO_VICT );
   act( AT_ACTION, "$n wakes $N.", ch, NULL, victim, TO_ROOM );
}

/* teleport a character to another room */
void teleportch( CHAR_DATA *ch, ROOM_INDEX_DATA *room, bool show )
{
   char buf[MSL];

   if( room_is_private( room ) )
      return;
   act( AT_ACTION, "$n disappears suddenly!", ch, NULL, NULL, TO_ROOM );
   char_from_room( ch );
   char_to_room( ch, room );
   act( AT_ACTION, "$n arrives suddenly!", ch, NULL, NULL, TO_ROOM );
   if( show )
      do_look( ch, "auto" );
   if( xIS_SET( ch->in_room->room_flags, ROOM_DEATH ) && !is_immortal( ch ) )
   {
      act( AT_DEAD, "$n falls prey to a terrible death!", ch, NULL, NULL, TO_ROOM );
      set_char_color( AT_DEAD, ch );
      send_to_char( "Oopsie... you're dead!\r\n", ch );
      snprintf( buf, sizeof( buf ), "%s hit a DEATH TRAP in room %d!", ch->name, ch->in_room->vnum );
      log_string( buf );
      to_channel( buf, "monitor", PERM_IMM );
      extract_char( ch, false );
   }
}

void teleport( CHAR_DATA *ch, int room, int flags )
{
   CHAR_DATA *nch, *nch_next;
   ROOM_INDEX_DATA *start = ch->in_room, *dest;
   bool show;

   if( !( dest = get_room_index( room ) ) )
   {
      bug( "%s: bad room vnum %d", __FUNCTION__, room );
      return;
   }

   if( IS_SET( flags, TELE_SHOWDESC ) )
      show = true;
   else
      show = false;
   if( !IS_SET( flags, TELE_TRANSALL ) )
   {
      teleportch( ch, dest, show );
      return;
   }

   /* teleport everybody in the room */
   for( nch = start->first_person; nch; nch = nch_next )
   {
      nch_next = nch->next_in_room;
      teleportch( nch, dest, show );
   }

   /* teleport the objects on the ground too */
   if( IS_SET( flags, TELE_TRANSALLPLUS ) )
   {
      OBJ_DATA *obj, *obj_next;

      for( obj = start->first_content; obj; obj = obj_next )
      {
         obj_next = obj->next_content;
         obj_from_room( obj );
         obj_to_room( obj, dest );
      }
   }
}

/* "Climb" in a certain direction. - Thoric */
CMDF( do_climb )
{
   EXIT_DATA *pexit;
   bool found;

   found = false;
   if( !argument || argument[0] == '\0' )
   {
      for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
      {
         if( xIS_SET( pexit->exit_info, EX_xCLIMB ) )
         {
            move_char( ch, pexit, 0 );
            return;
         }
      }
      send_to_char( "You can't climb here.\r\n", ch );
      return;
   }

   if( ( pexit = find_door( ch, argument, true ) ) && xIS_SET( pexit->exit_info, EX_xCLIMB ) )
   {
      move_char( ch, pexit, 0 );
      return;
   }
   send_to_char( "You can't climb there.\r\n", ch );
}

/* "enter" something (moves through an exit) - Thoric */
CMDF( do_enter )
{
   EXIT_DATA *pexit;
   bool found;

   found = false;
   if( !argument || argument[0] == '\0' )
   {
      for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
      {
         if( xIS_SET( pexit->exit_info, EX_xENTER ) )
         {
            move_char( ch, pexit, 0 );
            return;
         }
      }
      if( ch->in_room->sector_type != SECT_INSIDE && is_outside( ch ) )
      {
         for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
         {
            if( pexit->to_room
            && ( pexit->to_room->sector_type == SECT_INSIDE
            || xIS_SET( pexit->to_room->room_flags, ROOM_INDOORS ) ) )
            {
               move_char( ch, pexit, 0 );
               return;
            }
         }
      }
      send_to_char( "You can't find an entrance here.\r\n", ch );
      return;
   }

   if( ( pexit = find_door( ch, argument, true ) ) && xIS_SET( pexit->exit_info, EX_xENTER ) )
   {
      move_char( ch, pexit, 0 );
      return;
   }
   send_to_char( "You can't enter that.\r\n", ch );
}

/* Leave through an exit. - Thoric */
CMDF( do_leave )
{
   EXIT_DATA *pexit;
   bool found;

   found = false;
   if( !argument || argument[0] == '\0' )
   {
      for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
      {
         if( xIS_SET( pexit->exit_info, EX_xLEAVE ) )
         {
            move_char( ch, pexit, 0 );
            return;
         }
      }
      if( ch->in_room->sector_type == SECT_INSIDE || !is_outside( ch ) )
      {
         for( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
         {
            if( pexit->to_room && pexit->to_room->sector_type != SECT_INSIDE
            && !xIS_SET( pexit->to_room->room_flags, ROOM_INDOORS ) )
            {
               move_char( ch, pexit, 0 );
               return;
            }
         }
      }
      send_to_char( "You can't find an exit here.\r\n", ch );
      return;
   }

   if( ( pexit = find_door( ch, argument, true ) ) && xIS_SET( pexit->exit_info, EX_xLEAVE ) )
   {
      move_char( ch, pexit, 0 );
      return;
   }
   send_to_char( "You can't leave that way.\r\n", ch );
}

/*
 * Check to see if an exit in the room is pulling (or pushing) players around.
 * Some types may cause damage.					-Thoric
 *
 * People kept requesting currents (like SillyMUD has), so I went all out
 * and added the ability for an exit to have a "pull" or a "push" force
 * and to handle different types much beyond a simple water current.
 *
 * This check is called by violence_update().  I'm not sure if this is the
 * best way to do it, or if it should be handled by a special queue.
 *
 * Future additions to this code may include equipment being blown away in
 * the wind (mostly headwear), and people being hit by flying objects
 *
 * TODO:
 *	handle more pulltypes
 *	give "entrance" messages for players and objects
 *	proper handling of player resistance to push/pulling
 */
ch_ret pullcheck( CHAR_DATA *ch, int pulse )
{
   ROOM_INDEX_DATA *room;
   EXIT_DATA *xtmp, *xit = NULL;
   OBJ_DATA *obj, *obj_next;
   bool move = false, moveobj = true, showroom = true;
   int pullfact, pull;
   int resistance;
   char *tochar = NULL, *toroom = NULL, *objmsg = NULL;
   char *destrm = NULL, *destob = NULL, *dtxt = "somewhere";

   if( !( room = ch->in_room ) )
   {
      bug( "pullcheck: %s not in a room?!?", ch->name );
      return rNONE;
   }

   /* Find the exit with the strongest force (if any) */
   for( xtmp = room->first_exit; xtmp; xtmp = xtmp->next )
      if( xtmp->pull && xtmp->to_room && ( !xit || abs( xtmp->pull ) > abs( xit->pull ) ) )
         xit = xtmp;

   if( !xit )
      return rNONE;

   pull = xit->pull;

   /* strength also determines frequency */
   pullfact = URANGE( 1, 20 - ( abs( pull ) / 5 ), 20 );

   /* strongest pull not ready yet... check for one that is */
   if( ( pulse % pullfact ) != 0 )
   {
      for( xit = room->first_exit; xit; xit = xit->next )
         if( xit->pull && xit->to_room )
         {
            pull = xit->pull;
            pullfact = URANGE( 1, 20 - ( abs( pull ) / 5 ), 20 );
            if( ( pulse % pullfact ) != 0 )
               break;
         }

      if( !xit )
         return rNONE;
   }

   /* negative pull = push... get the reverse exit if any */
   if( pull < 0 )
      if( !( xit = get_exit( room, rev_dir[xit->vdir] ) ) )
         return rNONE;

   dtxt = rev_exit( xit->vdir );

   /*
    * First determine if the player should be moved or not
    * Check various flags, spells, the players position and strength vs.
    * the pull, etc... any kind of checks you like.
    */
   switch( xit->pulltype )
   {
      case PULL_CURRENT:
      case PULL_WHIRLPOOL:
         if( room->sector_type == SECT_WATER_SWIM || room->sector_type == SECT_WATER_NOSWIM )
            move = true;
         else if( room->sector_type == SECT_UNDERWATER || room->sector_type == SECT_OCEANFLOOR )
            move = true;
         break;

      case PULL_GEYSER:
      case PULL_WAVE:
         move = true;
         break;

      case PULL_WIND:
      case PULL_STORM:
         move = true;
         break;

      case PULL_COLDWIND:
         move = true;
         break;

      case PULL_HOTAIR:
         move = true;
         break;

      case PULL_BREEZE:
         move = false;
         break;

         /*
          * exits with these pulltypes should also be blocked from movement
          * ie: a secret locked pickproof door with the name "_sinkhole_", etc
          */
      case PULL_EARTHQUAKE:
      case PULL_SINKHOLE:
      case PULL_QUICKSAND:
      case PULL_LANDSLIDE:
      case PULL_SLIP:
      case PULL_LAVA:
         if( ( ch->mount && !is_floating( ch->mount ) ) || ( !ch->mount && !is_floating( ch ) ) )
            move = true;
         break;

         /* as if player moved in that direction him/herself */
      case PULL_UNDEFINED:
         return move_char( ch, xit, 0 );

         /* all other cases ALWAYS move */
      default:
         move = true;
         break;
   }

   /* assign some nice text messages */
   switch( xit->pulltype )
   {
      case PULL_MYSTERIOUS:
         /* no messages to anyone */
         showroom = false;
         break;
      case PULL_WHIRLPOOL:
      case PULL_VACUUM:
         tochar = "You are sucked $T!";
         toroom = "$n is sucked $T!";
         destrm = "$n is sucked in from $T!";
         objmsg = "$p is sucked $T.";
         destob = "$p is sucked in from $T!";
         break;
      case PULL_CURRENT:
      case PULL_LAVA:
         tochar = "You drift $T.";
         toroom = "$n drifts $T.";
         destrm = "$n drifts in from $T.";
         objmsg = "$p drifts $T.";
         destob = "$p drifts in from $T.";
         break;
      case PULL_BREEZE:
         tochar = "You drift $T.";
         toroom = "$n drifts $T.";
         destrm = "$n drifts in from $T.";
         objmsg = "$p drifts $T in the breeze.";
         destob = "$p drifts in from $T.";
         break;
      case PULL_GEYSER:
      case PULL_WAVE:
         tochar = "You are pushed $T!";
         toroom = "$n is pushed $T!";
         destrm = "$n is pushed in from $T!";
         destob = "$p floats in from $T.";
         break;
      case PULL_EARTHQUAKE:
         tochar = "The earth opens up and you fall $T!";
         toroom = "The earth opens up and $n falls $T!";
         destrm = "$n falls from $T!";
         objmsg = "$p falls $T.";
         destob = "$p falls from $T.";
         break;
      case PULL_SINKHOLE:
         tochar = "The ground suddenly gives way and you fall $T!";
         toroom = "The ground suddenly gives way beneath $n!";
         destrm = "$n falls from $T!";
         objmsg = "$p falls $T.";
         destob = "$p falls from $T.";
         break;
      case PULL_QUICKSAND:
         tochar = "You begin to sink $T into the quicksand!";
         toroom = "$n begins to sink $T into the quicksand!";
         destrm = "$n sinks in from $T.";
         objmsg = "$p begins to sink $T into the quicksand.";
         destob = "$p sinks in from $T.";
         break;
      case PULL_LANDSLIDE:
         tochar = "The ground starts to slide $T, taking you with it!";
         toroom = "The ground starts to slide $T, taking $n with it!";
         destrm = "$n slides in from $T.";
         objmsg = "$p slides $T.";
         destob = "$p slides in from $T.";
         break;
      case PULL_SLIP:
         tochar = "You lose your footing!";
         toroom = "$n loses $s footing!";
         destrm = "$n slides in from $T.";
         objmsg = "$p slides $T.";
         destob = "$p slides in from $T.";
         break;
      case PULL_VORTEX:
         tochar = "You are sucked into a swirling vortex of colors!";
         toroom = "$n is sucked into a swirling vortex of colors!";
         toroom = "$n appears from a swirling vortex of colors!";
         objmsg = "$p is sucked into a swirling vortex of colors!";
         objmsg = "$p appears from a swirling vortex of colors!";
         break;
      case PULL_HOTAIR:
         tochar = "A blast of hot air blows you $T!";
         toroom = "$n is blown $T by a blast of hot air!";
         destrm = "$n is blown in from $T by a blast of hot air!";
         objmsg = "$p is blown $T.";
         destob = "$p is blown in from $T.";
         break;
      case PULL_COLDWIND:
         tochar = "A bitter cold wind forces you $T!";
         toroom = "$n is forced $T by a bitter cold wind!";
         destrm = "$n is forced in from $T by a bitter cold wind!";
         objmsg = "$p is blown $T.";
         destob = "$p is blown in from $T.";
         break;
      case PULL_WIND:
         tochar = "A strong wind pushes you $T!";
         toroom = "$n is blown $T by a strong wind!";
         destrm = "$n is blown in from $T by a strong wind!";
         objmsg = "$p is blown $T.";
         destob = "$p is blown in from $T.";
         break;
      case PULL_STORM:
         tochar = "The raging storm drives you $T!";
         toroom = "$n is driven $T by the raging storm!";
         destrm = "$n is driven in from $T by a raging storm!";
         objmsg = "$p is blown $T.";
         destob = "$p is blown in from $T.";
         break;
      default:
         if( pull > 0 )
         {
            tochar = "You are pulled $T!";
            toroom = "$n is pulled $T.";
            destrm = "$n is pulled in from $T.";
            objmsg = "$p is pulled $T.";
            objmsg = "$p is pulled in from $T.";
         }
         else
         {
            tochar = "You are pushed $T!";
            toroom = "$n is pushed $T.";
            destrm = "$n is pushed in from $T.";
            objmsg = "$p is pushed $T.";
            objmsg = "$p is pushed in from $T.";
         }
         break;
   }

   /* Do the moving */
   if( move )
   {
      /* display an appropriate exit message */
      if( tochar )
      {
         act( AT_PLAIN, tochar, ch, NULL, dir_name[xit->vdir], TO_CHAR );
         send_to_char( "\r\n", ch );
      }
      if( toroom )
         act( AT_PLAIN, toroom, ch, NULL, dir_name[xit->vdir], TO_ROOM );

      /* display an appropriate entrance message */
      if( destrm && xit->to_room->first_person )
      {
         act( AT_PLAIN, destrm, xit->to_room->first_person, NULL, dtxt, TO_CHAR );
         act( AT_PLAIN, destrm, xit->to_room->first_person, NULL, dtxt, TO_ROOM );
      }

      /* move the char */
      if( xit->pulltype == PULL_SLIP )
         return move_char( ch, xit, 1 );
      char_from_room( ch );
      char_to_room( ch, xit->to_room );

      if( showroom )
         do_look( ch, "auto" );

      /* move the mount too */
      if( ch->mount )
      {
         char_from_room( ch->mount );
         char_to_room( ch->mount, xit->to_room );
         if( showroom )
            do_look( ch->mount, "auto" );
      }
   }

   /* move objects in the room */
   if( moveobj )
   {
      for( obj = room->first_content; obj; obj = obj_next )
      {
         obj_next = obj->next_content;

         if( is_obj_stat( obj, ITEM_BURIED ) || can_wear( obj, ITEM_NO_TAKE ) )
            continue;

         resistance = get_obj_weight( obj );
         if( is_obj_stat( obj, ITEM_METAL ) )
            resistance = ( resistance * 6 ) / 5;
         switch( obj->item_type )
         {
            case ITEM_SCROLL:
            case ITEM_TRASH:
               resistance >>= 2;
               break;
            case ITEM_SCRAPS:
            case ITEM_CONTAINER:
               resistance >>= 1;
               break;
            case ITEM_WAND:
               resistance = ( resistance * 5 ) / 6;
               break;

            case ITEM_CORPSE_PC:
            case ITEM_CORPSE_NPC:
            case ITEM_FOUNTAIN:
               resistance <<= 2;
               break;
         }

         /* is the pull greater than the resistance of the object? */
         if( ( abs( pull ) * 10 ) > resistance )
         {
            if( objmsg && room->first_person )
            {
               act( AT_PLAIN, objmsg, room->first_person, obj, dir_name[xit->vdir], TO_CHAR );
               act( AT_PLAIN, objmsg, room->first_person, obj, dir_name[xit->vdir], TO_ROOM );
            }
            if( destob && xit->to_room->first_person )
            {
               act( AT_PLAIN, destob, xit->to_room->first_person, obj, dtxt, TO_CHAR );
               act( AT_PLAIN, destob, xit->to_room->first_person, obj, dtxt, TO_ROOM );
            }
            obj_from_room( obj );
            obj_to_room( obj, xit->to_room );
         }
      }
   }
   return rNONE;
}

bool check_bolt( CHAR_DATA *ch, EXIT_DATA *pexit )
{
   if( !ch || !pexit )
      return false;
   if( !xIS_SET( pexit->exit_info, EX_ISDOOR ) )
   {
      send_to_char( "You can't do that.\r\n", ch );
      return false;
   }
   if( !xIS_SET( pexit->exit_info, EX_CLOSED ) )
   {
      send_to_char( "It's not closed.\r\n", ch );
      return false;
   }
   if( !xIS_SET( pexit->exit_info, EX_ISBOLT ) )
   {
      send_to_char( "You don't see a bolt.\r\n", ch );
      return false;
   }
   return true;
}

/* This function bolts a door. Written by Blackmane */
CMDF( do_bolt )
{
   EXIT_DATA *pexit;

   if( !argument || argument[0] == '\0' )
   {
      send_to_char( "Bolt what?\r\n", ch );
      return;
   }
   if( !( pexit = find_door( ch, argument, true ) ) )
   {
      ch_printf( ch, "You see no %s here.\r\n", argument );
      return;
   }
   if( !check_bolt( ch, pexit ) )
      return;
   if( xIS_SET( pexit->exit_info, EX_BOLTED ) )
   {
      send_to_char( "It's already bolted.\r\n", ch );
      return;
   }
   if( !xIS_SET( pexit->exit_info, EX_SECRET ) || ( pexit->keyword && nifty_is_name( argument, pexit->keyword ) ) )
   {
      send_to_char( "*Clunk*\r\n", ch );
      act( AT_ACTION, "$n bolts the $d.", ch, NULL, pexit->keyword, TO_ROOM );
      set_bexit_flag( pexit, EX_BOLTED );
   }
}

/* This function unbolts a door.  Written by Blackmane */
CMDF( do_unbolt )
{
   EXIT_DATA *pexit;

   if( !argument || argument[0] == '\0' )
   {
      send_to_char( "Unbolt what?\r\n", ch );
      return;
   }
   if( !( pexit = find_door( ch, argument, true ) ) )
   {
      ch_printf( ch, "You see no %s here.\r\n", argument );
      return;
   }
   if( !check_bolt( ch, pexit ) )
      return;
   if( !xIS_SET( pexit->exit_info, EX_BOLTED ) )
   {
      send_to_char( "It's already unbolted.\r\n", ch );
      return;
   }
   if( !xIS_SET( pexit->exit_info, EX_SECRET )  || ( pexit->keyword && nifty_is_name( argument, pexit->keyword ) ) )
   {
      send_to_char( "*Clunk*\r\n", ch );
      act( AT_ACTION, "$n unbolts the $d.", ch, NULL, pexit->keyword, TO_ROOM );
      remove_bexit_flag( pexit, EX_BOLTED );
   }
}