1stMud/CVS/
1stMud/area/CVS/
1stMud/backup/CVS/
1stMud/bin/
1stMud/bin/CVS/
1stMud/bin/extras/
1stMud/bin/extras/CVS/
1stMud/data/CVS/
1stMud/data/i3/CVS/
1stMud/doc/1stMud/
1stMud/doc/1stMud/CVS/
1stMud/doc/CVS/
1stMud/doc/Diku/
1stMud/doc/Diku/CVS/
1stMud/doc/MPDocs/CVS/
1stMud/doc/Merc/CVS/
1stMud/doc/Rom/
1stMud/doc/Rom/CVS/
1stMud/log/CVS/
1stMud/notes/
1stMud/notes/CVS/
1stMud/player/CVS/
1stMud/player/backup/CVS/
1stMud/player/deleted/CVS/
1stMud/src/CVS/
1stMud/src/config/CVS/
1stMud/src/h/CVS/
1stMud/src/o/CVS/
1stMud/win/CVS/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael         *
*  Chastain, Michael Quan, and Mitchell Tse.                              *
*                                                                         *
*  In order to use any part of this Merc Diku Mud, you must comply with   *
*  both the original Diku license in 'license.doc' as well the Merc       *
*  license in 'license.txt'.  In particular, you may not remove either of *
*  these copyright notices.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*          1stMud ROM Derivative (c) 2001-2004 by Markanth                *
*            http://www.firstmud.com/  <markanth@firstmud.com>            *
*         By using this code you have agreed to follow the term of        *
*             the 1stMud license in ../doc/1stMud/LICENSE                 *
***************************************************************************/


#include "merc.h"
#include "interp.h"
#include "recycle.h"
#include "vnums.h"
#include "tables.h"


Proto (int find_door, (CharData *, char *));
Proto (void quest_room_check, (CharData *));

Proto (bool has_key, (CharData *, vnum_t));

void
move_char (CharData * ch, int door, bool follow)
{
  CharData *fch;
  CharData *fch_next;
  RoomIndex *in_room;
  RoomIndex *to_room;
  ExitData *pexit;

  if (door < 0 || door > 5)
    {
      bugf ("Do_move: bad door %d.", door);
      return;
    }


  if (!IsNPC (ch)
      && (p_exit_trigger (ch, door, PRG_MPROG)
	  || p_exit_trigger (ch, door, PRG_OPROG)
	  || p_exit_trigger (ch, door, PRG_RPROG)))
    return;

  in_room = ch->in_room;
  if ((pexit = in_room->exit[door]) == NULL ||
      (to_room = pexit->u1.to_room) == NULL ||
      !can_see_room (ch, pexit->u1.to_room))
    {
      chprintln (ch, "Alas, you cannot go that way.");
      return;
    }

  if (IsSet (pexit->exit_info, EX_CLOSED) &&
      (!IsAffected (ch, AFF_PASS_DOOR) ||
       IsSet (pexit->exit_info, EX_NOPASS)) && !IsTrusted (ch, ANGEL))
    {
      act ("The $d is closed.", ch, NULL, pexit->keyword, TO_CHAR);
      return;
    }

  if (IsAffected (ch, AFF_CHARM) && ch->master != NULL &&
      in_room == ch->master->in_room)
    {
      chprintln (ch, "What?  And leave your beloved master?");
      return;
    }

  if (!is_room_owner (ch, to_room) && room_is_private (to_room))
    {
      chprintln (ch, "That room is private right now.");
      return;
    }

  if (IsSet (to_room->area->area_flags, AREA_CLOSED))
    {
      chprintln (ch, "That area is closed to players.");
      return;
    }
  if (!IsNPC (ch))
    {
      int move;

      if (to_room->guild > -1 && to_room->guild < top_class &&
	  !is_class (ch, to_room->guild) && !IsImmortal (ch))
	{
	  chprintln (ch, "You aren't allowed in there.");
	  return;
	}

      if (in_room->sector_type == SECT_AIR ||
	  to_room->sector_type == SECT_AIR)
	{
	  if (!IsAffected (ch, AFF_FLYING) && !IsImmortal (ch))
	    {
	      chprintln (ch, "You can't fly.");
	      return;
	    }
	}

      if ((in_room->sector_type == SECT_WATER_NOSWIM ||
	   to_room->sector_type == SECT_WATER_NOSWIM) &&
	  !IsAffected (ch, AFF_FLYING))
	{
	  ObjData *obj;
	  bool found;


	  found = false;

	  if (IsImmortal (ch))
	    found = true;

	  for (obj = ch->carrying_first; obj != NULL; obj = obj->next_content)
	    {
	      if (obj->item_type == ITEM_BOAT)
		{
		  found = true;
		  break;
		}
	    }
	  if (!found)
	    {
	      chprintln (ch, "You need a boat to go there.");
	      return;
	    }
	}

      move =
	movement_loss[Min (SECT_MAX - 1, in_room->sector_type)] +
	movement_loss[Min (SECT_MAX - 1, to_room->sector_type)];

      move /= 2;


      if (IsAffected (ch, AFF_FLYING) || IsAffected (ch, AFF_HASTE))
	move /= 2;

      if (IsAffected (ch, AFF_SLOW))
	move *= 2;

      if (ch->move < move)
	{
	  chprintln (ch, "You are too exhausted.");
	  return;
	}

      WaitState (ch, 1);
      ch->move -= move;
    }

  if (ValidStance (GetStance (ch, STANCE_CURRENT)))
    do_function (ch, &do_stance, "");

  if (!IsAffected (ch, AFF_SNEAK) && ch->invis_level < LEVEL_HERO)
    {
      char leave[MIL];

      if (IsAffected (ch, AFF_FLYING))
	strcpy (leave, "flies");
      else if (ch->desc && ch->desc->run_buf)
	strcpy (leave, "runs");
      else if (ch->in_room->sector_type == SECT_WATER_SWIM)
	strcpy (leave, "swims");
      else if (ch->hit < (ch->max_hit / 4))
	strcpy (leave, "crawls");
      else if (ch->hit < (ch->max_hit / 3))
	strcpy (leave, "limps");
      else if (ch->hit < (ch->max_hit / 2))
	strcpy (leave, "staggers");
      else if (IsDrunk (ch))
	strcpy (leave, "stumbles");
      else
	strcpy (leave, "leaves");

      act ("$n leaves $T.", ch, NULL, dir_name[door], TO_ROOM);
    }

  char_from_room (ch);
  char_to_room (ch, to_room);

  if (!IsAffected (ch, AFF_SNEAK) && ch->invis_level < LEVEL_HERO)
    {
      char enter[MIL];

      if (IsAffected (ch, AFF_FLYING))
	strcpy (enter, "flies in");
      else if (ch->desc && ch->desc->run_buf)
	strcpy (enter, "runs by");
      else if (ch->in_room->sector_type == SECT_WATER_SWIM)
	strcpy (enter, "swims in");
      else if (ch->hit < (ch->max_hit / 4))
	strcpy (enter, "crawls in");
      else if (ch->hit < (ch->max_hit / 3))
	strcpy (enter, "limps in");
      else if (ch->hit < (ch->max_hit / 2))
	strcpy (enter, "staggers in");
      else if (IsDrunk (ch))
	strcpy (enter, "stumbles in");
      else
	strcpy (enter, "arrives");
      act ("$n has arrived.", ch, NULL, NULL, TO_ROOM);
    }

  do_function (ch, &do_look, "auto");

  if (in_room == to_room)
    return;
  else if (in_room->area != to_room->area)
    {
      if (to_room->area->sound)
	send_sound (ch, to_room->area->sound);
    }

  for (fch = in_room->person_first; fch != NULL; fch = fch_next)
    {
      fch_next = fch->next_in_room;

      if (fch->master == ch && IsAffected (fch, AFF_CHARM) &&
	  fch->position < POS_STANDING)
	do_function (fch, &do_stand, "");

      if (fch->master == ch && fch->position == POS_STANDING &&
	  can_see_room (fch, to_room))
	{

	  if (IsSet (ch->in_room->room_flags, ROOM_LAW) &&
	      (IsNPC (fch) && IsSet (fch->act, ACT_AGGRESSIVE)))
	    {
	      act ("You can't bring $N into the city.", ch,
		   NULL, fch, TO_CHAR);
	      act ("You aren't allowed in the city.", fch, NULL,
		   NULL, TO_CHAR);
	      continue;
	    }

	  act ("You follow $N.", fch, NULL, ch, TO_CHAR);
	  move_char (fch, door, true);
	}
    }


  if (IsNPC (ch) && HasTriggerMob (ch, TRIG_ENTRY))
    p_percent_trigger (ch, NULL, NULL, NULL, NULL, NULL, TRIG_ENTRY);
  if (!IsNPC (ch))
    {
      p_greet_trigger (ch, PRG_MPROG);
      p_greet_trigger (ch, PRG_OPROG);
      p_greet_trigger (ch, PRG_RPROG);
      quest_room_check (ch);
    }

  return;
}

