1stMUD/corefiles/
1stMUD/gods/
1stMUD/notes/
1stMUD/player/
1stMUD/win32/
1stMUD/win32/ROM/
/**************************************************************************
*  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-2002 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/

#include <sys/types.h>
#if !defined(WIN32)
#include <sys/time.h>
#endif
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "merc.h"
#include "interp.h"

char *const dir_name[] = {
	"north", "east", "south", "west", "up", "down"
};

const int rev_dir[] = {
	2, 3, 0, 1, 5, 4
};

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

/*
 * Local functions.
 */
int find_door args((CHAR_DATA * ch, char *arg));
bool has_key args((CHAR_DATA * ch, vnum_t key));

void move_char(CHAR_DATA * ch, int door, bool follow)
{
	CHAR_DATA *fch;
	CHAR_DATA *fch_next;
	ROOM_INDEX_DATA *in_room;
	ROOM_INDEX_DATA *to_room;
	EXIT_DATA *pexit;

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

	/*
	 * Exit trigger, if activated, bail out. Only PCs are triggered.
	 */
	if (!IS_NPC(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 (IS_SET(pexit->exit_info, EX_CLOSED) &&
		(!IS_AFFECTED(ch, AFF_PASS_DOOR) ||
		 IS_SET(pexit->exit_info, EX_NOPASS)) && !IS_TRUSTED(ch, ANGEL))
	{
		act("The $d is closed.", ch, NULL, pexit->keyword, TO_CHAR);
		return;
	}

	if (IS_AFFECTED(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 (IS_SET(to_room->area->area_flags, AREA_CLOSED))
	{
		chprintln(ch, "That area is closed to players.");
		return;
	}
	if (!IS_NPC(ch))
	{
		int iClass, iGuild;
		int move;

		for (iClass = 0; iClass < maxClass; iClass++)
		{
			for (iGuild = 0; iGuild < MAX_GUILD; iGuild++)
			{
				if (!is_class(ch, iClass) &&
					to_room->vnum == class_table[iClass].guild[iGuild])
				{
					chprintln(ch, "You aren't allowed in there.");
					return;
				}
			}
		}

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

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

			/*
			 * Look for a boat.
			 */
			found = FALSE;

			if (IS_IMMORTAL(ch))
				found = TRUE;

			for (obj = ch->first_carrying; 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[UMIN(SECT_MAX - 1, in_room->sector_type)] +
			movement_loss[UMIN(SECT_MAX - 1, to_room->sector_type)];

		move /= 2;				/* i.e. the average */

		/* conditional effects */
		if (IS_AFFECTED(ch, AFF_FLYING) || IS_AFFECTED(ch, AFF_HASTE))
			move /= 2;

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

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

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

	if (!IS_AFFECTED(ch, AFF_SNEAK) && ch->invis_level < LEVEL_HERO)
		act("$n leaves $T.", ch, NULL, dir_name[door], TO_ROOM);

	char_from_room(ch);
	char_to_room(ch, to_room);
	if (!IS_AFFECTED(ch, AFF_SNEAK) && ch->invis_level < LEVEL_HERO)
		act("$n has arrived.", ch, NULL, NULL, TO_ROOM);

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

	if (in_room == to_room)		/* no circular follows */
		return;

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

		if (fch->master == ch && IS_AFFECTED(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 (IS_SET(ch->in_room->room_flags, ROOM_LAW) &&
				(IS_NPC(fch) && IS_SET(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 someone is following the char, these triggers get activated
	 * for the followers before the char, but it's safer this way...
	 */
	if (IS_NPC(ch) && HAS_TRIGGER_MOB(ch, TRIG_ENTRY))
		p_percent_trigger(ch, NULL, NULL, NULL, NULL, NULL, TRIG_ENTRY);
	if (!IS_NPC(ch))
	{
		p_greet_trigger(ch, PRG_MPROG);
		p_greet_trigger(ch, PRG_OPROG);
		p_greet_trigger(ch, PRG_RPROG);
	}

	return;
}

CH_CMD(do_north)
{
	ROOM_INDEX_DATA *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;
}

CH_CMD(do_east)
{
	ROOM_INDEX_DATA *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;
}

CH_CMD(do_south)
{
	ROOM_INDEX_DATA *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;
}

CH_CMD(do_west)
{
	ROOM_INDEX_DATA *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;
}

CH_CMD(do_up)
{
	ROOM_INDEX_DATA *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;
}

CH_CMD(do_down)
{
	ROOM_INDEX_DATA *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(CHAR_DATA * ch, char *arg)
{
	EXIT_DATA *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 <= 5; door++)
		{
			if ((pexit = ch->in_room->exit[door]) != NULL &&
				IS_SET(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 (!IS_SET(pexit->exit_info, EX_ISDOOR))
	{
		chprintln(ch, "You can't do that.");
		return -1;
	}

	return door;
}

CH_CMD(do_open)
{
	char arg[MAX_INPUT_LENGTH];
	OBJ_DATA *obj;
	int door;

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		chprintln(ch, "Open what?");
		return;
	}

	if ((obj = get_obj_here(ch, NULL, arg)) != NULL)
	{
		/* open portal */
		if (obj->item_type == ITEM_PORTAL)
		{
			if (!IS_SET(obj->value[1], EX_ISDOOR))
			{
				chprintln(ch, "You can't do that.");
				return;
			}

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

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

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

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

		REMOVE_BIT(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)
	{
		/* 'open door' */
		ROOM_INDEX_DATA *to_room;
		EXIT_DATA *pexit;
		EXIT_DATA *pexit_rev;

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

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

		/* open the other side */
		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)
		{
			CHAR_DATA *rch;

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

	return;
}

CH_CMD(do_close)
{
	char arg[MAX_INPUT_LENGTH];
	OBJ_DATA *obj;
	int door;

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		chprintln(ch, "Close what?");
		return;
	}

	if ((obj = get_obj_here(ch, NULL, arg)) != NULL)
	{
		/* portal stuff */
		if (obj->item_type == ITEM_PORTAL)
		{

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

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

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

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

		SET_BIT(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)
	{
		/* 'close door' */
		ROOM_INDEX_DATA *to_room;
		EXIT_DATA *pexit;
		EXIT_DATA *pexit_rev;

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

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

		/* close the other side */
		if ((to_room = pexit->u1.to_room) != NULL &&
			(pexit_rev = to_room->exit[rev_dir[door]]) != 0 &&
			pexit_rev->u1.to_room == ch->in_room)
		{
			CHAR_DATA *rch;

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

	return;
}

bool has_key(CHAR_DATA * ch, vnum_t key)
{
	OBJ_DATA *obj;

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

	return FALSE;
}

CH_CMD(do_lock)
{
	char arg[MAX_INPUT_LENGTH];
	OBJ_DATA *obj;
	int door;

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		chprintln(ch, "Lock what?");
		return;
	}

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

			if (obj->value[4] < 0 || IS_SET(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 (IS_SET(obj->value[1], EX_LOCKED))
			{
				chprintln(ch, "It's already locked.");
				return;
			}

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

		/* 'lock object' */
		if (obj->item_type != ITEM_CONTAINER)
		{
			chprintln(ch, "That's not a container.");
			return;
		}
		if (!IS_SET(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 (IS_SET(obj->value[1], CONT_LOCKED))
		{
			chprintln(ch, "It's already locked.");
			return;
		}

		SET_BIT(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)
	{
		/* 'lock door' */
		ROOM_INDEX_DATA *to_room;
		EXIT_DATA *pexit;
		EXIT_DATA *pexit_rev;

		pexit = ch->in_room->exit[door];
		if (!IS_SET(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 (IS_SET(pexit->exit_info, EX_LOCKED))
		{
			chprintln(ch, "It's already locked.");
			return;
		}

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

		/* lock the other side */
		if ((to_room = pexit->u1.to_room) != NULL &&
			(pexit_rev = to_room->exit[rev_dir[door]]) != 0 &&
			pexit_rev->u1.to_room == ch->in_room)
		{
			SET_BIT(pexit_rev->exit_info, EX_LOCKED);
		}
	}

	return;
}

CH_CMD(do_unlock)
{
	char arg[MAX_INPUT_LENGTH];
	OBJ_DATA *obj;
	int door;

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		chprintln(ch, "Unlock what?");
		return;
	}

	if ((obj = get_obj_here(ch, NULL, arg)) != NULL)
	{
		/* portal stuff */
		if (obj->item_type == ITEM_PORTAL)
		{
			if (!IS_SET(obj->value[1], EX_ISDOOR))
			{
				chprintln(ch, "You can't do that.");
				return;
			}

			if (!IS_SET(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 (!IS_SET(obj->value[1], EX_LOCKED))
			{
				chprintln(ch, "It's already unlocked.");
				return;
			}

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

		/* 'unlock object' */
		if (obj->item_type != ITEM_CONTAINER)
		{
			chprintln(ch, "That's not a container.");
			return;
		}
		if (!IS_SET(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 (!IS_SET(obj->value[1], CONT_LOCKED))
		{
			chprintln(ch, "It's already unlocked.");
			return;
		}

		REMOVE_BIT(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)
	{
		/* 'unlock door' */
		ROOM_INDEX_DATA *to_room;
		EXIT_DATA *pexit;
		EXIT_DATA *pexit_rev;

		pexit = ch->in_room->exit[door];
		if (!IS_SET(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 (!IS_SET(pexit->exit_info, EX_LOCKED))
		{
			chprintln(ch, "It's already unlocked.");
			return;
		}

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

		/* unlock the other side */
		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)
		{
			REMOVE_BIT(pexit_rev->exit_info, EX_LOCKED);
		}
	}

	return;
}

CH_CMD(do_pick)
{
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *gch;
	OBJ_DATA *obj;
	int door;

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		chprintln(ch, "Pick what?");
		return;
	}

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

	/* look for guards */
	for (gch = ch->in_room->first_person; gch; gch = gch->next_in_room)
	{
		if (IS_NPC(gch) && IS_AWAKE(gch) && ch->level + 5 < gch->level)
		{
			act("$N is standing too close to the lock.", ch, NULL,
				gch, TO_CHAR);
			return;
		}
	}

	if (!IS_NPC(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)
	{
		/* portal stuff */
		if (obj->item_type == ITEM_PORTAL)
		{
			if (!IS_SET(obj->value[1], EX_ISDOOR))
			{
				chprintln(ch, "You can't do that.");
				return;
			}

			if (!IS_SET(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 (IS_SET(obj->value[1], EX_PICKPROOF))
			{
				chprintln(ch, "You failed.");
				return;
			}

			REMOVE_BIT(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;
		}

		/* 'pick object' */
		if (obj->item_type != ITEM_CONTAINER)
		{
			chprintln(ch, "That's not a container.");
			return;
		}
		if (!IS_SET(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 (!IS_SET(obj->value[1], CONT_LOCKED))
		{
			chprintln(ch, "It's already unlocked.");
			return;
		}
		if (IS_SET(obj->value[1], CONT_PICKPROOF))
		{
			chprintln(ch, "You failed.");
			return;
		}

		REMOVE_BIT(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)
	{
		/* 'pick door' */
		ROOM_INDEX_DATA *to_room;
		EXIT_DATA *pexit;
		EXIT_DATA *pexit_rev;

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

		REMOVE_BIT(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);

		/* pick the other side */
		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)
		{
			REMOVE_BIT(pexit_rev->exit_info, EX_LOCKED);
		}
	}

	return;
}

CH_CMD(do_stand)
{
	OBJ_DATA *obj = NULL;

	if (argument[0] != '\0')
	{
		if (ch->position == POS_FIGHTING)
		{
			chprintln(ch, "Maybe you should finish fighting first?");
			return;
		}
		obj = get_obj_list(ch, argument, ch->in_room->first_content);
		if (obj == NULL)
		{
			chprintln(ch, "You don't see that here.");
			return;
		}
		if (obj->item_type != ITEM_FURNITURE ||
			(!IS_SET(obj->value[2], STAND_AT) &&
			 !IS_SET(obj->value[2], STAND_ON) &&
			 !IS_SET(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 (HAS_TRIGGER_OBJ(obj, TRIG_SIT))
			p_percent_trigger(NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);
	}

	switch (ch->position)
	{
	case POS_SLEEPING:
		if (IS_AFFECTED(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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;
	}

	return;
}

CH_CMD(do_rest)
{
	OBJ_DATA *obj = NULL;

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

	/* okay, now that we know we can rest, find an object to rest on */
	if (argument[0] != '\0')
	{
		obj = get_obj_list(ch, argument, ch->in_room->first_content);
		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 ||
			(!IS_SET(obj->value[2], REST_ON) &&
			 !IS_SET(obj->value[2], REST_IN) &&
			 !IS_SET(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 (HAS_TRIGGER_OBJ(obj, TRIG_SIT))
			p_percent_trigger(NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);

	}

	switch (ch->position)
	{
	case POS_SLEEPING:
		if (IS_AFFECTED(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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;
	}

	return;
}

CH_CMD(do_sit)
{
	OBJ_DATA *obj = NULL;

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

	/* okay, now that we know we can sit, find an object to sit on */
	if (argument[0] != '\0')
	{
		obj = get_obj_list(ch, argument, ch->in_room->first_content);
		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 ||
			(!IS_SET(obj->value[2], SIT_ON) &&
			 !IS_SET(obj->value[2], SIT_IN) && !IS_SET(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 (HAS_TRIGGER_OBJ(obj, TRIG_SIT))
			p_percent_trigger(NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);
	}
	switch (ch->position)
	{
	case POS_SLEEPING:
		if (IS_AFFECTED(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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 (IS_SET(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;
	}
	return;
}

CH_CMD(do_sleep)
{
	OBJ_DATA *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 (argument[0] == '\0' && ch->on == NULL)
		{
			chprintln(ch, "You go to sleep.");
			act("$n goes to sleep.", ch, NULL, NULL, TO_ROOM);
			ch->position = POS_SLEEPING;
		}
		else					/* find an object and sleep on it */
		{
			if (argument[0] == '\0')
				obj = ch->on;
			else
				obj = get_obj_list(ch, argument, ch->in_room->first_content);

			if (obj == NULL)
			{
				chprintln(ch, "You don't see that here.");
				return;
			}
			if (obj->item_type != ITEM_FURNITURE ||
				(!IS_SET(obj->value[2], SLEEP_ON) &&
				 !IS_SET(obj->value[2], SLEEP_IN) &&
				 !IS_SET(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 (HAS_TRIGGER_OBJ(obj, TRIG_SIT))
				p_percent_trigger(NULL, obj, NULL, ch, NULL, NULL, TRIG_SIT);

			if (IS_SET(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 (IS_SET(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;
	}

	return;
}

CH_CMD(do_wake)
{
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *victim;

	one_argument(argument, arg);
	if (arg[0] == '\0')
	{
		do_function(ch, &do_stand, "");
		return;
	}

	if (!IS_AWAKE(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 (IS_AWAKE(victim))
	{
		act("$N is already awake.", ch, NULL, victim, TO_CHAR);
		return;
	}

	if (IS_AFFECTED(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;
}

CH_CMD(do_sneak)
{
	AFFECT_DATA af;

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

	if (IS_AFFECTED(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;
}

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

	if (IS_AFFECTED(ch, AFF_HIDE))
		REMOVE_BIT(ch->affected_by, AFF_HIDE);

	if (number_percent() < get_skill(ch, gsn_hide))
	{
		SET_BIT(ch->affected_by, AFF_HIDE);
		check_improve(ch, gsn_hide, TRUE, 3);
	}
	else
		check_improve(ch, gsn_hide, FALSE, 3);

	return;
}

/*
 * Contributed by Alander.
 */
CH_CMD(do_visible)
{
	affect_strip(ch, gsn_invis);
	affect_strip(ch, gsn_mass_invis);
	affect_strip(ch, gsn_sneak);
	REMOVE_BIT(ch->affected_by, AFF_HIDE);
	REMOVE_BIT(ch->affected_by, AFF_INVISIBLE);
	REMOVE_BIT(ch->affected_by, AFF_SNEAK);
	chprintln(ch, "Ok.");
	return;
}

CH_CMD(do_recall)
{
	CHAR_DATA *victim;
	ROOM_INDEX_DATA *location;

	if (IS_NPC(ch) && !IS_SET(ch->act, ACT_PET))
	{
		chprintln(ch, "Only players can recall.");
		return;
	}
	if (IS_SET(ch->in_room->room_flags, ROOM_ARENA))
	{
		chprintln(ch, "You can't recall while in the arena!");
		return;
	}

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

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

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

	if (IS_SET(ch->in_room->room_flags, ROOM_NO_RECALL) ||
		IS_AFFECTED(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);
			WAIT_STATE(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 recall from combat!  You lose %d exps.", 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)
		do_function(ch->pet, &do_recall, "");

	return;
}

CH_CMD(do_train)
{
	char buf[MAX_STRING_LENGTH];
	CHAR_DATA *mob;
	int stat = -1;
	char *pOutput = NULL;
	int cost;

	if (IS_NPC(ch))
		return;

	/*
	 * Check for trainer.
	 */
	for (mob = ch->in_room->first_person; mob; mob = mob->next_in_room)
	{
		if (IS_NPC(mob) && IS_SET(mob->act, ACT_TRAIN))
			break;
	}

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

	if (argument[0] == '\0')
	{
		chprintlnf(ch, "You have %d training sessions.", ch->train);
		argument = "foo";
	}

	cost = 1;

	if (!str_cmp(argument, "str"))
	{
		if (is_prime_stat(ch, STAT_STR))
			cost = 1;
		stat = STAT_STR;
		pOutput = "strength";
	}

	else if (!str_cmp(argument, "int"))
	{
		if (is_prime_stat(ch, STAT_INT))
			cost = 1;
		stat = STAT_INT;
		pOutput = "intelligence";
	}

	else if (!str_cmp(argument, "wis"))
	{
		if (is_prime_stat(ch, STAT_WIS))
			cost = 1;
		stat = STAT_WIS;
		pOutput = "wisdom";
	}

	else if (!str_cmp(argument, "dex"))
	{
		if (is_prime_stat(ch, STAT_DEX))
			cost = 1;
		stat = STAT_DEX;
		pOutput = "dexterity";
	}

	else if (!str_cmp(argument, "con"))
	{
		if (is_prime_stat(ch, STAT_CON))
			cost = 1;
		stat = STAT_CON;
		pOutput = "constitution";
	}

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

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

	else
	{
		strcpy(buf, "You can train:");
		if (ch->perm_stat[STAT_STR] < get_max_train(ch, STAT_STR))
			strcat(buf, " str");
		if (ch->perm_stat[STAT_INT] < get_max_train(ch, STAT_INT))
			strcat(buf, " int");
		if (ch->perm_stat[STAT_WIS] < get_max_train(ch, STAT_WIS))
			strcat(buf, " wis");
		if (ch->perm_stat[STAT_DEX] < get_max_train(ch, STAT_DEX))
			strcat(buf, " dex");
		if (ch->perm_stat[STAT_CON] < get_max_train(ch, STAT_CON))
			strcat(buf, " con");
		strcat(buf, " hp mana");

		if (buf[strlen(buf) - 1] != ':')
		{
			strcat(buf, ".");
			chprintln(ch, buf);
		}
		else
		{
			/*
			 * This message dedicated to Jordan ... you big stud!
			 */
			act("You have nothing left to train, you $T!", ch, NULL,
				ch->sex == SEX_MALE ? "big stud" : ch->sex ==
				SEX_FEMALE ? "hot babe" : "wild thing", TO_CHAR);
		}

		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, pOutput, 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, pOutput, TO_CHAR);
	act("$n's $T increases!", ch, NULL, pOutput, TO_ROOM);
	return;
}

CH_CMD(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_string(ch->desc->run_buf, buf);
	ch->desc->run_head = ch->desc->run_buf;
	chprintln(ch, "You start running...");
	return;
}