1stMud4.5.3/
1stMud4.5.3/backup/
1stMud4.5.3/bin/
1stMud4.5.3/bin/extras/
1stMud4.5.3/data/i3/
1stMud4.5.3/doc/1stMud/
1stMud4.5.3/doc/Diku/
1stMud4.5.3/doc/MPDocs/
1stMud4.5.3/doc/Rom/
1stMud4.5.3/notes/
/**************************************************************************
*  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 || MudFlag(DISABLE_AREA_RECALL)
		|| !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;
}