Do_Fun (do_north)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_NORTH, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

Do_Fun (do_east)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_EAST, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

Do_Fun (do_south)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_SOUTH, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

Do_Fun (do_west)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_WEST, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

Do_Fun (do_up)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_UP, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

Do_Fun (do_down)
{
  RoomIndex *was_room;

  was_room = ch->in_room;
  move_char (ch, DIR_DOWN, false);
  if (ch->desc && was_room == ch->in_room)
    free_runbuf (ch->desc);
  return;
}

int
find_door (CharData * ch, char *arg)
{
  ExitData *pexit;
  int door;

  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
    {
      for (door = 0; door < MAX_DIR; door++)
	{
	  if ((pexit = ch->in_room->exit[door]) != NULL &&
	      IsSet (pexit->exit_info, EX_ISDOOR) &&
	      pexit->keyword != NULL && is_name (arg, pexit->keyword))
	    return door;
	}
      act ("I see no $T here.", ch, NULL, arg, TO_CHAR);
      return -1;
    }

  if ((pexit = ch->in_room->exit[door]) == NULL)
    {
      act ("I see no door $T here.", ch, NULL, arg, TO_CHAR);
      return -1;
    }

  if (!IsSet (pexit->exit_info, EX_ISDOOR))
    {
      chprintln (ch, "You can't do that.");
      return -1;
    }

  return door;
}

Do_Fun (do_open)
{
  char arg[MAX_INPUT_LENGTH];
  ObjData *obj;
  int door;

  one_argument (argument, arg);

  if (NullStr (arg))
    {
      chprintln (ch, "Open what?");
      return;
    }

  if ((obj = get_obj_here (ch, NULL, arg)) != NULL)
    {

      if (obj->item_type == ITEM_PORTAL)
	{
	  if (!IsSet (obj->value[1], EX_ISDOOR))
	    {
	      chprintln (ch, "You can't do that.");
	      return;
	    }

	  if (!IsSet (obj->value[1], EX_CLOSED))
	    {
	      chprintln (ch, "It's already open.");
	      return;
	    }

	  if (IsSet (obj->value[1], EX_LOCKED))
	    {
	      chprintln (ch, "It's locked.");
	      return;
	    }

	  RemBit (obj->value[1], EX_CLOSED);
	  act ("You open $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n opens $p.", ch, obj, NULL, TO_ROOM);
	  return;
	}


      if (obj->item_type != ITEM_CONTAINER)
	{
	  chprintln (ch, "That's not a container.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSED))
	{
	  chprintln (ch, "It's already open.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSEABLE))
	{
	  chprintln (ch, "You can't do that.");
	  return;
	}
      if (IsSet (obj->value[1], CONT_LOCKED))
	{
	  chprintln (ch, "It's locked.");
	  return;
	}

      RemBit (obj->value[1], CONT_CLOSED);
      act ("You open $p.", ch, obj, NULL, TO_CHAR);
      act ("$n opens $p.", ch, obj, NULL, TO_ROOM);
      return;
    }

  if ((door = find_door (ch, arg)) >= 0)
    {

      RoomIndex *to_room;
      ExitData *pexit;
      ExitData *pexit_rev;

      pexit = ch->in_room->exit[door];
      if (!IsSet (pexit->exit_info, EX_CLOSED))
	{
	  chprintln (ch, "It's already open.");
	  return;
	}
      if (IsSet (pexit->exit_info, EX_LOCKED))
	{
	  chprintln (ch, "It's locked.");
	  return;
	}

      RemBit (pexit->exit_info, EX_CLOSED);
      act ("$n opens the $d.", ch, NULL, pexit->keyword, TO_ROOM);
      chprintln (ch, "Ok.");


      if ((to_room = pexit->u1.to_room) != NULL &&
	  (pexit_rev = to_room->exit[rev_dir[door]]) != NULL &&
	  pexit_rev->u1.to_room == ch->in_room)
	{
	  CharData *rch;

	  RemBit (pexit_rev->exit_info, EX_CLOSED);
	  for (rch = to_room->person_first; rch != NULL;
	       rch = rch->next_in_room)
	    act ("The $d opens.", rch, NULL, pexit_rev->keyword, TO_CHAR);
	}
    }

  return;
}

Do_Fun (do_close)
{
  char arg[MAX_INPUT_LENGTH];
  ObjData *obj;
  int door;

  one_argument (argument, arg);

  if (NullStr (arg))
    {
      chprintln (ch, "Close what?");
      return;
    }

  if ((obj = get_obj_here (ch, NULL, arg)) != NULL)
    {

      if (obj->item_type == ITEM_PORTAL)
	{

	  if (!IsSet (obj->value[1], EX_ISDOOR) ||
	      IsSet (obj->value[1], EX_NOCLOSE))
	    {
	      chprintln (ch, "You can't do that.");
	      return;
	    }

	  if (IsSet (obj->value[1], EX_CLOSED))
	    {
	      chprintln (ch, "It's already closed.");
	      return;
	    }

	  SetBit (obj->value[1], EX_CLOSED);
	  act ("You close $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n closes $p.", ch, obj, NULL, TO_ROOM);
	  return;
	}


      if (obj->item_type != ITEM_CONTAINER)
	{
	  chprintln (ch, "That's not a container.");
	  return;
	}
      if (IsSet (obj->value[1], CONT_CLOSED))
	{
	  chprintln (ch, "It's already closed.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSEABLE))
	{
	  chprintln (ch, "You can't do that.");
	  return;
	}

      SetBit (obj->value[1], CONT_CLOSED);
      act ("You close $p.", ch, obj, NULL, TO_CHAR);
      act ("$n closes $p.", ch, obj, NULL, TO_ROOM);
      return;
    }

  if ((door = find_door (ch, arg)) >= 0)
    {

      RoomIndex *to_room;
      ExitData *pexit;
      ExitData *pexit_rev;

      pexit = ch->in_room->exit[door];
      if (IsSet (pexit->exit_info, EX_CLOSED))
	{
	  chprintln (ch, "It's already closed.");
	  return;
	}

      SetBit (pexit->exit_info, EX_CLOSED);
      act ("$n closes the $d.", ch, NULL, pexit->keyword, TO_ROOM);
      chprintln (ch, "Ok.");


      if ((to_room = pexit->u1.to_room) != NULL &&
	  (pexit_rev = to_room->exit[rev_dir[door]]) != NULL &&
	  pexit_rev->u1.to_room == ch->in_room)
	{
	  CharData *rch;

	  SetBit (pexit_rev->exit_info, EX_CLOSED);
	  for (rch = to_room->person_first; rch != NULL;
	       rch = rch->next_in_room)
	    act ("The $d closes.", rch, NULL, pexit_rev->keyword, TO_CHAR);
	}
    }

  return;
}

bool
has_key (CharData * ch, vnum_t key)
{
  ObjData *obj;

  for (obj = ch->carrying_first; obj != NULL; obj = obj->next_content)
    {
      if (obj->pIndexData->vnum == key)
	return true;
    }

  return false;
}

Do_Fun (do_lock)
{
  char arg[MAX_INPUT_LENGTH];
  ObjData *obj;
  int door;

  one_argument (argument, arg);

  if (NullStr (arg))
    {
      chprintln (ch, "Lock what?");
      return;
    }

  if ((obj = get_obj_here (ch, NULL, arg)) != NULL)
    {

      if (obj->item_type == ITEM_PORTAL)
	{
	  if (!IsSet (obj->value[1], EX_ISDOOR) ||
	      IsSet (obj->value[1], EX_NOCLOSE))
	    {
	      chprintln (ch, "You can't do that.");
	      return;
	    }
	  if (!IsSet (obj->value[1], EX_CLOSED))
	    {
	      chprintln (ch, "It's not closed.");
	      return;
	    }

	  if (obj->value[4] < 0 || IsSet (obj->value[1], EX_NOLOCK))
	    {
	      chprintln (ch, "It can't be locked.");
	      return;
	    }

	  if (!has_key (ch, obj->value[4]))
	    {
	      chprintln (ch, "You lack the key.");
	      return;
	    }

	  if (IsSet (obj->value[1], EX_LOCKED))
	    {
	      chprintln (ch, "It's already locked.");
	      return;
	    }

	  SetBit (obj->value[1], EX_LOCKED);
	  act ("You lock $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n locks $p.", ch, obj, NULL, TO_ROOM);
	  return;
	}


      if (obj->item_type != ITEM_CONTAINER)
	{
	  chprintln (ch, "That's not a container.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSED))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (obj->value[2] < 0)
	{
	  chprintln (ch, "It can't be locked.");
	  return;
	}
      if (!has_key (ch, obj->value[2]))
	{
	  chprintln (ch, "You lack the key.");
	  return;
	}
      if (IsSet (obj->value[1], CONT_LOCKED))
	{
	  chprintln (ch, "It's already locked.");
	  return;
	}

      SetBit (obj->value[1], CONT_LOCKED);
      act ("You lock $p.", ch, obj, NULL, TO_CHAR);
      act ("$n locks $p.", ch, obj, NULL, TO_ROOM);
      return;
    }

  if ((door = find_door (ch, arg)) >= 0)
    {

      RoomIndex *to_room;
      ExitData *pexit;
      ExitData *pexit_rev;

      pexit = ch->in_room->exit[door];
      if (!IsSet (pexit->exit_info, EX_CLOSED))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (pexit->key < 0)
	{
	  chprintln (ch, "It can't be locked.");
	  return;
	}
      if (!has_key (ch, pexit->key))
	{
	  chprintln (ch, "You lack the key.");
	  return;
	}
      if (IsSet (pexit->exit_info, EX_LOCKED))
	{
	  chprintln (ch, "It's already locked.");
	  return;
	}

      SetBit (pexit->exit_info, EX_LOCKED);
      chprintln (ch, "*Click*");
      act ("$n locks the $d.", ch, NULL, pexit->keyword, TO_ROOM);


      if ((to_room = pexit->u1.to_room) != NULL &&
	  (pexit_rev = to_room->exit[rev_dir[door]]) != NULL &&
	  pexit_rev->u1.to_room == ch->in_room)
	{
	  SetBit (pexit_rev->exit_info, EX_LOCKED);
	}
    }

  return;
}

Do_Fun (do_unlock)
{
  char arg[MAX_INPUT_LENGTH];
  ObjData *obj;
  int door;

  one_argument (argument, arg);

  if (NullStr (arg))
    {
      chprintln (ch, "Unlock what?");
      return;
    }

  if ((obj = get_obj_here (ch, NULL, arg)) != NULL)
    {

      if (obj->item_type == ITEM_PORTAL)
	{
	  if (!IsSet (obj->value[1], EX_ISDOOR))
	    {
	      chprintln (ch, "You can't do that.");
	      return;
	    }

	  if (!IsSet (obj->value[1], EX_CLOSED))
	    {
	      chprintln (ch, "It's not closed.");
	      return;
	    }

	  if (obj->value[4] < 0)
	    {
	      chprintln (ch, "It can't be unlocked.");
	      return;
	    }

	  if (!has_key (ch, obj->value[4]))
	    {
	      chprintln (ch, "You lack the key.");
	      return;
	    }

	  if (!IsSet (obj->value[1], EX_LOCKED))
	    {
	      chprintln (ch, "It's already unlocked.");
	      return;
	    }

	  RemBit (obj->value[1], EX_LOCKED);
	  act ("You unlock $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n unlocks $p.", ch, obj, NULL, TO_ROOM);
	  return;
	}


      if (obj->item_type != ITEM_CONTAINER)
	{
	  chprintln (ch, "That's not a container.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSED))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (obj->value[2] < 0)
	{
	  chprintln (ch, "It can't be unlocked.");
	  return;
	}
      if (!has_key (ch, obj->value[2]))
	{
	  chprintln (ch, "You lack the key.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_LOCKED))
	{
	  chprintln (ch, "It's already unlocked.");
	  return;
	}

      RemBit (obj->value[1], CONT_LOCKED);
      act ("You unlock $p.", ch, obj, NULL, TO_CHAR);
      act ("$n unlocks $p.", ch, obj, NULL, TO_ROOM);
      return;
    }

  if ((door = find_door (ch, arg)) >= 0)
    {

      RoomIndex *to_room;
      ExitData *pexit;
      ExitData *pexit_rev;

      pexit = ch->in_room->exit[door];
      if (!IsSet (pexit->exit_info, EX_CLOSED))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (pexit->key < 0)
	{
	  chprintln (ch, "It can't be unlocked.");
	  return;
	}
      if (!has_key (ch, pexit->key))
	{
	  chprintln (ch, "You lack the key.");
	  return;
	}
      if (!IsSet (pexit->exit_info, EX_LOCKED))
	{
	  chprintln (ch, "It's already unlocked.");
	  return;
	}

      RemBit (pexit->exit_info, EX_LOCKED);
      chprintln (ch, "*Click*");
      act ("$n unlocks the $d.", ch, NULL, pexit->keyword, TO_ROOM);


      if ((to_room = pexit->u1.to_room) != NULL &&
	  (pexit_rev = to_room->exit[rev_dir[door]]) != NULL &&
	  pexit_rev->u1.to_room == ch->in_room)
	{
	  RemBit (pexit_rev->exit_info, EX_LOCKED);
	}
    }

  return;
}

Do_Fun (do_pick)
{
  char arg[MAX_INPUT_LENGTH];
  CharData *gch;
  ObjData *obj;
  int door;

  one_argument (argument, arg);

  if (NullStr (arg))
    {
      chprintln (ch, "Pick what?");
      return;
    }

  WaitState (ch, skill_table[gsn_pick_lock].beats);


  for (gch = ch->in_room->person_first; gch; gch = gch->next_in_room)
    {
      if (IsNPC (gch) && IsAwake (gch) && ch->level + 5 < gch->level)
	{
	  act ("$N is standing too close to the lock.", ch, NULL,
	       gch, TO_CHAR);
	  return;
	}
    }

  if (!IsNPC (ch) && number_percent () > get_skill (ch, gsn_pick_lock))
    {
      chprintln (ch, "You failed.");
      check_improve (ch, gsn_pick_lock, false, 2);
      return;
    }

  if ((obj = get_obj_here (ch, NULL, arg)) != NULL)
    {

      if (obj->item_type == ITEM_PORTAL)
	{
	  if (!IsSet (obj->value[1], EX_ISDOOR))
	    {
	      chprintln (ch, "You can't do that.");
	      return;
	    }

	  if (!IsSet (obj->value[1], EX_CLOSED))
	    {
	      chprintln (ch, "It's not closed.");
	      return;
	    }

	  if (obj->value[4] < 0)
	    {
	      chprintln (ch, "It can't be unlocked.");
	      return;
	    }

	  if (IsSet (obj->value[1], EX_PICKPROOF))
	    {
	      chprintln (ch, "You failed.");
	      return;
	    }

	  RemBit (obj->value[1], EX_LOCKED);
	  act ("You pick the lock on $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n picks the lock on $p.", ch, obj, NULL, TO_ROOM);
	  check_improve (ch, gsn_pick_lock, true, 2);
	  return;
	}


      if (obj->item_type != ITEM_CONTAINER)
	{
	  chprintln (ch, "That's not a container.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_CLOSED))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (obj->value[2] < 0)
	{
	  chprintln (ch, "It can't be unlocked.");
	  return;
	}
      if (!IsSet (obj->value[1], CONT_LOCKED))
	{
	  chprintln (ch, "It's already unlocked.");
	  return;
	}
      if (IsSet (obj->value[1], CONT_PICKPROOF))
	{
	  chprintln (ch, "You failed.");
	  return;
	}

      RemBit (obj->value[1], CONT_LOCKED);
      act ("You pick the lock on $p.", ch, obj, NULL, TO_CHAR);
      act ("$n picks the lock on $p.", ch, obj, NULL, TO_ROOM);
      check_improve (ch, gsn_pick_lock, true, 2);
      return;
    }

  if ((door = find_door (ch, arg)) >= 0)
    {

      RoomIndex *to_room;
      ExitData *pexit;
      ExitData *pexit_rev;

      pexit = ch->in_room->exit[door];
      if (!IsSet (pexit->exit_info, EX_CLOSED) && !IsImmortal (ch))
	{
	  chprintln (ch, "It's not closed.");
	  return;
	}
      if (pexit->key < 0 && !IsImmortal (ch))
	{
	  chprintln (ch, "It can't be picked.");
	  return;
	}
      if (!IsSet (pexit->exit_info, EX_LOCKED))
	{
	  chprintln (ch, "It's already unlocked.");
	  return;
	}
      if (IsSet (pexit->exit_info, EX_PICKPROOF) && !IsImmortal (ch))
	{
	  chprintln (ch, "You failed.");
	  return;
	}

      RemBit (pexit->exit_info, EX_LOCKED);
      chprintln (ch, "*Click*");
      act ("$n picks the $d.", ch, NULL, pexit->keyword, TO_ROOM);
      check_improve (ch, gsn_pick_lock, true, 2);


      if ((to_room = pexit->u1.to_room) != NULL &&
	  (pexit_rev = to_room->exit[rev_dir[door]]) != NULL &&
	  pexit_rev->u1.to_room == ch->in_room)
	{
	  RemBit (pexit_rev->exit_info, EX_LOCKED);
	}
    }

  return;
}

Do_Fun (do_stand)
{
  ObjData *obj = NULL;

  if (!NullStr (argument))
    {
      if (ch->position == POS_FIGHTING)
	{
	  chprintln (ch, "Maybe you should finish fighting first?");
	  return;
	}
      obj = get_obj_list (ch, argument, ch->in_room->content_first);
      if (obj == NULL)
	{
	  chprintln (ch, "You don't see that here.");
	  return;
	}
      if (obj->item_type != ITEM_FURNITURE ||
	  (!IsSet (obj->value[2], STAND_AT) &&
	   !IsSet (obj->value[2], STAND_ON) &&
	   !IsSet (obj->value[2], STAND_IN)))
	{
	  chprintln (ch, "You can't seem to find a place to stand.");
	  return;
	}
      if (ch->on != obj && count_users (obj) >= obj->value[0])
	{
	  act_new ("There's no room to stand on $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  return;
	}
      ch->on = obj;
      if (HasTriggerObj (obj, TRIG_SIT))
	p_percent_trigger (NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);
    }

  switch (ch->position)
    {
    case POS_SLEEPING:
      if (IsAffected (ch, AFF_SLEEP))
	{
	  chprintln (ch, "You can't wake up!");
	  return;
	}

      if (obj == NULL)
	{
	  chprintln (ch, "You wake and stand up.");
	  act ("$n wakes and stands up.", ch, NULL, NULL, TO_ROOM);
	  ch->on = NULL;
	}
      else if (IsSet (obj->value[2], STAND_AT))
	{
	  act_new ("You wake and stand at $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and stands at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], STAND_ON))
	{
	  act_new ("You wake and stand on $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and stands on $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act_new ("You wake and stand in $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and stands in $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_STANDING;
      do_function (ch, &do_look, "auto");
      break;

    case POS_RESTING:
    case POS_SITTING:
      if (obj == NULL)
	{
	  chprintln (ch, "You stand up.");
	  act ("$n stands up.", ch, NULL, NULL, TO_ROOM);
	  ch->on = NULL;
	}
      else if (IsSet (obj->value[2], STAND_AT))
	{
	  act ("You stand at $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n stands at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], STAND_ON))
	{
	  act ("You stand on $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n stands on $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act ("You stand in $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n stands on $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_STANDING;
      break;

    case POS_STANDING:
      chprintln (ch, "You are already standing.");
      break;

    case POS_FIGHTING:
      chprintln (ch, "You are already fighting!");
      break;
    default:
      break;
    }

  return;
}

Do_Fun (do_rest)
{
  ObjData *obj = NULL;

  if (ch->position == POS_FIGHTING)
    {
      chprintln (ch, "You are already fighting!");
      return;
    }


  if (!NullStr (argument))
    {
      obj = get_obj_list (ch, argument, ch->in_room->content_first);
      if (obj == NULL)
	{
	  chprintln (ch, "You don't see that here.");
	  return;
	}
    }
  else
    obj = ch->on;

  if (obj != NULL)
    {
      if (obj->item_type != ITEM_FURNITURE ||
	  (!IsSet (obj->value[2], REST_ON) &&
	   !IsSet (obj->value[2], REST_IN) &&
	   !IsSet (obj->value[2], REST_AT)))
	{
	  chprintln (ch, "You can't rest on that.");
	  return;
	}

      if (obj != NULL && ch->on != obj && count_users (obj) >= obj->value[0])
	{
	  act_new ("There's no more room on $p.", ch, obj, NULL, TO_CHAR,
		   POS_DEAD);
	  return;
	}

      ch->on = obj;
      if (HasTriggerObj (obj, TRIG_SIT))
	p_percent_trigger (NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);

    }

  switch (ch->position)
    {
    case POS_SLEEPING:
      if (IsAffected (ch, AFF_SLEEP))
	{
	  chprintln (ch, "You can't wake up!");
	  return;
	}

      if (obj == NULL)
	{
	  chprintln (ch, "You wake up and start resting.");
	  act ("$n wakes up and starts resting.", ch, NULL, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_AT))
	{
	  act_new ("You wake up and rest at $p.", ch, obj, NULL,
		   TO_CHAR, POS_SLEEPING);
	  act ("$n wakes up and rests at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_ON))
	{
	  act_new ("You wake up and rest on $p.", ch, obj, NULL,
		   TO_CHAR, POS_SLEEPING);
	  act ("$n wakes up and rests on $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act_new ("You wake up and rest in $p.", ch, obj, NULL,
		   TO_CHAR, POS_SLEEPING);
	  act ("$n wakes up and rests in $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_RESTING;
      break;

    case POS_RESTING:
      chprintln (ch, "You are already resting.");
      break;

    case POS_STANDING:
      if (obj == NULL)
	{
	  chprintln (ch, "You rest.");
	  act ("$n sits down and rests.", ch, NULL, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_AT))
	{
	  act ("You sit down at $p and rest.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits down at $p and rests.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_ON))
	{
	  act ("You sit on $p and rest.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits on $p and rests.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act ("You rest in $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n rests in $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_RESTING;
      break;

    case POS_SITTING:
      if (obj == NULL)
	{
	  chprintln (ch, "You rest.");
	  act ("$n rests.", ch, NULL, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_AT))
	{
	  act ("You rest at $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n rests at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], REST_ON))
	{
	  act ("You rest on $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n rests on $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act ("You rest in $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n rests in $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_RESTING;
      break;
    default:
      break;
    }

  return;
}

Do_Fun (do_sit)
{
  ObjData *obj = NULL;

  if (ch->position == POS_FIGHTING)
    {
      chprintln (ch, "Maybe you should finish this fight first?");
      return;
    }


  if (!NullStr (argument))
    {
      obj = get_obj_list (ch, argument, ch->in_room->content_first);
      if (obj == NULL)
	{
	  chprintln (ch, "You don't see that here.");
	  return;
	}
    }
  else
    obj = ch->on;

  if (obj != NULL)
    {
      if (obj->item_type != ITEM_FURNITURE ||
	  (!IsSet (obj->value[2], SIT_ON) && !IsSet (obj->value[2], SIT_IN)
	   && !IsSet (obj->value[2], SIT_AT)))
	{
	  chprintln (ch, "You can't sit on that.");
	  return;
	}

      if (obj != NULL && ch->on != obj && count_users (obj) >= obj->value[0])
	{
	  act_new ("There's no more room on $p.", ch, obj, NULL, TO_CHAR,
		   POS_DEAD);
	  return;
	}

      ch->on = obj;
      if (HasTriggerObj (obj, TRIG_SIT))
	p_percent_trigger (NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);
    }
  switch (ch->position)
    {
    case POS_SLEEPING:
      if (IsAffected (ch, AFF_SLEEP))
	{
	  chprintln (ch, "You can't wake up!");
	  return;
	}

      if (obj == NULL)
	{
	  chprintln (ch, "You wake and sit up.");
	  act ("$n wakes and sits up.", ch, NULL, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], SIT_AT))
	{
	  act_new ("You wake and sit at $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and sits at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], SIT_ON))
	{
	  act_new ("You wake and sit on $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and sits at $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act_new ("You wake and sit in $p.", ch, obj, NULL,
		   TO_CHAR, POS_DEAD);
	  act ("$n wakes and sits in $p.", ch, obj, NULL, TO_ROOM);
	}

      ch->position = POS_SITTING;
      break;
    case POS_RESTING:
      if (obj == NULL)
	chprintln (ch, "You stop resting.");
      else if (IsSet (obj->value[2], SIT_AT))
	{
	  act ("You sit at $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], SIT_ON))
	{
	  act ("You sit on $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits on $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_SITTING;
      break;
    case POS_SITTING:
      chprintln (ch, "You are already sitting down.");
      break;
    case POS_STANDING:
      if (obj == NULL)
	{
	  chprintln (ch, "You sit down.");
	  act ("$n sits down on the ground.", ch, NULL, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], SIT_AT))
	{
	  act ("You sit down at $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits down at $p.", ch, obj, NULL, TO_ROOM);
	}
      else if (IsSet (obj->value[2], SIT_ON))
	{
	  act ("You sit on $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits on $p.", ch, obj, NULL, TO_ROOM);
	}
      else
	{
	  act ("You sit down in $p.", ch, obj, NULL, TO_CHAR);
	  act ("$n sits down in $p.", ch, obj, NULL, TO_ROOM);
	}
      ch->position = POS_SITTING;
      break;
    default:
      break;
    }
  return;
}

Do_Fun (do_sleep)
{
  ObjData *obj = NULL;

  switch (ch->position)
    {
    case POS_SLEEPING:
      chprintln (ch, "You are already sleeping.");
      break;

    case POS_RESTING:
    case POS_SITTING:
    case POS_STANDING:
      if (NullStr (argument) && ch->on == NULL)
	{
	  chprintln (ch, "You go to sleep.");
	  act ("$n goes to sleep.", ch, NULL, NULL, TO_ROOM);
	  ch->position = POS_SLEEPING;
	}
      else
	{

	  if (NullStr (argument))
	    obj = ch->on;
	  else
	    obj = get_obj_list (ch, argument, ch->in_room->content_first);

	  if (obj == NULL)
	    {
	      chprintln (ch, "You don't see that here.");
	      return;
	    }
	  if (obj->item_type != ITEM_FURNITURE ||
	      (!IsSet (obj->value[2], SLEEP_ON) &&
	       !IsSet (obj->value[2], SLEEP_IN) &&
	       !IsSet (obj->value[2], SLEEP_AT)))
	    {
	      chprintln (ch, "You can't sleep on that!");
	      return;
	    }

	  if (ch->on != obj && count_users (obj) >= obj->value[0])
	    {
	      act_new ("There is no room on $p for you.", ch,
		       obj, NULL, TO_CHAR, POS_DEAD);
	      return;
	    }

	  ch->on = obj;
	  if (HasTriggerObj (obj, TRIG_SIT))
	    p_percent_trigger (NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);

	  if (IsSet (obj->value[2], SLEEP_AT))
	    {
	      act ("You go to sleep at $p.", ch, obj, NULL, TO_CHAR);
	      act ("$n goes to sleep at $p.", ch, obj, NULL, TO_ROOM);
	    }
	  else if (IsSet (obj->value[2], SLEEP_ON))
	    {
	      act ("You go to sleep on $p.", ch, obj, NULL, TO_CHAR);
	      act ("$n goes to sleep on $p.", ch, obj, NULL, TO_ROOM);
	    }
	  else
	    {
	      act ("You go to sleep in $p.", ch, obj, NULL, TO_CHAR);
	      act ("$n goes to sleep in $p.", ch, obj, NULL, TO_ROOM);
	    }
	  ch->position = POS_SLEEPING;
	}
      break;

    case POS_FIGHTING:
      chprintln (ch, "You are already fighting!");
      break;
    default:
      break;
    }

  return;
}

Do_Fun (do_wake)
{
  char arg[MAX_INPUT_LENGTH];
  CharData *victim;

  one_argument (argument, arg);
  if (NullStr (arg))
    {
      do_function (ch, &do_stand, "");
      return;
    }

  if (!IsAwake (ch))
    {
      chprintln (ch, "You are asleep yourself!");
      return;
    }

  if ((victim = get_char_room (ch, NULL, arg)) == NULL)
    {
      chprintln (ch, "They aren't here.");
      return;
    }

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

  if (IsAffected (victim, AFF_SLEEP))
    {
      act ("You can't wake $M!", ch, NULL, victim, TO_CHAR);
      return;
    }

  act_new ("$n wakes you.", ch, NULL, victim, TO_VICT, POS_SLEEPING);
  do_function (ch, &do_stand, "");
  return;
}

Do_Fun (do_sneak)
{
  AffectData af;

  chprintln (ch, "You attempt to move silently.");
  affect_strip (ch, gsn_sneak);

  if (IsAffected (ch, AFF_SNEAK))
    return;

  if (number_percent () < get_skill (ch, gsn_sneak))
    {
      check_improve (ch, gsn_sneak, true, 3);
      af.where = TO_AFFECTS;
      af.type = gsn_sneak;
      af.level = ch->level;
      af.duration = ch->level;
      af.location = APPLY_NONE;
      af.modifier = 0;
      af.bitvector = AFF_SNEAK;
      affect_to_char (ch, &af);
    }
  else
    check_improve (ch, gsn_sneak, false, 3);

  return;
}

Do_Fun (do_hide)
{
  chprintln (ch, "You attempt to hide.");

  if (IsAffected (ch, AFF_HIDE))
    RemBit (ch->affected_by, AFF_HIDE);

  if (number_percent () < get_skill (ch, gsn_hide))
    {
      SetBit (ch->affected_by, AFF_HIDE);
      check_improve (ch, gsn_hide, true, 3);
    }
  else
    check_improve (ch, gsn_hide, false, 3);

  return;
}


Do_Fun (do_visible)
{
  affect_strip (ch, gsn_invis);
  affect_strip (ch, gsn_mass_invis);
  affect_strip (ch, gsn_sneak);
  RemBit (ch->affected_by, AFF_HIDE);
  RemBit (ch->affected_by, AFF_INVISIBLE);
  RemBit (ch->affected_by, AFF_SNEAK);
  chprintln (ch, "Ok.");
  return;
}

void
perform_recall (CharData * ch, RoomIndex * location, const char *what)
{
  CharData *victim;

  if (IsNPC (ch) && !IsSet (ch->act, ACT_PET))
    {
      chprintlnf (ch, "Only players can %s.", what);
      return;
    }
  if (IsSet (ch->in_room->room_flags, ROOM_ARENA))
    {
      chprintlnf (ch, "You can't %s while in the arena!", what);
      return;
    }

  act ("$n prays for transportation!", ch, 0, 0, TO_ROOM);

  if (location == NULL)
    {
      chprintln (ch, "You are completely lost.");
      return;
    }

  if (ch->in_room == location)
    return;

  if (IsSet (ch->in_room->room_flags, ROOM_NO_RECALL) ||
      IsAffected (ch, AFF_CURSE))
    {
      act ("$g has forsaken you.", ch, NULL, NULL, TO_CHAR);
      return;
    }

  if ((victim = ch->fighting) != NULL)
    {
      int lose, skill;

      skill = get_skill (ch, gsn_recall);

      if (number_percent () < 80 * skill / 100)
	{
	  check_improve (ch, gsn_recall, false, 6);
	  WaitState (ch, 4);
	  chprintln (ch, "You failed!.");
	  return;
	}

      lose = (ch->desc != NULL) ? 25 : 50;
      gain_exp (ch, 0 - lose);
      check_improve (ch, gsn_recall, true, 4);
      chprintlnf (ch, "You %s from combat!  You lose %d exps.", what, lose);
      stop_fighting (ch, true);

    }

  ch->move /= 2;
  act ("$n disappears.", ch, NULL, NULL, TO_ROOM);
  char_from_room (ch);
  char_to_room (ch, location);
  act ("$n appears in the room.", ch, NULL, NULL, TO_ROOM);
  do_function (ch, &do_look, "auto");

  if (ch->pet != NULL)
    {
      act ("$n disappears.", ch, NULL, NULL, TO_ROOM);
      char_from_room (ch->pet);
      char_to_room (ch->pet, location);
      act ("$n appears in the room.", ch, NULL, NULL, TO_ROOM);
    }
}

Do_Fun (do_recall)
{
  RoomIndex *location;

  if (!ch->in_room || !ch->in_room->area->recall
      || (location = get_room_index (ch->in_room->area->recall)) == NULL)
    location = get_room_index (ROOM_VNUM_TEMPLE);

  perform_recall (ch, location, "recall");
  return;
}

Do_Fun (do_train)
{
  char buf[MAX_STRING_LENGTH];
  CharData *mob;
  int stat = -1;
  int cost;

  if (IsNPC (ch))
    return;


  for (mob = ch->in_room->person_first; mob; mob = mob->next_in_room)
    {
      if (IsNPC (mob) && IsSet (mob->act, ACT_TRAIN))
	break;
    }

  if (mob == NULL)
    {
      chprintln (ch, "You can't do that here.");
      return;
    }

  if (NullStr (argument))
    {
      chprintlnf (ch, "You have %d training sessions.", ch->train);
      argument = "foo";
    }

  cost = 1;

  if ((stat = (int) flag_value (stat_types, argument)) != NO_FLAG)
    {
      if (is_prime_stat (ch, stat))
	cost = 1;
    }
  else if (!str_cmp (argument, "hp"))
    cost = 1;

  else if (!str_cmp (argument, "mana"))
    cost = 1;

  else
    {
      strcpy (buf, "You can train:");

      for (stat = 0; stat < STAT_MAX; stat++)
	{
	  if (ch->perm_stat[stat] < get_max_train (ch, stat))
	    sprintf (buf + strlen (buf), " %.3s", stat_types[stat].name);
	}
      strcat (buf, " hp mana.");

      chprintln (ch, buf);
      return;
    }

  if (!str_cmp ("hp", argument))
    {
      if (cost > ch->train)
	{
	  chprintln (ch, "You don't have enough training sessions.");
	  return;
	}

      ch->train -= cost;
      ch->pcdata->perm_hit += 10;
      ch->max_hit += 10;
      ch->hit += 10;
      act ("Your durability increases!", ch, NULL, NULL, TO_CHAR);
      act ("$n's durability increases!", ch, NULL, NULL, TO_ROOM);
      return;
    }

  if (!str_cmp ("mana", argument))
    {
      if (cost > ch->train)
	{
	  chprintln (ch, "You don't have enough training sessions.");
	  return;
	}

      ch->train -= cost;
      ch->pcdata->perm_mana += 10;
      ch->max_mana += 10;
      ch->mana += 10;
      act ("Your power increases!", ch, NULL, NULL, TO_CHAR);
      act ("$n's power increases!", ch, NULL, NULL, TO_ROOM);
      return;
    }

  if (ch->perm_stat[stat] >= get_max_train (ch, stat))
    {
      act ("Your $T is already at maximum.", ch, NULL,
	   stat_types[stat].name, TO_CHAR);
      return;
    }

  if (cost > ch->train)
    {
      chprintln (ch, "You don't have enough training sessions.");
      return;
    }

  ch->train -= cost;

  ch->perm_stat[stat] += 1;
  act ("Your $T increases!", ch, NULL, stat_types[stat].name, TO_CHAR);
  act ("$n's $T increases!", ch, NULL, stat_types[stat].name, TO_ROOM);
  return;
}

Do_Fun (do_run)
{
  char buf[MAX_STRING_LENGTH], arg[MAX_INPUT_LENGTH];
  char *p;
  bool dFound = false;

  if (!ch->desc || *argument == '\0')
    {
      chprintln (ch, "You run in place!");
      return;
    }

  buf[0] = '\0';

  while (*argument != '\0')
    {
      argument = one_argument (argument, arg);
      strcat (buf, arg);
    }

  for (p = buf + strlen (buf) - 1; p >= buf; p--)
    {
      if (!isdigit (*p))
	{
	  switch (*p)
	    {
	    case 'n':
	    case 's':
	    case 'e':
	    case 'w':
	    case 'u':
	    case 'd':
	      dFound = true;
	      break;

	    case 'o':
	      break;

	    default:
	      chprintln (ch, "Invalid direction!");
	      return;
	    }
	}
      else if (!dFound)
	*p = '\0';
    }

  if (!dFound)
    {
      chprintln (ch, "No directions specified!");
      return;
    }

  replace_str (&ch->desc->run_buf, buf);
  ch->desc->run_head = ch->desc->run_buf;
  chprintln (ch, "You start running...");
  return;
}