Mud20/accounts/
Mud20/accounts/c/
Mud20/accounts/f/
Mud20/accounts/k/
Mud20/accounts/s/
Mud20/accounts/t/
Mud20/area_current/
Mud20/area_current/newareas/
Mud20/bin/
Mud20/clans/
Mud20/gods/
Mud20/old-sources/
Mud20/player/
Mud20/player/a/del/
Mud20/player/b/
Mud20/player/b/bak/
Mud20/player/b/del/
Mud20/player/f/
Mud20/player/f/bak/
Mud20/player/f/del/
Mud20/player/k/
Mud20/player/k/bak/
Mud20/player/k/del/
Mud20/player/k/dmp/
Mud20/player/m/
Mud20/player/m/bak/
Mud20/player/o/
Mud20/player/o/bak/
Mud20/player/p/
Mud20/player/s/
Mud20/player/s/bak/
Mud20/player/s/del/
Mud20/player/t/
Mud20/player/t/del/
Mud20/player/v/
Mud20/public_html/
Mud20/races/
Mud20/skilltables/
__MACOSX/Mud20/accounts/
__MACOSX/Mud20/accounts/c/
__MACOSX/Mud20/accounts/f/
__MACOSX/Mud20/accounts/k/
__MACOSX/Mud20/accounts/s/
__MACOSX/Mud20/area_current/
__MACOSX/Mud20/area_current/core_areas/
__MACOSX/Mud20/area_current/helps/
__MACOSX/Mud20/area_current/newareas/
__MACOSX/Mud20/backups/
__MACOSX/Mud20/bin/
__MACOSX/Mud20/clans/
__MACOSX/Mud20/gods/
__MACOSX/Mud20/log/
__MACOSX/Mud20/old-sources/
__MACOSX/Mud20/player/
__MACOSX/Mud20/player/a/del/
__MACOSX/Mud20/player/b/
__MACOSX/Mud20/player/b/bak/
__MACOSX/Mud20/player/f/
__MACOSX/Mud20/player/f/bak/
__MACOSX/Mud20/player/f/del/
__MACOSX/Mud20/player/k/
__MACOSX/Mud20/player/k/bak/
__MACOSX/Mud20/player/k/del/
__MACOSX/Mud20/player/k/dmp/
__MACOSX/Mud20/player/m/
__MACOSX/Mud20/player/m/bak/
__MACOSX/Mud20/player/o/
__MACOSX/Mud20/player/o/bak/
__MACOSX/Mud20/player/p/
__MACOSX/Mud20/player/s/
__MACOSX/Mud20/player/s/bak/
__MACOSX/Mud20/player/t/del/
__MACOSX/Mud20/player/v/
__MACOSX/Mud20/public_html/
__MACOSX/Mud20/races/
__MACOSX/Mud20/skilltables/
/***************************************************************************
 * Mud20 1.0 by Todd H. Johnson (Kregor) a derivative of the Open Gaming   *
 * License by Wizards of the Coast. All comments referring to D20, OGL,    *
 * and SRD refer to the System Reference Document for the Open Gaming      *
 * system. Any inclusion of these derivatives must include credit to the   *
 * Mud20 system, the full and complete Open Gaming LIcense, and credit to  *
 * the respective authors. See ../doc/srd.txt for more information.        *
 *                                                                         *
 * Emud  2.2 by Igor van den Hoven, Michiel Lange, and Martin Bethlehem.   *
 *                                                                         *
 * MrMud 1.4 by David Bills, Dug Michael and Martin Gallwey                *
 *                                                                         *
 * Merc  2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael      *
 * Chastain, Michael Quan, and Mitchell Tse.                               *
 *                                                                         *
 * Original Diku Mud copyright (C) 1990 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik St{rfeld, Tom Madsen, and Katje Nyboe.     *
 ***************************************************************************/

/***************************************************************************
 * act_info.c: player-initiated informational functions.								   *
 ***************************************************************************/

#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <time.h>
#include "mud.h"

char *  const   where_name      [] =
{
	"         {200}<{078}carried{200}>{300} ",
	"  {200}<{078}floating about{200}>{300} ",
	"    {200}<{078}worn on head{200}>{300} ",
	"    {200}<{078}worn on face{200}>{300} ",
	"{200}<{078}worn on the ears{200}>{300} ",
	"{200}<{078}worn around neck{200}>{300} ",
	"{200}<{078}worn around neck{200}>{300} ",
	"{200}<{078}worn on the arms{200}>{300} ",
	" {200}<{078}worn on L wrist{200}>{300} ",
	" {200}<{078}worn on R wrist{200}>{300} ",
	"   {200}<{078}worn on hands{200}>{300} ",
	"{200}<{078}worn on L finger{200}>{300} ",
	"{200}<{078}worn on R finger{200}>{300} ",
	" {200}<{078}worn about body{200}>{300} ",
	"   {200}<{078}worn on torso{200}>{300} ",
	"  {200}<{078}worn as saddle{200}>{300} ",
	"    {200}<{078}worn on back{200}>{300} ",
	"{200}<{078}worn about waist{200}>{300} ",
	"    {200}<{078}worn on belt{200}>{300} ",
	"    {200}<{078}worn on belt{200}>{300} ",
	"    {200}<{078}worn on belt{200}>{300} ",
	"    {200}<{078}worn on legs{200}>{300} ",
	" {200}<{078}worn on L ankle{200}>{300} ",
	" {200}<{078}worn on R ankle{200}>{300} ",
	"    {200}<{078}worn on feet{200}>{300} ",
	"  {200}<{078}worn as shield{200}>{300} ",
	" {200}<{078}wielded in hand{200}>{300} ",
	"{200}<{078}off-hand wielded{200}>{300} ",
	"    {200}<{078}in two hands{200}>{300} ",
	"    {200}<{078}held in hand{200}>{300} "
};


struct race_enemy_type
{
	char	name[20];
	int		type;
	int		spec;
};

const struct race_enemy_type race_enemies[RACE_ENEMY_MAX] =
{
	{"@",0,0},
	{"aberrations", 				RTYPE_ABERRATION, RSPEC_NONE},
	{"animals", 						RTYPE_ANIMAL, 	RSPEC_NONE},
	{"constructs", 					RTYPE_CONSTRUCT, RSPEC_NONE},
	{"dragons-air", 				RTYPE_DRAGON, 	RSPEC_AIR},
	{"dragons-earth", 			RTYPE_DRAGON, 	RSPEC_EARTH},
	{"dragons-fire", 				RTYPE_DRAGON, 	RSPEC_FIRE},
	{"dragons-water", 			RTYPE_DRAGON, 	RSPEC_WATER},
	{"dragons-good", 				RTYPE_DRAGON, 	RSPEC_GOOD},
	{"dragons-evil", 				RTYPE_DRAGON, 	RSPEC_EVIL},
	{"dragons-lawful", 			RTYPE_DRAGON, 	RSPEC_LAWFUL},
	{"dragons-chaotic", 		RTYPE_DRAGON, 	RSPEC_CHAOTIC},
	{"fey", 								RTYPE_FEY, 			RSPEC_NONE},
	{"humanoids-aquatic", 	RTYPE_HUMANOID, RSPEC_AQUATIC},
	{"humanoids-drow", 			RTYPE_HUMANOID, RSPEC_DROW},
	{"humanoids-dwarf", 		RTYPE_HUMANOID, RSPEC_DWARF},
	{"humanoids-elf", 			RTYPE_HUMANOID, RSPEC_ELF},
	{"humanoids-giant", 		RTYPE_HUMANOID, RSPEC_GIANT},
	{"humanoids-goblinoid", RTYPE_HUMANOID, RSPEC_GOBLINOID},
	{"humanoids-gnoll", 		RTYPE_HUMANOID, RSPEC_GNOLL},
	{"humanoids-gnome", 		RTYPE_HUMANOID, RSPEC_GNOME},
	{"humanoids-halfling",	RTYPE_HUMANOID, RSPEC_HALFLING},
	{"humanoids-human", 		RTYPE_HUMANOID, RSPEC_HUMAN},
	{"humanoids-orc", 			RTYPE_HUMANOID, RSPEC_ORC},
	{"humanoids-reptilian", RTYPE_HUMANOID, RSPEC_REPTILIAN},
	{"magical beasts",			RTYPE_MAGICAL, 	RSPEC_NONE},
	{"monstrous humanoids", RTYPE_MONSTROUS, RSPEC_NONE},
	{"oozes", 							RTYPE_OOZE, 		RSPEC_NONE},
	{"outsiders-air", 			RTYPE_OUTSIDER, RSPEC_AIR},
	{"outsiders-chaotic", 	RTYPE_OUTSIDER, RSPEC_CHAOTIC},
	{"outsiders-earth", 		RTYPE_OUTSIDER, RSPEC_EARTH},
	{"outsiders-evil", 			RTYPE_OUTSIDER, RSPEC_EVIL},
	{"outsiders-fire", 			RTYPE_OUTSIDER, RSPEC_FIRE},
	{"outsiders-good", 			RTYPE_OUTSIDER, RSPEC_GOOD},
	{"outsiders-lawful",		RTYPE_OUTSIDER, RSPEC_LAWFUL},
	{"outsiders-native", 		RTYPE_OUTSIDER, RSPEC_NATIVE},
	{"outsiders-water", 		RTYPE_OUTSIDER, RSPEC_WATER},
	{"plants", 							RTYPE_PLANT, 		RSPEC_NONE},
	{"shapechangers", 			RTYPE_NONE, 		RSPEC_SHAPECHANGER},
	{"undead", 							RTYPE_UNDEAD, 	RSPEC_NONE},
	{"vermin", 							RTYPE_VERMIN, 	RSPEC_NONE}
};

char * const race_enemy_names	[] =
{
	"@",
	"aberrations",
	"animals",
	"constructs",
	"dragons-air",
	"dragons-earth",
	"dragons-fire",
	"dragons-water",
	"dragons-good",
	"dragons-evil",
	"dragons-lawful",
	"dragons-chaotic",
	"fey",
	"humanoids-aquatic",
	"humanoids-dwarf",
	"humanoids-elf",
	"humanoids-giant",
	"humanoids-goblinoid",
	"humanoids-gnoll",
	"humanoids-gnome",
	"humanoids-halfling",
	"humanoids-human",
	"humanoids-orc",
	"humanoids-reptilian",
	"magical beasts",
	"monstrous humanoids",
	"oozes",
	"outsiders-air",
	"outsiders-chaotic",
	"outsiders-earth",
	"outsiders-evil",
	"outsiders-fire",
	"outsiders-good",
	"outsiders-lawful",
	"outsiders-native",
	"outsiders-water",
	"plants",
	"shapechangers",
	"undead",
	"vermin",
	"*"
};


/*
	Local functions.
*/

char	*	format_obj_to_char		args( ( OBJ_DATA *obj, CHAR_DATA *ch, int fShort ) );
void		sprintf_list_to_char	args( ( char *outbuf, OBJ_DATA *list, CHAR_DATA *ch, int fShort, bool fShowNothing, bool fPeek ) );
void		get_affects_string		(CHAR_DATA *, CHAR_DATA *, char *);
void		get_string_score_v1 	(CHAR_DATA *, CHAR_DATA *);
void		show_room_to_char		 	(CHAR_DATA *, ROOM_INDEX_DATA *);
char		get_string_score_txt [MAX_STRING_LENGTH];

int obj_conceal_check( OBJ_DATA *obj )
{
	CHAR_DATA *ch;
	int hide;
	
	if ((ch = obj->carried_by) == NULL)
	{
		hide = 10;
		hide += 2 * (SIZE_MEDIUM - obj->size);
	}
	else
	{
		hide = sleight_of_hand_roll(ch);
			
		if (IS_WEAPON(obj) && (obj->value[0] == WEAPON_TYPE_DAGGER || obj->value[0] == WEAPON_TYPE_KNIFE))
		{
			hide += 2;
		}
		else if (get_size(ch) - obj->size > 2)
		{
			hide += 4;
		}
	}
	return hide;
}

int lookup_race( char *arg )
{
	int cnt;

	push_call("lookup_race(%p)",arg);

	for( cnt = 0 ; cnt < MAX_RACE ; cnt++)
	{
		if( !strcasecmp( arg, race_table[cnt].race_name) )
		{
			pop_call();
			return( cnt );
		}
	}
	for( cnt = 0 ; cnt < MAX_RACE ; cnt++)
	{
		if( !str_prefix( arg, race_table[cnt].race_name) )
		{
			pop_call();
			return( cnt );
		}
	}
	pop_call();
	return( -1 );
}

int lookup_class( char *arg )
{
	int cnt;

	push_call("lookup_class(%p)",arg);

	for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (!str_prefix(arg, class_table[cnt].who_name_long))
		{
			pop_call();
			return cnt;
		}
		if (!strcasecmp(arg, class_table[cnt].who_name))
		{
			pop_call();
			return cnt;
		}
		if (is_multi_name_short(arg, class_table[cnt].who_name_long))
		{
			pop_call();
			return cnt;
		}
	}
	pop_call();
	return -1;
}

int lookup_god( char *arg )
{
	int cnt;

	push_call("lookup_god(%p)",arg);

	for (cnt = 0 ; cnt < MAX_GOD ; cnt++)
	{
		if (!strcasecmp(arg, god_table[cnt].god_name))
		{
			pop_call();
			return cnt;
		}
	}
	pop_call();
	return -1;
}

/*
 * Lookup a language name - Kregor
 */
int lookup_tongue( char *tongue )
{
	int cnt;

	push_call("lookup_tongue(%p)",tongue);
	
	for (cnt = 0 ; cnt < MAX_LANG ; cnt++)
	{
		if (is_name(tongue, lang_names[cnt]))
		{
			pop_call();
			return cnt;
		}
	}
	pop_call();
	return -1;
}

AREA_DATA *lookup_area( char *arg )
{
	AREA_DATA *pArea;

	push_call("lookup_area(%p)",arg);

	for (pArea = mud->f_area ; pArea ; pArea = pArea->next)
	{
		if (!str_prefix(arg, pArea->name))
		{
			pop_call();
			return pArea;
		}
	}
	pop_call();
	return NULL;
}

char *format_obj_to_char( OBJ_DATA *obj, CHAR_DATA *ch, int fShort )
{
	static char buf[MAX_STRING_LENGTH], tmp[MAX_STRING_LENGTH];

	push_call("format_obj_to_char(%p,%p,%p)",obj,ch,fShort);

	buf[0] = '\0';

	if (show_build_vnum(ch, obj->pIndexData->vnum))
	{
		sprintf(tmp, "(%d)", obj->pIndexData->vnum);
		sprintf(buf, "{128}%7s {300}", tmp);
	}

	switch (fShort)
	{
		case 0:
			strcat(buf, OBJD(obj, ch));
			break;
		case 1:
			if (obj->long_descr[0] != '\0')
			{
				strcat(buf, obj->long_descr);
			}
			else
			{
				pop_call();
				return buf;
			}
			break;
		case 2:
			strcat(buf, obj->description);
			break;
	}

	strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_BOLD));

	if (IS_OBJ_STAT(obj, ITEM_ETHEREAL))
	{
		strcat(buf, " {108}(Ethereal)");
	}

	if (IS_OBJ_STAT(obj, ITEM_INVIS))
	{
		strcat(buf, " {108}(Invis)");
	}

	if (IS_BURNING(obj) || (IS_OBJ_TYPE(obj, ITEM_FIRE) && obj->value[2] != 0))
	{
		strcat( buf, " {118}(Flaming)");
	}
	else if (IS_OBJ_STAT(obj, ITEM_GLOW) || (IS_OBJ_TYPE(obj, ITEM_LIGHT) && !obj->value[3] && obj->value[2] != 0))
	{
		strcat( buf, " {178}(Glowing)");
	}

	if (WEAPON_FLAG(obj, WFLAG_FROST) || WEAPON_FLAG(obj, WFLAG_ICY_BURST))
	{
		strcat( buf, " {168}(Icy)");
	}

	if (WEAPON_FLAG(obj, WFLAG_SHOCK) || WEAPON_FLAG(obj, WFLAG_SHOCK_BURST))
	{
		strcat( buf, " {178}(Electric)");
	}

	if (IS_OBJ_STAT(obj, ITEM_HIDDEN))
	{
		strcat( buf, " {108}(Hidden)");
	}
	else if (IS_OBJ_STAT(obj, ITEM_CONCEALED))
	{
		strcat( buf, " {108}(Concealed)");
	}
	if (IS_OBJ_STAT(obj, ITEM_BURIED))
	{
		strcat( buf, " {038}(Buried)");
	}
	if (get_obj_trap(obj) && is_affected(ch, gsn_foresight))
	{
		strcat( buf, " {118}(TRAPPED!)");
	}
	pop_call();
	return buf;
}

void sprintf_exits(  char *outbuf, CHAR_DATA *ch, bool fAuto )
{
	extern char * const dir_name[];
	char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];
	char t1[MAX_INPUT_LENGTH];
	EXIT_DATA *pExit;
	int door, num_exits = 0;
	char dim[10], bold[10];

	push_call("sprintf_exits(%p,%p)",fAuto, ch);

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}

	sprintf(bold, "%s", get_color_string(ch, COLOR_EXITS, VT102_BOLD));
	sprintf( dim, "%s", get_color_string(ch, COLOR_EXITS, VT102_DIM ));

	for (buf[0] = '\0', door = 0 ; door <= 5 ; door++)
	{
		if ((pExit = get_exit(ch->in_room->vnum, door)) == NULL)
		{
			continue;
		}
		if (IS_SET(ch->in_room->room_flags, ROOM_FOG) && !can_see_smoke(ch))
		{
			continue;
		}
		if (IS_SET(pExit->exit_info, EX_HIDDEN) && !can_see_hidden(ch, door))
		{
			continue;
		}
		if (!can_use_exit(ch, pExit))
		{
			continue;
		}
		num_exits++;

		if (fAuto)
		{
			cat_sprintf(buf, "%s%c-", bold, toupper(dir_name[door][0]));
		}
		else
		{
			cat_sprintf(buf, "%s%5s - ", bold, capitalize(dir_name[door]));
		}

		if (show_build_vnum(ch, pExit->to_room))
		{
			cat_sprintf(buf, "%s%5d %s", bold, pExit->to_room, dim);
		}
		else if (IS_SET(pExit->exit_info, EX_HIDDEN))
		{
			cat_sprintf(buf, "%s (S) %s", "{108}", dim);
		}
		else
		{
			cat_sprintf(buf, "%s", dim);
		}

		if (!IS_SET(pExit->exit_info, EX_CLOSED))
		{
			if (!can_see_in_room(ch, room_index[pExit->to_room]))
			{
				sprintf(buf2, "%-18s", "Too dark to tell");
			}
			else
			{
				sprintf(buf2, "%-18s", get_dynamic_room_title(room_index[pExit->to_room]));
			}
		}
		else if (fAuto)
		{
			if (pExit->keyword[0] != '\0')
			{
				sprintf(buf2, "%s", pExit->keyword);
			}
			else
			{
				sprintf(buf2, "door");
			}
		}
		else
		{
			if (pExit->description[0] != '\0')
			{
				sprintf(buf2, "%s", pExit->description);
			}
			else
			{
				sprintf(buf2, "You see nothing special.");
			}
		}

		if (fAuto)
		{
			if (show_build_vnum(ch, pExit->to_room))
				cat_sprintf( buf, "%s", str_resize(buf2, t1, -17));
			else
				cat_sprintf( buf, "%s", str_resize(buf2, t1, -23));

			if (num_exits % 3 == 0)
			{
				strcat(buf, "\n\r");
			}
			else
			{
				strcat(buf, " ");
			}
		}
		else
		{
			cat_sprintf( buf, "%s\n\r", buf2);
		}
	}

	if (!num_exits)
	{
		strcat(buf, "You see no visible exits.");
	}

	if (str_suffix("\n\r", buf))
	{
		strcat(buf,"\n\r");
	}
	strcpy(outbuf, buf);
	
	pop_call();
	return;
}
	

/*
	Concatenate an object list as a character sees it.
	Can coalesce duplicated items.
*/
void sprintf_list_to_char( char *outbuf, OBJ_DATA *list, CHAR_DATA *ch, int fShort, bool fShowNothing, bool fPeek )
{
	char buf[MAX_STRING_LENGTH], dim[10], bld[10];
	char buf2[MAX_STRING_LENGTH];
	char *pstrShow;
	OBJ_DATA *obj, *tobj;
	int count, dc, leng;

	push_call("sprintf_list_to_char(%p,%p,%p,%p,%p)",list,ch,fShort,fShowNothing,fPeek);

	if (ch == NULL || ch->desc == NULL)
	{
		pop_call();
		return;
	}

	strcpy(dim, get_color_string(ch, COLOR_PROMPT, VT102_DIM));
	strcpy(bld, get_color_string(ch, COLOR_PROMPT, VT102_BOLD));

	for (leng = buf[0] = buf2[0] = 0, obj = list ; obj ; obj = obj->next_content)
	{
		if (WEAR_LOC(obj, WEAR_NONE) && can_see_obj(ch, obj))
		{
			dc = 10;

			switch (obj->size)
			{
				case SIZE_FINE:
					dc += 16;
					break;
				case SIZE_DIMINUTIVE:
					dc += 12;
					break;
				case SIZE_TINY:
					dc += 8;
					break;
				case SIZE_SMALL:
					dc += 4;
					break;
				case SIZE_LARGE:
					dc -= 4;
					break;
				case SIZE_HUGE:
					dc -= 8;
					break;
				case SIZE_GARGANTUAN:
					dc -= 12;
					break;
				case SIZE_COLOSSAL:
					dc -= 16;
					break;
			}
			if (fPeek && !perception_check(ch, NULL, perception_roll(ch, SENSE_SIGHT), dc))
				continue;
			for (tobj = obj->prev_content ; tobj ; tobj = tobj->prev_content)
			{
				if (WEAR_LOC(tobj, WEAR_NONE))
				{
					if (tobj->pIndexData->vnum == obj->pIndexData->vnum && tobj->short_descr == obj->short_descr)
					{
						if (can_see_obj(ch, tobj))
						{
							break;
						}
					}
				}
			}
			if (tobj)
			{
				continue;
			}
			pstrShow = format_obj_to_char(obj, ch, fShort);

			if (*pstrShow == '\0')
			{
				continue;
			}

			count = 1;

			for (tobj = obj->next_content ; tobj ; tobj = tobj->next_content)
			{
				if (WEAR_LOC(tobj, WEAR_NONE))
				{
					if (tobj->pIndexData->vnum == obj->pIndexData->vnum && tobj->short_descr == obj->short_descr)
					{
					 	if (can_see_obj(ch, tobj))
					 	{
							count++;
						}
					}
				}
			}

			if (count != 1)
			{
				sprintf(buf2, "%s(%2d) %s%s\n\r", dim, count, bld, pstrShow);
				leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
			}
			else
			{
				sprintf(buf2, "%s     %s%s\n\r", dim, bld, pstrShow);
				leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
			}
		}
	}

	if (fShowNothing && buf[0] == 0)
	{
		strcpy(outbuf, "Nothing.\n\r");
	}

	if (buf[0] != 0)
	{
		strcpy(outbuf, buf);
	}

	pop_call();
	return;
}

/*
 * print complete long desc of char to string for viewer - Kregor modified 7/8/12
 */
void get_long_descr( char *outbuf, CHAR_DATA *victim, CHAR_DATA *ch )
{
	char buf[MAX_STRING_LENGTH];

	push_call("get_long_descr(%p,%p)",victim,ch);

	if (IS_SET(victim->in_room->room_flags, ROOM_FOG) && !can_see_smoke(ch))
	{
		sprintf(outbuf, "{068}A %s %s shape is outlined in the cloud.\n\r", size_types[get_size(victim)], IS_HUMANOID(victim) ? "humanoid" : "monstrous");
		pop_call();
		return;
	}

	buf[0] = '\0';

	if (IS_NPC(victim) && show_build_vnum(ch, victim->pIndexData->vnum))
	{
		cat_sprintf(buf, "{128}(%d){300} ", victim->pIndexData->vnum);
	}
	if (!IS_NPC(victim))
	{
		if (IS_SET(victim->act, PLR_WIZINVIS))
		{
			strcat(buf, "{108}(WIZINVIS){300} ");
		}
		if (IS_SET(victim->act, PLR_WIZCLOAK))
		{
			strcat(buf, "{108}(WIZCLOAK){300} ");
		}
		if (!victim->desc)
		{
			strcat(buf, "{078}(LINK DEAD){300} ");
		}
		else if (victim->desc->connected == CON_EDITING)
		{
			strcat(buf, "{078}(WRITING){300} ");
		}
		if (IS_SET(victim->act, PLR_AFK))
		{
			strcat(buf, "{078}(AFK){300} ");
		}
		if (IS_SET(victim->act, PLR_KILLER) && IS_PLR(ch, PLR_HOLYLIGHT))
		{
			strcat(buf, "{018}(KILLER){300} ");
		}
		if (IS_SET(victim->act, PLR_THIEF) && IS_PLR(ch, PLR_HOLYLIGHT))
		{
			strcat( buf, "{018}(THIEF){300} ");
		}
		if (IS_SET(victim->act, PLR_OUTCAST) && IS_PLR(ch, PLR_HOLYLIGHT))
		{
			strcat(buf, "{018}(OUTCAST){300} ");
		}
	}
	if (is_affected(victim, gsn_mirror_image))
	{
		strcat(buf, "{168}(Multiple){300} ");
	}
	if (IS_AFFECTED(victim, AFF_INVISIBLE))
	{
		strcat(buf, "{108}(Invis){300} ");
	}
	if (IS_AFFECTED(victim, AFF_HIDE))
	{
		strcat(buf, "{108}(Hide){300} ");
	}
	if (IS_AFFECTED(victim, AFF_GASEOUS) || rspec_req(victim, RSPEC_INCORPOREAL))
	{
		strcat(buf, "{168}(Translucent){300} ");
	}
	if (is_affected(victim, gsn_fire_shield))
	{
		strcat(buf, "{118}(Flaming){300} ");
	}
	if (is_affected(victim, gsn_entropic_shield))
	{
		strcat(buf, "{118}(Sh{128}im{138}me{148}ri{158}ng{168}){300} ");
	}
	if (IS_AFFECTED(victim, AFF_SANCTUARY))
	{
		strcat(buf, "{178}(White Aura){300} ");
	}
	if (is_affected(victim, gsn_spell_turning))
	{
		strcat(buf, "{178}(S{078}pa{178}rk{078}li{178}ng{078}){300} ");
	}
	if (domain_apotheosis(victim, DOMAIN_SUN))
	{
		strcat(buf, "{138}(Radiant){300} ");
	}
	if (is_affected(victim, gsn_faerie_fire))
	{
		strcat(buf, "{158}(Glowing){300} ");
	}
	if (IS_NPC(victim))
	{
		if (victim->desc && IS_PLR(ch, PLR_HOLYLIGHT))
		{
			strcat(buf, "{078}(Possessed){300} ");
		}
		if (IS_SET(victim->act, ACT_MOBINVIS))
		{
			strcat(buf, "{108}(MOBINVIS){300} ");
		}

		if (!IS_AFFECTED(ch, AFF2_HALLUCINATE) && victim->position == POS_STANDING && !is_mounting(victim) && !IS_FLYING(victim) && !victim->furniture && victim->long_descr[0] != '\0')
		{
			strcat(buf, victim->long_descr);
			sprintf(outbuf, "%s%s{300}\n\r", get_color_string(ch, COLOR_MOBILES, VT102_DIM), ansi_justify(buf, get_page_width(ch)));
			pop_call();
			return;
		}
	}
	strcat(buf, capitalize(PERS(victim,ch)));

	if (victim->grappled_by)
	{
		strcat(buf, " is being grappled by ");
		
		if (victim->grappled_by == ch)
		{
			strcat(buf, "you!" );
		}
		else if (victim->in_room == victim->grappled_by->in_room)
		{
			cat_sprintf(buf, "%s.", PERS(victim->grappled_by, ch));
		}
		else
		{
			strcat(buf, "somone who left??");
		}
	}
	else if (victim->grappling)
	{
		strcat(buf, " is grappling ");
		
		if (victim->grappling == ch)
		{
			strcat(buf, "YOU!" );
		}
		else if (victim->in_room == victim->grappling->in_room)
		{
			cat_sprintf(buf, "%s.", PERS(victim->grappling, ch));
		}
		else
		{
			strcat(buf, "somone who left??");
		}
	}
	else if (is_affected(victim, gsn_crushing_hand) || is_affected(victim, gsn_grasping_hand))
	{
		strcat(buf, " is being grappled by a large hand of force!");
	}
	else if (is_affected(victim, gsn_irresistible_dance))
	{
		strcat(buf, " is shuffling and prancing about.");
	}
	else if (!IS_FLYING(victim))
	{
		switch (victim->position)
		{
			case POS_DEAD:
				strcat(buf, " is DEAD!!");
				break;
			case POS_MORTAL:
				strcat(buf, " is mortally wounded.");
				break;
			case POS_INCAP:
				strcat(buf, " is incapacitated.");
				break;
			case POS_STUNNED:
				strcat(buf, " is lying here stunned.");
				break;
			case POS_SLEEPING:
				if (victim->furniture)
				{
					if (IS_SET(victim->furniture->value[2], FURN_SLEEP_ON))
					{
						if (IS_RACE(ch, RACE_ELF))
							cat_sprintf(buf, " lies in reverie on %s.", victim->furniture->short_descr);
						else
							cat_sprintf(buf, " is sleeping on %s.", victim->furniture->short_descr);
					}
					else
					{
						if (IS_RACE(ch, RACE_ELF))
							cat_sprintf(buf, " lies in reverie in %s.", victim->furniture->short_descr);
						else
							cat_sprintf(buf, " is sleeping in %s.", victim->furniture->short_descr);
					}
				}
				else
				{
					if (IS_RACE(ch, RACE_ELF))
						strcat(buf, " lies in reverie here.");
					else
						strcat(buf, " is sleeping here.");
				}
				break;
			case POS_RESTING:
				if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
						cat_sprintf(buf, " is %s.", victim->pcdata->pose);				
				else if (victim->furniture)
				{
					if (IS_SET(victim->furniture->value[2], FURN_REST_ON))
					{
						cat_sprintf(buf, " is resting on %s here.", victim->furniture->short_descr);
					}
					else
					{
						cat_sprintf(buf, " is resting in %s here.", victim->furniture->short_descr);
					}
				}
				else
				{
					strcat(buf, " is resting here.");
				}
				break;
			case POS_KNEELING:
				if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
						cat_sprintf(buf, " is %s.", victim->pcdata->pose);				
				else if (victim->furniture)
				{
					cat_sprintf(buf, " is here, kneeling before %s.", victim->furniture->short_descr);
				}
				else
				{
					strcat(buf, " is kneeling here.");
				}
				break;
			case POS_CROUCHING:
				if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
						cat_sprintf(buf, " is %s.", victim->pcdata->pose);				
				else if (victim->furniture)
				{
					cat_sprintf(buf, " is crouching behind %s.", victim->furniture->short_descr);
				}
				else
				{
					strcat(buf, " is crouching here.");
				}
				break;
			case POS_SITTING:
				if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
						cat_sprintf(buf, " is %s.", victim->pcdata->pose);				
				else if (victim->furniture)
				{
					if (IS_SET(victim->furniture->value[2], FURN_SIT_ON))
					{
						cat_sprintf(buf, " is sitting on %s.", victim->furniture->short_descr);
					}
					else if (IS_SET(victim->furniture->value[2], FURN_SIT_AT))
					{
						cat_sprintf(buf, " is sitting at %s.", victim->furniture->short_descr);
					}
					else
					{
						cat_sprintf(buf, " is sitting in %s.", victim->furniture->short_descr);
					}
				}
				else
				{
					strcat(buf, " is sitting here.");
				}
				break;
			case POS_FIGHTING:
				if (is_mounting(victim))
				{
					cat_sprintf(buf, " is mounted on %s, fighting ", get_name(victim->mounting));
				}
				else
				{
					strcat(buf, " is fighting ");
				}
				if (who_fighting(victim) == NULL)
				{
					strcat(buf, "thin air??");
				}
				else if (who_fighting(victim) == ch)
				{
					strcat(buf, "YOU!" );
				}
				else if (victim->in_room == who_fighting(victim)->in_room)
				{
					cat_sprintf(buf, "%s.", PERS(who_fighting(victim), ch));
				}
				else
				{
					strcat(buf, "somone who left??");
				}
				break;
			default:
				if (!IS_NPC(victim) && is_string(victim->pcdata->pose))
						cat_sprintf(buf, " is %s.", victim->pcdata->pose);				
				else if (victim->furniture)
				{
					if (IS_SET(victim->furniture->value[2], FURN_STAND_ON))
					{
						cat_sprintf(buf, " is standing on %s.", victim->furniture->short_descr);
					}
					else
					{
						cat_sprintf(buf, " is standing in %s.", victim->furniture->short_descr);
					}
				}
				else if (is_mounting(victim))
				{
					cat_sprintf(buf, " sits mounted on %s.", get_name(victim->mounting));
				}
				else
				{
					strcat(buf, " is here.");
				}
				break;
		}
	}
	else
	{
		strcat(buf, " is hovering here.");
	}
	sprintf(outbuf, "%s%s{300}\n\r", get_color_string(ch, COLOR_MOBILES, VT102_DIM), ansi_justify(buf, get_page_width(ch)));
	pop_call();
	return;
}

/*
 * Displays a character to a viewer using do_look or do_examine
 * Passing NULL ch displays health status to room.
 * Added examine option to character. Examine is needed for detects,
 * spot checks etc to work against the viewed character.
 * Everything now concats to one string to buffer for better scrolling - Kregor
 */
void show_char_to_char( CHAR_DATA *victim, CHAR_DATA *ch, bool fExamine )
{
	char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];
	char descr[MAX_STRING_LENGTH], txt[MAX_STRING_LENGTH];
	char list[MAX_STRING_LENGTH];
	OBJ_DATA *obj;
	CHAR_DATA *fch;
	char t1[80];
	int iWear, WearLoc, layer, count, ch_roll, vic_roll, aff_level, aff_school, sn;
	bool covered = FALSE;

	push_call("show_char_to_char(%p,%p.%p)",victim,ch,fExamine);

	if (ch != NULL)
	{
		if (!mprog_desc_trigger(victim, ch, ""))
		{
			if (!IS_NPC(victim) && is_string(victim->pcdata->disguise_descr))
			{
				strcpy(descr, "{300}");
				act_sprintf(descr, victim->pcdata->disguise_descr, victim, NULL, ch);
			}
			else if (victim->description[0] != '\0')
			{
				strcpy(descr, "{300}");
				act_sprintf(descr, victim->description, victim, NULL, ch);
			}
			else
			{
				sprintf(descr, "{300}You see nothing special about %s.\n\r", victim->sex == SEX_MALE ? "him" : victim->sex == SEX_FEMALE ? "her" : "it");
			}
		}
		else
		{
			descr[0] = '\0';
		}
	}

	if (ch != NULL)
	{
		sprintf(buf, "{300}%s", capitalize(adjective(victim)));
	}
	else
	{
		*buf='\0';
	}

	int health = 10 * victim->hit / UMAX(1, get_max_hit(victim));
	int stun = UMAX(1,victim->hit - victim->nonlethal) * 10 / UMAX(1, victim->hit);
	
	switch (URANGE(0, health < stun ? health : stun, 10))
	{
		case 10: strcat(buf, " is in perfect visible health");	break;
		case  9: case  8: strcat(buf, " looks slightly hurt");	break;
		case  7: case  6: strcat(buf, " looks bruised and sore");			break;
		case  5: case  4: strcat(buf, " looks considerably hurt");		break;
		case  3: case  2: strcat(buf, " looks almost beaten down");	break;
		case  1: case  0: strcat(buf, " looks about ready to drop");		break;
	}

	buf[0] = UPPER(buf[0]);

	if (ch != NULL)
	{
		cat_sprintf(descr, "%s.\n\r", buf);
	}
	else
	{
		sprintf(buf2, "    $t$N%s.", buf);

		for (fch = victim->in_room->first_person ; fch ; fch = fch->next_in_room)
		{
			if (fch->desc && victim != fch)
			{
				if (fch->position > POS_SLEEPING && can_see(fch, victim) && !IS_SET(CH(fch->desc)->act, PLR_DAMAGE))
				{
					if (is_same_group(fch, victim))
					{
						if (!IS_SET(CH(fch->desc)->pcdata->spam, SPAM_PARTY_HIT))
						{
							act( buf2, fch, get_color_string(fch, COLOR_PARTY_HIT, VT102_DIM), victim, TO_CHAR);
						}
					}
					else if (who_fighting(victim) && is_same_group(fch, who_fighting(victim)))
					{
						if (!IS_SET(CH(fch->desc)->pcdata->spam, SPAM_THEY_HIT_PARTY) && !IS_SET(CH(fch->desc)->pcdata->spam, SPAM_YOU_HIT))
						{
							act( buf2, fch, get_color_string(fch, COLOR_YOU_HIT, VT102_DIM), victim, TO_CHAR);
						}
					}
					else if (!IS_SET(CH(fch->desc)->pcdata->spam, SPAM_OTHER_HIT))
					{
						act( buf2, fch, get_color_string(fch, COLOR_TEXT, VT102_DIM), victim, TO_CHAR);
					}
				}
			}
		}
		pop_call();
		return;
	}
	
	if (domain_apotheosis(victim, DOMAIN_SUN))
	{
		act_sprintf(descr, "$e glows with the radiance of pure sunlight.\n\r", victim, NULL, ch);
	}
	
	for (buf[0] = '\0', sn = 0 ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (is_affected(victim, sn) && skill_table[sn].vis_affect)
		{
			act_sprintf(descr, skill_table[sn].vis_affect, victim, NULL, ch);
			strcat(descr, "\n\r");
		}
	}

	if (fExamine)
	{
		if (ch != victim)
		{
			if (can_see(victim, ch))
			{
				act( "$n looks you over intently.", ch, NULL, victim, TO_VICT);
			}
			for (fch = ch->in_room->first_person ; fch ; fch = fch->next_in_room)
			{
				if (fch == ch || fch == victim)
					continue;
				if (!IS_AWAKE(fch))
					continue;
				if (!can_see(fch, ch))
					continue;
				ch_printf_color(fch, "%s looks %s over intently.\n\r", PERS(ch, fch), PERS(victim, fch));
			}
		}
	
		ch_roll = perception_roll(ch, SENSE_SIGHT);
		vic_roll = disguise_roll(victim);
	
		if (IS_AFFECTED(victim, AFF_HOODED) && victim != ch)
		{
			vic_roll += 4;
			if (ch != victim && perception_check(ch, victim, ch_roll, vic_roll))
				cat_sprintf(descr, "{078}You glimpse the face of %s beneath %s hood.{300}\n\r", PERS(victim, ch), his_her[victim->sex]);
			else
				cat_sprintf(descr, "{108}%s is wearing a hood, shrouding %s features.{300}\n\r",
					capitalize(he_she[victim->sex]), his_her[victim->sex]);
		}
		if (is_polymorph(victim) && ch != victim)
		{
			int check = 0;

			if ((check = disguise_check(victim, ch, vic_roll, 0)) <= -1)
			{
				cat_sprintf(descr, "{078}Something about %s doesn't seem very %s-like.{300}\n\r", adjective(victim), race_table[get_race(victim)].race_name);
			}
		}
		else if (!IS_NPC(victim) && is_string(victim->pcdata->disguise) && ch != victim)
		{
			int check = 0;

			if ((check = disguise_check(victim, ch, vic_roll, 0)) <= -4 && knows_char(ch, victim))
			{
				cat_sprintf(descr, "{078}You perceive %s is beneath a disguise.{300}\n\r", get_name(victim));
			}
			else if (check <= -1)
			{
				cat_sprintf(descr, "{078}You notice the person is attempting a disguise.{300}\n\r");
			}
		}
		if (IS_AFFECTED(victim, AFF_HIDE))
		{
			cat_sprintf(descr, "{108}%s is partially concealed.{300}\n\r", capitalize(he_she[victim->sex]));
		}
		if (is_affected(ch, gsn_analyze_dweomer) || domain_apotheosis(ch, DOMAIN_MAGIC))
		{
			get_affects_string(victim, ch, buf);
			cat_sprintf(descr, buf);
		}
		else if (CAN_SEE_MAGIC(ch) && (aff_level = get_highest_ch_aff_level(victim)) > 0)
		{
			aff_school = get_highest_ch_aff_school(victim);

			if (aff_level > 9)
				sprintf(txt, "overwhelming");
			else if (aff_level > 6)
				sprintf(txt, "strong");
			else if (aff_level > 3)
				sprintf(txt, "moderate");
			else
				sprintf(txt, "faint");

			cat_sprintf(descr, "{148}%s radiates a %s aura of %s.{300}\n\r", capitalize(he_she[victim->sex]), txt, school_types[aff_school]);
		}

		//Here's where the myriad detect spells do their thing, but nondetection might thwart them - Kregor
		
		if (IS_UNDEAD(victim) && is_affected(ch, gsn_detect_undead))
		{
			if (!check_nondetection(ch, victim, gsn_detect_undead))
			{
				cat_sprintf(descr, "{108}%s radiates ", capitalize(he_she[victim->sex]));
				if (victim->level >= 11)	
					strcat(descr, "an overwhelming ");
				else if (victim->level >= 5)	
					strcat(descr, "a strong ");
				else if (victim->level >= 2)	
					strcat(descr, "a moderate ");
				else	
					strcat(descr, "a faint ");
				strcat(descr, "aura of the undead.{300}\n\r");
			}
		}
		if ((is_polymorph(victim) || rspec_req(victim, RSPEC_SHAPECHANGER)) && is_affected(ch, gsn_detect_shapechanger))
		{
			if (!check_nondetection(ch, victim, gsn_detect_shapechanger))
			{
				cat_sprintf(descr, "{108}%s is not in his normal form.{300}\n\r", capitalize(he_she[victim->sex]));
			}
		}
		if (IS_EVIL(victim) && (is_affected(ch, gsn_detect_evil) || class_level(ch, CLASS_PALADIN)))
		{
			if (!check_nondetection(ch, victim, gsn_detect_evil))
			{
				int align = (int)victim->alignment;
				
				if (!class_level(victim, CLASS_CLERIC)
				&& !class_level(victim, CLASS_BLACKGUARD)
				&& !rspec_req(victim, RSPEC_EVIL)
				&& !IS_UNDEAD(victim))
					align /= 4;

				cat_sprintf(descr, "{018}%s radiates ", capitalize(he_she[victim->sex]));
				if (align <= -700)	
					strcat(descr, "an overwhelming ");
				else if (align <= -500)	
					strcat(descr, "a strong ");
				else if (align <= -300)	
					strcat(descr, "a moderate ");
				else if (align <= -50)	
					strcat(descr, "a faint ");
				strcat(descr, "Evil aura.{300}\n\r");
			}
		}
		if (IS_GOOD(victim) && (is_affected(ch, gsn_detect_good) || class_level(ch, CLASS_BLACKGUARD)))
		{
			if (!check_nondetection(ch, victim, gsn_detect_good))
			{
				int align = (int) victim->alignment;
				
				if (!class_level(victim, CLASS_CLERIC)
				&& !class_level(victim, CLASS_PALADIN)
				&& !rspec_req(victim, RSPEC_GOOD)
				&& !IS_UNDEAD(victim))
					align /= 4;

				cat_sprintf(descr, "{138}%s radiates ", capitalize(he_she[victim->sex]));
				if (align >= 700)	
					strcat(descr, "an overwhelming ");
				else if (align >= 500)	
					strcat(descr, "a strong ");
				else if (align >= 300)	
					strcat(descr, "a moderate ");
				else if (align >= 50)	
					strcat(descr, "a faint ");
				strcat(descr, "Good aura.{300}\n\r");
			}
		}
		if (IS_LAWFUL(victim) && is_affected(ch, gsn_detect_law))
		{
			if (!check_nondetection(ch, victim, gsn_detect_law))
			{
				int align = (int) victim->ethos;
				
				if (!class_level(victim, CLASS_CLERIC)
				&& !class_level(victim, CLASS_BLACKGUARD)
				&& !class_level(victim, CLASS_PALADIN)
				&& !rspec_req(victim, RSPEC_LAWFUL)
				&& !IS_UNDEAD(victim))
					align /= 4;

				cat_sprintf(descr, "{178}%s radiates ", capitalize(he_she[victim->sex]));
				if (align >= 700)	
					strcat(descr, "an overwhelming ");
				else if (align >= 500)	
					strcat(descr, "a strong ");
				else if (align >= 300)	
					strcat(descr, "a moderate ");
				else if (align >= 50)	
					strcat(descr, "a faint ");
				strcat(descr, "Lawful aura.{300}\n\r");
			}
		}
		if (IS_CHAOTIC(victim) && is_affected(ch, gsn_detect_chaos))
		{
			if (!check_nondetection(ch, victim, gsn_detect_chaos))
			{
				int align = (int) victim->ethos;
				
				if (!class_level(victim, CLASS_CLERIC)
				&& !class_level(victim, CLASS_BLACKGUARD)
				&& !class_level(victim, CLASS_PALADIN)
				&& !rspec_req(victim, RSPEC_CHAOTIC)
				&& !IS_UNDEAD(victim))
					align /= 4;

				cat_sprintf(descr, "{058}%s radiates ", capitalize(he_she[victim->sex]));
				if (align <= -700)	
					strcat(descr, "an overwhelming ");
				else if (align <= -500)	
					strcat(descr, "a strong ");
				else if (align <= -300)	
					strcat(descr, "a moderate ");
				else if (align <= -50)	
					strcat(descr, "a faint ");
				strcat(descr, "Chaotic aura.{300}\n\r");
			}
		}
	}
	
	for (list[0] = '\0', count = WearLoc = iWear = 0 ; iWear < MAX_WEAR ; iWear++)
	{
		if (iWear == WEAR_NONE)
			continue;
		for (covered = FALSE, layer = LAYER_OVER ; layer >= 0 ; layer--)
		{
			if (covered)
				continue;
			for (obj = victim->first_carrying; obj; obj = obj->next_content)
			{
				if (IS_SET(obj->extra_flags, ITEM_CONCEALED))
				{
					if (!fExamine || !perception_check(ch, victim, ch_roll, obj_conceal_check(obj)))
						continue;
				}
				if (WEAR_LOC(obj, iWear) && get_layer(obj) == layer)
				{
					if (WearLoc == iWear)
						cat_sprintf(list, "   {200}<{078}layered under{200}>{300} %s\n\r", str_resize(format_obj_to_char(obj, ch, 0), t1, -60));
					else
						cat_sprintf(list, "{078}%s{178}%s\n\r", where_name[iWear], str_resize(format_obj_to_char(obj, victim, 0), t1, -60));
					count++;
					if (!IS_OBJ_STAT(obj, ITEM_TRANSPARENT))
						covered = TRUE;
					WearLoc = iWear;
				}
			}
		}
	}
	if (count == 0)
	{
		cat_sprintf(descr, "%s is not equipped with anything remarkable.\n\r", capitalize(he_she[victim->sex]));
	}
	else
	{
		cat_sprintf(descr, "%s%s is equipped with:\n\r%s", get_color_string(ch, COLOR_ACCENT, VT102_DIM), capitalize(he_she[victim->sex]), list);
	}

	if (victim != ch && IS_PET(victim, ch))
	{
		sprintf_list_to_char(list, victim->first_carrying, ch, 0, TRUE, FALSE);
		cat_sprintf(descr, "{200}%s is carrying:\n\r%s", capitalize(PERS(victim, ch)), list);
	}

	send_to_char_color( descr, ch );

	pop_call();
	return;
}

bool check_blind( CHAR_DATA *ch )
{
	push_call("check_blind(%p)",ch);

	if (IS_PLR(ch, PLR_HOLYLIGHT))
	{
		pop_call();
		return TRUE;
	}

	if (IS_AFFECTED(ch, AFF_BLIND))
	{
		send_to_char( "You can't see a thing!\n\r", ch );
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}

void show_char_room(CHAR_DATA *ch, ROOM_INDEX_DATA *rm, bool dir, bool range, int sight)
{
	char colc[10], colw[10], colW[10];
	CHAR_DATA *rch;

	push_call("show_char_room(%p,%p,%p,%p)",ch,rm,dir,sight);

	if (--sight < 0)
	{
		pop_call();
		return;
	}
	
	strcpy(colc, get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	strcpy(colw, get_color_string(ch, COLOR_TEXT, VT102_DIM));
	strcpy(colW, get_color_string(ch, COLOR_TEXT, VT102_BOLD));

	if (!can_see_in_room(ch, rm))
	{
		ch_printf_color(ch, "  %s%d%s: {108}nothing but darkness!{300}\n\r", colw, range, colc);

		pop_call();
		return;
	}
	else
	{
		ch_printf_color(ch, "  %s%d%s: %s%s\n\r", colw, range, colc, colW, get_dynamic_room_title(rm));

		for (rch = rm->first_person ; rch ; rch = rch->next_in_room)
		{
			if (IS_AFFECTED(rch, AFF_HIDE) || !can_see(ch, rch))
			{
				continue;
			}
			// spot DC = 0 + 1/10 feet
			if (!perception_check(ch, rch, perception_roll(ch, SENSE_SIGHT), range * 3))
			{
				continue;
			}
			ch_printf_color(ch, "%s     %s is there.\n\r", colw, capitalize(PERS(rch, ch)));
		}
	}

	if (!is_valid_exit(ch, rm, dir))
	{
		pop_call();
		return;
	}

	if (!can_use_exit(ch, rm->exit[dir]))
	{
		pop_call();
		return;
	}

	// take the worse of previous room or current room's LOS
	sight = UMIN(sight, get_sight(ch, rm));

	show_char_room(ch, room_index[rm->exit[dir]->to_room], dir, range + 1, sight);

	pop_call();
	return;
}


void show_char_dir(CHAR_DATA *ch, bool dir, bool fScry)
{
	ROOM_TIMER_DATA *rtd;
	EXIT_DATA *pExit;
	int sight;
	bool fKeyword = FALSE;
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];

	push_call("show_char_dir(%p,%p)",ch,dir);

	ch_printf_color(ch, "%sLooking %s you see:\n\r", get_color_string(ch, COLOR_TEXT, VT102_BOLD), dir_name[dir]);

	if ((pExit = get_exit(ch->in_room->vnum, dir)) == NULL)
	{
		send_to_char("Nothing special about that direction.\n\r", ch);
		pop_call();
		return;
	}

	if (is_string(pExit->description))
	{
		ch_printf_color(ch, "%s\n\r", pExit->description);
	}

	if (IS_SET(pExit->exit_info, EX_CLIMB|EX_JUMP))
	{
		sprintf(buf1, "The distance %s looks to be about %d feet", dir_name[dir], pExit->fall_dist ? pExit->fall_dist : 20);
		if (IS_SET(pExit->exit_info, EX_CLIMB))
		{
			int chance = pExit->climb_dc - (10 + stat_bonus(TRUE, ch, APPLY_STR) + learned(ch, gsn_climb));
			
			if (chance > 10)
				sprintf(buf2, "impossible");
			else if (chance >= 9)
				sprintf(buf2, "unlikely");
			else if (chance >= 7)
				sprintf(buf2, "difficult");
			else if (chance >= 5)
				sprintf(buf2, "challenging");
			else if (chance >= 3)
				sprintf(buf2, "moderate");
			else if (chance >= 1)
				sprintf(buf2, "easy");
			else
				sprintf(buf2, "effortless");
			
			cat_sprintf(buf1, ", and %s %s task to climb.\n\r", a_an(buf2), buf2);
		}
		else
		{
			strcat(buf1, ". You would have to jump or fly to get there.\n\r");
		}
		send_to_char(buf1, ch);
	}	

	if (pExit->keyword[0] != '\0')
	{
		fKeyword = TRUE;
	}
	
	if ((rtd = get_room_affect(ch->in_room, ROOM_BLOCK)) == NULL)
	{
		rtd = get_room_affect_vnum(pExit->to_room, ROOM_BLOCK);
	}
	if (rtd && is_string(skill_table[rtd->type].vis_affect))
	{
		act(skill_table[rtd->type].vis_affect, ch, dir_name[dir], NULL, TO_CHAR);
		pop_call();
		return;
	}
	
	if (!IS_SET(pExit->exit_info, EX_BASHED))
	{
		if (IS_SET(pExit->exit_info, EX_CLOSED))
		{
			act( "The $d is closed.", ch, NULL, fKeyword ? pExit->keyword : "door", TO_CHAR);

			if (IS_SET(pExit->exit_info, EX_LOCKED) && learned(ch, gsn_pick_lock))
			{
				int chance = lock_dc(pExit, NULL) - (10 + stat_bonus(TRUE, ch, APPLY_DEX) + learned(ch, gsn_pick_lock));
				
				if (pExit->key < 0)
					ch_printf(ch, "There is no visible lock that can be picked.\n\r");
				else
				{
					if (chance > 10)
						sprintf(buf2, "impossible");
					else if (chance >= 9)
						sprintf(buf2, "unlikely");
					else if (chance >= 7)
						sprintf(buf2, "difficult");
					else if (chance >= 5)
						sprintf(buf2, "challenging");
					else if (chance >= 3)
						sprintf(buf2, "moderate");
					else if (chance >= 1)
						sprintf(buf2, "easy");
					else
						sprintf(buf2, "effortless");
					
					ch_printf(ch, "The lock appears to be %s %s task to pick.\n\r", a_an(buf2), buf2);
				}
			}
		}
		else if (IS_SET(pExit->exit_info, EX_ISDOOR))
		{
			act( "The $d is open.", ch, NULL, fKeyword ? pExit->keyword : "door", TO_CHAR );
		}
	}
	else
	{
		act( "The $d has been bashed from its hinges!",	ch, NULL, fKeyword ? pExit->keyword : "door", TO_CHAR);
	}
	
	if (!is_valid_exit(ch, ch->in_room, dir))
	{
		if (is_string(pExit->description))
		{
			pop_call();
			return;
		}
		else if (!IS_SET(pExit->exit_info, EX_WINDOW))
		{
// 			send_to_char( "Nothing special about that direction.\n\r", ch );
			pop_call();
			return;
		}
	}
	
	// added support for clairvoyance to look into adjacent room - Kregor 1/29/11
	if (fScry)
	{
		if (room_index[pExit->to_room] != NULL && !IS_SET(room_index[pExit->to_room]->room_flags, ROOM_RIP|ROOM_NOSCRY))
		{
			show_room_to_char(ch, room_index[pExit->to_room]);
			pop_call();
			return;
		}
		else
		{
			send_to_char("Something prevents you from scrying in.\n\r", ch);
		}
	}

	sight  = get_sight(ch, ch->in_room);

	show_char_room(ch, room_index[pExit->to_room], dir, 1, sight);

	pop_call();
	return;
}


CHAR_DATA *find_char_room(CHAR_DATA *ch, ROOM_INDEX_DATA *rm, bool dir, char *name, int sight)
{
	CHAR_DATA *rch;
	ROOM_INDEX_DATA *old_room;

	push_call("find_char_room(%p,%p,%p,%p)",ch,rm,dir,sight);

	if (--sight < 0)
	{
		pop_call();
		return NULL;
	}

	if (!can_see_in_room(ch, rm))
	{
		pop_call();
		return NULL;
	}

	old_room    = ch->in_room;
	ch->in_room = rm;
	rch         = get_char_room(ch, name);
	ch->in_room = old_room;

	if (rch && rch != ch)
	{
		pop_call();
		return rch;
	}

	if (!is_valid_exit(ch, rm, dir))
	{
		pop_call();
		return NULL;
	}

	if (!can_use_exit(ch, rm->exit[dir]))
	{
		pop_call();
		return NULL;
	}

	// take the worse of previous room or current room's LOS
	sight = UMIN(sight, get_sight(ch, rm));

	pop_call();
	return find_char_room(ch, room_index[rm->exit[dir]->to_room], dir, name, sight);
}


/*
 * find a character by name in given direction for ranged casting and attacks
 */
CHAR_DATA *find_char_dir(CHAR_DATA *ch, bool dir, char *name)
{
	EXIT_DATA *pExit;
	CHAR_DATA *victim;
	int sight;

	push_call("find_char_dir(%p,%p)",ch,dir);

	if ((pExit = get_exit(ch->in_room->vnum, dir)) == NULL)
	{
		pop_call();
		return NULL;
	}

	if (!is_valid_exit(ch, ch->in_room, dir))
	{
		pop_call();
		return NULL;
	}

	if (!can_use_exit(ch, pExit))
	{
		pop_call();
		return NULL;
	}

	sight  = get_sight(ch, ch->in_room);

	victim = find_char_room(ch, room_index[pExit->to_room], dir, name, sight);

	pop_call();
	return victim;
}

OBJ_DATA *find_obj_dir(CHAR_DATA *ch, bool dir, char *name)
{
	EXIT_DATA *pExit;
	OBJ_DATA *obj;
	ROOM_INDEX_DATA *to_room;
	
	push_call("find_obj_dir(%p,%p)",ch,dir);

	if ((pExit = get_exit(ch->in_room->vnum, dir)) == NULL)
	{
		pop_call();
		return NULL;
	}

	if (!is_valid_exit(ch, ch->in_room, dir))
	{
		pop_call();
		return NULL;
	}

	if (!can_use_exit(ch, pExit))
	{
		pop_call();
		return NULL;
	}
	
	if ((to_room = get_room_index(pExit->to_room)) == NULL)
	{
		pop_call();
		return NULL;
	}
	
	if ((obj = get_obj_list(ch, name, to_room->first_content)) == NULL)
	{
		pop_call();
		return NULL; 
	}
	
	if (!can_see_obj(ch, obj))
	{
		pop_call();
		return NULL; 
	}
	
	pop_call();
	return obj;
}

/*
 * below two functions return TRUE if ch can see victim
 * at a distance. Used for range finding during hunting
 * and mobile ranged attacks - Kregor
 */
bool see_char_dir(CHAR_DATA *ch, CHAR_DATA *victim, ROOM_INDEX_DATA *rm, bool dir, int sight)
{
	push_call("find_char_room(%p,%p,%p,%p)",ch,rm,dir,sight);

	if (victim == NULL || victim->in_room == NULL)
	{
		pop_call();
		return FALSE;
	}

	if (--sight < 0)
	{
		pop_call();
		return FALSE;
	}
	
	if (!can_see_in_room(ch, rm))
	{
		pop_call();
		return FALSE;
	}

	if (victim->in_room == rm && can_see(ch, victim))
	{
		pop_call();
		return TRUE;
	}

	if (!is_valid_exit(ch, rm, dir))
	{
		pop_call();
		return FALSE;
	}

	if (!can_use_exit(ch, rm->exit[dir]))
	{
		pop_call();
		return FALSE;
	}

	// take the worse of previous room or current room's LOS
	sight = UMIN(sight, get_sight(ch, rm));

	pop_call();
	return see_char_dir(ch, victim, room_index[rm->exit[dir]->to_room], dir, sight);
}

/*
 * can victim be seen at a distance in x direction - Kregor
 */
bool can_see_dir(CHAR_DATA *ch, CHAR_DATA *victim, bool dir)
{
	EXIT_DATA *pExit;
	int sight;

	push_call("find_char_dir(%p,%p)",ch,dir);

	if ((pExit = get_exit(ch->in_room->vnum, dir)) == NULL)
	{
		pop_call();
		return FALSE;
	}

	if (!is_valid_exit(ch, ch->in_room, dir))
	{
		pop_call();
		return FALSE;
	}

	if (!can_use_exit(ch, pExit))
	{
		pop_call();
		return FALSE;
	}

	sight  = get_sight(ch, ch->in_room);

	pop_call();
	return see_char_dir(ch, victim, room_index[pExit->to_room], dir, sight);
}

void do_search( CHAR_DATA *ch, char *argument)
{
	OBJ_DATA *obj, *container;
	int dir, count, roll;
	EXIT_DATA *pExit;
	CHAR_DATA *victim, *rch;
	char arg1[MAX_INPUT_LENGTH];
	char *cmd = STRALLOC(argument);

	push_call("do_search(%p,%p)",ch,argument);

	roll = search_roll(ch);

	if (argument[0] == '\0')
	{
		if (!ch->concentrating)
		{
			act( "You search the room...", ch, NULL, NULL, TO_CHAR);
			act( "$n searches the room...", ch, NULL, NULL, TO_ROOM);
			ch->concentrating = TRUE;
			ch->skill_timer		= 12;
			ch->timer_fun			= do_search;
			RESTRING(ch->cmd_argument, cmd);
			TAKE_ACTION(ch, ACTION_FULL);
			pop_call();
			return;
		}
		for (count = 0, obj = ch->in_room->first_content ; obj ; obj = obj->next_content)
		{
			if(is_trap_armed(obj))
			{
				ch_printf_color(ch,"{138}You discover %s!\n\r",obj->short_descr);
				count++;
			}
			else if(IS_SET(obj->extra_flags,ITEM_HIDDEN))
			{
				if (search_check(ch, NULL, roll, obj->value[7]))
				{
					ch_printf_color(ch,"You reveal %s\n\r",obj->short_descr);
					REMOVE_BIT(obj->extra_flags,ITEM_HIDDEN);
					obj->value[7] = 0;
					count++;
				}
			}
		}
		for (rch = ch->in_room->first_person ; rch ; rch = rch->next_in_room)
		{
			if (is_same_group(ch, rch))
				continue;
			if (!IS_AFFECTED(rch, AFF_HIDE))
				continue;
			if (!search_check(ch, rch, roll, hide_roll(rch)))
				continue;
						
			if (rch->furniture != NULL)
			{
				if (!IS_NPC(ch))
				{
					if (!IS_NPC(rch))
						ch->pcdata->found_pvnum[rch->pcdata->pvnum] = 1;
					else
						ch->pcdata->found_vnum[rch->pIndexData->vnum] = 1;
				}
				act( "You discover $N concealed behind $p.", ch, ch->furniture, rch, TO_CHAR);
				act( "$n discovers your concealment.", ch, NULL, rch, TO_VICT);
				count++;
				continue;
			}
			else
			{
				if (!IS_NPC(ch))
				{
					if (!IS_NPC(rch))
						ch->pcdata->found_pvnum[rch->pcdata->pvnum] = 1;
					else
						ch->pcdata->found_vnum[rch->pIndexData->vnum] = 1;
				}
				act( "You discover $N concealed amongst the surroundings.", ch, NULL, rch, TO_CHAR);
				act( "$n discovers your concealment.", ch, NULL, rch, TO_VICT);
				count++;
				continue;
			}
		}
		if (count == 0)
		{
			ch_printf_color(ch,"You didn't uncover anything unusual.\n\r");
		}
		pop_call();
		return;
	}
	argument = one_argument(argument, arg1);
	
	if (!strcasecmp(arg1, "allow"))
	{
		if (IS_NPC(ch))
		{
			pop_call();
			return;
		}
		send_to_char("You are allowing others to search you, type SEARCH DENY to remove.\n\r", ch);
		SET_BIT(ch->act, PLR_SEARCH);
		pop_call();
		return;
	}
			
	if (!strcasecmp(arg1, "deny"))
	{
		if (IS_NPC(ch))
		{
			pop_call();
			return;
		}
		send_to_char( "You are not allowing others to search you, type SEARCH ALLOW to allow.\n\r", ch);
		REMOVE_BIT(ch->act, PLR_SEARCH);
		pop_call();
		return;
	}

	if ((victim = get_char_room(ch, arg1)) != NULL )
	{
		if (argument[0] == '\0' && ch == victim)
		{
			act( "Search yourself for what?", ch, NULL, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if (ch == victim)
		{
			if (!ch->concentrating)
			{
				act( "You rummage through your belongings.", ch, NULL, NULL, TO_CHAR);
				act( "$n rummages through $s belongings.",   ch, NULL, NULL, TO_ROOM);	
				ch->concentrating = TRUE;
				ch->skill_timer		= 12;
				ch->timer_fun			= do_search;
				RESTRING(ch->cmd_argument, cmd);
				TAKE_ACTION(ch, ACTION_FULL);
				pop_call();
				return;
			}
			if (!search_inventory(ch, ch->first_carrying, argument))
				act( "You discover nothing in your search.", ch, NULL, NULL, TO_CHAR);
			TAKE_ACTION(ch, ACTION_FULL);
			pop_call();
			return;
		}
		if (IS_HELPLESS(victim) || IS_PLR(victim, PLR_SEARCH))
		{
			if (!ch->concentrating)
			{
				act( "You rummage through $N's belongings.", ch, NULL, victim, TO_CHAR);
				act( "$n rummages through your belongings.", ch, NULL, victim, TO_VICT);
				act( "$n rummages through $N's belongings.", ch, NULL, victim, TO_NOTVICT);	
				ch->concentrating = TRUE;
				ch->skill_timer		= 12;
				ch->timer_fun			= do_search;
				RESTRING(ch->cmd_argument, cmd);
				TAKE_ACTION(ch, ACTION_FULL);
				pop_call();
				return;
			}
			search_inventory(ch, victim->first_carrying, argument);
				act( "You discover nothing in your search.", ch, NULL, NULL, TO_CHAR);
			REMOVE_BIT(victim->act, PLR_SEARCH);
			TAKE_ACTION(ch, ACTION_FULL);
			pop_call();
			return;
		}
		else
		{
			act( "$N is not allowing you to search $M.", ch, NULL, victim, TO_CHAR);
			act( "$n wants to search you, type SEARCH ALLOW to allow it.", ch, NULL, victim, TO_VICT);	
			pop_call();
			return;
		}
	}

	if ((container = get_obj_here(ch, arg1)) != NULL )
	{
		if (IS_SET(container->value[1], CONT_CLOSED))
		{
			act( "$p is closed.", ch, container, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if (!ch->concentrating)
		{
			act( "You rummage through $p.", ch, container, NULL, TO_CHAR);
			act( "$n rummages through $p.", ch, container, NULL, TO_ROOM);	
			ch->concentrating = TRUE;
			ch->skill_timer		= 12;
			ch->timer_fun			= do_search;
			RESTRING(ch->cmd_argument, cmd);
			TAKE_ACTION(ch, ACTION_FULL);
			pop_call();
			return;
		}
		if (!search_inventory(ch, container->first_content, argument))
			act( "You discover nothing in your search.", ch, NULL, NULL, TO_CHAR);
		TAKE_ACTION(ch, ACTION_FULL);
		pop_call();
		return;
	}

	/*
	 * Search in a direction
	 */
	if ((dir = direction_door(arg1)) != -1)
	{
		if (!ch->concentrating)
		{
			act( "$n searches toward the $t.", ch, dir_name[dir], NULL, TO_ROOM);
			act( "You search toward the $t.", ch, dir_name[dir], NULL, TO_CHAR);
			ch->concentrating = TRUE;
			ch->skill_timer		= 12;
			ch->timer_fun			= do_search;
			RESTRING(ch->cmd_argument, cmd);
			TAKE_ACTION(ch, ACTION_FULL);
			pop_call();
			return;
		}
		if ((pExit = get_exit(ch->in_room->vnum, dir)) == NULL)
		{
			send_to_char("You see nothing special there.\n\r", ch);
			pop_call();
			return;
		}
		if (IS_SET(pExit->exit_info, EX_HIDDEN))
		{
			if (can_see_hidden(ch, dir) || search_check(ch, NULL, roll, 20))
			{
				act( "{138}You spot a hidden door to the $t.",ch, dir_name[dir], NULL, TO_CHAR);
				if (!IS_NPC(ch) && !IS_SET(ch->pcdata->found_dir[ch->in_room->vnum], 1 << dir))
					SET_BIT(ch->pcdata->found_dir[ch->in_room->vnum], 1 << dir);
				for (rch = ch->in_room->first_person ; rch ; rch = rch->next_in_room)
				{
					if (!IS_NPC(rch) && can_see(rch, ch) && is_same_group(rch, ch)
					&& !IS_SET(rch->pcdata->found_dir[ch->in_room->vnum], 1 << dir))
					{
						act( "{138}$N reveals a hidden door to the $t.", rch, dir_name[dir], ch, TO_CHAR);
						SET_BIT(rch->pcdata->found_dir[ch->in_room->vnum], 1 << dir);
					}
				}
			}
			else
			{
				send_to_char("You see nothing special there.\n\r", ch);
			}
			pop_call();
			return;
		}
	}
	pop_call();
	return;
}

bool search_inventory( CHAR_DATA *ch, OBJ_DATA *search_obj, char *argument )
{
	OBJ_DATA *obj;
	bool found;

	push_call("search_inventory(%p,%p,%p)",ch,search_obj,argument);

	for (found = FALSE , obj = search_obj ; obj ; obj = obj->next_content)
	{
		if (obj->first_content)
		{
			act( "You search inside $p.", ch, obj, NULL, TO_CHAR);
			if (search_inventory(ch, obj->first_content, argument))
				found = TRUE;
		}

		if (argument[0] == '\0'
		&& ch != obj->carried_by && IS_OBJ_STAT(obj, ITEM_CONCEALED)
		&& search_check(ch, obj->carried_by, search_roll(ch)+4, obj_conceal_check(obj)))
		{
			if (obj->carried_by)
			{
				act( "$p is concealed on $N's person.", ch, obj, obj->carried_by, TO_CHAR);
				act( "$n discovers $p concealed upon you.", ch, obj, obj->carried_by, TO_VICT);
			}
			else if (obj->in_obj)
			{
				act( "$p is concealed inside $P.", ch, obj, obj->in_obj, TO_CHAR);
			}
			REMOVE_BIT(obj->extra_flags, ITEM_CONCEALED);
			found = TRUE;
			continue;
		}
		if (can_see_obj(ch, obj)
		&& (is_multi_name_list_short(argument, OBJD(obj, ch))
		|| is_multi_name_list_short(argument, obj->name)))
		{
			if (obj->in_obj)
			{
				act( "$p is inside $P.", ch, obj, obj->in_obj, TO_CHAR);
				found = TRUE;
			}
			else if (IS_WORN(obj))
			{
				act( "$p is being worn.", ch, obj, NULL, TO_CHAR);
				found = TRUE;
			}
			else if (obj->carried_by && WEAR_LOC(obj, WEAR_NONE))
			{
				act( "$p is being carried by $N.", ch, obj, obj->carried_by, TO_CHAR);
				found = TRUE;
			}
			continue;
		}
		if (ch != obj->carried_by && IS_OBJ_STAT(obj, ITEM_CONCEALED)
		&& search_check(ch, obj->carried_by, search_roll(ch)+4, obj_conceal_check(obj))
		&& (is_multi_name_list_short(argument, OBJD(obj, ch))
		|| is_multi_name_list_short(argument, obj->name)))
		{
			if (obj->in_obj)
			{
				act( "$p is concealed inside $P.", ch, obj, obj->in_obj, TO_CHAR);
			}
			else if (obj->carried_by)
			{
				act( "$p is concealed on $N's person.", ch, obj, obj->carried_by, TO_CHAR);
				act( "$n discovers $p concealed upon you.", ch, obj, obj->carried_by, TO_VICT);
			}
			REMOVE_BIT(obj->extra_flags, ITEM_CONCEALED);
			found = TRUE;
			continue;
		}
	}
	pop_call();
	return found;
}

void do_peek( CHAR_DATA *ch, char *argument )
{
	CHAR_DATA *victim;
	char arg[MAX_STRING_LENGTH], colc[10], colw[10];
	char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];
	int vict_roll, bluff;

	push_call("do_peek(%p,%p)",ch,argument);

	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}

	if (*argument == '\0')
	{
		send_to_char("Peek at whom?\n\r", ch);
		pop_call();
		return;
	}
	
	sprintf(colc, "%s", get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	sprintf(colw, "%s", get_color_string(ch, COLOR_TEXT, VT102_DIM));

	argument = one_argument(argument, arg);

	if ((victim = get_char_room(ch, arg)) == NULL)
	{
		send_to_char("That person isn't here.\n\r", ch);
		pop_call();
		return;
	}

	vict_roll = sense_motive_roll(victim);
	bluff = bluff_roll(ch);

	if (can_see(victim, ch) && sense_motive_check(victim, ch, vict_roll, bluff))
	{
		act( "You try to look $N over without $M noticing.", ch, NULL, victim, TO_CHAR);
		act( "You notice $n looking you over.", ch, NULL, victim, TO_VICT   );
		if (IS_NPC(victim))
		{
			if (number_bits(1) == 0)
			{
				if (CAN_TALK(victim))
				{
					act("$N frowns at $n, $t\"What are YOU looking at?!\"", ch, get_color_string(ch, COLOR_SPEECH, VT102_DIM), victim, TO_ROOM);
					act("$N frowns at you, $t\"What are YOU looking at?!\"", ch, get_color_string(ch, COLOR_SPEECH, VT102_DIM), victim, TO_CHAR);
					if (IS_ACT(victim, ACT_CITIZEN))
					{
						do_shout(ch, format("GUARDS! There is a freak at %s leering at everyone!", ch->in_room->name));
						gain_reputation(ch, -1);
					}
				}
				else
				{
					act("$N makes eye contact with you and scowls.", ch, NULL, victim, TO_CHAR);
					act("$N looks aggressively at $n.", ch, NULL, victim, TO_CHAR);
				}
				if (IS_ACT(victim, ACT_AGGRESSIVE))
				{
					fight(victim, ch);
				}
				else if (IS_ACT(ch, ACT_WIMPY))
				{
					act("$n scoots off quickly.", victim, NULL, NULL, TO_ROOM);
					junk_mob(victim);
				}
			}
		}
	}
	else
		act( "You try to look $N over without $M noticing.", ch, NULL, victim, TO_CHAR);

	sprintf(buf, "%s%s is carrying:%s\n\r", colc, capitalize(PERS(victim, ch)), colw);
	sprintf_list_to_char(buf2, victim->first_carrying, ch, 0, TRUE, TRUE);
	strcat(buf, buf2);
	send_to_char_color(buf, ch);

	pop_call();
	return;
}

/*
 * support for scrying, using Voltec's window code
 * with lots of editing and additional functions for 
 * looking at people and objects in the room - Kregor
 */
void show_char_scrying( CHAR_DATA *victim, CHAR_DATA *ch )
{
	push_call("show_char_scrying()");
	
	if (IS_SET(victim->in_room->room_flags, ROOM_RIP|ROOM_NOSCRY) || check_nondetection(ch, victim, gsn_clairvoyance))
	{
		act("$N is warded against your scrying.", ch, NULL, victim, TO_CHAR);
		pop_call();
		return;
	}
	show_char_to_char( victim, ch, FALSE );
	if (is_affected(victim, gsn_detect_scrying))
	{
		if (dice(1,20) + get_affect_level(victim, gsn_detect_scrying) >= dice(1,20) + get_affect_level(ch, gsn_clairvoyance))
			act("You sense that $n is watching you.", ch, NULL, victim, TO_VICT);
		else
			act("You sense that someone is watching you.", ch, NULL, victim, TO_VICT);
	}
	pop_call();
	return;
}


void show_obj_scrying( OBJ_DATA *obj, CHAR_DATA *ch )
{
	push_call("show_obj_scrying()");
	
	if (IS_SET(obj->in_room->room_flags, ROOM_RIP|ROOM_NOSCRY))
	{
		act("$p is warded against your scrying.", ch, obj, NULL, TO_CHAR);
		pop_call();
		return;
	}
	if (!is_string(obj->description))
	{
		act("You see nothing remarkable about $p", ch, obj, NULL, TO_CHAR);
	}
	else
	{
		ch_printf_color(ch, "{300}%s", obj->description);
	}
	pop_call();
	return;
}


void look_scrying( CHAR_DATA *ch, OBJ_DATA *obj, char *argument )
{
	CHAR_DATA *victim = NULL;
	OBJ_DATA *robj = NULL;
	char buf[MAX_STRING_LENGTH];
	int room_vnum;
	
	push_call("look_scrying(%p,%p)",ch,obj);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (IS_OBJ_TYPE(obj, ITEM_WINDOW) || obj->value[0] == 0 )
	{
		sprintf(buf, "%s\n\r", obj->description );
		send_to_char(buf, ch);
		pop_call();
		return;
	}

	// if the window is owned, only the owner can scry, and crash protection - Kregor
	if ((obj->owned_by && obj->owned_by != ch->pcdata->pvnum)
	|| (room_vnum = obj->value[0]) <= 0 || room_index[room_vnum] == NULL)
	{
		send_to_char( "You see nothing but your reflection.\n\r", ch );
		pop_call();
		return;
	}
	
	if (IS_OBJ_STAT(obj, ITEM_MAGIC) && IS_SET(room_index[room_vnum]->room_flags, ROOM_RIP|ROOM_NOSCRY))
	{
		send_to_char( "{068}You see nothing but a cloudy haze.\n\r", ch );
		pop_call();
		return;
	}
	
	if (*argument != '\0')
	{
		victim = get_char_list(ch, argument, room_index[room_vnum]->first_person);
		robj = get_obj_list(ch, argument, room_index[room_vnum]->first_content);
		if (!victim && !robj)
		{
			act("You see no $t there.", ch, argument, NULL, TO_CHAR);
			pop_call();
			return;
		}
	}
	
	act( "$n looks into $p.", ch, obj, NULL, TO_CAN_SEE);
	act( "Through $p you see:", ch, obj, NULL, TO_CHAR);

	if (victim)
	{
		show_char_scrying(victim, ch);
	}
	else if (robj)
	{
		show_obj_scrying(robj, ch);
	}
	else
	{
		show_room_to_char(ch, room_index[room_vnum]);
	}
	
	pop_call();
	return;
}

/*
 * Reworked look into separate functions that concat to
 * player buffer for better buffer scrolling - Kregor
 */
void show_room_to_char( CHAR_DATA *ch, ROOM_INDEX_DATA *room )
{
	ROOM_TIMER_DATA *rtd;
	CHAR_DATA *rch;
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	bool CanSee = FALSE;
	int leng = 0;
	int cnt;
	
	push_call("show_room_to_char()");
	
	buf[0] = '\0';
	buf2[0] = '\0';
	
	if ((CanSee = can_see_in_room(ch, room)) == FALSE)
	{
		send_to_char_color( "{108}All you can see is blackness!\n\r", ch );
		pop_call();
		return;
	}

	if (IS_SET(room->room_flags, ROOM_WILDERNESS) || IS_SET(room->area->flags, AFLAG_WILDERNESS))
	{
		sprintf_wilderness_map(ch, buf, get_sight(ch, room) * 2, TRUE);
	}
	else
	{
		if (show_build_vnum(ch, room->vnum))
		{
			cat_sprintf(buf2, "%s(%d) ", get_color_string(ch, COLOR_PROMPT, VT102_DIM), room->vnum);
		}
		cat_sprintf(buf2, "%s%s\n\r", get_color_string(ch, COLOR_PROMPT,VT102_BOLD), get_dynamic_room_title(room) );
		
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
		
		if (ch->desc && IS_SET(CH(ch->desc)->act, PLR_AUTOEXIT))
		{
			sprintf_exits(buf2, ch, TRUE);
			leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
		}	
		if (!IS_ACT(ch, PLR_BRIEF))
		{
			if (IS_SET(room->room_flags, ROOM_DYNAMIC))
			{
				sprintf(buf2, "{300}%s", ansi_justify(get_dynamic_description(ch), 80));
			}
			else if (room->night_desc[0] != '\0' && mud->sunlight == SUN_DARK)
			{
				sprintf(buf2, "{300}%s", room->night_desc);
			}
			else
			{
				sprintf(buf2, "{300}%s", room->description);
			}
			leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
		}
	}
	
	for (rtd = mud->f_room_timer ; rtd ; rtd = rtd->next)
	{
		if (rtd->vnum != room->vnum)
			continue;

		if (rtd->type == gsn_creeping_doom)
		{
			strcpy( buf2, "{038}A carpet of tiny vermin crawls along the ground.\n\r");
		}
		else if (rtd->type == gsn_cloudkill)
		{
			strcpy( buf2, "{128}A cloud of noxious gases is billowing.\n\r");
		}
		else if (rtd->type == gsn_fog_cloud)
		{
			strcpy( buf2, "{068}A dark cloud of fog partially obscures your vision.\n\r");
		}
		else if (IS_SET(rtd->bitvector, ROOM_ICE))
		{
			strcpy( buf2, "{168}Sheets of ice covering every surface chill the air.\n\r");
		}
		else if (rtd->type == gsn_entangle)
		{
			strcpy( buf2, "{128}Tendrils of plants and vines whip and snag at everything that moves.\n\r");
		}
		else if (rtd->type == gsn_black_tentacles)
		{
			strcpy( buf2, "{108}Black, rubbery tentacles whip and snag at everything that moves.\n\r");
		}
		else
		{
			continue;
		}
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
	}

	for (cnt = 0 ; cnt < MAX_LAST_LEFT ; cnt++)
	{
		if (IS_SET(room->last_left_bits[cnt], TRACK_BLOOD))
		{
			sprintf(buf2, "{118}     A trail of blood leads %s.\n\r", dir_name[UNSHIFT(room->last_left_bits[cnt])]);
			leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
		}
	}

	if (room->first_content)
	{
		sprintf_list_to_char( buf2, room->first_content, ch, 1, FALSE, FALSE );
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
	}
	
	for (rch = room->first_person ; rch != NULL ; rch = rch->next_in_room)
	{
		if (rch == ch || !can_see(ch, rch))
		{
			continue;
		}
		get_long_descr(buf2, rch, ch);
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
	}
	
	if (IS_SET(room->room_flags, ROOM_SAFE))
	{
		sprintf(buf2, "{300}You feel very safe here.\n\r");
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
	}
	else if (IS_SET(room->room_flags, ROOM_INN))
	{
		sprintf(buf2, "{300}You feel safe to rest here.\n\r");
		leng = str_apd_max(buf, buf2, leng, MAX_STRING_LENGTH);
	}
	send_to_char_color(buf, ch);

	if (IS_SET(room->progtypes, DESC_PROG))
	{
		rprog_examine_trigger(ch);
	}
	pop_call();
	return;
}


void show_obj_to_char( OBJ_DATA *obj, CHAR_DATA *ch )
{
	char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];
	
	push_call("show_obj_to_char()");

	if (IS_OBJ_TYPE(obj, ITEM_PAPER))
	{
		do_read(ch, obj->name);
	}
	else if (IS_OBJ_TYPE(obj, ITEM_WINDOW))
	{
		look_scrying(ch, obj, "");
	}
	else if (!is_string(obj->description))
	{
		ch_printf_color(ch, "You see nothing remarkable about %s.\n\r", OBJD(obj, ch) );
	}
	else
	{
		sprintf(buf, "{300}%s", obj->description);
		if (IS_OBJ_TYPE(obj, ITEM_FURNITURE) && obj->first_content)
		{
			act_sprintf( buf, "Sitting atop of $p is:\n\r", ch, obj, NULL );
			sprintf_list_to_char( buf2, obj->first_content, ch, 0, TRUE, FALSE );
			strcat(buf, buf2);
		}
		send_to_char_color(buf, ch);
	}
	oprog_examine_trigger(ch, obj);

	pop_call();
	return;
}


void show_container_to_char( OBJ_DATA *obj, CHAR_DATA *ch )
{
	char buf[MAX_STRING_LENGTH], buf2[MAX_STRING_LENGTH];
	
	push_call("show_container_to_char()");

	buf[0] = buf2[0] = '\0';

	switch (obj->item_type)
	{
		default:
			send_to_char( "That is not a container.\n\r", ch );
			break;

		case ITEM_DRINK_CON:
			if (obj->value[1] <= 0)
			{
				act("$p is empty.", ch, obj, NULL, TO_CHAR);
				break;
			}
			sprintf(buf, "$p is %s half full of a %s liquid.\n\r",
				obj->value[1] <  obj->value[0] / 4 ? "less than" :
				obj->value[1] < 3 * obj->value[0] / 4 ? "about" : "more than",
				liq_table[obj->value[2]].liq_color);
			act( buf, ch, obj, NULL, TO_CHAR );
			break;

		case ITEM_CONTAINER:
		case ITEM_SPELLPOUCH:
		case ITEM_SHEATH:
		case ITEM_CART:
		case ITEM_QUIVER:
		case ITEM_CORPSE_NPC:
		case ITEM_CORPSE_PC:
			if (IS_SET(obj->value[1], CONT_CLOSED))
			{
				act( "$p is closed.", ch, obj, NULL, TO_CHAR );
				break;
			}
			if (!obj->first_content)
			{
				act( "There is nothing in $p.", ch, obj, NULL, TO_CHAR );
				break;
			}
			act_sprintf( buf, "$p contains:\n\r", ch, obj, NULL );
			sprintf_list_to_char( buf2, obj->first_content, ch, 0, TRUE, FALSE );
			strcat(buf, buf2);
			send_to_char_color(buf, ch);
			break;
	}
	pop_call();
	return;
}


void do_look( CHAR_DATA *ch, char *argument )
{
	char arg1 [MAX_INPUT_LENGTH];
	char arg2 [MAX_INPUT_LENGTH];
	char colc[10], colw[10];
	CHAR_DATA *victim;
	OBJ_DATA *obj;
	char *pdesc;
	int door;

	push_call("do_look(%p,%p)",ch,argument);

	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}

	if (ch->position < POS_SLEEPING)
	{
		send_to_char( "You can't see anything but stars!\n\r", ch );
		pop_call();
		return;
	}

	if (ch->position == POS_SLEEPING)
	{
		send_to_char( "You can't see anything, you're sleeping!\n\r", ch );
		pop_call();
		return;
	}

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}
	
	sprintf(colc, "%s", get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	sprintf(colw, "%s", get_color_string(ch, COLOR_TEXT, VT102_DIM));

	argument = one_argument( argument, arg1 );
	argument = one_argument( argument, arg2 );

	if (arg1[0] == '\0' || !strcasecmp( arg1, "auto" ) )
	{
		if (!IS_NPC(ch) && ch->pcdata->exp <= 0 && ch->in_room->vnum == ROOM_VNUM_SCHOOL)
		{
			do_help(ch, "tos");
			ch->pcdata->exp++;
			pop_call();
			return;
		}
		show_room_to_char(ch, ch->in_room);
		pop_call();
		return;
	}

	if (!strcasecmp(arg1, "i") || !strcasecmp(arg1, "in"))
	{
		if (arg2[0] == '\0')
		{
			send_to_char( "Look in what?\n\r", ch );
			pop_call();
			return;
		}

		if ((obj = get_obj_here(ch, arg2)) == NULL)
		{
			send_to_char( "You do not see that here.\n\r", ch );
			pop_call();
			return;
		}

		show_container_to_char(obj, ch);
		
		pop_call();
		return;
	}

	if ((victim = get_char_room(ch, arg1)) != NULL)
	{
		show_char_to_char( victim, ch, (victim == ch || IS_PET(victim, ch)) );
		pop_call();
		return;
	}

	if (arg2[0] != '\0' && (victim = get_char_room(ch, arg2)) != NULL)
	{
		if (IS_NPC(victim) && can_see(ch, victim))
		{
			if ((pdesc = get_extra_descr(arg1, victim->pIndexData->first_extradesc)) != NULL)
			{
				ch_printf_color(ch, "{300}%s", pdesc);
				pop_call();
				return;
			}
		}
		if (is_keeper(victim) && (obj = get_obj_carry_keeper(victim, arg1, ch)) != NULL)
		{
			if (!is_string(obj->description))
			{
				ch_printf_color(ch, "You see nothing remarkable about %s.\n\r", OBJD(obj, ch) );
			}
			else
			{
				ch_printf_color(ch, "{300}%s", obj->description);
			}
		}
		else if ((obj = get_obj_wear(victim, arg1)) != NULL && can_see_obj(ch, obj))
		{
			act("You look at $p on $N.", ch, obj, victim, TO_CHAR);
			if (!is_string(obj->description))
			{
				ch_printf_color(ch, "You see nothing remarkable about %s.\n\r", OBJD(obj, ch) );
			}
			else
			{
				ch_printf_color(ch, "{300}%s", obj->description);
			}
		}
		else
		{
			ch_printf_color(ch, "You see nothing like that on %s.\n\r", PERS(victim, ch) );
		}
		pop_call();
		return;
	}

	for (obj = ch->first_carrying ; obj ; obj = obj->next_content)
	{
		if (can_see_obj(ch, obj))
		{
			if ((pdesc = get_extra_descr(arg1, obj->pIndexData->first_extradesc)) != NULL)
			{
				ch_printf_color(ch, "{300}%s", pdesc);
				pop_call();
				return;
			}
		}
	}

	for (obj = ch->in_room->first_content ; obj ; obj = obj->next_content)
	{
		if (can_see_obj(ch, obj))
		{
			if ((pdesc = get_extra_descr(arg1, obj->pIndexData->first_extradesc)) != NULL)
			{
				ch_printf_color(ch, "{300}%s", pdesc);
				oprog_examine_trigger(ch, obj);
				pop_call();
				return;
			}
		}
	}

	for (victim = ch->in_room->first_person ; victim ; victim = victim->next_in_room)
	{
		if (IS_NPC(victim) && can_see(ch, victim))
		{
			if ((pdesc = get_extra_descr(arg1, victim->pIndexData->first_extradesc)) != NULL)
			{
				ch_printf_color(ch, "{300}%s", pdesc);
				oprog_examine_trigger(ch, obj);
				pop_call();
				return;
			}
		}
	}

	if ((pdesc = get_extra_descr(arg1, ch->in_room->first_extradesc)) != NULL)
	{
		ch_printf_color(ch, "{300}%s", pdesc);
		pop_call();
		return;
	}

	if ((obj = get_obj_here(ch, arg1)) != NULL)
	{
		show_obj_to_char(obj, ch);
		pop_call();
		return;
	}

	if ((door = direction_door(arg1)) == -1)
	{
		send_to_char( "You do not see that here.\n\r", ch);
		pop_call();
		return;
	}

	// added clairvoyance to look at character in adjacent room - Kregor 1/29/11
	if (is_string(arg2))
	{
		if (!is_affected(ch, gsn_clairvoyance))
		{
			act("You cannot focus that closely upon $t.", ch, arg2, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if ((victim = find_char_dir(ch, door, arg2)) != NULL)
		{
			show_char_scrying(victim, ch);
		}
		else if ((obj = find_obj_dir(ch, door, arg2)) != NULL)
		{
			show_obj_scrying(obj, ch);
		}
		else
		{
			act("You see no $t in that direction.", ch, arg2, NULL, TO_CHAR);
		}
		pop_call();
		return;
	}

	show_char_dir(ch, door, is_affected(ch, gsn_clairvoyance));

	pop_call();
	return;
}

/*
 * The new, improved examine command, allows
 * examining characters as well as objects, and
 * also supports examining shop inventory - Kregor
 */
void do_examine( CHAR_DATA *ch, char *argument )
{
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	OBJ_DATA *obj;
	CHAR_DATA *victim;

	push_call("do_examine(%p,%p)",ch,argument);

	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}

	argument = one_argument( argument, arg1 );
	argument = one_argument( argument, arg2 );

	if (arg1[0] == '\0')
	{
		send_to_char( "Examine what?\n\r", ch );
		pop_call();
		return;
	}

	TAKE_ACTION(ch, ACTION_FULL);

	if (arg2[0] != '\0')
	{
		if ((victim = get_char_room(ch, arg2)) == NULL)
		{
			act( "You do not see $t here.", ch, arg2, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if (!IS_NPC(victim) || !victim->pIndexData->pShop)
		{
			act( "You cannot get that close up to $N's possessions.", ch, NULL, victim, TO_CHAR);
			pop_call();
			return;
		}
		if ((obj = get_obj_carry_keeper(victim, arg1, ch)) == NULL)
		{
			act( "$N does not seem to have $t.", ch, arg1, victim, TO_CHAR);
			pop_call();
			return;
		}
		show_examine_string(obj, ch);
		pop_call();
		return;
	}		
	
	if ((victim = get_char_room(ch, arg1)) != NULL)
	{
		show_char_to_char( victim, ch, TRUE );
		pop_call();
		return;
	}

	if ((obj = get_obj_here(ch, arg1)) != NULL)
	{
		show_examine_string(obj, ch);
		pop_call();
		return;
	}
	
	act( "You do not see any '$t' here to examine.", ch, arg1, NULL, TO_CHAR);

	pop_call();
	return;
}

void show_examine_string( OBJ_DATA *obj, CHAR_DATA *ch )
{
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char txt[MAX_STRING_LENGTH];
	int aff_level, aff_school;

	push_call("show_examine_string(%p,%p)",obj,ch);

	act( "$n examines $p carefully.", ch, obj, NULL, TO_ROOM);
	
	if (IS_OBJ_TYPE(obj, ITEM_PAPER))
	{
		do_read(ch, obj->name);
	}
	else if (obj->description[0] != '\0')
	{
		ch_printf_color(ch, "{300}%s", obj->description);
	}
	if (IS_OBJ_TYPE(obj, ITEM_BOOK))
	{
		ch_printf_color(ch, "It is a book, with %d pages. Syntax: read <pagenumber|next|prev>\n\r", obj->value[2]);
	}
	
	switch (obj->item_type)
	{
		default:
			break;
		case ITEM_FURNITURE:
			if (obj->first_content)
			{
				buf[0] = '\0';
				act_sprintf( buf, "sitting atop of $p is:\n\r", ch, obj, NULL );
				sprintf_list_to_char( buf2, obj->first_content, ch, 0, TRUE, FALSE );
				strcat(buf, buf2);
				send_to_char(buf, ch);
			}
			break;
		case ITEM_CONTAINER:
		case ITEM_SPELLPOUCH:
		case ITEM_SHEATH:
		case ITEM_CART:
		case ITEM_QUIVER:
		case ITEM_CORPSE_NPC:
		case ITEM_CORPSE_PC:
			if (IS_SET(obj->value[1], CONT_CLOSED))
			{
				act( "$p is closed.", ch, obj, NULL, TO_CHAR );
				break;
			}
			if (obj->first_content == NULL)
			{
				act( "There is nothing in $p.", ch, obj, NULL, TO_CHAR );
				break;
			}
			buf[0] = '\0';
			act_sprintf( buf, "$p contains:", ch, obj, NULL );
			sprintf_list_to_char( buf2, obj->first_content, ch, 0, TRUE, FALSE );
			strcat(buf, buf2);
			send_to_char(buf, ch);
			break;
		case ITEM_DRINK_CON:
			if (obj->value[1] <= 0)
			{
				send_to_char( "It is empty.\n\r", ch );
				break;
			}
			sprintf(buf, "It's %s half full of a %s liquid.\n\r",
				obj->value[1] <     obj->value[0] / 4 ? "less than" :
				obj->value[1] < 3 * obj->value[0] / 4 ? "about"     : "more than",
				liq_table[obj->value[2]].liq_color	);
			send_to_char( buf, ch );
			break;
		case ITEM_SCROLL:
			if (obj->value[0] == 0)
				ch_printf_color(ch, "%s is blank.\n\r", capitalize(obj->short_descr));
			if (IS_OWNER(obj, ch) || obj->identified || is_affected(ch, gsn_read_magic)
			|| spellcraft_check(ch, NULL, obj->value[1], spellcraft_roll(ch), 20 + get_spell_circle(NULL, obj->value[1])))
			{
				obj->identified = TRUE;
			}
			break;
		case ITEM_POTION:
			if (obj->value[0] == 0)
				ch_printf_color(ch, "%s is empty.\n\r", capitalize(obj->short_descr));
			else if (IS_OWNER(obj, ch) || obj->identified
			|| spellcraft_check(ch, NULL, obj->value[1], spellcraft_roll(ch), 25))
			{
				obj->identified = TRUE;
			}
			break;
		case ITEM_ARMOR:
			if (ARMOR_TYPE(obj, ARMOR_TYPE_CLOTH))
				ch_printf_color(ch, "It does not offer any mundane protection, nor restrict movement.\n\r");
			else if (ARMOR_TYPE(obj, ARMOR_TYPE_BUCKLER)
			|| ARMOR_TYPE(obj, ARMOR_TYPE_LIGHT_SHIELD)
			|| ARMOR_TYPE(obj, ARMOR_TYPE_HEAVY_SHIELD)
			|| ARMOR_TYPE(obj, ARMOR_TYPE_TOWER_SHIELD))
				ch_printf_color(ch, "It is a %s.\n\r", armor_table[obj->value[0]].name);
			else
				ch_printf_color(ch, "It protects as %s armor.\n\r", armor_table[obj->value[0]].name);
			if (ARMOR_FLAG(obj, ARMORFLAG_HOODED))
				ch_printf_color(ch, "It has a hood, which can you can wear and remove.\n\r");
			if (obj->value[3])
			{
				if (IS_OBJ_STAT(obj, ITEM_RESIZE))
				{
					ch_printf_color(ch, "It is just the right size for you to wear.\n\r", size_types[obj->value[3]]);
				}
				else
				{
					ch_printf_color(ch, "It is sized for a %s sized creature.\n\r", size_types[obj->value[3]]);
				}
			}
			break;
		case ITEM_WEAPON:
			sprintf(buf, "%s", weapon_table[obj->value[0]].name);
			ch_printf_color(ch, "It is %s %s, which you %s proficient in.\n\r", a_an(buf), buf, weapon_skill(ch, obj) ? "are" : "are not");
			if (WSPEC(obj, WSPEC_MISSILE))
				ch_printf_color(ch, "It is a missile weapon, for which you need ammo.\n\r");
			if (WSPEC(obj, WSPEC_THROW))
				ch_printf_color(ch, "It may be thrown for a ranged attack.\n\r");
			if (WSPEC(obj, WSPEC_MONK))
				ch_printf_color(ch, "A monk may use it for their special attacks.\n\r");
			if (WSPEC(obj, WSPEC_DOUBLE))
				ch_printf_color(ch, "It is a double weapon, allowing it to be used for two-weapon fighting.\n\r");
			if (WSPEC(obj, WSPEC_DISARM))
				ch_printf_color(ch, "It can be used to disarm opponents easier.\n\r");
			if (WSPEC(obj, WSPEC_TRIP))
				ch_printf_color(ch, "It can be used to trip up your opponent.\n\r");
			if (WSPEC(obj, WSPEC_REACH))
				ch_printf_color(ch, "It is long enough to double your reach.\n\r");
			break;
		case ITEM_TOOLS:
			sprintf(buf, "%s", tool_table[obj->value[0]].name);
			ch_printf_color(ch, "It is %s, used for %s.\n\r", buf, tool_table[obj->value[0]].trade);
			break;
		case ITEM_AMMO:
			sprintf(buf, "%s", weapon_table[obj->value[0]].name);
			ch_printf_color(ch, "It is made for %s %s.\n\r", a_an(buf), buf);
			break;
		case ITEM_TRAP:
			if (learned(ch, gsn_disable_device))
			{
				sprintf(buf, "%s", trap_types[obj->value[0]]);
				
				if (!is_trap_armed(obj))
				{
					ch_printf_color(ch, "It is %s %s trap, and presently disabled.", a_an(buf), buf);
				}
				else
				{
					ch_printf_color(ch, "It is %s %s trap, and is ARMED!", a_an(buf), buf);
	
					int chance = trap_dc(obj) - (10 + stat_bonus(TRUE, ch, APPLY_INT) + learned(ch, gsn_disable_device) + (multi_class_level(ch, gsn_trapfinding) / 2));
					
					if (chance > 10)
						sprintf(txt, "impossible");
					else if (chance >= 9)
						sprintf(txt, "unlikely");
					else if (chance >= 7)
						sprintf(txt, "difficult");
					else if (chance >= 5)
						sprintf(txt, "challenging");
					else if (chance >= 3)
						sprintf(txt, "moderate");
					else if (chance >= 1)
						sprintf(txt, "easy");
					else
						sprintf(txt, "effortless");
					
					ch_printf_color(ch, "Disabling the trap would be %s %s task for you.\n\r", a_an(txt), txt);
				}
			}
			break;
	}
	if (!IS_NPC(ch) && obj->poison && (craft_poison_roll(ch) > poison_table[obj->poison->type].dc))
	{
		ch_printf_color(ch, "{128}There is a taint of %s on this object.\n\r", poison_table[obj->poison->type].name);
	}
	if (CAN_SEE_MAGIC(ch) && (aff_level = get_highest_obj_aff_level(obj)) > 0)
	{
		aff_school = get_highest_obj_aff_school(obj);

		if (aff_level > 9)
			sprintf(txt, "overwhelming");
		else if (aff_level > 6)
			sprintf(txt, "strong");
		else if (aff_level > 3)
			sprintf(txt, "moderate");
		else
			sprintf(txt, "faint");

		ch_printf_color(ch, "{148}There is a %s dweaomer of %s this object.{300}\n\r",
			txt, school_types[aff_school]);
	}
	if (IS_OBJ_EVIL(obj) && (is_affected(ch, gsn_detect_evil) || class_level(ch, CLASS_PALADIN)))
	{
		if (obj->level >= 9)	
			act( "{018}It radiates an overwhelming aura of evil.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 6)	
			act( "{018}It radiates a strong aura of evil.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 3)	
			act( "{018}It radiates a moderate aura of evil.{300}", ch, NULL, NULL, TO_CHAR);
		else	
			act( "{018}It radiates a faint aura of evil.{300}", ch, NULL, NULL, TO_CHAR);
	}
	if (IS_OBJ_GOOD(obj) && (is_affected(ch, gsn_detect_good) || class_level(ch, CLASS_BLACKGUARD)))
	{
		if (obj->level >= 9)	
			act( "{138}It radiates an overwhelming aura of good.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 6)	
			act( "{138}It radiates a strong aura of good.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 3)	
			act( "{138}It radiates a moderate aura of good.{300}", ch, NULL, NULL, TO_CHAR);
		else	
			act( "{138}It radiates a faint aura of good.{300}", ch, NULL, NULL, TO_CHAR);
	}
	if (IS_OBJ_LAWFUL(obj) && is_affected(ch, gsn_detect_law))
	{
		if (obj->level >= 9)
			act( "{178}It radiates an overwhelming aura of law.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 6)
			act( "{178}It radiates a strong aura of law.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 3)
			act( "{178}It radiates a moderate aura of law.{300}", ch, NULL, NULL, TO_CHAR);
		else	
			act( "{178}It radiates a faint aura of law.{300}", ch, NULL, NULL, TO_CHAR);
	}
	if (IS_OBJ_CHAOTIC(obj) && is_affected(ch, gsn_detect_chaos))
	{
		if (obj->level >= 9)
			act( "{058}It radiates an overwhelming aura of chaos.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 6)
			act( "{058}It radiates a strong aura of chaos.{300}", ch, NULL, NULL, TO_CHAR);
		else if (obj->level >= 3)
			act( "{058}It radiates a moderate aura of chaos.{300}", ch, NULL, NULL, TO_CHAR);
		else	
			act( "{058}It radiates a faint aura of chaos.{300}", ch, NULL, NULL, TO_CHAR);
	}

	if (!obj->identified)
	{
		if (is_affected(ch, gsn_analyze_dweomer) || domain_apotheosis(ch, DOMAIN_MAGIC))
			obj->identified = TRUE;
	}

	if (obj->identified)
	{
		show_identify_string(obj, ch);
	}

	if (obj->weight <= 1)
		sprintf(buf, "next to nothing");
	else if (obj->weight < 10)
		sprintf(buf, "less than a pound");
	else if (obj->weight <= 15)
		sprintf(buf, "about a pound");
	else
		sprintf(buf, "about %d pounds", (int)(obj->weight / 10));
		
	ch_printf_color(ch, "It is %s in size, made of %s, and weighs %s.\n\r", 
		size_types[obj->size], material_types[obj->material], buf);

	pop_call();
	return;
}


/*
	Thanks to Zrin for auto-exit part.
*/


void do_exits( CHAR_DATA *ch, char *argument )
{
	char outbuf[MAX_STRING_LENGTH];
	bool fAuto;

	push_call("do_exits(%p,%p)",ch,argument);

	fAuto  = !strcasecmp(argument, "auto");

	if (!check_blind(ch))
	{
		pop_call();
		return;
	}

	sprintf_exits(outbuf, ch, fAuto);
	send_to_char_color(outbuf, ch);
	
	pop_call();
	return;
}

/*	Listen skill
 *	Author: Tek (tek@xnet.org)
 *	of FrozenMUD (empire.digiunix.net 4000)
 *	If you modify this code and use/distribute modified versions
 *	you must give credit to the original author(s).
 */

void do_listen (CHAR_DATA *ch, char *argument)
{
	char	arg[MAX_INPUT_LENGTH];
	EXIT_DATA	*pExit;
	EXIT_DATA	*pExit_rev;
	ROOM_INDEX_DATA	*to_room;
	CHAR_DATA	*tch, *tch_next;
	int	door;
	int		count = 0;
	char	buf[MAX_STRING_LENGTH];

	push_call("do_listen(%p,%p)",ch,argument);

	if (in_combat(ch))
	{
		send_to_char("You can barely hear over the sounds of battle.\n\r", ch);
		pop_call();
		return;
	}
	if (ch->in_room->sector_type == SECT_UNDER_WATER)
	{
		send_to_char_color("{148}You have trouble hearing underwater.\n\r", ch);
		pop_call();
		return;
	}

	int diceroll = perception_roll(ch, SENSE_SOUND);

	if (ch->in_room->sector_type == SECT_OCEAN)
	{
		diceroll -= 5;
	}

	one_argument (argument, arg);

	wait_state(ch, PULSE_VIOLENCE);

	if (arg[0] == '\0')	/*No argument means the character is listening in the room he/she is in*/
	{
		if (!ch->concentrating)
		{
			act ("You listen intently...", ch, NULL, NULL, TO_CHAR);
			act ("$n seems to listen intently for something.", ch, NULL, NULL, TO_ROOM);
			ch->concentrating = TRUE;
			ch->skill_timer		= 6;
			ch->timer_fun			= do_listen;
			RESTRING(ch->cmd_argument, argument);
			TAKE_ACTION(ch, ACTION_MOVE);
			pop_call();
			return;
		}

		for (tch = ch->in_room->first_person; tch; tch = tch_next) 
		{
			tch_next = tch->next_in_room;

			if (ch != tch
			&& !can_see (ch, tch)
			&& tch->level < LEVEL_IMMORTAL
			&& (!IS_AFFECTED(tch, AFF_SNEAK) || !sneak_check(tch, ch, sneak_roll(tch), 0)))
				count++;
		}

		if (ch->in_room->listen_desc[0] != '\0')
			send_to_char_color( ch->in_room->listen_desc, ch );
			
		if (count > 0) 
		{
			sprintf( buf, "{178}You hear the presence of %s.\n\r",((count == 1)? "an unseen creature": "unseen creatures"));
			send_to_char_color( buf, ch );
		}
		else 
		{
			send_to_char("You don't hear anything unusual.\n\r", ch); 
		}
		pop_call();
		return;
	}

	else if ((door = direction_door(arg)) == -1) /* The argument must be a direction*/
	{
		send_to_char("Listen in WHAT direction?\n\r", ch);
		pop_call();
		return;
	}

	if (!ch->concentrating)
	{
		act ("You listen intently to the $t...", ch, dir_name[door], NULL, TO_CHAR);
		act ("$n seems to listen intently for something.", ch, NULL, NULL, TO_ROOM);
		ch->concentrating = TRUE;
		ch->skill_timer		= 6;
		ch->timer_fun			= do_listen;
		RESTRING(ch->cmd_argument, argument);
		TAKE_ACTION(ch, ACTION_MOVE);
		pop_call();
		return;
	}

	if ((pExit = ch->in_room->exit[door]) == NULL)
	{
		send_to_char("You hear nothing unusual.\n\r", ch);
		pop_call();
		return;
	}

	if ((to_room = room_index[pExit->to_room]) != NULL
	&&  (pExit_rev = to_room->exit[rev_dir[door]]) != NULL
	&&  room_index[pExit_rev->to_room] == ch->in_room)
	{
		for (tch = to_room->first_person; tch; tch = tch_next)
		{
			tch_next = tch->next_in_room;
			if (ch != tch
			&& !can_see (ch, tch)
			&& tch->level < LEVEL_IMMORTAL
			&& perception_check(ch, tch, diceroll, 0))
				count++;
		}
	}

	if (to_room->listen_desc[0] != '\0' && perception_check(ch, NULL, diceroll, 10))
		send_to_char_color( to_room->listen_desc, ch );
		
	if (count > 0) 
	{
		sprintf( buf, "{178}You hear what sounds like %d %s lurking %s%s.\n\r", count,
			((count == 1)? "creature": "creatures"),
			((door == 5)? "below": (door == 4)? "above": "to the "),
			((door == 5)? "": (door == 4)? "": dir_name[door]));
		send_to_char_color( buf, ch );
	}
	else 
	{
		send_to_char("You don't hear anything unusual.\n\r", ch );
	}
	pop_call();
	return;
}


char *  const   condition_flags [] =
{
	"blind",
	"@",
	"deaf",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"hidden",
	"@",
	"@",
	"invisible",
	"@",
	"@",
	"@",
	"@",
	"@",
	"sleeping",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"@",
	"*"
};

char *  const   condition2_flags [] =
{
	"@",
	"@",
	"bleeding",
	"@",
	"@",
	"confused",
	"dazed",
	"dazzled",
	"entangled",
	"@",
	"exhausted",
	"@",
	"fascinated",
	"fatigued",
	"fearful",
	"@",
	"@",
	"fascinated",
	"paralyzed",
	"petrified",
	"@",
	"sickened",
	"staggered",
	"stunned",
	"unconscious",
	"charmed",
	"drowning",
	"*"
};

char *condition_string( CHAR_DATA *ch )
{
	push_call("condition_string(%p)", ch);
	
	static char buf[MAX_STRING_LENGTH];
	
	buf[0] = '\0';
	
	if (ch->affected_by)
	{
		strcat(buf, flag_string(ch->affected_by, condition_flags));
	}

	if (ch->affected2_by)
	{
		strcat(buf, flag_string(ch->affected2_by, condition2_flags));
	}

	pop_call();
	return buf;
}

/*
 * Total revamp of the score for D20 stuff - Kregor 2/2007
 */

void do_score( CHAR_DATA *ch, char *argument )
{
	push_call("do_score(%p,%p)",ch,argument);

	get_string_score_v1(ch, ch);

	send_to_char_color( get_string_score_txt, ch );
	pop_call();
	return;
}

void get_string_score_v1( CHAR_DATA *ch, CHAR_DATA *viewer)
{
	char buf0[MAX_STRING_LENGTH];
	char buf1[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH], buf3[MAX_INPUT_LENGTH];
	char skp1[42], skp2[30];
	char colc[10], colC[10], colw[10], colW[10], colR[10], colG[10], colK[10];
	int cnt, leng, cls;
	PET_DATA *pet;
	OBJ_DATA *wield, *wield2;

	push_call("get_string_score_v1(%p,%p)",ch,viewer);

	*get_string_score_txt = '\0';

	strcpy(colc, "{200}");
	strcpy(colC, get_color_string(ch, COLOR_ACCENT, VT102_BOLD));
	strcpy(colK, "{108}");
	strcpy(colw, "{078}");
	strcpy(colW, "{178}");
	strcpy(colR, "{118}");
	strcpy(colG, "{128}");

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s {200}]{078}", ch->name);

	for (cnt = 0 ; cnt < 39-(strlen(ch->name))/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	sprintf(buf3, "%s\n\r", buf1);
	leng = str_cpy_max(get_string_score_txt, buf3, MAX_STRING_LENGTH);

	sprintf(buf0, " %sVital Statistics:\n\r", colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "  %sSTR%s[%s%2d%s|%s%2d%s]   %sDEX%s[%s%2d%s|%s%2d%s]   %sCON%s[%s%2d%s|%s%2d%s]   %sINT%s[%s%2d%s|%s%2d%s]   %sWIS%s[%s%2d%s|%s%2d%s]   %sCHA%s[%s%2d%s|%s%2d%s]\n\r",
		colw, colc, get_curr_str(ch) < ch->perm_str ? colR : get_curr_str(ch) > ch->perm_str ? colG : colW, get_curr_str(ch), colw, colW, ch->perm_str, colc, 
		colw, colc, get_curr_dex(ch) < ch->perm_dex ? colR : get_curr_dex(ch) > ch->perm_dex ? colG : colW, get_curr_dex(ch), colw, colW, ch->perm_dex, colc, 
		colw, colc, get_curr_con(ch) < ch->perm_con ? colR : get_curr_con(ch) > ch->perm_con ? colG : colW, get_curr_con(ch), colw, colW, ch->perm_con, colc, 
		colw, colc, get_curr_int(ch) < ch->perm_int ? colR : get_curr_int(ch) > ch->perm_int ? colG : colW, get_curr_int(ch), colw, colW, ch->perm_int, colc, 
		colw, colc, get_curr_wis(ch) < ch->perm_wis ? colR : get_curr_wis(ch) > ch->perm_wis ? colG : colW, get_curr_wis(ch), colw, colW, ch->perm_wis, colc, 
		colw, colc, get_curr_cha(ch) < ch->perm_cha ? colR : get_curr_cha(ch) > ch->perm_cha ? colG : colW, get_curr_cha(ch), colw, colW, ch->perm_cha, colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "  %sbonus%s[%s%+3d%s]   %sbonus%s[%s%+3d%s]   %sbonus%s[%s%+3d%s]   %sbonus%s[%s%+3d%s]   %sbonus%s[%s%+3d%s]   %sbonus%s[%s%+3d%s]\n\r",
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_STR), colc,
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_DEX), colc,
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_CON), colc,
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_INT), colc,
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_WIS), colc,
		colw, colc, colW, stat_bonus(TRUE, ch, APPLY_CHA), colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf1, "%sHeight:%s %dft%din        %sWeight:%s %dlbs",
		colw, colW, (get_height(ch)/12), (get_height(ch)%12),
		colw, colW, get_weight(ch));

	sprintf(buf0, "  %sRace:%s %-10s   %sSex:%s %-6s   %62s\n\r",
		colw, colW, race_table[ch->race].race_name,
		colw, colW, ch->sex <= 0 ? "Neuter" : ch->sex == 1 ? "Male" : "Female",
		buf1);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf3, "%s  -----------------------------------------+----------------------------------\n\r", colK);
	leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);

	if (IS_UNCONCERNED(ch))
		sprintf(buf1, "Neutral");
	if (IS_UNCONCERNED(ch) && IS_NEUTRAL(ch))
		sprintf(buf1, "True");
	if (IS_LAWFUL(ch))
		sprintf(buf1, "Lawful");
	if (IS_CHAOTIC(ch))
		sprintf(buf1, "Chaotic");
	if (IS_GOOD(ch))
		sprintf(buf1, "%s Good", buf1);
	if (IS_NEUTRAL(ch))
		sprintf(buf1, "%s Neutral", buf1);
	if (IS_EVIL(ch))
		sprintf(buf1, "%s Evil", buf1);

	sprintf(buf0, " %sCombat Statistics:                        %s| %sScores:\n\r", colc, colK, colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "  %sAC: %s%2d %s(%sFlatfooted: %s%2d%s, Touch: %s%2d%s)       %s|  %sAlignment: %s%s\n\r",
		colw, colW, GET_AC(ch, FALSE) + dodge_bonus(ch) + shield_bonus(ch),
		colc, colw, colW, GET_AC(ch, FALSE) + dodge_penalty(ch),
		colw, colW, GET_AC(ch, TRUE) + dodge_bonus(ch), colc, colK, colw, colW, buf1);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	wield = get_wield(ch, FALSE);
	wield2 = get_wield(ch, TRUE);
	int bab = base_attack(ch);
	sprintf(buf2, "%+3d", GET_HITROLL(ch, NULL, -1, wield));
	if (wield2)
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield2));
	if (bab > 5)
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield) - 5);
	if (bab > 5 && ((wield2 && learned(ch, gsn_imp_two_weapon)) || (COMBATMODE(ch, COMBAT_FLURRY) && learned(ch, gsn_imp_flurry))))
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield2) - 5);
	if (bab > 10)
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield) - 10);
	if (bab > 10 && ((wield2 && learned(ch, gsn_greater_two_weapon)) || (COMBATMODE(ch, COMBAT_FLURRY) && learned(ch, gsn_greater_flurry))))
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield2) - 10);
	if (bab > 15)
		cat_sprintf(buf2, ",%+3d", GET_HITROLL(ch, NULL, -1, wield) - 15);

	switch(encumberance(ch))
	{
		case OVERLOADED:
			sprintf(buf1, "{018}(Overloaded)");
			break;
		case VERY_ENCUMBERED:
			sprintf(buf1, "{118}(Heavy)");
			break;
		case ENCUMBERED:
			sprintf(buf1, "{138}(Medium)");
			break;
		default:
			sprintf(buf1, "{178}(Light)");
			break;
	}

	sprintf(buf0, "  %sAttack Bonus: %s%-27s%s|  %sCarryWt: %s%d.%d%s[%s%d%s]%s %s\n\r",
		colw, colW, ansi_translate(buf2), colK,
		colw, colW, ch->carry_weight/10, ch->carry_weight%10, colc, colW, can_carry_w(ch)/10, colc,
		colw, buf1);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "  %sHitPts: %s%4d%s[%s%4d%s]  %sMovePts: %s%4d%s[%s%4d%s]%s  |  %sExperience: %s%s%s[%s",
		colw, colW, ch->hit, colc, colW, get_max_hit(ch), colc,
		colw, colW, ch->move, colc, colW, get_max_move(ch), colc,
		colK, colw, colW, IS_NPC(ch) ? 0 : format_number(ch->pcdata->exp), colc, colW);
	cat_sprintf(buf0, "%s%s]\n\r", IS_NPC(ch) ? 0 : format_number(exp_level(ch, ch->level)), colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "  %sSaves:  WILL%s[%s%+3d%s]%s  REFL%s[%s%+3d%s]%s  FORT%s[%s%+3d%s]%s  |  %sWealth: %s\n\r",
		colw, colc, colW, get_will_save(ch), colc,
		colw, colc, colW, get_refl_save(ch), colc,
		colw, colc, colW, get_fort_save(ch), colc,
		colK, colw, format_coins(ch->gold, FALSE));
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	if (!IS_NPC(ch))
	{
		switch(get_reputation(ch))
		{
			case 1:
			case 2:
			case 3:
				sprintf(buf1, "villainous");
				break;
			case 4:
			case 5:
				sprintf(buf1, "criminal");
				break;
			case 6:
			case 7:
				sprintf(buf1, "ill-reputed");
			case 8:
			case 9:
				sprintf(buf1, "distrusted");
				break;
			case 10:
			case 11:
				sprintf(buf1, "anonymous");
				break;
			case 12:
			case 13:
				sprintf(buf1, "reputable");
				break;
			case 14:
			case 15:
				sprintf(buf1, "upstanding");
				break;
			default:
				sprintf(buf1, "heroic");
				break;
		}
		sprintf(buf0, "  %sCombat Maneuver Bonus: %s%+3d %sDefense: %s%+3d %s |  %sReputation: %s%d %s(%s)\n\r",
			colw, colW, combat_maneuver_bonus(ch), 
			colw, colW, combat_maneuver_defense(ch, NULL) + dodge_bonus(ch), 
			colK, colw, colW, get_reputation(ch), colw, buf1);
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	}
	
	sprintf(buf3, "%s  -----------------------------------------+----------------------------------\n\r", colK);
	leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);

	sprintf(buf0, " %sClass Statistics:\n\r", colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	int col;
	sprintf(buf0, "%s", colc);
	for (cls = col = 0 ; cls < MAX_CLASS ; cls++)
	{
		if ( cls == ch->class )
		{
			cat_sprintf(buf0, " %s*%s%2d%s] %s%-14s", colW, colW, class_level(ch, cls), colc, colw, class_table[cls].who_name_long);
			col++;
		}
		else if ( class_level(ch, cls) > 0 )
		{
			cat_sprintf(buf0, "  %s%2d%s] %s%-14s", colW, class_level(ch, cls), colc, colw, class_table[cls].who_name_long);
			col++;
		}
		else
			continue;
		if (col % 4 == 0)
		{
			cat_sprintf(buf0, "\n\r");
		}
	}
	if (col % 4 != 0)
		cat_sprintf(buf0, "\n\r");
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf0, "%s  Favored Class%s: %-40s   %sExp Penalty%s: %3d%%\n\r",
		colw, colW, favored_class(ch) == CLASS_MONSTER ? "monster" : class_table[favored_class(ch)].who_name_long,
		colw, colW, IS_NPC(ch) ? 0 : level_diff(ch) * 20);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	if (IS_NPC(ch))
	{
		sprintf(buf3, "%s--------------------------------------------------------------------------------\n\r", colw);
		leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);

		if (ch->poison || ch->first_disease)
		{
			sprintf(buf0, "{038}You're not feeling well at all.\n\r");
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
		}		

		pop_call();
		return;
	}

	sprintf(buf3, "%s  ----------------------------------------------------------------------------\n\r", colK);
	leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);

	if (ch->god)
	{
		sprintf(buf0, " %sFaith: %s%-20s    %sFaith Rank: %s%-15s    %sFavor %s[%s%4d%s]\n\r",
			colc, colW, god_table[ch->god].god_name,
			colw, colW, faith_rank(ch),
			colw, colc, colW, ch->pcdata->god_favor, colc);
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	
		sprintf(buf3, "  %s----------------------------------------------------------------------------{300}\n\r", colK);
		leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);
	}

	sprintf(buf0, " %sOther Statistics:\n\r", colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	if (ch->poison || ch->first_disease)
	{
		sprintf(buf0, "  {038}You're not feeling well at all.");
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	}
	
	if (is_string(condition_string(ch)))
	{
		sprintf(buf0, "  %sConditions:%s %s\n\r",
			colw, colW, condition_string(ch));
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	}
	
	switch (drunk_level(ch))
	{
		default:
			sprintf(buf0, "  %sYou're rip-roaringly shit-faced drunk.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_TIPSY:
			sprintf(buf0, "  %sYou're feeling a bit tipsy.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_MERRY:
			sprintf(buf0, "  %sYou're feeling quite buzzed.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_DRUNK:
			sprintf(buf0, "  %sYou're feeling a bit drunk.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_HAMMERED:
			sprintf(buf0, "  %sYou're feeling quite drunk.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_PLASTERED:
			sprintf(buf0, "  %sYou're feeling plastered.", colw);
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
			break;
		case DRUNK_SOBER:
			break;
	}

	if (is_string(ch->pcdata->pose))
	{
		sprintf(buf0, "  %sPose:%s %s is %s.\n\r",
			colw, colW, capitalize(get_name(ch)), ch->pcdata->pose);
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	}

	if (is_string(ch->pcdata->disguise))
	{
		sprintf(buf0, "  %sDisguise:%s %s %s\n\r",
			colw, colW, a_an(ch->pcdata->disguise), ch->pcdata->disguise);
		leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
	}

	for (cnt = 0, pet = mud->f_pet ; pet ; pet = pet->next)
	{
		if (pet->ch->master == ch && IS_ACT(pet->ch, ACT_PET))
		{
			sprintf(buf1, "%s %s", get_name(pet->ch),
					IS_ACT(pet->ch, ACT_FAMILIAR) ? "(familiar)" : IS_ACT(pet->ch, ACT_COMPANION) ? "(companion)" : IS_ACT(pet->ch, ACT_WARHORSE) ? "(warhorse)" : "");
			cnt++;
			sprintf(buf0, "  %sMinion %d:%s %s %s%s\n\r",
				colw, cnt, colW, str_resize(buf1, skp1, -38), colw, in_same_room(ch, pet->ch) ? "Following you" : str_resize(pet->ch->in_room->name, skp2, -28));
			leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);
		}
	}

	sprintf(buf0, "  %sHours Played %s[%s%6d%s]    %sSkill Pts %s[%s%3d%s]    %sStat Pts %s[%s%3d%s]    %sFeat Pts %s[%s%3d%s]\n\r",
		colw, colc, colW, IS_NPC(ch) ? 0 : ch->pcdata->played/3600, colc,
		colw, colc, colW, IS_NPC(ch) ? 0 : ch->pcdata->practice, colc,
		colw, colc, colW, IS_NPC(ch) ? 0 : ch->pcdata->stat_pts, colc,
		colw, colc, colW, IS_NPC(ch) ? 0 : ch->pcdata->feat_pts, colc);
	leng = str_apd_max(get_string_score_txt, buf0, leng, MAX_STRING_LENGTH);

	sprintf(buf3, "  %s----------------------------------------------------------------------------{300}\n\r", colK);
	leng = str_apd_max(get_string_score_txt, buf3, leng, MAX_STRING_LENGTH);

	pop_call();
	return;
}

char *  const   month_name      [] =
{
	"Hammer",			"Alturiak",	"Ches",
	"Tarsakh",		"Mirtul",		"Kythorn",
	"Flamerule",	"Elasias",	"Eleint",
	"Marpenoth",	"Uktar",		"Nightal",
};

/* Enhanced AFFECTS command  -  Chaos  4/3/99  */

void do_affects( CHAR_DATA *ch, char *argument )
{
	char affbuf[MAX_STRING_LENGTH];

	push_call("do_affects(%p,%p)",ch,argument);

	get_affects_string(ch, ch, affbuf);
	send_to_char_color(affbuf, ch);

	pop_call();
	return;
}

char *format_duration( int duration )
{
	static char buf[MAX_STRING_LENGTH];
	int hours = duration / 100;
	int minutes = duration % 100 / 10;
	int rounds = duration % 100 % 10;

	push_call("format_duration(%p)",duration);

	buf[0] = '\0';

	if (duration == -1)
		cat_sprintf(buf, " indefinitely.");
	else if (duration == 0)
		cat_sprintf(buf, " none.");
	else
	{
		if (hours > 0)
			cat_sprintf(buf, " %d hrs.", hours);
		if (minutes > 0)
			cat_sprintf(buf, " %d min.", minutes);
		if (rounds > 0)
			cat_sprintf(buf, " %d rds.", rounds);
	}
	pop_call();
	return buf;
}

void get_affects_string( CHAR_DATA *ch, CHAR_DATA *viewer, char *outbuf)
{
	AFFECT_DATA *paf;
	char buf0[MAX_STRING_LENGTH];
	char buf1[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH], buf3[MAX_INPUT_LENGTH];
	char skp1[80], skp2[80], colc[10], colw[10], colW[10], colC[10];
	int leng, type;

	push_call("get_affects_string(%p,%p,%p,%p)",ch,viewer,outbuf, MAX_STRING_LENGTH);

	*outbuf = '\0';

	strcpy(colc, "{200}");
	strcpy(colw, "{078}");
	strcpy(colW, "{178}");
	strcpy(colC, "{168}");

	if (ch == viewer)
	{
		sprintf(buf3, "%s-----------------------------------%s[ %sAffects %s]%s----------------------------------\n\r",
			colw, colc, colW, colc, colw);
	}
	else
	{
		sprintf(buf3, "{078}%s is affected by:\n\r", PERS(ch, viewer));
	}
	leng = str_cpy_max(outbuf, buf3, MAX_STRING_LENGTH);

	if (ch->first_affect != NULL)
	{
		for (type = 0, paf = ch->first_affect ; paf != NULL ; paf = paf->next)
		{
			if (ch != viewer && !is_spell(paf->type))
				continue;

			if (paf->type != type)
			{
				sprintf(buf1, "%s%s: %s%s",
					colw, is_spell(paf->type) ? "Spell" : "Skill",
					colW, skill_table[paf->type].name);
			}
			else
			{
				sprintf(buf1, "%s%s", colw, colW);
			}
			if (paf->location != APPLY_NONE)
			{
				switch (paf->location)
				{
					default:
						sprintf(buf2, "%s%+d %s for %s", colc, paf->modifier, affect_loc_name(paf->location), format_duration(paf->duration));
						break;
					case APPLY_IMM_ACID:
					case APPLY_IMM_COLD:
					case APPLY_IMM_ELECTRIC:
					case APPLY_IMM_FIRE:
					case APPLY_IMM_SONIC:
					case APPLY_IMM_AIR:
					case APPLY_IMM_CHAOTIC:
					case APPLY_IMM_DARKNESS:
					case APPLY_IMM_DEATH:
					case APPLY_IMM_DISEASE:
					case APPLY_IMM_EARTH:
					case APPLY_IMM_EVIL:
					case APPLY_IMM_FEAR:
					case APPLY_IMM_FORCE:
					case APPLY_IMM_GOOD:
					case APPLY_IMM_ILLUSION:
					case APPLY_IMM_LAWFUL:
					case APPLY_IMM_LIGHT:
					case APPLY_IMM_MAGIC:
					case APPLY_IMM_MIND:
					case APPLY_IMM_NEGATIVE:
					case APPLY_IMM_PARALYSIS:
					case APPLY_IMM_POLYMORPH:
					case APPLY_IMM_PETRI:
					case APPLY_IMM_POISON:
					case APPLY_IMM_HEALING:
					case APPLY_IMM_SLEEP:
					case APPLY_IMM_WATER:
					case APPLY_LOWLIGHT_VISION:
						sprintf(buf2, "%s%s for %s", colc, affect_loc_name(paf->location), format_duration(paf->duration));
						break;
					case APPLY_DR_BASH:
					case APPLY_DR_SLASH:
					case APPLY_DR_PIERCE:
					case APPLY_DR_MAGIC:
					case APPLY_DR_IRON:
					case APPLY_DR_SILVER:
					case APPLY_DR_ADAMANTINE:
					case APPLY_DR_NONE:
					case APPLY_DR_GOOD:
					case APPLY_DR_EVIL:
					case APPLY_DR_LAW:
					case APPLY_DR_CHAOS:
						sprintf(buf2, "%sBestows DR %d%s for %s", colc, paf->modifier, affect_loc_name(paf->location), format_duration(paf->duration));
						break;
					case APPLY_WEAPON_FLAG:
						sprintf(buf2, "%s%s %s for %s", colc, affect_loc_name(paf->location), weapon_flags[paf->modifier], format_duration(paf->duration));
						break;
				}
			}
			else if (paf->bittype == AFFECT_TO_CHAR)
			{
				if (paf->bitvector < 0)
					sprintf(buf2, "%sApplies %s for %s", colc, flag_string(0-paf->bitvector, a2_flags), format_duration(paf->duration));
				else
					sprintf(buf2, "%sApplies %s for %s", colc, flag_string(paf->bitvector, a_flags), format_duration(paf->duration));
			}
			else
				sprintf(buf2, "%sApplies %s for %s", colc, skill_table[paf->type].name, format_duration(paf->duration));
			sprintf(buf0, " %-20s %-56s\n\r",
				str_resize(buf1, skp1, -20),
				str_resize(buf2, skp2, -56));
			leng = str_apd_max(outbuf, buf0, leng, MAX_STRING_LENGTH);
			type = paf->type;
		}
	}
	else
	{
		sprintf(buf0, " %s%-74s\n\r", colW, "None");
		leng = str_apd_max(outbuf, buf0, leng, MAX_STRING_LENGTH);
	}
	pop_call();
	return;
}

void do_time( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_INPUT_LENGTH];

	push_call("do_time(%p,%p)",ch,argument);

	sprintf(buf,     "It is %s, the %s day of the Month of %s.\n\r\n\r", tocivilian(mud->time_info->hour), numbersuf(mud->time_info->day + 1), month_name[mud->time_info->month]);

	cat_sprintf(buf, "The mud started up at: %s\n\r", get_time_string(mud->boot_time));
	cat_sprintf(buf, "The system time is: %s\n\r", get_time_string(mud->current_time));
// 	cat_sprintf(buf, "It has been %ld years since January 1, 1970.\n\r", mud->current_time / 31536000);

	send_to_char(buf, ch);

	pop_call();
	return;
}

void do_mana( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	int sn, col, cnt, feats, circle, level;

	push_call("do_mana(%p,%p)",ch,argument);

	if (*argument == '\0')
	{
		sprintf(buf, "{200}Spell points for %s:\n\r", get_name(ch));
		for (col = cnt = 0 ; cnt < MAX_CLASS ; cnt++)
		{
			if (!class_level(ch, cnt))
				continue;
			if (get_max_mana(ch, cnt) <= 0)
				continue;
			
			cat_sprintf(buf, " {078}%4s {178}%3d{200}[{178}%3d{200}]", class_table[cnt].who_name, ch->mana[cnt], get_max_mana(ch, cnt));
			col++;
			
			if (col % 5 != 0)
				cat_sprintf(buf, "  ");
			else
				cat_sprintf(buf, "\n\r");
		}
		if (col % 5 != 0)
				cat_sprintf(buf, "\n\r");
			
		cat_sprintf(buf, "{078}Syntax: mana <spell name> to see usage for a spell.\n\r", ch);
		send_to_char_color(buf, ch);
		pop_call();
		return;
	}

	sn = skill_lookup( argument );

	if (sn < 0 || !is_spell(sn))
	{
		send_to_char( "That is not a spell.\n\r", ch );
		pop_call();
		return;
	}
	
	if ((cnt = casting_class(ch, sn, FALSE)) <= 0)
	{
		send_to_char( "You cannot cast that spell using mana.\n\r", ch );
		pop_call();
		return;
	}

	if ((level = get_caster_level(ch, sn)) == -1)
	{
		pop_call();
		return;
	}
	if ((circle = circle_by_class(ch, sn, cnt)) < 0)
	{
		send_to_char( "Error getting spell circle.\n\r", ch );
		pop_call();
		return;
	}
	feats = get_metamagic(ch, sn, ch->metamagic, circle, cnt, TRUE);
// 	circle = get_metamagic(ch, sn, ch->metamagic, circle, cnt, FALSE);

	sprintf(buf, "Mana usage for {178}%s{078}: {178}%d\n\r", skill_table[sn].name, get_mana(ch, sn, circle, cnt));
	cat_sprintf(buf, "{078}  Using currently set feats: {178}%s\n\r", flag_string(feats, metamagic_flags));
	send_to_char_color(buf, ch);
	pop_call();
	return;
}

void do_weather( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_INPUT_LENGTH];

	push_call("do_weather(%p,%p)",ch,argument);

	if (!IS_OUTSIDE(ch))
	{
		ch_printf_color(ch, "You can't see the weather indoors.\n\r");
		ch_printf_color(ch, "The temperature is about %d degrees celcius.\n\r", ch->in_room->area->weather_info->temperature);
		pop_call();
		return;
	}

	if (ch->in_room->area->weather_info->temperature < 0)
	{
		switch (ch->in_room->area->weather_info->sky)
		{
			case 0: strcpy( buf, "The sky is clear");		break;
			case 1: strcpy( buf, "The sky is a bit frosty");	break;
			case 2: strcpy( buf, "It is snowing");			break;
			case 3: strcpy( buf, "You are in a blizzard");	break;
		}
	}
	else if (ch->in_room->area->weather_info->temperature < 4)
	{
		switch (ch->in_room->area->weather_info->sky)
		{
			case 0: strcpy( buf, "The sky is clear");		break;
			case 1: strcpy( buf, "The sky is chilling");		break;
			case 2: strcpy( buf, "It is sleeting");			break;
			case 3: strcpy( buf, "You are in a hail storm");	break;
		}
	}
	else
	{
		switch (ch->in_room->area->weather_info->sky)
		{
			case 0: strcpy( buf, "The sky is clear");		break;
			case 1: strcpy( buf, "The sky is cloudy");		break;
			case 2: strcpy( buf, "It is raining");			break;
			case 3: strcpy( buf, "You are in a storm");	break;
		}
	}

	if (ch->in_room->area->weather_info->wind_speed > 3)
	{
		strcat(buf, " and a ");

		if (ch->in_room->area->weather_info->temperature < 0)
		{
			strcat(buf, "freezing ");
		}
		else if (ch->in_room->area->weather_info->temperature < 4)
		{
			strcat(buf, "chilling ");
		}
		else if (ch->in_room->area->weather_info->temperature < 10)
		{
			strcat(buf, "cold ");
		}
		else if (ch->in_room->area->weather_info->temperature < 16)
		{
			strcat(buf, "cool ");
		}
		else if (ch->in_room->area->weather_info->temperature < 20)
		{
			strcat(buf, "mild ");
		}
		else if (ch->in_room->area->weather_info->temperature < 25)
		{
			strcat(buf, "warm ");
		}
		else
		{
			strcat(buf, "sultry ");
		}

		if (ch->in_room->area->weather_info->wind_speed < 6)
		{
			cat_sprintf(buf, "%s wind blows", wind_dir_name[ch->in_room->area->weather_info->wind_dir]);
		}
		else if (ch->in_room->area->weather_info->wind_speed < 8)
		{
			cat_sprintf(buf, "%s breeze blows", wind_dir_name[ch->in_room->area->weather_info->wind_dir]);
		}
		else
		{
			cat_sprintf(buf, "%s gust blows", wind_dir_name[ch->in_room->area->weather_info->wind_dir]);
		}
	}

	ch_printf_color(ch, "%s.\n\rThe temperature is about %d degrees celcius.\n\r", buf, ch->in_room->area->weather_info->temperature);

	if (ch->level < LEVEL_IMMORTAL)
	{
		pop_call();
		return;
	}

	ch_printf_color(ch, "\n\rSky: %d\n\rChange: %d\n\rWind: %d\n\rDir: %d\n\rSunlight: %d\n\rTemp: %d\n\r",
		ch->in_room->area->weather_info->sky,
		ch->in_room->area->weather_info->change,
		ch->in_room->area->weather_info->wind_speed,
		ch->in_room->area->weather_info->wind_dir,
		mud->sunlight,
		ch->in_room->area->weather_info->temperature);

	pop_call();
	return;
}

HELP_DATA *get_help( CHAR_DATA *ch, char *argument )
{
	AREA_DATA *pArea;
	HELP_DATA *pHelp;

	push_call("get_help(%p,%p)",ch,argument);

	for (pArea = mud->f_area ; pArea ; pArea = pArea->next)
	{
		for (pHelp = pArea->first_help ; pHelp ; pHelp = pHelp->next)
		{
			if (pHelp->level == 90)
			{
				if (ch->level < 90 && !PLR_FUNCTION(ch, FUNCTION_ENFORCER|FUNCTION_GOD))
				{
					continue;
				}
			}
			else if (pHelp->level > ch->level)
			{
				continue;
			}
			// skip builder helps for non-builders
			if (!strcasecmp(pHelp->area->name, "Builder") && !PLR_FUNCTION(ch, FUNCTION_BUILDER|FUNCTION_BETA_TESTER|FUNCTION_GOD))
			{
				continue;
			}
			if (is_name(argument, pHelp->keyword))
			{
				pop_call();
				return pHelp;
			}
		}
	}

	for (pArea = mud->f_area ; pArea ; pArea = pArea->next)
	{
		for (pHelp = pArea->first_help ; pHelp ; pHelp = pHelp->next)
		{
			if (pHelp->level == 90)
			{
				if (ch->level < 90 && !PLR_FUNCTION(ch, FUNCTION_ENFORCER|FUNCTION_GOD))
				{
					continue;
				}
			}
			else if (pHelp->level > ch->level)
			{
				continue;
			}
			// skip builder helps for non-builders
			if (!strcasecmp(pHelp->area->name, "Builder") && !PLR_FUNCTION(ch, FUNCTION_BUILDER|FUNCTION_BETA_TESTER|FUNCTION_GOD))
			{
				continue;
			}
			if (is_name_short(argument, pHelp->keyword))
			{
				pop_call();
				return pHelp;
			}
		}
	}
	pop_call();
	return NULL;
}


/*
 * contextual help system responds to simply "?"
 * to pull up relevant help menu for the player's
 * situation - Kregor 6/20/10
 */
void do_context_help( CHAR_DATA *ch, char *argument )
{
	push_call("do_context_help(%p,%p)",ch,argument);

	if (!IS_NPC(ch))
	{
		if (ch->pcdata->edittype == EDIT_TYPE_GOD)
		{
			do_help(ch, "god editor");
			pop_call();
			return;
		}
			
		if (ch->pcdata->edittype == EDIT_TYPE_RACE)
		{
			do_help(ch, "race editor");
			pop_call();
			return;
		}
			
		if (ch->pcdata->edittype == EDIT_TYPE_ROOM)
		{
			do_help(ch, "race editor");
			pop_call();
			return;
		}
			
		if (IS_DEAD(ch))
		{
			do_help(ch, "death");
			pop_call();
			return;
		}
		if (find_keeper(ch) != NULL)
		{
			do_help(ch, "shop commands");
			pop_call();
			return;
		}
		if (in_combat(ch))
		{
			do_help(ch, "combat");
			pop_call();
			return;
		}
	}
	
	do_help(ch, "");

	pop_call();
	return;
}

/*
 * snarf a title for help file.
 * If no title set, then grab keywords - Kregor
 */
char *help_title(HELP_DATA *pHelp)
{
	push_call("format_help(%p)",pHelp);

	if (!is_string(pHelp->title))
	{
		pop_call();
		return capitalize_title(strlower(pHelp->keyword));
	}
	pop_call();
	return (pHelp->title);
}


/*
 * Format a help entry to player with
 * title and color prefs - Kregor
 */
void format_help_to_char( CHAR_DATA *ch, HELP_DATA *pHelp  )
{
	HELP_MENU_DATA *menu;
	char txt[MAX_STRING_LENGTH];
	char link[MAX_STRING_LENGTH];
	char colW[20], colg[20], colw[20];
	int cnt, len, col, count, length;

	push_call("format_help(%p,%p)",ch,pHelp);

	if (IS_NPC(ch) && !ch->desc)
	{
		pop_call();
		return;
	}

	strcpy(colg, get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	strcpy(colw, get_color_string(ch, COLOR_TEXT, VT102_DIM));
	strcpy(colW, get_color_string(ch, COLOR_TEXT, VT102_BOLD));

	if (!strcasecmp(pHelp->keyword, "motd"))
	{
		sprintf(txt, "%s%s", colw, pHelp->text);
		send_to_char_color(txt, ch);
		pop_call();
		return;
	}

	txt[0] = '\0';
	
	sprintf(txt, "%s%s\n\r", colW, help_title(pHelp));
	len = ansi_strlen(help_title(pHelp));

	strcat(txt, colg);
	for (cnt = 0 ; cnt < len ; cnt++)
	{
		cat_sprintf(txt, "=");
	}
	strcat(txt, "\n\r");
	cat_sprintf(txt, "%s%s", colw, pHelp->text);


	if (!strstr(pHelp->text, "{000}"))
	{
		// count menu entries to decide how many columns to format - Kregor
		for (col = 0, menu = pHelp->first_menu ; menu ; menu = menu->next)
		{
			if (menu->option == '-')
				continue;
			col++;
		}
		if (col > 6)
		{
			count = 3;
			length = 20;
		}
		else if (col == 1)
		{
			count = 1;
			length = 79;
		}
		else
		{
			count = 2;
			length = 32;
		}
		strcat(txt, "\n\r");
		for (col = 0, menu = pHelp->first_menu ; menu ; menu = menu->next)
		{
			if (menu->option == '-')
				continue;
			strcpy(link, ansi_strip(ansi_translate(help_title(menu->help))));
			link[length] = '\0';
			if (count == 2)
				cat_sprintf(txt, "  %s[%s%c%s]%s %-32s", colg, colw, UPPER(menu->option), colg, colW, link);
			else if (count == 3)
				cat_sprintf(txt, "  %s[%s%c%s]%s %-20s", colg, colw, UPPER(menu->option), colg, colW, link);
			else
				cat_sprintf(txt, "  %s[%s%c%s]%s %s", colg, colw, UPPER(menu->option), colg, colW, link);
			col++;
			if (col && col % count == 0)
				strcat(txt, "\n\r");
		}
		if (col && col % count != 0)
				strcat(txt, "\n\r");
		for (col = 0, menu = pHelp->first_menu ; menu ; menu = menu->next)
		{
			if (menu->option == '-')
			{
				cat_sprintf(txt, "  %s[%s%c%s]%s %s\n\r", colg, colw, UPPER(menu->option), colg, colW, help_title(menu->help));
				break;
			}
		}
		if (col && col % count != 0)
				strcat(txt, "\n\r");
	}

	send_to_char_color(txt, ch);

	pop_call();
	return;
}


void do_help( CHAR_DATA *ch, char *argument )
{
	HELP_DATA *pHelp;
	char nohelp[MAX_STRING_LENGTH];

	push_call("do_help(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		argument = "mainmenu";
	}
	
	strcpy(nohelp, argument);

	if ((pHelp = get_help(ch, argument)) == NULL)
	{
		ch_printf_color(ch, "No help available for '%s'.\n\r", argument);
		append_file(ch, HELP_FILE, NULL, nohelp);
		pop_call();
		return;
	}

	ch->pcdata->prev_help = ch->pcdata->last_help;
	ch->pcdata->last_help = pHelp;

	SET_BIT(ch->pcdata->interp, INTERP_HELP);

	if (!strcasecmp(pHelp->keyword, "motd"))
	{
		send_to_char_color(pHelp->text, ch);
		pop_call();
		return;
	}

	format_help_to_char(ch, pHelp);
	pop_call();
	return;
}

void do_languages( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	int lang, col, cnt;

	push_call("do_languages(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_languages(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	sprintf(buf1, "{178}");
	sprintf(buf2, "{200}[{078} %s's Languages {200}]{178}", capitalize(get_name(ch)));

	for (cnt = 0 ; cnt < 39-(strlen(get_name(ch))+14)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	ch_printf_color(ch, "%s\n\r", buf1);

	for (lang = col = 0, buf1[0] = '\0' ; lang < MAX_LANG ; lang++)
	{
		if (lang_names[lang] != NULL)
		{
			if (!IS_SET(ch->language, 1 << lang))
				continue;

			sprintf(buf2, " {078}%-16s", lang_names[lang]);

			if (++col % 4 != 1)
			{
				strcat(buf1, "  ");
			}
			strcat(buf1, buf2);

			if (col % 4 == 0)
			{
				ch_printf_color(ch, "%s\n\r", buf1);
				buf1[0] = '\0';
			}
		}
	}
	if (col % 4 != 0)
	{
		ch_printf_color(ch, "%s\n\r", buf1);
	}
	ch_printf_color(ch, "{078}You have %d skill points to spend.\n\r", ch->pcdata->practice);
	ch_printf_color(ch, "{078}You know %d additional languages; you can learn %d total.\n\r", count_tongues(ch), max_tongues(ch));
	pop_call();
	return;
}

int bonus_bonus_langs( CHAR_DATA *ch )
{
	int langs = 0;
	
	push_call("bonus_bonus_langs(%p)",ch);
	
	if (class_level(ch, CLASS_WIZARD))
		SET_BIT(langs, LANG_DRACONIC);
	if (class_level(ch, CLASS_DRUID))
		SET_BIT(langs, LANG_SYLVAN);
	if (class_level(ch, CLASS_CLERIC))
		SET_BIT(langs, LANG_ABYSSAL|LANG_INFERNAL|LANG_CELESTIAL);
	
	pop_call();
	return langs;
}

void do_resistances( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	int res, col, cnt, count;

	push_call("do_resistances(%p,%p)",ch,argument);

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s's Resistances {200}]{078}", capitalize(get_name(ch)));

	for (cnt = 0 ; cnt < 39-(strlen(get_name(ch))+14)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, "\n\r");

	strcat(buf1, "{200}Damage Reductions:\n\r");
	for (col = count = 0, res = APPLY_DR_BASH ; res <= APPLY_DR_NONE ; res++)
	{
		if (get_apply(ch, res) <= 0)
			continue;

		cat_sprintf(buf1, " {078}DR {178}%2d{078}%-13s", get_apply(ch, res), affect_loc_name(res));
		count++;

		if (++col % 4 != 1)
		{
			strcat(buf1, "  ");
		}

		if (col % 4 == 0)
		{
			strcat(buf1, "\n\r");
		}
	}
	if (count <= 0)
		strcat(buf1, " {078}None\n\r");
	if (col % 4 != 0)
	{
		strcat(buf1, "\n\r");
	}
	strcat(buf1, "{200}Energy Resistance:\n\r");
	for (col = count = 0, res = MIN_ENERGY ; res < MAX_ENERGY - 2 ; res++)
	{
		if (energy_resistance(ch, 0, 1 << res) == 0)
			continue;

		cat_sprintf(buf1, " {078}%-12s {178}%2d ", capitalize(dtype_flags[res]), energy_resistance(ch, 0, 1 << res));
		count++;

		if (++col % 5 == 0)
		{
			strcat(buf1, "\n\r");
		}
	}
	if (count <= 0)
		strcat(buf1, " {078}None\n\r");
	if (col % 5 != 0)
	{
		strcat(buf1, "\n\r");
	}
	strcat(buf1, "{200}Save Bonuses:\n\r");
	for (col = count = res = 0 ; res < SDESC_MAX ; res++)
	{
		if (save_bonus(NULL, ch, -1, 1 << res) == 0)
			continue;

		cat_sprintf(buf1, " {078}%-11s {178}%+2d ", capitalize(sdesc_flags[res]), save_bonus(NULL, ch, -1, 1 << res));
		count++;

		if (++col % 5 == 0)
		{
			strcat(buf1, "\n\r");
		}
	}
	if (count <= 0)
		strcat(buf1, " {078}None\n\r");
	if (col % 5 != 0)
	{
		strcat(buf1, "\n\r");
	}
	send_to_char_color(buf1, ch);
	pop_call();
	return;
}

void do_who( CHAR_DATA *ch, char *argument )
{
	CLAN_DATA 		*iClan	= NULL;
	AREA_DATA 		*iArea	= NULL;
	CHAR_DATA			*wch		= NULL;
	PLAYER_GAME		*fpl		= NULL;
	char buf[MAX_STRING_LENGTH];
	char tmp[MAX_INPUT_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	int nMatch, nTotal;
	int type, iGod;

	char colr[10], colg[10], colc[10], col1[10], colm[10], col2[10], colw[10], col3[10];
	char colR[10], colG[10], colY[10], colB[10], colM[10], colC[10], colW[10];

	push_call("do_who(%p,%p)",ch,argument);

	strcpy(colr, "{018}");
	strcpy(colg, "{028}");
	strcpy(colc, "{200}");
	strcpy(col1, "{002}");
	strcpy(colm, "{058}");
	strcpy(col2, "{134}");
	strcpy(colw, "{078}");
	strcpy(col3, "{088}");

	strcpy(colR, "{118}");
	strcpy(colG, "{128}");
	strcpy(colY, "{138}");
	strcpy(colB, "{148}");
	strcpy(colM, "{158}");
	strcpy(colC, "{168}");
	strcpy(colW, "{178}");

	type = 0;
	iGod = -1;

	argument = one_argument(argument, arg);

	if (!IS_IMMORTAL(ch))
		type = 2;
	else
	{
		if (!strcasecmp(arg, "clan"))		type = 1;
		if (!strcasecmp(arg, "all"))		type = 2;
		if (!strcasecmp(arg, ""))				type = 2;
		if (!strcasecmp(arg, "area"))		type = 3;
		if (!strcasecmp(arg, "god"))		type = 4;
	}

	switch (type)
	{
		case 1:
			if (strlen(argument) >= 2)
			{
				iClan = get_clan(argument);
			}
			else
			{
				iClan = ch->pcdata->clan;
			}
			if (iClan == NULL)
			{
				send_to_char("You must specify an existing clan.\n\r", ch);
				pop_call();
				return;
			}
			break;
		case 3:
			iArea = ch->in_room->area;
			if (strlen(argument) >= 2)
			{
				iArea = lookup_area(argument);
			}
			if (iArea == NULL)
			{
				send_to_char("You must specify an existing area.\n\r", ch);
				pop_call();
				return;
			}
			break;
		case 4:
			if (strlen(argument) > 0)
			{
				iGod = lookup_god(argument);
			}
			else
			{
				iGod = which_god(ch);
			}
			if (iGod == -1)
			{
				send_to_char("You must specify an existing god.\n\r", ch);
				pop_call();
				return;
			}
			break;
	}

	/*
		Now show matching chars.
	*/

	nMatch = 0;
	nTotal = 0;
	buf[0] = '\0';

	for (fpl = mud->f_player ; fpl ; fpl = fpl->next)
	{
		wch = fpl->ch;

		if (!can_see_world(ch, wch))
		{
			continue;
		}
		else
		{
			nTotal++;
		}

		switch (type)
		{
			case 1: if (wch->pcdata->clan != iClan)		continue; break;
			case 3: if (wch->in_room->area != iArea) continue; break;
			case 4: if (which_god(wch) != iGod)		continue; break;
		}
		nMatch++;

		sprintf(tmp, "  %s%s%s%-60s                                     ", colW, wch->name, colw, ansi_strip(ansi_translate(wch->pcdata->title)));

		if (wch->pcdata->clan_name[0] != '\0')
		{
			tmp[56+strlen(colw)] = '\0';
			cat_sprintf(tmp, "%s%-10s", col1, wch->pcdata->clan_name);
		}
		else
		{
			tmp[66+strlen(colw)] = '\0';
		}

		cat_sprintf(buf, "%s%s", tmp, col2);

		if (wch->desc != NULL && ch->level >= LEVEL_IMMORTAL)
			cat_snprintf(buf, 13, "%-14s", wch->in_room->area->name);
		else if (IS_SET(wch->act, PLR_AFK))
			cat_snprintf(buf, 13, "     AFK     ");
		else if (wch->pcdata->switched)
			cat_snprintf(buf, 13, "   Switched  ");
		else if (wch->desc == NULL)
			cat_snprintf(buf, 13, "  Link Lost  ");
		else
			cat_snprintf(buf, 13, "   Unknown   ");
		cat_sprintf(buf, "%s\n\r", col3);
	}
	cat_sprintf(buf, "%s%d/%d players.  %s\n\r", colw, nMatch, nTotal, (nMatch != nTotal) ? "This is a partial list." : "");

	send_to_char_color(buf, ch);
	pop_call();
	return;
}

void do_inventory( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	
	push_call("do_inventory(%p,%p)",ch,argument);

	sprintf( buf, "%sYou are carrying:%s\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM), get_color_string(ch, COLOR_TEXT, VT102_DIM) );
	sprintf_list_to_char( buf2, ch->first_carrying, ch, 0, TRUE, FALSE );
	strcat(buf, buf2);
	
	send_to_char_color(buf, ch);
	
	pop_call();
	return;
}

void do_equipment( CHAR_DATA *ch, char *argument )
{
	OBJ_DATA *obj;
	int iWear, count, layer, WearLoc;
	char buf[MAX_STRING_LENGTH];
	char line[MAX_STRING_LENGTH];
	char txt[10], colW[10], colw[10], colc[10], t1[80];
	int DexBonus = -1;

	push_call("do_equipment(%p,%p)",ch,argument);

	sprintf(colW, "%s", get_color_string(ch, COLOR_TEXT, VT102_BOLD));
	sprintf(colw, "%s", get_color_string(ch, COLOR_TEXT, VT102_DIM));
	sprintf(colc, "%s", get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	
	sprintf(buf, "%sYou are equipped with:%s\n\r", colc, colw);

	for (WearLoc = count = iWear = 0 ; iWear < MAX_WEAR ; iWear++)
	{
		if (iWear == WEAR_NONE)
		{
			continue;
		}
		for (layer = LAYER_OVER ; layer >= 0 ; layer--)
		{
			for (obj = ch->first_carrying; obj; obj = obj->next_content)
			{
				if (WEAR_LOC(obj, iWear))
				{
					if (get_layer(obj) == layer)
					{
						if (WearLoc == iWear)
							sprintf(line, "   %s<%slayered under%s>%s ", colc, colw, colc, colw);
						else
							sprintf(line, "%s", where_name[iWear]);
				
						if (can_see_obj(ch, obj))
						{
							cat_sprintf(line, "%s\n\r", str_resize(format_obj_to_char(obj, ch, 0), t1, -60));
						}
						else
						{
							cat_sprintf(line, "Something\n\r");
						}
						strcat(buf, line);
						count++;
						WearLoc = iWear;
					}
				}
			}
		}
	}
	if (count == 0)
	{
		cat_sprintf(buf, "Nothing.\n\r");
	}

	switch (armor_type_worn(ch))
	{
		default:
			strcpy(txt, "heavy");
			break;
		case ARMOR_NONE:
			strcpy(txt, "none");
			break;
		case ARMOR_LIGHT:
			strcpy(txt, "light");
			break;
		case ARMOR_MEDIUM:
			strcpy(txt, "medium");
			break;
	}
	
	if ((DexBonus = dex_bonus_max(ch)) >= 0)
		cat_sprintf(buf, "%sArmor Type: %s%s, %sDex Bonus Max: %s%d, %sArmor Check: %s%s%d %sSpell Fail: %s%d%%\n\r",
			colw, colW, txt, colw, colW, DexBonus, colw, colW, armor_check_penalty(ch) > 0 ? "-" : "", armor_check_penalty(ch), colw, colW, spell_failure(ch));
	else
		cat_sprintf(buf, "%sArmor Type: %s%s, %sDex Bonus Max: %snone, %sArmor Check: %s%s%d %sSpell Fail: %s%d%%\n\r",
			colw, colW, txt, colw, colW, colw, colW, armor_check_penalty(ch) > 0 ? "-" : "", armor_check_penalty(ch), colw, colW, spell_failure(ch));
	send_to_char_color(buf, ch);

	pop_call();
	return;
}

void do_compare( CHAR_DATA *ch, char *argument )
{
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	OBJ_DATA *obj1;
	OBJ_DATA *obj2;
	int value1;
	char *msg;

	push_call("do_compare(%p,%p)",ch,argument);

	argument = one_argument(argument, arg1);
	argument = one_argument(argument, arg2);

	if (arg1[0] == '\0')
	{
		send_to_char("Compare what to what?\n\r", ch);
		pop_call();
		return;
	}

	if ((obj1 = get_obj_carry(ch, arg1)) == NULL)
	{
		send_to_char("You do not have that item.\n\r", ch);
		pop_call();
		return;
	}

	if (arg2[0] == '\0')
	{
		for (obj2 = ch->first_carrying; obj2 != NULL; obj2 = obj2->next_content)
		{
			if (IS_WORN(obj2) && can_see_obj(ch, obj2)
			&& (obj1->wear_flags & obj2->wear_flags & ~CAN_WEAR_TAKE) != 0)
			{
				break;
			}
		}
		if (obj2 == NULL)
		{
			send_to_char( "You aren't wearing anything comparable.\n\r", ch);
			pop_call();
			return;
		}
	}
	else
	{
		if ( ( obj2 = get_obj_carry( ch, arg2 ) ) == NULL )
		{
			send_to_char( "You do not have that item.\n\r", ch );
			pop_call();
			return;
		}
	}

	msg         = NULL;
	value1      = 0;

	if (obj1 == obj2)
	{
		msg = "You compare $p to itself.  It looks about the same.";
	}
	else
	{
		value1 = compare_obj( obj1, obj2 );
	}

	if ( msg == NULL )
	{
		if ( value1 == 0 )
			msg = "$p and $P look about the same.";
		else if ( value1  > 0 )
			msg = "$p looks better than $P.";
		else
			msg = "$p looks worse than $P.";
 	}

	act( msg, ch, obj1, obj2, TO_CHAR );
	pop_call();
	return;
}

int compare_obj( OBJ_DATA *obj1, OBJ_DATA *obj2)
{
	int value1, value2;

	push_call("compare_obj(%p,%p)",obj1,obj2);

	value1 = obj_level_estimate(obj1->pIndexData);
	value2 = obj_level_estimate(obj2->pIndexData);

 	pop_call();
	return( value1 - value2 );
}

void do_credits( CHAR_DATA *ch, char *argument )
{
	push_call("do_credits(%p,%p)",ch,argument);

	do_help( ch, "credits" );

	pop_call();
	return;
}

void do_consider( CHAR_DATA *ch, char *argument )
{
	CHAR_DATA *victim, *rch;
	char *msg;
	int diff, all;

	push_call("do_consider(%p,%p)",ch,argument);

	all = FALSE;

 	if (argument[0] == '\0')
	{
		send_to_char( "Consider killing whom?\n\r", ch );
		pop_call();
		return;
	}

	if ((victim = get_char_room(ch, argument)) == NULL)
	{
		if (strcasecmp(argument, "all"))
		{
			send_to_char( "They're not here.\n\r", ch );
			pop_call();
			return;
		}
		all = TRUE;
	}

	for (rch = ch->in_room->first_person ; rch ; rch = rch->next_in_room)
	{
		if (!can_see(ch, rch))
		{
			continue;
		}

		if (!all && victim != rch)
		{
			continue;
		}

		if (all && rch == ch)
		{
			continue;
		}

		victim = rch;

		diff = (get_exp_worth(victim) - get_exp_worth(ch)) * 10 / (10 + get_exp_worth(ch));

		if (diff <= -9)
		{
			msg = "You could kill $N naked and weaponless.";
		}
		else if (diff <= -5)
		{
			msg = "$N is no match for you.";
		}
		else if (diff <= -2)
		{
			msg = "$N looks like an easy kill.";
		}
		else if (diff <=  1)
		{
			msg = "$N looks like the perfect match!";
		}
		else if (diff <=  4)
		{
			msg = "$N would take a lot of luck, and equipment.";
		}
		else if (diff <=  9)
		{
			msg = "You are no match for $N.";
		}
		else if (diff <= 20)
		{
			msg = "$N could kill you naked and weaponless.";
		}
		else
		{
			msg = "Death will thank you for your gift.";
		}
		act( msg, ch, NULL, victim, TO_CHAR );
	}
	if (all && victim == NULL)
	{
		act( "You are all by yourself here.", ch, NULL, NULL, TO_CHAR);
	}
	pop_call();
	return;
}

bool set_title( CHAR_DATA *ch, char *title )
{
	char buf[MAX_INPUT_LENGTH];

	push_call("set_title(%p,%p)",ch,title);

	if (IS_NPC(ch))
	{
		bug( "Set_title: NPC.", 0 );
		pop_call();
		return FALSE;
	}

	if (title[0] == ',')
	{
		sprintf(buf, "%s", title);
	}
	else if (title[0] == '.')
	{
		buf[0] = '\0';
	}
	else if (isalpha(title[0]))
	{
		sprintf(buf, " %s", title);
	}
	else
	{
		send_to_char("You may only use alpha characters in your title.\n\r", ch);
		pop_call();
		return FALSE;
	}
	buf[60] = '\0';

	STRFREE (ch->pcdata->title );
	ch->pcdata->title = STRALLOC( buf );
	pop_call();
	return TRUE;
}

void do_email( CHAR_DATA *ch, char *argument )
{
	push_call("do_email(%p,%p)",ch,argument);

	if ( IS_NPC(ch) || !is_desc_valid( ch ))
	{
		pop_call();
		return;
	}

	if ( argument[0] == '\0' )
	{
		send_to_char( "Set your email address to?\n\r", ch );
		pop_call();
		return;
	}

	if (*argument == '.' && *(argument+1) == '\0')
	{
		STRFREE(ch->pcdata->mail_address );
		ch->pcdata->mail_address = STRALLOC( "" );
		send_to_char( "Email address cleared.\n\r", ch );
		pop_call();
		return;
	}

	argument[55] = '\0';

	smash_tilde( argument );
	STRFREE(ch->pcdata->mail_address );
	ch->pcdata->mail_address = STRALLOC( argument );
	ch_printf_color(ch, "Address set to: %s\n\r", argument );

	pop_call();
	return;
}

void do_html( CHAR_DATA *ch, char *argument )
{
	char *pt;

	push_call("do_html(%p,%p)",ch,argument);

	if (IS_NPC(ch) || !is_desc_valid(ch))
	{
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char( "Set your homepage address to?\n\r", ch );
		pop_call();
		return;
	}

	if (*argument == '.' && *(argument+1) == '\0')
	{
		STRFREE(ch->pcdata->html_address );
		ch->pcdata->html_address = STRALLOC( "" );
		send_to_char( "HTML Home Page address cleared.\n\r", ch );
		pop_call();
		return;
	}

	argument[55] = '\0';

	ch_printf_color(ch, "Html address set to: %s\n\r", argument);

	for (pt = argument ; *pt != '\0' ; pt++)
	{
		if ((unsigned char) *pt == '~')
		{
			*pt = '*';
		}
	}

	STRFREE(ch->pcdata->html_address );
	ch->pcdata->html_address = STRALLOC( argument );

	pop_call();
	return;
}

void do_title( CHAR_DATA *ch, char *argument )
{
	push_call("do_title(%p,%p)",ch,argument);

	if ( IS_NPC(ch) )
	{
		pop_call();
		return;
	}

	if ( argument[0] == '\0' )
	{
		send_to_char( "Change your title to what?\n\r", ch );
		pop_call();
		return;
	}

	if (argument[0] == '.' && argument[1] == '\0')
	{
		if (set_title(ch, "."))
			send_to_char("Title cleared.\n\r", ch);

		pop_call();
		return;
	}

	argument[60] = '\0';

	smash_tilde( argument );
	if (set_title( ch, argument ))
		send_to_char( "Title set.\n\r", ch );

	pop_call();
	return;
}

void do_unalias( CHAR_DATA *ch, char *argument )
{
	int cnt;
	bool found = FALSE;

	push_call("do_unalias(%p,%p)",ch,argument);

	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}

	smash_tilde( argument );

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

	for (cnt = 0 ; cnt < MAX_ALIAS ; cnt++)
	{
		if (!strcasecmp(CH(ch->desc)->pcdata->alias_c[cnt], argument))
		{
			STRFREE(CH(ch->desc)->pcdata->alias[cnt]);
			CH(ch->desc)->pcdata->alias[cnt] = STRALLOC("");

			STRFREE(CH(ch->desc)->pcdata->alias_c[cnt]);
			CH(ch->desc)->pcdata->alias_c[cnt] = STRALLOC("");

			ch_printf_color(ch, "Alias %s removed.\n\r", argument );
			found = TRUE;
			pop_call();
			return;
		}
	}

	if (!found)
	{
		if (!strcasecmp(argument, "all"))
		{
			for (cnt = 0 ; cnt < MAX_ALIAS ; cnt++)
			{
				RESTRING(CH(ch->desc)->pcdata->alias[cnt], "");
				RESTRING(CH(ch->desc)->pcdata->alias_c[cnt], "");
			}
			ch_printf_color(ch, "All aliases removed.\n\r");
		}
		else
		{
			ch_printf_color(ch, "You don't have an alias for %s.\n\r", argument);
		}
	}
	pop_call();
	return;
}

/*
	Sort alphabetically   -  Chaos  4/4/99
*/

void sort_alias( CHAR_DATA *ch )
{
	int cnt1, cnt2;
	int acase;
	char *pt1, *pt2;

	push_call("sort_alias(%p)",ch);

	/* Use simple Sieve filter here */

	for (cnt1 = 0 ; cnt1 < MAX_ALIAS-2 ; cnt1++)
	{
		for (cnt2 = MAX_ALIAS-2 ; cnt2 >= cnt1 ; cnt2--)
		{
			acase = 0;
			if (ch->pcdata->alias[cnt2][0] == '\0')
			{
				if (ch->pcdata->alias[cnt2+1][0] != '\0')
				{
					acase = 1;
				}
				else
				{
					acase = 0;
				}
			}
			else if (ch->pcdata->alias[cnt2+1][0] == '\0')
			{
				acase = -1;
			}
			else
			{
				acase = strcasecmp(ch->pcdata->alias_c[cnt2], ch->pcdata->alias_c[cnt2+1]);
			}

			/* Swap */

			if (acase > 0)
			{
				pt1 = ch->pcdata->alias_c[cnt2];
				pt2 = ch->pcdata->alias  [cnt2];
				ch->pcdata->alias_c[cnt2]	= ch->pcdata->alias_c[cnt2+1];
				ch->pcdata->alias  [cnt2]	= ch->pcdata->alias  [cnt2+1];
				ch->pcdata->alias_c[cnt2+1]	= pt1;
				ch->pcdata->alias  [cnt2+1]	= pt2;
			}
		}
	}
	pop_call();
	return;
}

void do_alias( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char tbuf[MAX_STRING_LENGTH];
	char colc[10], colw[10], colW[10];
	char *pt, *ptt;
	int blen, tot, percent, cnt, found = 4096;
	bool match = FALSE, lowest = FALSE;

	push_call("do_alias(%p,%p)",ch,argument);

	/*
		Re-wrote most of this to get rid of the overrunning buffers and also
		to speed it up a little...Martin
	*/
	if (ch->desc == NULL)
	{
		pop_call();
		return;
	}
	
	strcpy(colc, get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	strcpy(colw, get_color_string(ch, COLOR_TEXT, VT102_DIM));
	strcpy(colW, get_color_string(ch, COLOR_TEXT, VT102_BOLD));

	if (argument[0] == '\0')
	{
		tot = 0;
		blen = str_cpy_max( buf, colc, MAX_STRING_LENGTH);
		blen = str_apd_max( buf, "Alias list:\n\r", blen, MAX_STRING_LENGTH);

		for (cnt = 0 ; cnt < MAX_ALIAS ; cnt++)
		{
			if (CH(ch->desc)->pcdata->alias[cnt][0] != '\0')
			{
				tot++;
				sprintf(tbuf, "  %s%-12s %s%s\n\r", colW, CH(ch->desc)->pcdata->alias_c[cnt], colw, CH(ch->desc)->pcdata->alias[cnt] );
				blen = str_apd_max( buf, tbuf, blen, MAX_STRING_LENGTH);
			}
		}
		sprintf( tbuf, "%s%d Aliases used out of %d available.\n\r", colc, tot, MAX_ALIAS );
		blen = str_apd_max( buf, tbuf, blen, MAX_STRING_LENGTH);
		send_to_char( ansi_translate_text( ch, buf), ch );
		pop_call();
		return;
	}

	smash_tilde( argument );

	argument = one_argument_nolower(argument, buf);

	if (strlen(buf) > 12)
	{
		send_to_char( "You cannot make an alias command longer than 12 characters.\n\r", ch);
		pop_call();
		return;
	}

	for (pt = buf ; *pt != '\0' ; pt++)
	{
		if (!isalpha(*pt))
		{
			send_to_char( "You cannot make an alias command with non-letters.\n\r", ch);
			pop_call();
			return;
		}
	}

	if (argument[0] == '\0')
	{
		for (cnt = 0 ; cnt < MAX_ALIAS ; cnt++)
		{
			if (!strcasecmp(CH(ch->desc)->pcdata->alias_c[cnt], buf))
			{
				ch_printf_color(ch, "Use unalias to delete your alias for %s (currently %s).\n\r", buf, CH(ch->desc)->pcdata->alias[cnt]);
				pop_call();
				return;
			}
		}
		ch_printf_color(ch, "Alias %s to what ?\n\r", buf);
		pop_call();
		return;
	}

	if (strlen(argument) > 400)
	{
		send_to_char("You cannot make an alias longer than 400 characters.\n\r", ch);
		pop_call();
		return;
	}

	for (cnt = 0 ; cnt < MAX_ALIAS ; cnt++)
	{
		if (CH(ch->desc)->pcdata->alias[cnt][0] == '\0' && !match)
		{
			if (!lowest)
			{
				found  = cnt;
				lowest = TRUE;
			}
			continue;
		}
		if (!strcasecmp(CH(ch->desc)->pcdata->alias_c[cnt], buf))
		{
			found = cnt;
			match = TRUE;
		}
	}
	if (found != 4096)
	{
		/*
			prevents aliasses like
			alias boo chat %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
			there are people who _love_ to crash the mud thus, and now
			this is one of the answers. We're gonna cap the input length
			of the commands so that there will not happen that kinda things.
			Manwe,
			03/11/1999.
		*/

		for (percent = 0, ptt = argument ; *ptt != '\0' ; ptt++)
		{
			if (*ptt == '%')
			{
				percent++;
			}
			if (percent > 1)
			{
				send_to_char("You may only use one % for every &.\n\r", ch);
				log_printf("%s used an illegal alias: %s", ch->name, argument);
				pop_call();
				return;
			}
			if (*ptt == '&')
			{
				percent = 0;
			}
		}
		STRFREE (CH(ch->desc)->pcdata->alias[found] );
		STRFREE (CH(ch->desc)->pcdata->alias_c[found] );
		CH(ch->desc)->pcdata->alias_c[found] = STRALLOC(lower_all(buf));
		CH(ch->desc)->pcdata->alias[found]   = STRALLOC(argument);

		sort_alias( CH(ch->desc) );
		send_to_char( "Alias set.\n\r" , ch );
		pop_call();
		return;
	}
	send_to_char( "You reached your maximum amount of aliasses.\n\r", ch);
	pop_call();
	return;
}

/*
 * Allows a wizard to set his specialty school
 */
void do_school( CHAR_DATA *ch, char *argument )
{
	int school;

	push_call("do_school(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_school(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}
	
	if (!class_level(ch, CLASS_WIZARD))
	{
		send_to_char( "Only wizards can choose a specialty school.\n\r", ch );
		pop_call();
		return;
	}
	
	school = get_school(ch);
	
	if (school <= 0 && !ch->learned[gsn_school_univ])
	{
		send_to_char( "You have not learned a school of specialty yet.\n\r", ch );
		pop_call();
		return;
	}

	ch_printf_color(ch, "{200}Your school of specialty:{300} %s\n\r", capitalize(school_types[school]));
	ch_printf_color(ch, "{200}Your prohibited school:{300} %s\n\r", opp_school(ch) == 0 ? "None" : capitalize(school_types[opp_school(ch)]));

	pop_call();
	return;
}

/*
 * allows a cleric to set his domains
 */
void do_domain( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char names[MAX_INPUT_LENGTH];
	int count, cnt, level;

	push_call("do_domain(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_domain(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}
	
	if ((level = class_level(ch, CLASS_CLERIC)) == 0)
	{
		send_to_char( "Only clerics can choose a domain.\n\r", ch );
		pop_call();
		return;
	}

	if (!ch->god)
	{
		send_to_char( "You must serve a diety to choose your domains.\n\r", ch );
		pop_call();
		return;
	}

	for (names[0] = '\0', cnt = count = 0 ; cnt < MAX_DOMAIN ; cnt++)
	{
		if (ch->pcdata->domain[cnt])
		{
			if (count > 0)
				cat_sprintf(names, ", ");
			cat_sprintf(names, capitalize(domain_types[cnt]));
			count++;
		}
	}

	if (argument[0] == '\0')
	{
		ch_printf_color(ch, "{200}Your domains:{300} %s\n\r", is_string(names) ? names : "None");

		if (count < UMIN((level / 5) + 1, 2))
		{
			for (*buf = '\0', cnt = 0 ; cnt < MAX_DOMAIN ; cnt++)
			{
				if (!god_table[which_god(ch)].domain[cnt] || ch->pcdata->domain[cnt])
					continue;
				if (cnt && strlen(buf))
					strcat(buf, ", ");

				cat_sprintf(buf, "%s", domain_types[cnt]);
			}
			cat_sprintf(buf, "?");
			ch_printf_color(ch, "  Choose what domain: %s\n\r", ansi_justify(buf, get_page_width(ch)));
			pop_call();
			return;
		}
		else
		{
			pop_call();
			return;
		}
	}
	
	if ((cnt = get_flag(argument, domain_types)) == -1)
	{
		send_to_char("That is not an accessible domain.\n\r", ch);
		pop_call();
		return;
	}
	if (count >= UMIN((level / 5) + 1, 2))
	{
		send_to_char("You cannot choose another domain at this level.\n\r", ch);
		pop_call();
		return;
	}
	if (ch->pcdata->domain[cnt])
	{
		ch_printf_color(ch, "You already chose %s.\n\r", domain_types[cnt]);
		pop_call();
		return;
	}
	if (!god_table[ch->god].domain[cnt])
	{
		send_to_char( "Your deity does not have access to that domain.\n\r", ch );
		pop_call();
		return;
	}
	if ((cnt == DOMAIN_GOOD && !IS_GOOD(ch))
	|| (cnt == DOMAIN_EVIL && !IS_EVIL(ch))
	|| (cnt == DOMAIN_LAW && !IS_LAWFUL(ch))
	|| (cnt == DOMAIN_CHAOS && !IS_CHAOTIC(ch)))
	{
		send_to_char("That domain is against your alignment.", ch);
		pop_call();
		return;
	}
	ch->pcdata->domain[cnt] = 1;
	act( "You choose $T as one of your clerical domains.", ch, NULL, domain_types[cnt], TO_CHAR );
	pop_call();
	return;
}


/*
 * Allows a ranger to set his favored enemies
 */
void do_faveenemy (CHAR_DATA * ch, char *argument)
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	int col, cnt, enemy;

	push_call("do_faveenemy(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		ch_printf_color(ch, "Only on PCs.\n\r");
		pop_call();
		return;
	}
	if (fave_enemy_max(ch) <= 0 )
	{
		ch_printf_color(ch, "You cannot adopt a favored enemy.\n\r");
		pop_call();
		return;
	}
	
	if (argument[0] == '\0')
	{
		ch_printf_color(ch, "{078}------------------------------{200}[{178} Favored Enemies {200}]{078}-------------------------------\n\r");
		for (cnt = col = 0, buf1[0] = '\0' ; cnt < RACE_ENEMY_MAX ; cnt++)
		{
			if (is_string(race_enemy_names[cnt]))
			{
				if (ch->pcdata->race_enemy[cnt] > 0)
				{
					sprintf(buf2, "  {078}%-18s {200}[{178}+%2d{200}]", race_enemy_names[cnt], ch->pcdata->race_enemy[cnt]);
				}
				else
				{
					continue;
				}
				if (++col % 3 != 1)
				{
					strcat(buf1, " ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					ch_printf_color(ch, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
		if (col == 0)
			ch_printf_color(ch, " None\n\r");
		else if (col % 3 != 0)
			ch_printf_color(ch, "%s\n\r", buf1);

		if (fave_enemy_count(ch) < fave_enemy_max(ch))
			ch_printf_color(ch, "{078}You may adopt %d additional favored enemies.\n\r", fave_enemy_max(ch) - fave_enemy_count(ch));
		ch_printf_color(ch, "Syntax: faveenemy <race name>.\n\r");
		pop_call();
		return;
	}
	if (fave_enemy_count(ch) >= fave_enemy_max(ch))
	{
		ch_printf_color(ch, "You cannot adopt another favored enemy now.\n\r");
		pop_call();
		return;
	}
	if ((enemy = get_flag(argument, race_enemy_names)) == -1)
	{
		sprintf(buf1, "Syntax: faveenemy <%s>", give_flags(race_enemy_names));
		ch_printf_color(ch, "%s\n\r", ansi_justify(buf1, get_page_width(ch)));
		pop_call();
		return;
	}
	if (ch->pcdata->race_enemy[enemy])
	{
		ch_printf_color(ch, "You already have %s as a favored enemy!\n\r", race_enemy_names[enemy]);
		pop_call();
		return;
	}
	for (cnt = 0 ; cnt < RACE_ENEMY_MAX ; cnt++)
	{
		if (cnt == enemy)
		{
			ch_printf_color(ch, "You adopt the racial type %s as a favored enemy.\n\r", race_enemy_names[cnt]);
			ch->pcdata->race_enemy[enemy] += 2;
		}
		else if (ch->pcdata->race_enemy[cnt])
		{
			ch->pcdata->race_enemy[cnt] += 1;
			ch_printf_color(ch, "Your emnity of racial type %s increases to +%d.\n\r", race_enemy_names[cnt], ch->pcdata->race_enemy[cnt]);
			continue;
		}
	}
	pop_call();
	return;
}

int fave_enemy_max(CHAR_DATA * ch)
{
	int value = 0;

	push_call("fave_enemy_max(%p)",ch);

	if ( class_level(ch, CLASS_RANGER) )
	{
		value = 1 + (class_level(ch, CLASS_RANGER) / 5);
	}
	if (learned(ch, gsn_foe_hunter))
	{
		value += 1;
	}
	pop_call();
	return value;
}

int fave_enemy_count (CHAR_DATA * ch)
{
	int value, cnt;

	push_call("fave_enemy_count(%p)",ch);

	for (value = cnt = 0 ; cnt < RACE_ENEMY_MAX ; cnt++)
	{
		if (ch->pcdata->race_enemy[cnt] > 0)
			value++;
	}

	pop_call();
	return value;
}
	
int fave_enemy_bonus (CHAR_DATA *ch, CHAR_DATA *victim)
{
	int value, cnt;

	push_call("fave_enemy_bonus(%p,%p)",ch,victim);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return 0;
	}
	if (!class_level(ch, CLASS_RANGER) && !learned(ch, gsn_foe_hunter))
	{
		pop_call();
		return 0;
	}
	
	for (cnt = value = 0 ; cnt < RACE_ENEMY_MAX ; cnt++)
	{
		if ((!race_enemies[cnt].type || race_type(victim) == race_enemies[cnt].type)
		&& IS_SET(race_table[victim->race].flags, race_enemies[cnt].spec))
		{
			if (ch->pcdata->race_enemy[cnt] > value)
				value = ch->pcdata->race_enemy[cnt];
		}
	}
	pop_call();
	return( value );
}


/*
 * Support for bonus feats in do_train
 * derived from D20 SRD - Kregor
 */
bool is_bonus_feat(CHAR_DATA *ch, int class, int sn)
{
	push_call("is_bonus_feat(%p,%p,%p)",ch,class,sn);
	
	if (skill_table[sn].skilltype != FSKILL_FEAT)
	{
		pop_call();
		return FALSE;
	}
	if (class_level(ch, class) >= skill_table[sn].bonus_feat[class])
	{
		pop_call();
		return TRUE;
	}
	if (class == CLASS_FIGHTER && IS_SET(skill_table[sn].spell_school, FEAT_FIGHTER))
	{
		pop_call();
		return TRUE;
	}
	if (class == CLASS_SORCERER)
	{
		if (get_bloodline(ch) > 0 && class_level(ch, class) >= skill_table[sn].bloodline[get_bloodline(ch)])
		{
			pop_call();
			return TRUE;
		}
	}
	pop_call();
	return FALSE;
}


/* Checks prerequisites for feats,
 * returns false if all prerequisites not met - Kregor 12/23/06
 */
bool can_train_feat( CHAR_DATA *ch, int sn, bool fDisplay )
{
	bool train = TRUE;
	char buf[MAX_STRING_LENGTH];
	int cls;

	push_call("can_train_feat(%p,%p)",ch,sn);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return FALSE;
	}

	if (learned(ch, sn))
	{
		if (skill_table[sn].skilltype == FSKILL_WEAPON)
		{
			strcpy(buf, "You are already proficient in that weapon.\n\r");
			train = FALSE;
		}
		if (sn == gsn_weapon_focus
		|| sn == gsn_weapon_specialization
		|| sn == gsn_imp_critical
		|| sn == gsn_power_critical)
		{
			if (sn == gsn_weapon_specialization
			|| sn == gsn_power_critical)
			{
				if (!class_level(ch, CLASS_FIGHTER))
				{
					strcpy(buf, "You have to be a fighter to train that feat.\n\r");
					train = FALSE;
				}
			}
			if (sn == gsn_weapon_focus && base_attack(ch) < 1)
			{
				strcpy(buf, "You must have a BAB of +1 or better to train Weapon Focus.\n\r");
				train = FALSE;
			}
			else
			{
				pop_call();
				return TRUE;
			}			
		}
		if (sn == gsn_spell_mastery)
		{
			if (!class_level(ch, CLASS_WIZARD))
			{
				strcpy(buf, "You do not have to use a spellbook.\n\r");
				train = FALSE;
			}
			if (learned(ch, sn) >= 9)
			{
				strcpy(buf, "You have already mastered all of your spell circles.\n\r");
				train = FALSE;
			}
			if (learned(ch, sn) * 2 + 2 < learned(ch, gsn_spellcraft))
			{
				strcpy(buf, "You do not have enough ranks in Spellcraft to master that circle.\n\r");
				train = FALSE;
			}
		}
		if (sn == gsn_focus_abj
		|| sn == gsn_focus_conj
		|| sn == gsn_focus_div
		|| sn == gsn_focus_ench
		|| sn == gsn_focus_evoc
		|| sn == gsn_focus_illus
		|| sn == gsn_focus_necro
		|| sn == gsn_focus_trans)
		{
			if ((sn == gsn_focus_abj && opp_school(ch) == SCHOOL_ABJURATION)
			|| (sn == gsn_focus_conj && opp_school(ch) == SCHOOL_CONJURATION)
			|| (sn == gsn_focus_div && opp_school(ch) == SCHOOL_DIVINATION)
			|| (sn == gsn_focus_ench && opp_school(ch) == SCHOOL_ENCHANTMENT)
			|| (sn == gsn_focus_evoc && opp_school(ch) == SCHOOL_EVOCATION)
			|| (sn == gsn_focus_illus && opp_school(ch) == SCHOOL_ILLUSION)
			|| (sn == gsn_focus_necro && opp_school(ch) == SCHOOL_NECROMANCY)
			|| (sn == gsn_focus_trans && opp_school(ch) == SCHOOL_TRANSMUTATION))
			{
				strcpy(buf, "You cannot focus in an opposition school.\n\r");
				train = FALSE;
			}				
			if (learned(ch, sn) >= 3)
			{
				strcpy(buf, "You are already as focused in that school as you can be.\n\r");
				train = FALSE;
			}
			else if (learned(ch, sn) == 2)
			{
				if (max_spell_circle(ch, CLASS_CLERIC) < 9
				&& max_spell_circle(ch, CLASS_DRUID) < 9
				&& max_spell_circle(ch, CLASS_SORCERER) < 9
				&& max_spell_circle(ch, CLASS_WIZARD) < 9)
				{
					strcpy(buf, "You must be able to cast 9th level spells to Epic Focus in a school.\n\r");
					train = FALSE;
				}
			}
		}
		else if (sn == gsn_spell_penetration || sn == gsn_newfound_arcana)
		{
			if (learned(ch, sn) >= 3)
			{
				strcpy(buf, "You can't train that feat any higher.\n\r");
				train = FALSE;
			}
		}
		else
		{
			strcpy(buf, "You already know that feat.\n\r");
			train = FALSE;
		}
	}
	if (IS_SET(skill_table[sn].spell_school, FEAT_METAMAGIC|FEAT_CREATION) && !is_caster(ch))
	{
		if (fDisplay)
			send_to_char("That feat can only be learned by a spell caster.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (strstr(skill_table[sn].name, "spell focus") && !is_caster(ch))
	{
		if (fDisplay)
			send_to_char("That feat can only be learned by a spell caster.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (IS_SET(skill_table[sn].spell_school, FEAT_RANGER_STYLE))
	{
		if (class_level(ch, CLASS_RANGER) < 2)
		{
			if (fDisplay)
				send_to_char("Only a 2nd level ranger can choose a combat style.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (get_ranger_style(ch))
		{
			if (fDisplay)
				send_to_char("You have already chosen a combat style.\n\r", ch);
			pop_call();
			return FALSE;
		}
		pop_call();
		return TRUE;
	}
	if (IS_SET(skill_table[sn].spell_school, FEAT_MONK_STYLE))
	{
		if (!class_level(ch, CLASS_MONK))
		{
			if (fDisplay)
				send_to_char("Only a monk may choose a fighting style.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (get_monk_style(ch))
		{
			if (fDisplay)
				send_to_char("You have already chosen a fighting style.\n\r", ch);
			pop_call();
			return FALSE;
		}
		pop_call();
		return TRUE;
	}				
	if (IS_SET(skill_table[sn].spell_school, FEAT_SORCERER_BLOODLINE))
	{
		if (!class_level(ch, CLASS_SORCERER))
		{
			if (fDisplay)
				send_to_char("Only a sorcerer can have a bloodline.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (get_bloodline(ch) != -1)
		{
			if (fDisplay)
				send_to_char("You have already chosen a bloodline.\n\r", ch);
			pop_call();
			return FALSE;
		}
		pop_call();
		return TRUE;
	}
	if (IS_SET(skill_table[sn].spell_school, FEAT_WIZARD_SCHOOL))
	{
		if (!class_level(ch, CLASS_WIZARD))
		{
			if (fDisplay)
				send_to_char("Only a wizard can have a school of specialty.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (get_school(ch) != -1)
		{
			if (fDisplay)
				send_to_char("You have already chosen a school of specialty.\n\r", ch);
			pop_call();
			return FALSE;
		}
		pop_call();
		return TRUE;
	}
	if (!IS_SET(skill_table[sn].spell_school, FEAT_GENERAL))
	{
		for (cls = 0 ; cls < MAX_CLASS ; cls++)
		{
			if (is_bonus_feat(ch, cls, sn))
			{
				pop_call();
				return TRUE;
			}
		}
		if (fDisplay)
			send_to_char("That feat can only be learned as a bonus feat.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (sn == gsn_stunning_fist)
	{
		if (!learned(ch, gsn_imp_unarmed_strike))
		{
			strcpy(buf, "You must know Improved Unarmed Strike to train Stunning Fist.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 8)
		{
			strcpy(buf, "You need a BAB of +8 to train Stunning Fist.\n\r");
			train = FALSE;
		}
		if (ch->perm_dex < 13 || ch->perm_wis < 13)
		{
			strcpy(buf, "You need a DEX and WIS of 13 to train Stunning Fist.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_noble_birth)
	{
		if (ch->level > 1)
		{
			strcpy(buf, "You can only train this feat at 1st level.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_agile_combatant)
	{
		if (ch->perm_dex < 13)
		{
			strcpy(buf, "You must have a DEX of 13 to train Agile Combatant.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_agile_feint)
	{
		if (base_attack(ch) < 1)
		{
			strcpy(buf, "You need a BAB of +1 to train Agile Feint.\n\r");
			train = FALSE;
		}
		if (ch->perm_dex < 13)
		{
			strcpy(buf, "You must have a DEX of 13 to train Agile Feint.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_parry)
	{
		if (!learned(ch,gsn_combat_expertise))
		{
			strcpy(buf, "You must know Combat Expertise to train Parry.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 6)
		{
			strcpy(buf, "You need a BAB of +6 to train Parry.\n\r");
			train = FALSE;
		}
		if (ch->perm_int < 13)
		{
			strcpy(buf, "You must have an INT of 13 to train Parry.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_craft_magic_arms)
	{
		if (learned(ch, gsn_spellcraft) < 8)
		{
			strcpy(buf, "You must have 8 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_craft_staff)
	{
		if (learned(ch, gsn_spellcraft) < 12)
		{
			strcpy(buf, "You must have 12 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_craft_wand)
	{
		if (learned(ch, gsn_spellcraft) < 8)
		{
			strcpy(buf, "You must have 8 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_craft_wondrous)
	{
		if (learned(ch, gsn_spellcraft) < 12)
		{
			strcpy(buf, "You must have 12 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_forge_ring)
	{
		if (learned(ch, gsn_spellcraft) < 15)
		{
			strcpy(buf, "You must have 15 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_brew_potion)
	{
		if (learned(ch, gsn_spellcraft) < 4)
		{
			strcpy(buf, "You must have 4 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_scribe_scroll)
	{
		if (learned(ch, gsn_spellcraft) < 4)
		{
			strcpy(buf, "You must have 4 ranks in Spellcraft train that feat.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_riposte)
	{
		if (!learned(ch,gsn_combat_expertise))
		{
			strcpy(buf, "You must know Combat Expertise to train Riposte.\n\r");
			train = FALSE;
		}
		if (!learned(ch,gsn_parry))
		{
			strcpy(buf, "You must know Parry to train Riposte.\n\r");
			train = FALSE;
		}
		if (ch->perm_int < 13)
		{
			strcpy(buf, "You must have an INT of 13 to train Riposte.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_armor_proficiency_medium && !learned(ch,gsn_armor_proficiency_light))
	{
		strcpy(buf, "You must have light armor proficiency to train medium armor proficiency.\n\r");
		train = FALSE;
	}
	if (sn == gsn_armor_proficiency_heavy && !learned(ch,gsn_armor_proficiency_medium))
	{
		strcpy(buf, "You must have medium armor proficiency to train heavy armor proficiency.\n\r");
		train = FALSE;
	}
	if (sn == gsn_jack_of_all_trades)
	{
		if (ch->perm_int < 13)
		{
			strcpy(buf, "You need an INT of 13 to train Jack of All Trades.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_arcane_armor_training)
	{
		if (max_spell_circle(ch, ch->class) < 3)
		{
			strcpy(buf, "You must be a caster of 3rd level or higher.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_armor_proficiency_light))
		{
			strcpy(buf, "You must be trained in light armor proficiency.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_augment_summoning && !learned(ch,gsn_focus_conj))
	{
		strcpy(buf, "You must focus in conjuration to augment your summoning.\n\r");
		train = FALSE;
	}
	if (sn == gsn_cleave)
	{
		if (ch->perm_str < 13)
		{
			strcpy(buf, "You need a STR of 13 to train Cleave.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_power_attack))
		{
			strcpy(buf, "You need to know Power Attack to train Cleave.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_diehard)
	{
		if (ch->perm_con < 13)
		{
			strcpy(buf, "You need a CON of 13 to train Diehard.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_endurance))
		{
			strcpy(buf, "You need to know Endurance to train Diehard.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_improved_feint || sn == gsn_greater_feint)
	{
		if (ch->perm_int < 13)
		{
			strcpy(buf, "You need an INT of 13 or better.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_combat_expertise))
		{
			strcpy(buf, "You need to know Combat Expertise first.\n\r");
			train = FALSE;
		}
		if (sn == gsn_greater_feint && !learned(ch, gsn_improved_feint))
		{
			strcpy(buf, "You need to know Improved Feint first.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_combat_expertise && ch->perm_int < 13)
	{
		strcpy(buf, "You need an INT of 13 to train Combat Expertise.\n\r");
		train = FALSE;
	}
	if (sn == gsn_deflect_arrows)
	{
		if (ch->perm_dex < 13)
		{
			strcpy(buf, "You need a DEX of 13 to Deflect Missiles.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_imp_unarmed_strike))
		{
			strcpy(buf, "You need to know Improved Unarmed Strike to Deflect Missiles.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_dodge && ch->perm_dex < 13)
	{
		strcpy(buf, "You need a DEX of 13 to train Dodge.\n\r");
		train = FALSE;
	}
	if (sn == gsn_extra_turning && !learned(ch, gsn_turn_undead))
	{
		strcpy(buf, "You do not know how to turn undead.\n\r");
		train = FALSE;
	}
	if ((sn == gsn_empower_song || sn == gsn_extra_song || sn == gsn_extend_song) && !learned(ch, gsn_bardic_song))
	{
		strcpy(buf, "You do not have the Bardic Song ability.\n\r");
		train = FALSE;
	}
	if (sn == gsn_gouge && base_attack(ch) < 2)
	{
		strcpy(buf, "You need a BAB of +2 to train Gouge.\n\r");
		train = FALSE;
	}
	if (sn == gsn_great_cleave)
	{
		if (!learned(ch, gsn_cleave))
		{
			strcpy(buf, "You must know Cleave to train Great Cleave.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 6)
		{
			strcpy(buf, "You need a BAB of +6 to train Great Cleave.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_greater_two_weapon)
	{
		if (ch->perm_dex < 19 || base_attack(ch) < 11)
		{
			strcpy(buf, "You need a DEX of 19 and a BAB of +11 to train Greater Two-Weapon.\n\r");
			train = FALSE;
		}
		if (!learned(ch,gsn_imp_two_weapon))
		{
			strcpy(buf, "You must know Improved Two-Weapon to train Greater Two-Weapon.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_power_critical)
	{
		if (!learned(ch, gsn_cleave))
		{
			strcpy(buf, "You must know Cleave to train Power Critical.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_power_attack))
		{
			strcpy(buf, "You must know Power Attack to train Power Critical.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_align_ki)
	{
		if (!learned(ch, gsn_ki_strike_adamant))
		{
			strcpy(buf, "You do not have the Ki Strike (Adamantine) ability.\n\r");
			train = FALSE;
		}
		if (!IS_GOOD(ch) && !IS_EVIL(ch))
		{
			strcpy(buf, "You lack a moral committment to align your strikes.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_imp_critical && base_attack(ch) < 8)
	{
		strcpy(buf, "You need a BAB of +8 to train Improved Critical.\n\r");
		train = FALSE;
	}
	if (sn == gsn_disarm && base_attack(ch) < 1)
	{
		strcpy(buf, "You need a BAB of +1 to train Disarm.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_grapple && (ch->perm_dex < 13 || !learned(ch, gsn_imp_unarmed_strike)))
	{
		strcpy(buf, "You need an DEX of 13 and Improved Unarmed Strike to train Improved Disarm.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_disarm && (ch->perm_int < 13 || !learned(ch, gsn_combat_expertise)))
	{
		strcpy(buf, "You need an INT of 13 and Combat Expertise to train Improved Disarm.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_bullrush && (ch->perm_str < 13 || !learned(ch, gsn_power_attack)))
	{
		strcpy(buf, "You need a STR of 13 and Power Attack to train Improved Bull Rush.\n\r");
		train = FALSE;
	}
	if (sn == gsn_improved_trip && (ch->perm_int < 13 || !learned(ch,gsn_combat_expertise)))
	{
		strcpy(buf, "You need an INT of 13 and Combat Expertise to train Improved Trip.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_turning && !learned(ch, gsn_turn_undead))
	{
		strcpy(buf, "You do not know how to turn undead.\n\r");
		train = FALSE;
	}
	if (sn == gsn_empower_turning && !learned(ch, gsn_imp_turning))
	{
		strcpy(buf, "You have to know Improved Turning to train Empower Turning.\n\r");
		train = FALSE;
	}
	if (sn == gsn_twin_sword)
	{
		if (ch->perm_dex < 15 || base_attack(ch) < 9)
		{
			strcpy(buf, "You need a DEX of 15 and a BAB of +9 to train Twin Sword Mastery.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_two_weapon))
		{
			strcpy(buf, "You must know Two-Weapon Fighting to train Twin Sword Mastery.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_imp_two_weapon)
	{
		if (ch->perm_dex < 13 || base_attack(ch) < 6)
		{
			strcpy(buf, "You need a DEX of 13 and a BAB of +6 to train Improved Two-Weapon.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_two_weapon))
		{
			strcpy(buf, "You must know Two-Weapon Fighting to train Greater Two-Weapon.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_leadership && ch->perm_cha < 13)
	{
		strcpy(buf, "You need a Charisma of 13 or higher to train that.\n\r");
		train = FALSE;
	}
	if (sn == gsn_manyshot)
	{
		if (ch->perm_dex < 17 || base_attack(ch) < 6)
		{
			strcpy(buf, "You need a DEX of 17 and a BAB of +6 to train Manyshot.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_point_blank))
		{
			strcpy(buf, "You need to know Point Blank Shot to train Manyshot.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_rapid_shot))
		{
			strcpy(buf, "You need to know Rapid Shot to train Manyshot.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_mobility && (ch->perm_dex < 13 || !learned(ch,gsn_dodge)))
	{
		strcpy(buf, "You need a DEX of 13 and to know Dodge to train Mobility.\n\r");
		train = FALSE;
	}
	if (sn == gsn_mounted_combat && !learned(ch, gsn_mount))
	{
		strcpy(buf, "You don't even know how to ride a mount!\n\r");
		train = FALSE;
	}
	if (sn == gsn_mounted_defense)
	{
		if(learned(ch, gsn_mount) < 3)
		{
			strcpy(buf, "You need 3 ranks in mount to learn Mounted Defense.\n\r");
			train = FALSE;
		}
		if(!learned(ch, gsn_mounted_combat))
		{
			strcpy(buf, "You must learn Mounted Combat to learn Mounted Defense.\n\r");
			train = FALSE;
		}
		if(ch->perm_dex < 13)
		{
			strcpy(buf, "You must have a DEX of 13 to learn Mounted Defense.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_power_attack && (ch->perm_str < 13 || base_attack(ch) < 1))
	{
		strcpy(buf, "You need a STR of 13 and a BAB of +1 to train Power Attack.\n\r");
		train = FALSE;
	}
	if (sn == gsn_called_shot && (ch->perm_dex < 13 || base_attack(ch) < 1))
	{
		strcpy(buf, "You need a DEX of 13 and a BAB of +1 to train Called Shot.\n\r");
		train = FALSE;
	}
	if (sn == gsn_precise_shot && !learned(ch, gsn_point_blank))
	{
		strcpy(buf, "You need to know Point Blank Shot to train Precise Shot.\n\r");
		train = FALSE;
	}
	if (sn == gsn_quick_draw && base_attack(ch) < 1)
	{
		strcpy(buf, "You need a BAB of +1 to train Quick Draw.\n\r");
		train = FALSE;
	}
	if (sn == gsn_combat_archery)
	{
		if (!learned(ch, gsn_point_blank))
		{
			strcpy(buf, "You need to know Point Blank Shot to train Combat Archery.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_dodge))
		{
			strcpy(buf, "You need to know Dodge to train Combat Archery.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_mobility))
		{
			strcpy(buf, "You need to know Mobility to train Combat Archery.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_greater_sneak_attack && multi_skill_level(ch, gsn_backstab) < 9)
	{
		strcpy(buf, "You must have 9 class levels of Sneak Attack to train that.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_shield_bash)
	{
		if (!learned(ch, gsn_shield_proficiency))
		{
			strcpy(buf, "You aren't even proficient in a shield.\n\r");
			train = FALSE;
		}
		if (ch->perm_str < 13)
		{
			strcpy(buf, "You need a STR of 13 to train Improved Shield Bash.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_shield_slam)
	{
		if (!learned(ch, gsn_imp_shield_bash))
		{
			strcpy(buf, "You need to know Improved Shield Bash to train Shield Slam.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 13)
		{
			strcpy(buf, "You need a BAB of 6 to train Shield Slam.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_shield_specialization)
	{
		if (!learned(ch, gsn_shield_proficiency))
		{
			strcpy(buf, "You aren't even proficient in a shield.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_shield_ward)
	{
		if (!learned(ch, gsn_shield_proficiency) || !learned(ch, gsn_shield_specialization))
		{
			strcpy(buf, "You must be specialized in shields to learn Shield Ward.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_active_shield_defense)
	{
		if (!learned(ch, gsn_shield_proficiency) || !learned(ch, gsn_shield_specialization))
		{
			strcpy(buf, "You must be specialized in shields to learn Active Shield Defense.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_sniper)
	{
		if (learned(ch, gsn_stealth) < 10)
		{
			strcpy(buf, "You need 10 ranks in Stealth to train Sniper.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_precise_shot))
		{
			strcpy(buf, "You need to know Precise Shot to train Sniper.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_point_blank))
		{
			strcpy(buf, "You need to know Point Blank Shot to train Sniper.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 4)
		{
			strcpy(buf, "You need a BAB of 4 to train Sniper.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_snatch_arrows && (!learned(ch, gsn_deflect_arrows) || ch->perm_dex < 15))
	{
		strcpy(buf, "You need a DEX of 15 and the Deflect Missiles feat to train Snatch Missiles.\n\r");
		train = FALSE;
	}
	if (sn == gsn_spirited_charge)
	{
		if (!learned(ch, gsn_mounted_combat))
		{
			strcpy(buf, "You need to know Mounted Combat to train Spirited Charge.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_ride_by_attack))
		{
			strcpy(buf, "You need to know Ride-by Attack to train Spirited Charge.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_tower_shield_prof && !learned(ch, gsn_shield_proficiency))
	{
		strcpy(buf, "You need shield proficiency first to train in Tower Shields.\n\r");
		train = FALSE;
	}
	if (sn == gsn_track && !learned(ch, gsn_survival))
	{
		strcpy(buf, "You need learn survival to train in Tracking.\n\r");
		train = FALSE;
	}
	if (sn == gsn_trample)
	{
		if (!learned(ch, gsn_mounted_combat))
		{
			strcpy(buf, "You need to know Mounted Combat to train Trample.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_imp_shield_bash))
		{
			strcpy(buf, "You need to know Improved Shield Bash to train Trample.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_two_weapon_defense && !learned(ch,gsn_two_weapon))
	{
		strcpy(buf, "You lack the Two-Weapon Fighting feat to train Two-Weapon Defense.\n\r");
		train = FALSE;
	}
	if (sn == gsn_two_weapon && ch->perm_dex < 15)
	{
		strcpy(buf, "You need a DEX of 15 to train Two-Weapon Fighting.\n\r");
		train = FALSE;
	}
	if (sn == gsn_weapon_finesse && ch->perm_dex < 13)
	{
		strcpy(buf, "You need a DEX of 13 to train Weapon Finesse.\n\r");
		train = FALSE;
	}
	if (sn == gsn_imp_weapon_finesse && !learned(ch, gsn_weapon_finesse))
	{
		strcpy(buf, "You must know Weapon Finesse to train Improved Weapon Finesse.\n\r");
		train = FALSE;
	}
	if (sn == gsn_spring_attack)
	{
		if (!learned(ch, gsn_dodge))
		{
			strcpy(buf, "You need to know Dodge to train Spring Attack.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_mobility))
		{
			strcpy(buf, "You need to know Mobility to train Spring Attack.\n\r");
			train = FALSE;
		}
		if (ch->perm_dex < 13)
		{
			strcpy(buf, "You need a DEX of 13 to train Spring Attack.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_battle_cry)
	{
		if (!learned(ch, gsn_barbarian_rage))
		{
			strcpy(buf, "You have to have rage ability to train Battle Cry.\n\r");
			train = FALSE;
		}
		if (learned(ch, gsn_intimidate) < 9)
		{
			strcpy(buf, "You need 9 or more ranks in Intimidate to train Battle Cry.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_whirl)
	{
		if (!learned(ch, gsn_combat_expertise))
		{
			strcpy(buf, "You need to know Combat Expertise to train Whirlwind Attack.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_dodge))
		{
			strcpy(buf, "You need to know Dodge to train Whirlwind Attack.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_mobility))
		{
			strcpy(buf, "You need to know Mobility to train Whirlwind Attack.\n\r");
			train = FALSE;
		}
		if (!learned(ch, gsn_spring_attack))
		{
			strcpy(buf, "You need to know Spring Attack to train Whirlwind Attack.\n\r");
			train = FALSE;
		}
		if (base_attack(ch) < 4)
		{
			strcpy(buf, "You a BAB of +4 to train Whirlwind Attack.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_shadow_casting)
	{
		if (ch->perm_wis < 15 && !ch->pcdata->domain[DOMAIN_DARKNESS])
		{
			strcpy(buf, "You need a WIS of 15 or the Darkness domain to use Shadow Casting.\n\r");
			train = FALSE;
		}
		if (opp_school(ch) == SCHOOL_ILLUSION || opp_school(ch) == SCHOOL_NECROMANCY)
		{
			strcpy(buf, "You oppose one of the schools of Shadow Casting.\n\r");
			train = FALSE;
		}
		if (IS_GOOD(ch))
		{
			strcpy(buf, "You cannot Shadow Cast due to your alignment.\n\r");
			train = FALSE;
		}
	}
	if (sn == gsn_maximize_spell && !learned(ch, gsn_empower_spell))
	{
		strcpy(buf, "You need to know Empower Spell to train Maximize Spell.\n\r");
		train = FALSE;
	}
	if (sn == gsn_persistent_spell && !learned(ch, gsn_extend_spell))
	{
		strcpy(buf, "You need to know Extend Spell to train Persistent Spell.\n\r");
		train = FALSE;
	}
	if (sn == gsn_repeat_spell && !learned(ch, gsn_quicken_spell))
	{
		strcpy(buf, "You need to know Quicken Spell to train Repeat Spell.\n\r");
		train = FALSE;
	}
	if (sn == gsn_disguise_spell)
	{
		if (!class_level(ch, CLASS_BARD))
		{
			strcpy(buf, "You must be a bard to train that.\n\r");
			train = FALSE;
		}
		if (learned(ch, gsn_perform) < 12)
		{
			strcpy(buf, "You need 12 ranks in Perform to train Disguise Spell.\n\r");
			train = FALSE;
		}
	}
	if (!train)
	{
		if (fDisplay)
			send_to_char(buf, ch);
		else
			buf[0] = '\0';
		pop_call();
		return FALSE;
	}
	pop_call();
	return TRUE;
}


/* Checks prerequisites for class training,
 * returns false if all prerequisites not met - Kregor 11/13/07
 */
bool can_train_class( CHAR_DATA *ch, int class, bool fDisplay )
{
	int cls, count;

	push_call("can_train_class(%p,%p)",ch,class);
	
	for (count = cls = 0 ; cls < MAX_CLASS ; cls++)
	{
		if (class_level(ch, cls))
			count++;
	}
	if (count >= 4 && !class_level(ch, class))
	{
		if (fDisplay)
			send_to_char("You cannot multiclass in more than 4 classes.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (!class_table[class].pc_class)
	{
		if (fDisplay)
			send_to_char("You cannot train in that class.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (class_table[class].max_level && class_level(ch, class) >= class_table[class].max_level)
	{
		if (fDisplay)
			send_to_char("You have already advanced as far as you can in that class.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (ch->class == CLASS_PALADIN && !class_level(ch, class) && class_level(ch, CLASS_PALADIN) < 6)
	{
		if (fDisplay)
			send_to_char("You must be 6th level as a paladin before you can multiclass.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (class_level(ch, CLASS_MONK) && (class_level(ch, class) + 1 >= class_level(ch, CLASS_MONK)))
	{
		if (fDisplay)
			send_to_char("Your monk level must be higher than your other classes.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (class >= MAX_CORE_CLASS && class < MAX_PRESTIGE_CLASS)
	{
		if (!class_level(ch, class))
		{
			if (fDisplay)
				send_to_char("Only a GM or quest can advance you into a prestige class.\n\r", ch);
			pop_call();
			return FALSE;
		}
		for (cls = MAX_CORE_CLASS ; cls < MAX_PRESTIGE_CLASS ; cls++)
		{
			if (class != cls && class_level(ch, cls))
			{
				if (fDisplay)
					act("You may only adopt ONE prestige class.", ch, NULL, NULL, TO_CHAR);
				pop_call();
				return FALSE;
			}
		}
	}
	if ((class == CLASS_WIZARD && class_level(ch, CLASS_SORCERER))
	|| (class == CLASS_SORCERER && class_level(ch, CLASS_WIZARD)))
	{
		if (fDisplay)
			send_to_char("The nature of Sorcery prevents you from also being a Wizard.\n\r", ch);
		pop_call();
		return FALSE;
	}
	if (class == CLASS_BARBARIAN)
	{
		if (IS_LAWFUL(ch))
		{
			if (fDisplay)
				send_to_char("You are far too civilized for the Barbarian's ways.\n\r", ch);
			pop_call();
			return FALSE;
		}
	}
	if (class == CLASS_BARD)
	{
		if (!IS_NEUTRAL(ch) && !IS_UNCONCERNED(ch))
		{
			if (fDisplay)
				send_to_char("You have too many commitments to be a Bard.\n\r", ch);
			pop_call();
			return FALSE;
		}
	}
	if (class == CLASS_CLERIC)
	{
		if (class_level(ch, CLASS_CLERIC) >= 3 && ch->god == GOD_NEUTRAL)
		{
			if (fDisplay)
				send_to_char("Show a little commitment before you advance further as a cleric.\n\r", ch);
			pop_call();
			return FALSE;
		}
	}
	if (class == CLASS_DRUID)
	{
		if (!IS_NEUTRAL(ch) && !IS_UNCONCERNED(ch))
		{
			if (fDisplay)
				send_to_char("You lack the commitment to neutrality to be a Druid.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (class_level(ch, CLASS_MONK))
		{
			if (fDisplay)
				send_to_char("You cannot multiclass Monk with Druid.\n\r", ch);
			pop_call();
			return FALSE;
		}
	}
	if (class == CLASS_MONK)
	{
		if (!IS_LAWFUL(ch))
		{
			if (fDisplay)
				send_to_char("Only the orderly may walk the Enlightened Path.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (!class_level(ch, CLASS_MONK))
		{
			if (fDisplay)
				send_to_char("You can only begin at 1st level as a monk.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (class_level(ch, CLASS_MONK) && ch->class != CLASS_MONK)
		{
			if (!ch->god || !god_table[ch->god].monk_multi || !IS_SHIFT(god_table[ch->god].monk_multi, ch->class))
			{
				if (fDisplay)
					send_to_char("You may not resume the Monk's path.\n\r", ch);
				pop_call();
				return FALSE;
			}
		}
		if (class_level(ch, CLASS_DRUID))
		{
			if (fDisplay)
				send_to_char("You cannot multiclass Monk with Druid.\n\r", ch);
			pop_call();
			return FALSE;
		}
	}
	if (class == CLASS_PALADIN)
	{
		if (!IS_GOOD(ch) || !IS_LAWFUL(ch))
		{
			if (fDisplay)
				send_to_char("Only the truly pure may advance as a Paladin.\n\r", ch);
			pop_call();
			return FALSE;
		}
		if (class_level(ch, CLASS_PALADIN) && ch->class != CLASS_PALADIN)
		{
			if (!ch->god || !god_table[ch->god].paladin_multi || !IS_SHIFT(god_table[ch->god].paladin_multi, ch->class))
			{
				if (fDisplay)
					send_to_char("You may not resume the Paladin's path.\n\r", ch);
				pop_call();
				return FALSE;
			}
		}
	}
	pop_call();
	return TRUE;
}

/* The all-inclusive mob TRAIN command
 * Added ability to train level on trainers 
 * vs. auto advance - Kregor 11/20/2006
 * Unified with practice functions to make one command - Kregor 11/25/2006
 * Consolidated multiclassing with level, and added languages - 11/09/2007
 */
void teach_char( CHAR_DATA *ch, CHAR_DATA *teacher, char *argument )
{
	char	buf[MAX_INPUT_LENGTH];
	char	buf2[MAX_INPUT_LENGTH];
	char	arg[MAX_INPUT_LENGTH];
	sh_int *pAbility;
	char *pOutput;
	bool fBonus = FALSE;
	int cost, points = 1, count, adept, sn, cnt;

	push_call("teach_char(%p,%p)",ch,argument);

	if (argument[0] == '\0' && IS_NPC(teacher))
	{
		ch_printf_color(ch, "Experience: %d, Skill points: %d, Stat Points: %d, Feat Points: %d\n\r", ch->pcdata->exp, ch->pcdata->practice, ch->pcdata->stat_pts, ch->pcdata->feat_pts);
	}
	
	cost = 0;

	argument = one_argument(argument, arg);

	/* added multiclassing into train - Kregor 11/13/07 */
	if (!strcasecmp(arg, "level"))
	{
		if (ch->pcdata->exp < exp_level(ch, ch->level))
		{
			act("You need more experience to embark further on a profession.", ch, NULL, NULL, TO_CHAR);
			act("$N needs more experience to embark further on a profession.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}	
		if (is_polymorph(ch))
		{
			act("You may not advance level in shapechanged form.", ch, NULL, NULL, TO_CHAR);
			act("$N may not advance in $S present form.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}
		if (argument[0] == '\0')
		{
			strcpy(buf,"");
			for (count = cnt = 0 ; cnt < MAX_CLASS ; cnt++)
			{
				if (!class_level(teacher, cnt))
					continue;
				if (can_train_class(ch, cnt, FALSE))
				{
					cat_sprintf(buf, " %-24s", class_table[cnt].who_name_long);
					count++;
					if (count %3 == 0)
						strcat(buf,"\n\r");
				}
			}
			if (count %3 != 0)
				strcat(buf,"\n\r");			
			act("You may advance in the following classes:\n\r$tSyntax: train level <class name>", ch, buf, NULL, TO_CHAR);
			act("$N may advance in the following classes:\n\r$tSyntax: teach <target> level <class name>", teacher, buf, ch, TO_CHAR);
			send_to_char(buf, ch);
			pop_call();
			return;
		}
		if ((cnt = lookup_class(argument)) == -1)
		{
			ch_printf_color(ch, "That is not a class.\n\r");
			ch_printf_color(teacher, "That is not a class.\n\r");
			pop_call();
			return;
		}
		if (!can_train_class(ch, cnt, TRUE))
		{
			act("$N cannot learn to follow that path right now.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}

		sprintf(buf2, "%s", class_table[cnt].who_name_long);

		if (!class_level(teacher, cnt))
		{
			act("You need to find $t $T to advance in that profession.", ch, a_an(buf2), buf2, TO_CHAR);
			act("How can you teach $N to be one when you are not?", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}
		/* Mob cannot train past his own level in a class */
		if (ch->mclass[cnt] >= teacher->mclass[cnt])
		{
			act("You are already beyond $N's own skills.", ch, NULL, NULL, TO_CHAR);
			act("$N's skills are already beyond your own.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}
		if (IS_NPC(teacher))
		{
			cost = haggle(ch, teacher, exp_level(ch, ch->level) * 10, TRUE);
			if (!gold_transaction(ch, 0 - cost))
			{
				act("$N is not willing to train for the coin you have to offer.", ch, NULL, teacher, TO_CHAR);
				pop_call();
				return;
			}
		}
		act( "You instruct $n in the ways of the $t", ch, buf2, teacher, TO_VICT);
		act( "$N instructs $n in the ways of the $t", ch, buf2, teacher, TO_NOTVICT);
		act( "$N instructs you in the ways of the $t", ch, buf2, teacher, TO_CHAR);
		if (class_level(ch, cnt) <= 0)
			ch_printf_color(ch, "You have multiclassed to: %s\n\r", buf2);
		ch->class     = cnt;
		ch->mclass[cnt] += 1;
		ch->level += 1;
		advance_level (ch, TRUE);

		pop_call();
		return;
	}
	else if (!strcasecmp(arg, "language"))
	{
		if ((cnt = get_flag(argument, lang_names)) == -1)
		{
			send_to_char("That is not a known language in the realms.\n\r", ch);
			send_to_char("That is not a known language in the realms.\n\r", teacher);
			pop_call();
			return;
		}
		if (IS_SET(ch->language, 1 << cnt))
		{
			act("You already know $t", ch, lang_names[cnt], NULL, TO_CHAR);
			act("$N already knows $t", teacher, lang_names[cnt], ch, TO_CHAR);
			pop_call();
			return;
		}
		if (IS_NPC(teacher))
		{
			if (!IS_SET(race_table[teacher->race].speaks, 1 << cnt))
			{
				act("$N cannot teach you that language.", ch, NULL, teacher, TO_CHAR);
				pop_call();
				return;
			}
		}
		else if (!IS_SHIFT(teacher->language, cnt))
		{
			act("You cannot teach what you do not know yourself.", teacher, NULL, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if (count_tongues(ch) >= max_tongues(ch))
		{
			send_to_char("You would need more intelligence to learn more languages.\n\r", ch);
			act("$N is unable to learn another tongue at this time.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}

		points = 1;

		if (points > ch->pcdata->practice)
		{
			send_to_char("You need a skill point to learn a new language.\n\r", ch);
			act("$N is unable to learn another tongue at this time.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}
		if (IS_NPC(teacher))
		{
			cost = haggle(ch, teacher, ch->level * ch->level * 100, TRUE);

			if (!gold_transaction(ch, 0 - cost))
			{
				act("$N is not willing to train for the coin you have to offer.", ch, NULL, teacher, TO_CHAR);
				pop_call();
				return;
			}
		}
		SET_SHIFT(ch->language, cnt);
		ch->pcdata->practice -= points;
		if (cost)
		{
			act("You pay $t to $N.", ch, format_coins(cost, FALSE), teacher, TO_CHAR);
		}
		ch->learned[gsn_speak_languages] = count_tongues(ch);
		act( "You learn $T as a new language.", ch, NULL, lang_names[cnt], TO_CHAR );
		act( "$n learns $T as a new language.", ch, NULL, lang_names[cnt], TO_ROOM );
		pop_call();
		return;
	}
	else if (!strcasecmp(arg, "str"))
	{
		pAbility = &ch->perm_str;
		pOutput  = "strength";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "int"))
	{
		pAbility = &ch->perm_int;
		pOutput  = "intelligence";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "wis"))
	{
		pAbility = &ch->perm_wis;
		pOutput  = "wisdom";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "dex"))
	{
		pAbility = &ch->perm_dex;
		pOutput  = "dexterity";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "con"))
	{
		pAbility = &ch->perm_con;
		pOutput  = "constitution";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "cha"))
	{
		pAbility = &ch->perm_cha;
		pOutput  = "charisma";
		cost = (5*(ch->perm_str+ch->perm_int+ch->perm_wis+ch->perm_dex+ch->perm_con+ch->perm_cha));
	}
	else if (!strcasecmp(arg, "song"))
	{
		if (*argument == '\0')
		{
			send_to_char("Teach what song?\n\r", teacher);
			pop_call();
			return;
		}
		if ((sn = skill_lookup(argument)) == -1)
		{
			act("$t is not a song.", ch, argument, NULL, TO_CHAR);
			act("$t is not a song.", teacher, argument, NULL, TO_CHAR);
			pop_call();
			return;
		}
		if (!learned(teacher, sn))
		{
			act("You do not know $t to teach it.", ch, skill_table[sn].name, NULL, TO_CHAR);
			act("$n does not know $t to teach it.", teacher, skill_table[sn].name, ch, TO_VICT);
			pop_call();
			return;
		}
		if (learned(ch, sn))
		{
			act("$N already knows $t.", teacher, skill_table[sn].name, ch, TO_CHAR);
			ch_printf(ch, "You already know %s.\n\r", skill_table[sn].name);
			pop_call();
			return;
		}
		// must have enough perform ranks to learn it
		if (skill_table[sn].native_level > learned(ch, gsn_perform))
		{
			act("$t is too powerful for $N to perform.", teacher, skill_table[sn].name, ch, TO_CHAR);
			ch_printf(ch, "%s is too powerful for you to perform.\n\r", skill_table[sn].name);
			pop_call();
			return;
		}
		ch->learned[sn] = 1;
		act( "You teach $N to perform $t.", teacher, skill_table[sn].name, ch, TO_CHAR);
		act( "You learn to perform $t from $N.", ch, skill_table[sn].name, teacher, TO_CHAR);
		ch->pcdata->skill_level[ch->level][sn] = 1;
		pop_call();
		return;
	}
	else if (!strcasecmp(arg, "feat"))
	{
		if (argument[0] == '\0')
		{
			feat_display(ch, "all", TRUE);
			if (!IS_NPC(teacher))
				feat_display(teacher, "all", TRUE); 
			pop_call();
			return;
		}
		
		argument = snarf_skill_name(argument, arg);

		if ((sn = skill_lookup(arg)) < 0)
		{
			send_to_char( "There is no such skill or feat.\n\r", ch );
			send_to_char( "There is no such skill or feat.\n\r", teacher );
			pop_call();
			return;
		}
		if (skill_table[sn].skilltype == FSKILL_WEAPON)
		{
			if (skill_table[sn].spell_desc == WEAPON_CLASS_SIMPLE)
			{
				send_to_char("Train 'weapon prof (simple)' instead.\n\r", ch);
				send_to_char("Teach 'weapon prof (simple)' instead.\n\r", teacher);
				pop_call();
				return;
			}
			else if (skill_table[sn].spell_desc == WEAPON_CLASS_MARTIAL)
			{
				send_to_char("Train 'weapon prof (martial)' instead.\n\r", ch);
				send_to_char("Teach 'weapon prof (martial)' instead.\n\r", teacher);
				pop_call();
				return;
			}
// 			else
// 			{
// 				send_to_char("Train 'weapon prof (exotic)' instead.\n\r", ch);
// 				send_to_char("Teach 'weapon prof (exotic)' instead.\n\r", teacher);
// 				pop_call();
// 				return;
// 			}
		}
		else if (skill_table[sn].skilltype != FSKILL_FEAT)
		{
			send_to_char("That is not a feat.\n\r", ch);
			send_to_char("That is not a feat.\n\r", teacher);
			pop_call();
			return;
		}
		int bitv = 0;
		int weap = -1;

		if (!can_train_feat(ch,sn,TRUE))
		{
			act("$N is unable to learn that feat at this time.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}

		if (sn == gsn_weapon_focus
		|| sn == gsn_weapon_specialization
		|| sn == gsn_imp_critical
		|| sn == gsn_power_critical)
		{
			if (argument[0] == '\0')
			{
				ch_printf_color(ch, "Train %s in what weapon?\n\r  Syntax: train '%s' <weapon name>\n\r", skill_table[sn].name, skill_table[sn].name);
				ch_printf_color(teacher, "Teach %s in what weapon?\n\r  Syntax: train '%s' <weapon name>\n\r", skill_table[sn].name, skill_table[sn].name);
				pop_call();
				return;
			}
			if ((weap = skill_lookup(argument)) == -1 || skill_table[weap].skilltype != FSKILL_WEAPON)
			{
				send_to_char("That is not a weapon skill.\n\r", ch);
				send_to_char("That is not a weapon skill.\n\r", teacher);
				pop_call();
				return;
			}
			if (!learned(ch, weap))
			{
				send_to_char("You have to be proficient in a weapon to train further in it.\n\r", ch);
				act("$N is not proficient enough in the weapon to learn that feat.", teacher, NULL, ch, TO_CHAR);					
				pop_call();
				return;
			}
			if (sn == gsn_weapon_focus)
			{
				if (!IS_SET(learned(ch, weap), WSKILL_FOCUS))
					bitv = WSKILL_FOCUS;
				else if (!IS_SET(learned(ch, weap), WSKILL_GREATER_FOCUS))
					bitv = WSKILL_GREATER_FOCUS;
				else if (!IS_SET(learned(ch, weap), WSKILL_EPIC_FOCUS))
					bitv = WSKILL_EPIC_FOCUS;
				else
				{
					send_to_char("You've already perfected your focus in that weapon.\n\r", ch);
					act("$N has already perfected $S focus in that weapon.", teacher, NULL, ch, TO_CHAR);
					pop_call();
					return;
				}
			}
			if (sn == gsn_weapon_specialization)
			{
				if (!IS_SET(learned(ch, weap), WSKILL_SPECIALIZED))
					bitv = WSKILL_SPECIALIZED;
				else if (!IS_SET(learned(ch, weap), WSKILL_GREATER_SPEC))
					bitv = WSKILL_GREATER_SPEC;
				else if (!IS_SET(learned(ch, weap), WSKILL_EPIC_SPEC))
					bitv = WSKILL_EPIC_SPEC;
				else
				{
					send_to_char("You've already fully specialized in that weapon.\n\r", ch);
					act("$N has already fully specialized in that weapon.", teacher, NULL, ch, TO_CHAR);
					pop_call();
					return;
				}
			}
			if (sn == gsn_imp_critical)
				bitv = WSKILL_IMP_CRITICAL;
			if (sn == gsn_power_critical)
				bitv = WSKILL_PWR_CRITICAL;
			if (IS_SET(ch->learned[weap], bitv))
			{
				send_to_char("You already know that feat with that weapon.\n\r", ch);
				act("$N has already trained that feat with that weapon.", teacher, NULL, ch, TO_CHAR);
				pop_call();
				return;
			}
			if (bitv == WSKILL_PWR_CRITICAL && !IS_SET(ch->learned[weap], WSKILL_IMP_CRITICAL))
			{
				send_to_char("You have to train Improved Critical with that weapon first.\n\r", ch);
				act("$N is not yet trained in Improved Critical with that weapon.", teacher, NULL, ch, TO_CHAR);
				pop_call();
				return;
			}
		}

		/* Feats cost 100 gold after initial PC creation, subject to haggle */
		if (IS_NPC(teacher))
		{
			cost = haggle(ch, teacher, 10000, TRUE);	
			if (!gold_transaction(ch, 0 - cost))
			{
				act("You do not have enough coin on hand to train $t.", ch, skill_table[sn].name, NULL, TO_CHAR);
				pop_call();
				return;
			}
		}

		// No feat points to train fighting style or bloodline or school of magic.
		if (!IS_SET(skill_table[sn].spell_school, FEAT_RANGER_STYLE|FEAT_MONK_STYLE|FEAT_SORCERER_BLOODLINE|FEAT_WIZARD_SCHOOL))
		{
			/* deduct from bonus feats first, then character feats */
			for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
			{
				if (is_bonus_feat(ch, cnt, sn) && ch->pcdata->bonus_feat[cnt] > 0)
				{
					ch->pcdata->bonus_feat[cnt]--;
					fBonus = TRUE;
					break;
				}
			}
			if (!fBonus)
			{
				if ( ch->pcdata->feat_pts <= 0 )
				{
					send_to_char( "You have no feat points.\n\r", ch );
					act("$N is unable to learn that feat at this time.", teacher, NULL, ch, TO_CHAR);
					pop_call();
					return;
				}
				else
				{
					ch->pcdata->feat_pts -= 1;
				}
			}
		}

		if (learned(ch,sn) == 1)
			ch->learned[sn] = 2;
		else
			ch->learned[sn] += 1;
		if (cost)
		{
			act("You pay $t to $N.", ch, format_coins(cost, FALSE), teacher, TO_CHAR);
		}
		act( "You learn the finer points of $T.", ch, NULL, skill_table[sn].name, TO_CHAR );
		act( "$n learns the finer points of $T.", ch, NULL, skill_table[sn].name, TO_ROOM );
		
		if (weap != -1)
			SET_BIT(ch->learned[weap], bitv);
			
		ch->pcdata->skill_level[ch->level][sn] += 1;
		
		pop_call();
		return;
	}
	else if (!strcasecmp(arg, "skill"))
	{
		if (argument[0] == '\0')
		{
			if (!IS_NPC(teacher))
				send_to_char("Teach what skill?\n\r", ch);
			else
				do_skills(ch, "");
			pop_call();
			return;
		}
		argument = snarf_skill_name(argument, arg);

		if ((sn = skill_lookup(arg)) < 0)
		{
			send_to_char("There is no such skill.\n\r", ch);
			send_to_char("There is no such skill.\n\r", teacher);
			pop_call();
			return;
		}
		if (skill_table[sn].skilltype != FSKILL_SKILL
			&&skill_table[sn].skilltype != FSKILL_CRAFT
			&&skill_table[sn].skilltype != FSKILL_KNOWLEDGE)
		{
			send_to_char("That is not a skill.\n\r", ch);
			send_to_char("That is not a skill.\n\r", teacher);
			pop_call();
			return;
		}

		if (skill_table[sn].skilltype == FSKILL_CRAFT || skill_table[sn].skilltype == FSKILL_KNOWLEDGE)
		{			
			if (!learned(teacher, sn))
			{
				if (!IS_NPC(teacher))
					send_to_char("You cannot teach what you do not know.\n\r", teacher);
				else
					send_to_char("You need to learn that from a different sort of teacher.\n\r", ch);
				pop_call();
				return;
			}
		}

		/* Can only train a mob's class skills from a mob, unless hard coded */
		if (!learned(teacher, sn))
		{
			if (IS_NPC(teacher) && multi(teacher, sn) == -1)
			{
				act("$N is unable to teach you that skill.", ch, NULL, teacher, TO_CHAR);
				pop_call();
				return;
			}
			else
			{
				send_to_char("You cannot teach what you do not know.\n\r", teacher);
				pop_call();
				return;
			}
		}

		/* adept = level+3 for class skills, level+3/2 for cross skills */
		adept = ch->level + 3;

		if (multi(ch, sn) == -1)
		{
			if (sn == gsn_craft_alchemy
			|| sn == gsn_use_magic
			|| sn == gsn_decipher_script)
			{
				send_to_char("That is not a class skill for you.\n\r", ch);
				act("$N is unable to learn that skill at this time.", teacher, NULL, ch, TO_CHAR);
				pop_call();
				return;
			}
			adept /= 2;
		}

		/* PC cannot train higher than his adept level - Kregor */
		if (learned(ch, sn) >= adept) 
		{
			ch_printf_color(ch, "You cannot raise %s past its rank at your present level.\n\r", skill_table[sn].name);
			act("$N is unable to improve that skill at this time.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}
		/* mobs cannot train higher than their own adept level - Mud20 variant */
		else if (learned(ch, sn) >= learned(teacher, sn)) 
		{
			if (IS_NPC(teacher))
				act("$N is unable to train you any higher in $t.", ch, skill_table[sn].name, teacher, TO_CHAR);
			else
				act("$N's skill has already exceeded your own.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}

		/* 1 skill point for active class skills, 2 for all others */
		if (ch->mclass[ch->class] >= skill_table[sn].skill_level[ch->class]
		|| (ch->class == CLASS_CLERIC && domain_skill(ch, sn))
		|| learned(ch, gsn_skill_prodigy))
			points = 1;
		else
			points = 2;

		if ( ch->pcdata->practice < points )
		{
			send_to_char( "You don't have enough skill points.\n\r", ch );
			act("$N is unable to learn that skill at this time.", teacher, NULL, ch, TO_CHAR);
			pop_call();
			return;
		}

		/* training costs 50 gold per rank after initial creation, with haggle */
		if (IS_NPC(teacher))
		{
			cost = haggle(ch, teacher, 5000, TRUE);
			if (!gold_transaction(ch, 0 - cost))
			{
				act("You do not have enough coin on hand to train $t.", ch, skill_table[sn].name, NULL, TO_CHAR);
				pop_call();
				return;
			}
		}

		if (cost)
		{
			act("You pay $t to $N.", ch, format_coins(cost, FALSE), teacher, TO_CHAR);
		}
		ch->pcdata->practice -= points;
		ch->learned[sn] += 1;
		ch->pcdata->skill_level[ch->level][sn] += 1;

		act( "You practice the finer points of $T.", ch, NULL, skill_table[sn].name, TO_CHAR );
		ch_printf_color(ch, "You now have %d skill points left.\n\r", ch->pcdata->practice);
		act( "$n practices the finer points of $T.", ch, NULL, skill_table[sn].name, TO_ROOM );

		if (ch->learned[sn] >= adept )
		{
			act( "You have raised the rank of $T as far as you can for your level.", ch, NULL, skill_table[sn].name, TO_CHAR );
		}
		pop_call();
		return;
	}
	else
	{
		if (!IS_NPC(teacher))
		{
			strcpy(buf, "You can teach:");
	
			cat_sprintf(buf, "%s", ch->pcdata->exp >= exp_level(ch, ch->level) ? " level" : "");
	
			cat_sprintf(buf, "\n\r%s", ch->pcdata->practice > 0 ? "skills:    teach <target> skill <'skill name'>\n\r" : "");
			cat_sprintf(buf, "%s", (ch->pcdata->practice > 0 && count_tongues(ch) < max_tongues(ch)) ? "languages: teach <target> language <language name>\n\r" : "");
			cat_sprintf(buf, "%s", ch->pcdata->feat_pts > 0 ? "feats:     teach <target> feat <'feat name'>\n\r" : "");
			cat_sprintf(buf, "%s", count_songs(ch) < max_songs(ch) ? "songs:     teach <target> song <'song name'>\n\r" : "");
			send_to_char_color(buf, teacher);
			pop_call();
			return;
		}
		else
		{
			strcpy(buf, "You can train:");
	
			cat_sprintf(buf, "%s", ch->pcdata->stat_pts ? " str dex con int wis cha" : "");
			cat_sprintf(buf, "%s", ch->pcdata->exp >= exp_level(ch, ch->level) ? " level" : "");
	
			cat_sprintf(buf, "\n\r%s", ch->pcdata->practice > 0 ? "skills:    train skill <'skill name'>\n\r" : "");
			cat_sprintf(buf, "%s", (ch->pcdata->practice > 0 && count_tongues(ch) < max_tongues(ch)) ? "languages: train language <language name>\n\r" : "");
			cat_sprintf(buf, "%s", ch->pcdata->feat_pts > 0 ? "feats:     train feat <'feat name'>\n\r" : "");
			send_to_char_color(buf, ch);
			pop_call();
			return;
		}
	}
	
	if (!IS_NPC(teacher))
	{
		send_to_char("You cannot teach that.\n\r", teacher);
		pop_call();
		return;
	}

	if (points > ch->pcdata->stat_pts)
	{
		ch_printf_color(ch, "You need more stat points to train your %s.\n\r", pOutput);
		pop_call();
		return;
	}

	cost = haggle(ch, teacher, cost, TRUE);

	if (!gold_transaction(ch, 0 - cost))
	{
		command(teacher, do_say, "I ask %s to train your %s.", format_coins(cost, FALSE), pOutput);
		pop_call();
		return;
	}

	ch->pcdata->stat_pts 	-= points;
	*pAbility 						+= 1;
	act( "You pay $t to train your $T.", ch, format_coins(cost, FALSE), pOutput, TO_CHAR );
	act( "$n trains $s $T.", ch, NULL, pOutput, TO_ROOM );
	pop_call();
	return;
}

void newbie_train( CHAR_DATA *ch, char *argument )
{
	char	arg[MAX_INPUT_LENGTH];
	bool fBonus = FALSE;
	int points, adept, sn, cnt;

	push_call("newbie_train(%p,%p)",ch,argument);

	if (ch->in_room->vnum == ROOM_VNUM_SKILLS)
	{
		if (!ch->pcdata->practice)
		{
			send_to_char_color("You have no more skill points to spend. Type DONE.\n\r", ch);
			pop_call();
			return;
		}
	}
	else if (ch->in_room->vnum == ROOM_VNUM_FEATS)
	{
		if (!ch->pcdata->feat_pts && !ch->pcdata->bonus_feat[ch->class])
		{
			send_to_char_color("You have no more feat points to spend. Type DONE.\n\r", ch);
			pop_call();
			return;
		}
	}

	if (*argument == '\0')
	{
		if (ch->in_room->vnum == ROOM_VNUM_SKILLS)
		{
			send_to_char_color("Train what skill?\n\r", ch);
			ch_printf_color(ch, "You have %d skill points to spend.\n\r", ch->pcdata->practice);
		}
		else if (ch->in_room->vnum == ROOM_VNUM_FEATS)
		{
			send_to_char_color("Train what feat?\n\r", ch);
			ch_printf_color(ch, "You have %d feat points to spend.\n\r", ch->pcdata->feat_pts);
			ch_printf_color(ch, "You have %d bonus feats.\n\r", ch->pcdata->bonus_feat[ch->class]);
		}
		else
		{
			send_to_char_color("You cannot do that here.\n\r", ch);
		}
		pop_call();
		return;
	}
	
	argument = one_argument(argument, arg);

	if (!strcasecmp(arg, "feat"))
	{
		if (ch->in_room->vnum != ROOM_VNUM_FEATS)
		{
			send_to_char_color("You cannot do that here.\n\r", ch);
			pop_call();
			return;
		}
			
		if (argument[0] == '\0')
		{
			feat_display(ch, "all", TRUE);
			pop_call();
			return;
		}
		
		argument = snarf_skill_name(argument, arg);

		if ((sn = skill_lookup(arg)) < 0)
		{
			send_to_char( "There is no such skill or feat.\n\r", ch );
			pop_call();
			return;
		}
		if (skill_table[sn].skilltype == FSKILL_WEAPON)
		{
			if (skill_table[sn].spell_desc == WEAPON_CLASS_SIMPLE)
			{
				send_to_char("Train 'weapon prof (simple)' instead.\n\r", ch);
				pop_call();
				return;
			}
			else if (skill_table[sn].spell_desc == WEAPON_CLASS_MARTIAL)
			{
				send_to_char("Train 'weapon prof (martial)' instead.\n\r", ch);
				pop_call();
				return;
			}
		}
		else if (skill_table[sn].skilltype != FSKILL_FEAT)
		{
			send_to_char("That is not a feat.\n\r", ch);
			pop_call();
			return;
		}
		int bitv = 0;
		int weap = -1;

		if (!can_train_feat(ch,sn,TRUE))
		{
			pop_call();
			return;
		}

		if (sn == gsn_weapon_focus
		|| sn == gsn_weapon_specialization
		|| sn == gsn_imp_critical
		|| sn == gsn_power_critical)
		{
			if (argument[0] == '\0')
			{
				ch_printf_color(ch, "Train %s in what weapon?\n\r  Syntax: train '%s' <weapon name>\n\r", skill_table[sn].name, skill_table[sn].name);
				pop_call();
				return;
			}
			if ((weap = skill_lookup(argument)) == -1 || skill_table[weap].skilltype != FSKILL_WEAPON)
			{
				send_to_char("That is not a weapon skill.\n\r", ch);
				pop_call();
				return;
			}
			if (!learned(ch, weap))
			{
				send_to_char("You have to be proficient in a weapon to train further in it.\n\r", ch);
				pop_call();
				return;
			}
			if (sn == gsn_weapon_focus)
			{
				if (!IS_SET(learned(ch, weap), WSKILL_FOCUS))
					bitv = WSKILL_FOCUS;
				else if (!IS_SET(learned(ch, weap), WSKILL_GREATER_FOCUS))
					bitv = WSKILL_GREATER_FOCUS;
				else if (!IS_SET(learned(ch, weap), WSKILL_EPIC_FOCUS))
					bitv = WSKILL_EPIC_FOCUS;
				else
				{
					send_to_char("You've already perfected your focus in that weapon.\n\r", ch);
					pop_call();
					return;
				}
			}
			if (sn == gsn_weapon_specialization)
			{
				if (!IS_SET(learned(ch, weap), WSKILL_SPECIALIZED))
					bitv = WSKILL_SPECIALIZED;
				else if (!IS_SET(learned(ch, weap), WSKILL_GREATER_SPEC))
					bitv = WSKILL_GREATER_SPEC;
				else if (!IS_SET(learned(ch, weap), WSKILL_EPIC_SPEC))
					bitv = WSKILL_EPIC_SPEC;
				else
				{
					send_to_char("You've already fully specialized in that weapon.\n\r", ch);
					pop_call();
					return;
				}
			}
			if (sn == gsn_imp_critical)
				bitv = WSKILL_IMP_CRITICAL;
			if (sn == gsn_power_critical)
				bitv = WSKILL_PWR_CRITICAL;
			if (IS_SET(ch->learned[weap], bitv))
			{
				send_to_char("You already know that feat with that weapon.\n\r", ch);
				pop_call();
				return;
			}
			if (bitv == WSKILL_PWR_CRITICAL && !IS_SET(ch->learned[weap], WSKILL_IMP_CRITICAL))
			{
				send_to_char("You have to train Improved Critical with that weapon first.\n\r", ch);
				pop_call();
				return;
			}
		}

		// No feat points to train fighting style or bloodline or school of magic.
		if (!IS_SET(skill_table[sn].spell_school, FEAT_RANGER_STYLE|FEAT_MONK_STYLE|FEAT_SORCERER_BLOODLINE|FEAT_WIZARD_SCHOOL))
		{
			/* deduct from bonus feats first, then character feats */
			for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
			{
				if (is_bonus_feat(ch, cnt, sn) && ch->pcdata->bonus_feat[cnt] > 0)
				{
					ch->pcdata->bonus_feat[cnt]--;
					fBonus = TRUE;
					break;
				}
			}
			if (!fBonus)
			{
				if ( ch->pcdata->feat_pts <= 0 )
				{
					send_to_char( "You have no feat points.\n\r", ch );
					pop_call();
					return;
				}
				else
				{
					ch->pcdata->feat_pts -= 1;
				}
			}
		}

		if (learned(ch,sn) == 1)
			ch->learned[sn] = 2;
		else
			ch->learned[sn] += 1;
		act( "You learn $T as a feat.", ch, NULL, skill_table[sn].name, TO_CHAR );

		if (!ch->pcdata->feat_pts && !ch->pcdata->bonus_feat[ch->class])
			send_to_char_color( "Type DONE to continue.\n\r", ch);
		else
		{
			if (ch->pcdata->feat_pts)
				ch_printf_color(ch, "You now have %d feat points left.\n\r", ch->pcdata->feat_pts);
			if (ch->pcdata->bonus_feat[ch->class])
				ch_printf_color(ch, "You now have %d bonus feats left.\n\r", ch->pcdata->bonus_feat[ch->class]);
		}
		if (weap != -1)
			SET_BIT(ch->learned[weap], bitv);
			
		ch->pcdata->skill_level[ch->level][sn] += 1;
	}
	else if (!strcasecmp(arg, "skill"))
	{
		if (ch->in_room->vnum != ROOM_VNUM_SKILLS)
		{
			send_to_char_color("You cannot do that here.\n\r", ch);
			pop_call();
			return;
		}
			
		if (argument[0] == '\0')
		{
			do_skills(ch, "");
			pop_call();
			return;
		}
		argument = snarf_skill_name(argument, arg);

		if ((sn = skill_lookup(arg)) < 0)
		{
			send_to_char("There is no such skill.\n\r", ch);
			pop_call();
			return;
		}
		if (skill_table[sn].skilltype != FSKILL_SKILL
			&&skill_table[sn].skilltype != FSKILL_CRAFT
			&&skill_table[sn].skilltype != FSKILL_KNOWLEDGE)
		{
			send_to_char("That is not a skill.\n\r", ch);
			pop_call();
			return;
		}

		/* adept = level+3 for class skills, level+3/2 for cross skills */
		adept = ch->level + 3;

		if (multi(ch, sn) == -1)
		{
			if (sn == gsn_craft_alchemy
			|| sn == gsn_use_magic
			|| sn == gsn_decipher_script)
			{
				send_to_char("Only certain classes can train that skill.\n\r", ch);
				pop_call();
				return;
			}
			adept /= 2;
		}

		/* PC cannot train higher than his adept level - Kregor */
		if (learned(ch, sn) >= adept) 
		{
			ch_printf_color(ch, "You cannot raise %s past its rank at your present level.\n\r", skill_table[sn].name);
			pop_call();
			return;
		}
		/* 1 skill point for active class skills, 2 for all others */
		if (ch->mclass[ch->class] >= skill_table[sn].skill_level[ch->class]
		|| (ch->class == CLASS_CLERIC && domain_skill(ch, sn))
		|| learned(ch, gsn_skill_prodigy))
			points = 1;
		else
			points = 2;

		if ( ch->pcdata->practice < points )
		{
			send_to_char( "You don't have enough skill points.\n\r", ch );
			pop_call();
			return;
		}

		ch->pcdata->practice -= points;
		ch->learned[sn] += 1;
		ch->pcdata->skill_level[ch->level][sn] += 1;

		ch_printf_color(ch, "You gain one rank in %s. You have %d ranks.\n\r", skill_table[sn].name, ch->learned[sn]);
		if (ch->pcdata->practice)
			ch_printf_color(ch, "You now have %d skill points left.\n\r", ch->pcdata->practice);
		else
			send_to_char_color( "Type DONE to continue.\n\r", ch);
	}
	else
	{
		act("You cannot do that here.", ch, NULL, NULL, TO_CHAR);
	}
	pop_call();
	return;
}

void do_train( CHAR_DATA *ch, char *argument )
{
	CHAR_DATA *mob;

	push_call("do_train(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (ch->in_room->vnum >= ROOM_VNUM_SKILLS && ch->in_room->vnum <= ROOM_VNUM_DOMAINS)
	{
		newbie_train(ch, argument);
		pop_call();
		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)
	{
		send_to_char( "You can't do that here.\n\r", ch );
		pop_call();
		return;
	}
	if (in_combat(mob) || who_fighting(mob) != NULL)
	{
		send_to_char( "The trainer is a little busy right now.\n\r", ch );
		pop_call();
		return;
	}
	if ( !IS_AWAKE(ch) )
	{
		send_to_char( "In your dreams, or what?\n\r", ch );
		pop_call();
		return;
	}
	if ( !IS_AWAKE(mob) )
	{
		send_to_char( "In their dreams, or what?\n\r", ch );
		pop_call();
		return;
	}
	if (is_grudging(mob, ch))
	{
		pop_call();
		return;
	}
	if (!can_see(mob, ch))
	{
		act("$N cannot train someone $E cannot see!", ch, NULL, mob, TO_CHAR);
		pop_call();
		return;
	}
	teach_char(ch, mob, argument);
	pop_call();
	return;
}

void do_teach( CHAR_DATA *ch, char *argument )
{
	char	arg[MAX_INPUT_LENGTH];
	CHAR_DATA *victim;

	push_call("do_train(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	argument = one_argument(argument, arg);
	
// 	if (arg[0] == '\0')
// 	{
// 		send_to_char( "Who do you want to teach?\n\r", ch );
// 		pop_call();
// 		return;
// 	}
	
	if ((victim = get_char_room(ch, arg)) == NULL)
	{
		send_to_char( "Who do you want to teach?\n\r", ch );
		pop_call();
		return;
	}
	
	if (IS_NPC(victim))
	{
		send_to_char( "You can't teach NPCs\n\r", ch );
		pop_call();
		return;
	}
	if (!IS_AWAKE(ch))
	{
		send_to_char( "In your dreams, or what?\n\r", ch );
		pop_call();
		return;
	}
	if (!IS_AWAKE(victim))
	{
		send_to_char( "In their dreams, or what?\n\r", ch );
		pop_call();
		return;
	}
	/* prevents spam teaching to unwilling students (hey, could happen) */
	if (!is_same_group(ch, victim))
	{
		act( "$N must be grouped with you to teach $M.", ch, NULL, victim, TO_CHAR );
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		send_to_char( "Teach them what?\n\r", ch );
		pop_call();
		return;
	}
	teach_char(victim, ch, argument);
	pop_call();
	return;
}
	

void do_password( CHAR_DATA *ch, char *argument )
{
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	char arg3[MAX_INPUT_LENGTH];

	push_call("do_password(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	argument = one_argument_nolower(argument, arg1);
	argument = one_argument_nolower(argument, arg2);
	argument = one_argument_nolower(argument, arg3);

	if (arg1[0] == '\0' || arg2[0] == '\0' || arg3[0] == '\0')
	{
		send_to_char( "Syntax: password <old> <new> <new>.\n\r", ch );
		pop_call();
		return;
	}

	if (strcmp(arg2, arg3))
	{
		send_to_char( "Your new password does not verify.\n\rPlease try again.\n\r", ch);
		pop_call();
		return;
	}

	if (encrypt64(arg1) != ch->pcdata->password)
	{
		send_to_char( "Wrong password.  Try again.\n\r", ch );
		pop_call();
		return;
	}

	if (strlen(arg2) < 5)
	{
		send_to_char("New password must be at least five characters long.\n\r", ch );
		pop_call();
		return;
	}

	/*
		No tilde allowed because of player file format.
	*/

	if (!is_valid_password(arg2))
	{
		send_to_char("New password not acceptable, try again.\n\r", ch);
		send_to_char("The password must only contain letters (case sensitive), or numbers.\n\r", ch);
		send_to_char("You are required to include at least one number in the password.\n\r", ch);
		pop_call();
		return;
	}

	ch->pcdata->password = encrypt64(arg2);

	save_char_obj(ch, NORMAL_SAVE);
	send_to_char( "Password changed.\n\r", ch );
	pop_call();
	return;
}

void do_socials( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	int iSocial, col;

	push_call("do_socials(%p,%p)",ch,argument);

	for (col = 0, buf[0] = '\0', iSocial = 0 ; social_table[iSocial].name[0] != '\0' ; iSocial++)
	{
		if (col % 6 == 0)
		{
			strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_DIM));
		}
		cat_snprintf(buf, 13, "%-13s", social_table[iSocial].name);
		if (++col % 6 == 0)
		{
			strcat(buf, "\n\r");
		}
	}
	if (col % 6 != 0)
	{
		strcat(buf, "\n\r");
	}
	send_to_char(buf, ch);
	pop_call();
	return;
}

void do_skills( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char buf3[MAX_STRING_LENGTH];
	char buf4[MAX_STRING_LENGTH];
	int sn, col, cnt, cls;

	push_call("do_skills(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_skills(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	for (cnt = 0, cls = ch->class, buf4[0] = '\0' ; cnt < MAX_CLASS ; cnt++)
	{
		if (argument[0] != '\0' && !str_prefix(argument, class_table[cnt].who_name_long))
		{
			cls = cnt;
			strcpy(buf3, class_table[cnt].who_name_long);
			break;
		}
		sprintf(buf3, "%s's", capitalize(ch->name));
	}

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s Skills {200}]{078}", buf3);

	for (cnt = 0 ; cnt < 39-(strlen(buf3)+9)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	cat_sprintf(buf4, "%s\n\r", buf1);

	if (argument[0] != '\0' && !str_prefix(argument, class_table[cls].who_name_long))
	{
		for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype == FSKILL_SKILL)
				{
					if (skill_table[sn].skill_level[cls] == 1 || (cls == CLASS_CLERIC && domain_skill(ch, sn)))
					{
						sprintf(buf2, " {078}%-19s {200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
					}
					else
					{
						continue;
					}
					if (++col % 3 != 1)
					{
						strcat(buf1, "  ");
					}
					strcat(buf1, buf2);

					if (col % 3 == 0)
					{
						cat_sprintf(buf4, "%s\n\r", buf1);
						buf1[0] = '\0';
					}
				}
			}
		}
	}
	else
	{
		for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype == FSKILL_SKILL)
				{
					if (class_skill(ch, sn))
					{
						sprintf(buf2, " {200}c {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
					}
					else
					{
						sprintf(buf2, " {200}x {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
					}
					if (++col % 3 != 1)
					{
						strcat(buf1, "  ");
					}
					strcat(buf1, buf2);
	
					if (col % 3 == 0)
					{
						cat_sprintf(buf4, "%s\n\r", buf1);
						buf1[0] = '\0';
					}
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	strcat(buf4, "{200}Knowledge\n\r");
	if (argument[0] != '\0' && !str_prefix(argument, class_table[cls].who_name_long))
	{
		for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype == FSKILL_KNOWLEDGE)
				{
					if (skill_table[sn].skill_level[cls] == 1)
					{
						sprintf(buf2, " {078}%-19s {200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
					}
					else
					{
						continue;
					}
					if (++col % 3 != 1)
					{
						strcat(buf1, "  ");
					}
					strcat(buf1, buf2);

					if (col % 3 == 0)
					{
						cat_sprintf(buf4, "%s\n\r", buf1);
						buf1[0] = '\0';
					}
				}
			}
		}
	}
	else
	for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (is_string(skill_table[sn].name))
		{
			if (skill_table[sn].skilltype == FSKILL_KNOWLEDGE)
			{
				if (skill_table[sn].skill_level[ch->class] == 1)
				{
					sprintf(buf2, " {200}c {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
				}
				else
				{
					sprintf(buf2, " {200}x {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
				}
				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}	
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	strcat(buf4, "{200}Trades (Crafts and Professions)\n\r");
	if (argument[0] != '\0' && !str_prefix(argument, class_table[cls].who_name_long))
	{
		for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype == FSKILL_CRAFT)
				{
					if (skill_table[sn].skill_level[cls] == 1)
					{
						sprintf(buf2, " {078}%-19s {200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
					}
					else
					{
						continue;
					}
					if (++col % 3 != 1)
					{
						strcat(buf1, "  ");
					}
					strcat(buf1, buf2);

					if (col % 3 == 0)
					{
						cat_sprintf(buf4, "%s\n\r", buf1);
						buf1[0] = '\0';
					}
				}
			}
		}
	}
	else
	for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (is_string(skill_table[sn].name))
		{
			if (skill_table[sn].skilltype == FSKILL_CRAFT)
			{
				if (skill_table[sn].skill_level[ch->class] == 1)
				{
					sprintf(buf2, " {200}c {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
				}
				else
				{
					sprintf(buf2, " {200}x {078}%-18s{200}[{178}%2d{200}]", capitalize_title(skill_table[sn].name), ch->learned[sn]);
				}
			}
			else
			{
				continue;
			}
			if (++col % 3 != 1)
			{
				strcat(buf1, "  ");
			}
			strcat(buf1, buf2);

			if (col % 3 == 0)
			{
				cat_sprintf(buf4, "%s\n\r", buf1);
				buf1[0] = '\0';
			}
		}
	}	
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	if (argument[0] == '\0' || str_prefix(argument, class_table[cls].who_name_long))
		cat_sprintf(buf4, "{200}c {078}Class skill.  {200}x {078}Cross-class skill.\n\r", ch->pcdata->practice);
	cat_sprintf(buf4, "{078}You have %d skill points to spend.\n\r", ch->pcdata->practice);
	send_to_char_color(buf4, ch);
	pop_call();
	return;
}

void do_spells( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char names[MAX_STRING_LENGTH];
	char buf4[MAX_STRING_LENGTH];
	char skp1[80], skp2[80], clr[10];
	char tstc;
	int sn, col, cnt, lev, cls, range, count;

	push_call("do_spells(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_spells(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	if (*argument == '\0')
	{
		cls = ch->class;
		sprintf(names, "%s's", short_to_name(ch->name, 1));
	}
	else if ((cls = lookup_class(argument)) == -1)
	{
		act("That is not a class.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}

	if (class_table[cls].mana_table == MANA_NONE)
	{
		act("The $t class has no mana.", ch, class_table[cls].who_name_long, NULL, TO_CHAR);
		pop_call();
		return;
	}
	else if (!class_level(ch, cls) || (range = max_spell_circle(ch, cls)) == -1)
	{
		act("You cannot cast any spells in that class.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}
	else if (*argument)
	{
		strcpy(names, class_table[cls].who_name_long);
	}

	strcpy(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s Spells {200}]{078}", names);

	for (cnt = 0 ; cnt < 39-(strlen(names)+9)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	sprintf(buf4, "%s\n\r", buf1);
		
	if (class_table[cls].mana_table == MANA_WARRIOR || class_table[cls].mana_table == MANA_PRESTIGE)
		lev = 1;
	else
		lev = 0;

	for ( buf1[0] = '\0' ; lev <= range ; lev++)
	{
		if (cls == CLASS_SORCERER || cls == CLASS_BARD)
			cat_sprintf(buf4, "{200}Circle %d (Spells learned: %d/%d)\n\r", lev, slots_learned(ch,cls,lev), can_learn_spells(ch,cls,lev));
		else
			cat_sprintf(buf4, "{200}Circle %d (Prepared: %d/%d)\n\r", lev, slots_full(ch,cls,lev), get_slot_count(ch,cls,lev));

		for (sn = col = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (skill_table[sn].name != NULL)
			{
				if (skill_table[sn].skilltype != FSKILL_SPELL)
					continue;

				strcpy(names, capitalize_title(skill_table[sn].name));
				names[22] = '\0';
				
				if (IS_SET(learned(ch, sn), 1 << cls))
				{
					strcpy(clr, "{078}");
				}
				else
				{
					strcpy(clr, "{108}");
				}

				if (cls == CLASS_CLERIC)
				{
					if (opposing_domain(ch, sn))
						continue;
						
					if (domain_spell(ch, sn, lev))
					{
						sprintf(buf2, " {168}D %s%-22s", clr, names);

						if (++col % 3 != 1)
						{
							strcat(buf1, "  ");
						}
						strcat(buf1, buf2);

						if (col % 3 == 0)
						{
							cat_sprintf(buf4, "%s\n\r", buf1);
							buf1[0] = '\0';
						}
						continue;
					}
				}
				if (cls == CLASS_SORCERER && lev > 0)
				{
					if (get_bloodline(ch) > 0 && lev == skill_table[sn].bloodline[get_bloodline(ch)])
					{
						sprintf(buf2, "   %s%-22s", clr, names);

						if (++col % 3 != 1)
						{
							strcat(buf1, "  ");
						}
						strcat(buf1, buf2);

						if (col % 3 == 0)
						{
							cat_sprintf(buf4, "%s\n\r", buf1);
							buf1[0] = '\0';
						}
						continue;
					}
				}
				if (cls == CLASS_WIZARD && opposing_school(ch, sn))
					continue;
				if (learned(ch, gsn_shadow_casting) && IS_SET(skill_table[sn].spell_desc, SDESC_LIGHT))
					continue;
					
				if (skill_table[sn].skill_level[cls] == lev)
				{
					if (prepared(ch, sn) == cls)
					{
						tstc = '*'; /* prepared spell */
					}
					else
					{
						tstc = ' ';
					}

					sprintf(buf2, " {168}%c %s%-22s", tstc, clr, names);

					if (++col % 3 != 1)
					{
						strcat(buf1, "  ");
					}
					strcat(buf1, buf2);

					if (col % 3 == 0)
					{
						cat_sprintf(buf4, "%s\n\r", buf1);
						buf1[0] = '\0';
					}
				}
			}
		}
		if (col % 3 != 0)
		{
			cat_sprintf(buf4, "%s\n\r", buf1);
	 		buf1[0] = '\0';
		}
	}
	if (cls == CLASS_CLERIC && class_level(ch, CLASS_CLERIC))
	{
		cat_sprintf(buf4, "{200}Your Domains: {078}");
		for (names[0] = '\0', cnt = count = 0 ; cnt < MAX_DOMAIN ; cnt++)
		{
			if (has_domain(ch, cnt))
			{
				if (count > 0)
					cat_sprintf(names, ", ");
				cat_sprintf(names, capitalize(domain_types[cnt]));
				count++;
			}
		}	
		if (names[0] == '\0')
			cat_sprintf(buf4, "None (Use DOMAIN to select your domains.)\n\r");
		else
		{
			cat_sprintf(buf4, "%s\n\r", names);
		}
	}
	if (cls == CLASS_WIZARD)
	{
		cat_sprintf(buf4, "{200}School of Specialty: {078}%-20s\n\r", get_school(ch) == -1 ? "None" : capitalize(school_types[get_school(ch)]));
		cat_sprintf(buf4, "{200}Prohibited School: {078}%-20s\n\r", opp_school(ch) == -1 ? "None" : capitalize(school_types[opp_school(ch)]));
	}
	if (cls == CLASS_SORCERER)
	{
		cat_sprintf(buf4, "{200}Bloodline: {078}%-20s\n\r", get_bloodline(ch) == -1 ? "None" : capitalize(bloodline_types[get_bloodline(ch)]));
	}
	
	strcpy(buf1, "{168}* {078}Prepared Spell");
	if (cls == CLASS_CLERIC)
		cat_sprintf(buf1, " {168}D {078}Domain spell");
	str_resize(buf1, skp1, -39);
	sprintf(buf2, "{200}Mana{178}: %-5d {200}[{078}%-5d{200}]", ch->mana[cls], get_max_mana(ch, cls));
	str_resize(buf2, skp2, 39);
	cat_sprintf(buf4, "%s %s\n\r", skp1, skp2);

	send_to_char_color(buf4, ch);
	pop_call();
	return;
}


void do_components( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char list[MAX_STRING_LENGTH];
	char bld[10], dim[10];
	int sn, cnt;

	push_call("do_components(%p,%p)",ch,argument);

	if (argument[0] == '\0')
	{
		send_to_char( "Syntax: components <'spell name'|list>\n\r", ch );
		pop_call();
		return;
	}

	strcpy(bld, ansi_translate_text(ch, "{178}"));
	strcpy(dim, ansi_translate_text(ch, "{078}"));


	if (!strcasecmp(argument, "list"))
	{
		strcpy(buf, "{200}Component list:\n\r");

		for (sn = 0 ; *skill_table[sn].name != '\0' ; sn++)
		{
			switch (skill_table[sn].skilltype)
			{
				default:
					continue;
					break;
				case FSKILL_SPELL:
					break;
			}

			if (!learned(ch, sn))
				continue;

			for (*list = '\0', cnt = 0 ; cnt < 5 ; cnt++)
			{
				if (skill_table[sn].components[cnt] == 0)
					continue;
				if (*list != '\0')
					strcat(list, ",");
				cat_sprintf(list, " %s", capitalize_title(component_table[skill_table[sn].components[cnt]].name));
			}
			if (*list == '\0')
				continue;

			cat_sprintf(buf, "  %s%-22s %s%s\n\r", bld, capitalize_title(skill_table[sn].name), dim, list);
		}
		send_to_char_color(buf, ch);
		pop_call();
		return;
	}

	if ((sn = skill_lookup(argument)) == -1)
	{
		send_to_char("No such spell.\n\r", ch);
		pop_call();
		return;
	}
	
	if (!is_spell(sn))
	{
		send_to_char("That is not a spell.\n\r", ch);
		pop_call();
		return;
	}

	if (!learned(ch, sn))
	{
		send_to_char("You do not know that spell.\n\r", ch);
		pop_call();
		return;
	}
	
	sprintf(buf, "{200}Components for %s:\n\r{078}", capitalize_title(skill_table[sn].name));

	for (*list = '\0', cnt = 0 ; cnt < 5 ; cnt++)
	{
		if (skill_table[sn].components[cnt] == 0)
			continue;

		cat_sprintf(list, "  %s\n\r", capitalize_title(component_table[skill_table[sn].components[cnt]].name));
	}
	if (*list == '\0')
		strcat(buf, "  None\n\r");
	else
		strcat(buf, list);

	send_to_char_color(buf, ch);

	pop_call();
	return;
}


void do_songs( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char buf3[MAX_STRING_LENGTH];
	char buf4[MAX_STRING_LENGTH];
	char tstc;
	int sn, col, cnt;

	push_call("do_songs(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_songs(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}
	
	if (!learned(ch, gsn_bardic_song))
	{
		send_to_char("You do not know how to sing.\n\r", ch);
		pop_call();
		return;
	}

	sprintf(buf3, "%s's", capitalize(ch->name));

	strcpy(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s Songs {200}]{078}", buf3);

	for (cnt = 0 ; cnt < 39-(strlen(buf3)+9)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	sprintf(buf4, "%s\n\r", buf1);

	for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_BARDSONG)
			{
				if (learned(ch, sn))
				{
					tstc = '*'; /* known spell */
				}
				else
				{
					tstc = '-';
				}

				sprintf(buf2, " {078}%-23s{168}%c", capitalize_title(skill_table[sn].name),tstc);

				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	cat_sprintf(buf4, "{168}* {078}Known song                                               You can learn {178}%2d {078}more\n\r", max_songs(ch) - count_songs(ch));
	send_to_char_color(buf4, ch);
	pop_call();
	return;
}


void feat_display( CHAR_DATA *ch, char *argument, bool CanTrain )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char buf3[MAX_STRING_LENGTH];
	char buf4[MAX_STRING_LENGTH];
	char tstc;
	int sn, col, cnt, type;
	int class = -1;

	push_call("do_feats(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_feats(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	if (argument[0] == '\0')
	{
		sprintf(buf3, "%s's Feats", ch->name);
		type = -1;
	}
	else if (!str_prefix(argument, "metamagic"))
	{
		sprintf(buf3, "Metamagic Feats");
		type = FEAT_METAMAGIC;
	}
	else if (!str_prefix(argument, "creation"))
	{
		sprintf(buf3, "Item Creation Feats");
		type = FEAT_CREATION;
	}
	else if (!str_prefix(argument, "combat"))
	{
		sprintf(buf3, "Combat Feats");
		type = FEAT_FIGHTER;
	}
	else if (!str_prefix(argument, "all") || !str_prefix(argument, "list"))
	{
		sprintf(buf3, "Feats List");
		type = 0;
	}
	else if ((class = lookup_class(argument)) != -1)
	{
		sprintf(buf3, "%s Bonus Feats", capitalize(class_table[class].who_name_long));
		type = 0;
	}
	else
	{
		sprintf(buf3, "%s's Feats", ch->name);
		type = -1;
	}

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s {200}]{078}", buf3);

	for (cnt = 0, buf4[0] = '\0' ; cnt < 39-(strlen(buf3))/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	cat_sprintf(buf4, "%s\n\r", buf1);

	for (col = sn = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_FEAT)
			{
				if (learned(ch,sn))
				{
					tstc = '*'; /* known feat */
				}
				else
				{
					tstc = '-';
				}
				if (type == -1 && !learned(ch, sn))
					continue;

				if (type > 0 && !IS_SET(skill_table[sn].spell_school, type))
					continue;
					
				if (type == 0 && !learned(ch, sn) && CanTrain && !can_train_feat(ch, sn, FALSE))
					continue;
					
				if (class != -1 && !is_bonus_feat(ch, class, sn))
					continue;
					
				if (sn == gsn_focus_abj
				|| sn == gsn_focus_conj
				|| sn == gsn_focus_div
				|| sn == gsn_focus_ench
				|| sn == gsn_focus_evoc
				|| sn == gsn_focus_illus
				|| sn == gsn_focus_necro
				|| sn == gsn_focus_trans
				|| sn == gsn_spell_penetration)
				{
					int rank = learned(ch, sn);
					sprintf(buf2, " {078}%-21s{168}%s", skill_table[sn].name, rank == 0 ? "---" : rank == 1 ? "--*" : rank == 2 ? "-**" : "***");
				}
				else
				{
					sprintf(buf2, " {078}%-23s{168}%c", skill_table[sn].name, tstc);
				}

				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	if (type != -1)
		cat_sprintf(buf4, "{168}* {078}Known feat\n\r");
	else
	{
		cat_sprintf(buf4, "{078}syntax: feats <all|metamagic|creation|class name>\n\r");
	}
	if (CanTrain)
		cat_sprintf(buf4, "{078}type HELP <feat name> for help on a certain feat.\n\r");
	cat_sprintf(buf4, "{078}You have %d feat points to spend.\n\r", ch->pcdata->feat_pts);
	for (cnt = 0 ; cnt < MAX_CLASS ; cnt++)
	{
		if (class_level(ch, cnt) > 0 && ch->pcdata->bonus_feat[cnt] > 0)
		cat_sprintf(buf4, "{078}You have %d %s bonus feats to spend.\n\r", ch->pcdata->bonus_feat[cnt], class_table[cnt].who_name_long);
	}
	send_to_char_color(buf4, ch);
	pop_call();
	return;
}

void do_feats( CHAR_DATA *ch, char *argument )
{
	push_call("do_feats(%p,%p)",ch,argument);
	
	feat_display(ch, argument, TRUE);

	pop_call();
	return;
}

void do_abilities( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char name[MAX_INPUT_LENGTH];
	int sn, col, cnt, lev, cls, dom, use, style;
	bool BadAlign = FALSE;

	push_call("do_abilities(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_abilities(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	if (*argument == '\0')
	{
		cls = ch->class;
		sprintf(name, "%s's", short_to_name(ch->name, 1));
	}
	else if ((cls = lookup_class(argument)) == -1)
	{
		act("That is not a class.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}
	else if (!class_level(ch, cls))
	{
		act("You do not have any levles in that class.", ch, NULL, NULL, TO_CHAR);
		pop_call();
		return;
	}
	else
	{
		strcpy(name, class_table[cls].who_name_long);
	}

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s Abilities {200}]{078}", name);

	for (cnt = 0 ; cnt < 39-(strlen(name)+12)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, "\n\r");

	if (argument[0] == '\0')
		cat_sprintf(buf1, "{200}%s Abilities\n\r", class_table[cls].who_name_long);

	for (lev = 1, col = 0 ; lev <= LEVEL_HERO ; lev++)
	{
		for (sn = 0 ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype != FSKILL_ABILITY
// 				&& skill_table[sn].skilltype != FSKILL_BARDSONG
				&& skill_table[sn].skilltype != FSKILL_FEAT)
					continue;
					
				if (sn == gsn_armor_proficiency_heavy
				|| sn == gsn_armor_proficiency_light
				|| sn == gsn_armor_proficiency_medium
				|| sn == gsn_weapon_prof_exotic
				|| sn == gsn_weapon_prof_martial
				|| sn == gsn_weapon_prof_simple
				|| sn == gsn_shield_proficiency
				|| sn == gsn_tower_shield_prof)
					continue;

				if (cls == CLASS_CLERIC)
				{
					bool fMatch = FALSE;

					for (dom = 0 ; dom <= MAX_DOMAIN ; dom++)
					{
						if (has_domain(ch, dom) && skill_table[sn].domains[dom] == lev)
						{
							if ((use = get_max_uses(ch, sn)) > 0)
								cat_sprintf(buf1, "{078}%2d{200}] {078}%-19s{168}%3d", skill_table[sn].domains[dom], capitalize_title(skill_table[sn].name), use - ch->uses[sn]);
							else if (class_level(ch, cls) >= lev)
								cat_sprintf(buf1, "{078}%2d{200}] {078}%-21s{168}*", skill_table[sn].domains[dom], capitalize_title(skill_table[sn].name));								
							else
								cat_sprintf(buf1, "{078}%2d{200}] {078}%-21s{168}-", skill_table[sn].domains[dom], capitalize_title(skill_table[sn].name));								

							if (++col % 3 != 0)
							{
								strcat(buf1, " ");
							}
	
							if (col % 3 == 0)
							{
								strcat(buf1, "\n\r");
							}
							fMatch = TRUE;
							break;
						}
					}
					if (fMatch)
						continue; // make sure we don't loop thru it again.
				}
				if (skill_table[sn].skill_level[cls] == lev
				|| (cls == CLASS_RANGER && (style = get_ranger_style(ch)) != 0 && skill_table[sn].styles[style] == lev)
				|| (cls == CLASS_MONK && (style = get_monk_style(ch)) != 0 && skill_table[sn].styles[style] == lev))
				{
					if ((cls == CLASS_PALADIN && (!IS_LAWFUL(ch) || !IS_GOOD(ch) || (!IS_NPC(ch) && ch->pcdata->reputation < 9)))
					|| (cls == CLASS_BLACKGUARD && !IS_EVIL(ch)))
					{
						cat_sprintf(buf1, "{078}%2d{200}] {078}%-21s{168}-", lev, capitalize_title(skill_table[sn].name));
						BadAlign = TRUE;
					}
					else if ((use = get_max_uses(ch, sn)) > 0)
						cat_sprintf(buf1, "{078}%2d{200}] {078}%-19s{168}%3d", lev, capitalize_title(skill_table[sn].name), use);
					else if (class_level(ch, cls) >= lev)
						cat_sprintf(buf1, "{078}%2d{200}] {078}%-21s{168}*", lev, capitalize_title(skill_table[sn].name));
					else
						cat_sprintf(buf1, "{078}%2d{200}] {078}%-21s{168}-", lev, capitalize_title(skill_table[sn].name));
				}
				else
				{
					continue;
				}
				if (++col % 3 != 0)
				{
					strcat(buf1, " ");
				}

				if (col % 3 == 0)
				{
					strcat(buf1, "\n\r");
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		strcat(buf1, "\n\r");
	}

	if (argument[0] == '\0')
	{
		strcat(buf1, "{200}Racial Abilities\n\r");

		for (col = sn = 0 ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (skill_table[sn].skilltype != FSKILL_ABILITY
				&& skill_table[sn].skilltype != FSKILL_RACEATTACK
				&& skill_table[sn].skilltype != FSKILL_FEAT)
					continue;

				if (race_skill(ch, sn))
				{
					if ((use = get_max_uses(ch, sn)) > 0)
						cat_sprintf(buf1, " {078}%-22s{168}%3d", capitalize_title(skill_table[sn].name), use);
					else
						cat_sprintf(buf1, " {078}%-24s{168}*", capitalize_title(skill_table[sn].name));
				}
				else
				{
					continue;
				}
				if (++col % 3 != 0)
				{
					strcat(buf1, " ");
				}

				if (col % 3 == 0)
				{
					strcat(buf1, "\n\r");
				}
			}
		}
		if (!col)
		{
			strcat(buf1, " {300}None.\n\r");
		}
		else if (col % 3 != 0)
		{
			strcat(buf1, "\n\r");
		}
		strcat(buf1, "{200}Spell-Like Abilities\n\r");

		for (sn = col = 0 ; *skill_table[sn].name != '\0' ; sn++)
		{
			if (is_string(skill_table[sn].name))
			{
				if (!is_spell(sn))
					continue;

				if ((use = skill_table[sn].race_skill[ch->race]) > 0)
				{
					if (use < 75)
						cat_sprintf(buf1, " {078}%-22s{168}%3d", capitalize_title(skill_table[sn].name), use);
					else
						cat_sprintf(buf1, " {078}%-23s {168}*", capitalize_title(skill_table[sn].name));
				}
				else
				{
					continue;
				}
				if (++col % 3 != 0)
				{
					strcat(buf1, " ");
				}

				if (col % 3 == 0)
				{
					strcat(buf1, "\n\r");
				}
			}
		}
		if (!col)
		{
			strcat(buf1, " {300}None.\n\r");
		}
		else if (col % 3 != 0)
		{
			strcat(buf1, "\n\r");
		}
	}

	if (cls == CLASS_RANGER)
	{
		cat_sprintf(buf1, "{200}Combat Style: {078}%s", capitalize(combat_styles[get_ranger_style(ch)]));
		if (armor_type_worn(ch) > ARMOR_MEDIUM)
			strcat(buf1, " (Armor too heavy!)\n\r");
		else
			strcat(buf1, "\n\r");
	}
	if (cls == CLASS_MONK)
		cat_sprintf(buf1, "{200}Fighting Style: {078}%s\n\r", capitalize(combat_styles[get_monk_style(ch)]));		
	if (cls == CLASS_DRUID && wears_metal(ch, FALSE))
		strcat(buf1, "{178}Your abilities are negated for wearing metal!\n\r");
	if (BadAlign)
		strcat(buf1, "{178}Your abilities are denied due to disfavor!\n\r");
	else
		strcat(buf1, "{168}* {078}Unlimited\n\r");
	send_to_char_color(buf1, ch);
	pop_call();
	return;
}

void do_weapons( CHAR_DATA *ch, char *argument )
{
	char buf1[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	char buf3[MAX_STRING_LENGTH];
	char buf4[MAX_STRING_LENGTH];
	int sn, col, cnt;

	push_call("do_weapons(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		log_printf("do_weapons(%s, %s)", get_name(ch), argument);
		pop_call();
		return;
	}

	sprintf(buf3, "Weapon Proficiencies");

	sprintf(buf1, "{078}");
	sprintf(buf2, "{200}[{178} %s {200}]{078}", buf3);

	for (cnt = 0, buf4[0] = '\0' ; cnt < 39-(strlen(buf3)+2)/2 ; cnt++)
	{
		strcat(buf1, "-");
	}
	strcat(buf1, buf2);

	for (cnt = ansi_strlen(buf1) ; cnt < 80 ; cnt++)
	{
		strcat(buf1, "-");
	}
	cat_sprintf(buf4, "%s\n\r", buf1);
	cat_sprintf(buf4, "{200}Simple weapons\n\r", buf1);

	for (sn = col = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_WEAPON)
			{
				if (skill_table[sn].spell_desc == WEAPON_CLASS_SIMPLE)
				{
					if (learned(ch,sn))
					{
						sprintf(buf3, "*"); /* known weapon */
					}
					else
					{
						sprintf(buf3, "-");
					}
					sprintf(buf2, " {078}%-23s{168}%s", capitalize_title(skill_table[sn].name), buf3);
				}
				else
				{
				continue;
				}
				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	cat_sprintf(buf4, "{200}Martial weapons\n\r", buf1);
	for (sn = col = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_WEAPON)
			{
				if (skill_table[sn].spell_desc == WEAPON_CLASS_MARTIAL)
				{
					if (learned(ch,sn))
					{
						sprintf(buf3, "*"); /* known weapon */
					}
					else
					{
						sprintf(buf3, "-");
					}
					sprintf(buf2, " {078}%-23s{168}%s", capitalize_title(skill_table[sn].name), buf3);
				}
				else
				{
				continue;
				}
				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	cat_sprintf(buf4, "{200}Exotic weapons\n\r", buf1);
	for (sn = col = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_WEAPON)
			{
				if (skill_table[sn].spell_desc == WEAPON_CLASS_EXOTIC)
				{
					if (learned(ch,sn))
					{
						sprintf(buf3, "*"); /* known weapon */
					}
					else
					{
						sprintf(buf3, "-");
					}
					sprintf(buf2, " {078}%-23s{168}%s", capitalize_title(skill_table[sn].name), buf3);
				}
				else
				{
				continue;
				}
				if (++col % 3 != 1)
				{
					strcat(buf1, "  ");
				}
				strcat(buf1, buf2);

				if (col % 3 == 0)
				{
					cat_sprintf(buf4, "%s\n\r", buf1);
					buf1[0] = '\0';
				}
			}
		}
	}
	if (col % 3 != 0)
	{
		cat_sprintf(buf4, "%s\n\r", buf1);
	}
	cat_sprintf(buf4, "{200}Weapon Specialties\n\r", buf1);
	for (sn = col = 0, buf1[0] = '\0' ; *skill_table[sn].name != '\0' ; sn++)
	{
		if (skill_table[sn].name != NULL)
		{
			if (skill_table[sn].skilltype == FSKILL_WEAPON)
			{
				if (learned(ch, sn) > 1)
					cat_sprintf(buf4, " {078}%s: {178}%s\n\r", capitalize_title(skill_table[sn].name), flag_string(learned(ch, sn), prof_flags));
			}
		}
	}
	cat_sprintf(buf4, "{078}You have %d feat points to spend.\n\r", ch->pcdata->feat_pts);
	send_to_char_color(buf4, ch);
	pop_call();
	return;
}

/*
	Contributed by Alander.
*/
void do_commands( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	int cmd, col;

	push_call("do_commands(%p,%p)",ch,argument);

	for (buf[0] = '\0', cmd = col = 0 ; cmd_table[cmd].name[0] != '\0' ; cmd++)
	{
		if (cmd_table[cmd].level < LEVEL_IMMORTAL && cmd_table[cmd].level <= get_trust(ch))
		{
			if (IS_SET(cmd_table[cmd].flags, CMD_HIDE))
			{
				continue;
			}
			if (IS_AFFECTED(ch, AFF2_BERSERK) && !IS_SET(cmd_table[cmd].flags, CMD_BERSERK))
			{
				continue;
			}
			if (IS_DEAD(ch) && cmd_table[cmd].position >= POS_STUNNED && !IS_SET(cmd_table[cmd].flags, CMD_DEAD))
			{
				continue;
			}

			if (col % 6 == 0)
			{
				strcat(buf, get_color_string(ch, COLOR_TEXT, VT102_DIM));
			}
			cat_snprintf(buf, 13, "%-13s", cmd_table[cmd].name);
			if (++col % 6 == 0)
			{
				strcat(buf, "\n\r");
			}
		}
	}
	if (col % 6 != 0)
	{
		strcat(buf, "\n\r");
	}
	send_to_char(buf, ch);
	pop_call();
	return;
}

typedef struct chan_list CHAN_LIST;
struct chan_list
{
	CHAR_DATA *ch;
	CHAN_LIST *next;
};

/*  By Chaos   9/20/93   */
void do_level ( CHAR_DATA *ch, char *argument )
{
	int lvn, lvx, cnt, ld;

	push_call("do_level(%p,%p)",ch,argument);

	if( ch->level >= LEVEL_HERO )
	{
		send_to_char( "You are already at the maximum level.\n\r", ch );
		pop_call();
		return;
	}

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	lvn = class_level(ch, ch->class)-3;
	lvx = class_level(ch, ch->class)+3;
	ld  = ch->level - class_level(ch, ch->class);

	if (lvn < 0)
	{
		lvn = 0;
	}
	if (ld == 0 && lvn < 1)
	{
		lvn = 1;
	}
	if (lvx + ld > MAX_LEVEL)
	{
		lvx = MAX_LEVEL - ld;
	}
	if (lvx > LEVEL_IMMORTAL - 1)
	{
		lvx = LEVEL_IMMORTAL - 1;
	}
	send_to_char( "Level - Experience Required\n\r", ch);
	for (cnt = lvn ; cnt <= lvx ; cnt++)
	{
		if (ch->level - ld == cnt)
		{
			ch_printf_color(ch, " You  - %12d\n\r", ch->pcdata->exp);
		}
		ch_printf_color(ch, " %3d  - %12d\n\r", cnt+1+ld, exp_level(ch,cnt+ld));
	}
	if (ch->level < LEVEL_HERO - 1)
	{
		ch_printf_color(ch, " You need %d experience points to gain a level.\n\r", exp_level(ch, ch->level) - ch->pcdata->exp);
	}
	pop_call();
	return;
}

/* By Chaos 9/20/93  */

void do_clock( CHAR_DATA *ch, char *arg)
{
	char buf[MAX_STRING_LENGTH];
	int clh, clm;

	push_call("do_clock(%p,%p)",ch,arg);

	if( ch->desc->original != NULL)
	{
		pop_call();
		return;
	}

	if( IS_NPC(ch) )
	{
		pop_call();
		return;
	}

	one_argument( arg, buf);
	if (buf[0]=='\0')
	{
		send_to_char( "Usage:   clock <offset>\n\rThis sets the clock to your local time zone.\n\r", ch );
		send_to_char( "         clock mil  - This sets the clock to military time.\n\r" , ch);
		send_to_char( "         clock civ  - This sets the clock to normal am/pm mode.\n\r", ch);

		pop_call();
		return;
	}

	if (!strcasecmp(buf, "mil"))
	{
		ch->pcdata->clock = ch->pcdata->clock % 100 + 100;
		pop_call();
		return;
	}

	if (!strcasecmp(buf, "civ"))
	{
		ch->pcdata->clock = ch->pcdata->clock % 100;
		pop_call();
		return;
	}

	if (!strcasecmp(buf, "cnt"))
	{
		ch->pcdata->clock = ch->pcdata->clock % 100 + 1000;
		pop_call();
		return;
	}

	clh = atol(buf);
	clm = 0;

	if (!is_number(buf))
	{
		sscanf(buf, "%d:%d", &clh, &clm);
	}

	while (clh < 0)
	{
		clh += 24;
	}
	clh = clh % 24;

	while (clm < 0)
	{
		clm += 24;
	}
	clm = clm % 60;

	ch->pcdata->clock = (ch->pcdata->clock / 100) * 100 + clh;
	ch->pcdata->clock = (ch->pcdata->clock % 10000) + clm * 10000;

	pop_call();
	return;
}

void do_vt100( CHAR_DATA *ch, char *arg)
{
	int cnt;
	char buf0[MAX_INPUT_LENGTH];
	char buf1[90], buf2[90], buf4[90], skp1[80], skp2[80];
	char colg[10], colw[10], colW[10];

	push_call("do_vt100(%p,%p)",ch,arg);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	arg = one_argument(arg, buf0);

	if (buf0[0] == '\0')
	{
		strcpy(colg, ansi_translate_text(ch, "{200}"));
		strcpy(colw, get_color_string(ch, COLOR_PROMPT, VT102_DIM));
		strcpy(colW, get_color_string(ch, COLOR_PROMPT, VT102_BOLD));

		sprintf(buf4, "%s +----------------------------------------------------------------------------+\n\r", colg);

		strcpy(buf0, buf4);

		sprintf(buf1, "%s", ch->pcdata->vt100 ? "ON" : "OFF");
		sprintf(buf2, "%s", IS_SET(ch->pcdata->vt100_flags, VT102_FAST) ? "FAST" : "SLOW");
		cat_sprintf(buf0, "%s |%s         VT102 Mode%s:%s %-11s %s             VT102 Speed%s:%s %-11s      %s|\n\r",
		colg,
		colw, colW, colw, str_resize(buf1, skp1, -11),
		colw, colW, colw, str_resize(buf2, skp2, -11),
		colg);


		sprintf(buf1, "%d", ch->pcdata->vt100_type % 100);
		sprintf(buf2, "%d", ch->pcdata->vt100_type / 100);
		cat_sprintf(buf0, "%s |%s      Terminal Rows%s:%s %-11s %s        Terminal Columns%s:%s %-11s      %s|\n\r",
			colg,
			colw, colW, colw, str_resize(buf1, skp1, -11),
			colw, colW, colw, str_resize(buf2, skp2, -11),
			colg);

		strcat(buf0, buf4);

		send_to_char_color(buf0, ch);
		pop_call();
		return;
	}

	if (!strcasecmp(buf0,  "on") && ch->pcdata->vt100 == 0)
	{
		vt100on(ch);
		pop_call();
		return;
	}

	if (!strcasecmp(buf0, "off") && ch->pcdata->vt100 != 0)
	{
		vt100off(ch);
		pop_call();
		return;
	}

	if (!strcasecmp(buf0, "rows"))
	{
		strcpy(buf0, arg);
	}

	if ((cnt = atol(buf0)) != 0)
	{
		if (cnt < 15 || cnt > 99)
		{
			send_to_char("Rows must be between 15 and 99.\n\r", ch );
			pop_call();
			return;
		}
		ch->pcdata->vt100_type = (ch->pcdata->vt100_type / 100) * 100 + cnt;

		cnt = URANGE(1, (ch->pcdata->vt100_type % 100) / 2, MAX_TACTICAL_ROW - 1);

		if (ch->pcdata->tactical_mode % 100 > cnt)
		{
			ch->pcdata->tactical_mode = ch->pcdata->tactical_mode - ch->pcdata->tactical_mode % 100 + cnt;
		}
		do_refresh(ch, "");
		pop_call();
		return;
	}

	if (!strcasecmp(buf0, "columns"))
	{
		cnt = atol(arg);

		if (cnt < 80 || cnt > MAX_TACTICAL_COL)
		{
			send_to_char("Columns must be between 80 and 160.\n\r", ch );
			pop_call();
			return;
		}
		ch->pcdata->vt100_type = (ch->pcdata->vt100_type % 100) + 100 * cnt;

		do_refresh(ch, "");
		pop_call();
		return;
	}

	if (!strcasecmp(buf0, "fast"))
	{
		send_to_char("VT102 mode set to FAST.\n\r", ch);
		SET_BIT(ch->pcdata->vt100_flags, VT102_FAST);
		pop_call();
		return;
	}

	if (!strcasecmp(buf0, "slow"))
	{
		send_to_char("VT102 mode set to SLOW.\n\r", ch);
		REMOVE_BIT(ch->pcdata->vt100_flags, VT102_FAST);
		pop_call();
		return;
	}

	send_to_char("Syntax: VT102 [ON/OFF|FAST/SLOW|ROWS|COLUMNS] [VALUE]\n\r", ch);
	pop_call();
	return;
}


void do_config( CHAR_DATA *ch, char *argument )
{
	char arg1[MAX_INPUT_LENGTH];
	char arg2[MAX_INPUT_LENGTH];
	char *arg3;
	char buf[MAX_STRING_LENGTH];
	int  cnt, cnt2;

	push_call("do_config(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	argument	= one_argument( argument, arg1 );
	argument	= one_argument( argument, arg2 );
	arg3		= argument;

	if (arg1[0] == '-' || arg1[0] == 'x')
	{
		ch_printf_color(ch, "\n\r{178}Configuration finished.\n\r");

		if (ch->pcdata->last_command)
		{
			STRFREE(ch->pcdata->last_command);
		}
		pop_call();
		return;
	}

	/*
		Display Options
	*/

	if (*arg1 == 'a')
	{
		switch( *arg2 )
		{
			case 'a':
				TOGGLE_BIT(ch->act, PLR_AUTOEXIT);
				break;
			case 'b':
				TOGGLE_BIT(ch->act, PLR_QUIET);
				break;
			case 'c':
				TOGGLE_BIT(ch->act, PLR_BLANK);
				break;
			case 'd':
				TOGGLE_BIT(ch->act, PLR_BRIEF);
				break;
			case 'e':
				TOGGLE_BIT(ch->act, PLR_PAGER);
				break;
			case 'f':
				TOGGLE_BIT(ch->act, PLR_REPEAT);
				break;
			case 'g':
				if (IS_IMMORTAL(ch))
					TOGGLE_BIT(ch->act, PLR_HOLYLIGHT);
				else
				{
					ch_printf_color(ch, "{118}'%c' Is not an option.\n\r", *arg2);
	
					RESTRING(ch->pcdata->last_command, "config A ");
					pop_call();
					return;
				}
				break;
			case '\0':
				break;
			case 'x':
				do_config(ch, "x");
				pop_call();
				return;
			case '-':
				if (ch->pcdata->last_command)
				{
					STRFREE(ch->pcdata->last_command);
				}
				do_config(ch, "");
				pop_call();
				return;
			default:
				ch_printf_color(ch, "{118}'%c' Is not an option.\n\r", *arg2);

				RESTRING(ch->pcdata->last_command, "config A ");
				pop_call();
				return;
		}

		do_refresh(ch, "");

		sprintf( buf, "%sDisplay Setup Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));

		strcat(  buf, IS_SET(ch->act, PLR_AUTOEXIT)
			? "{200}  [{078}A{200}] {178}[AUTOEXIT ] {078}You automatically see exits.\n\r"
			: "{200}  [{078}A{200}] {078}[autoexit ] {078}You don't automatically see exits.\n\r" );

		strcat(  buf, IS_SET(ch->act, PLR_QUIET)
			? "{200}  [{078}B{200}] {178}[QUIET    ] {078}You don't see items that spill out of corpses.\n\r"
			: "{200}  [{078}B{200}] {078}[quiet    ] {078}You see items that spill out of corpses.\n\r");

		strcat( buf,  IS_SET(ch->act, PLR_BLANK)
			? "{200}  [{078}C{200}] {178}[BLANK    ] {078}You have a blank line before your prompt.\n\r"
			: "{200}  [{078}C{200}] {078}[blank    ] {078}You have no blank line before your prompt.\n\r" );

		strcat( buf,  IS_SET(ch->act, PLR_BRIEF)
			? "{200}  [{078}D{200}] {178}[BRIEF    ] {078}You see brief descriptions.\n\r"
			: "{200}  [{078}D{200}] {078}[brief    ] {078}You see long descriptions.\n\r" );

		strcat( buf,  !IS_SET(ch->act, PLR_PAGER)
			? "{200}  [{078}E{200}] {178}[PAGER    ] {078}You are using the page pauser.\n\r"
			: "{200}  [{078}E{200}] {078}[pager    ] {078}You are not using the page pauser.\n\r" );

		strcat( buf,  IS_SET(ch->act, PLR_REPEAT)
			? "{200}  [{078}F{200}] {178}[REPEAT   ] {078}You see what you've typed in.\n\r"
			: "{200}  [{078}F{200}] {078}[repeat   ] {078}You don't see what you type in.\n\r");

		if (IS_IMMORTAL(ch))
		{
			strcat( buf,  IS_SET(ch->act, PLR_HOLYLIGHT)
				? "{200}  [{078}G{200}] {178}[HOLYLIGHT] {078}You can see everything in the game.\n\r"
				: "{200}  [{078}G{200}] {078}[holylight] {078}You only see what players see.\n\r");
		}

		strcat( buf, "\n{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color(buf, ch );

		RESTRING(ch->pcdata->last_command, "config A ");

		pop_call();
		return;
	}

	/*
		Terminal configuration
	*/

	if (arg1[0] == 'b')
	{
		switch (*arg2)
		{
			case 'a':
				if (ch->pcdata->vt100 == 0)
				{
					vt100on(ch);
				}
				else
				{
					vt100off(ch);
				}
				break;
			case 'b':
				if (*arg3 == '\0')
				{
					ch_printf_color(ch, "\n\r{168}Enter amount of terminal lines:\n\r");

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config B B " );
					pop_call();
					return;
				}
				cnt = atol(arg3);
				if (cnt < 15 || cnt > 99)
				{
					ch_printf_color(ch, "\n\r{118}Terminal rows must be between 15 and 99.\n\r" );

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config B B " );

					pop_call();
					return;
				}
				ch->pcdata->vt100_type = (ch->pcdata->vt100_type / 100) * 100 + cnt;
				break;
			case 'c':
				if (*arg3 == '\0')
				{
					ch_printf_color(ch, "\n\r{168}Enter amount of terminal columns:\n\r");

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config B C " );
					pop_call();
					return;
				}
				cnt = atol(arg3);
				if (cnt < 80 || cnt > MAX_TACTICAL_COL)
				{
					ch_printf_color(ch, "\n\r{118}Terminal columns must be between 80 and 160.\n\r" );

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config B C " );

					pop_call();
					return;
				}
				ch->pcdata->vt100_type = (ch->pcdata->vt100_type % 100) + 100 * cnt;
				break;
			case 'd':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT102_FAST);
				break;
			case 'e':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_HIGHLIGHT);
				break;
			case 'f':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_BEEP);
				break;
			case 'g':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_ECMA48);
				break;
			case 'h':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_BOLD);
				break;
			case 'i':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_UNDERLINE);
				break;
			case 'j':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_FLASH);
				break;
			case 'k':
				TOGGLE_BIT(ch->pcdata->vt100_flags, VT100_REVERSE);
				break;
			case '\0':
				break;
			case 'x':
				do_config(ch, "x");
				pop_call();
				return;
			case '-':
				STRFREE( ch->pcdata->last_command );
				do_config( ch, "" );
				pop_call();
				return;
			default:
				ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );

				RESTRING(ch->pcdata->last_command, "config B ");

				pop_call();
				return;
		}
		do_refresh(ch, "");

		sprintf( buf, "%sTerminal Setup Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));
		strcat( buf, ch->pcdata->vt100 != 0
			? "{200}  [{078}A{200}] {178}[ ON  ]  {078}VT102 mode is enabled.\n\r"
			: "{200}  [{078}A{200}] {078}[ OFF ]  {078}VT102 mode is disabled.\n\r" );

		cat_sprintf(buf, "{200}  [{078}B{200}] {178}[ %-3d ]  {078}VT102 terminal lines.   (15-99)\n\r",  ch->pcdata->vt100_type%100);
		cat_sprintf(buf, "{200}  [{078}C{200}] {178}[ %-3d ]  {078}VT102 terminal columns. (80-160)\n\r", ch->pcdata->vt100_type/100);

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT102_FAST)
			? "{200}  [{078}D{200}] {178}[ ON  ]  {078}VT102 speed is set to FAST.\n\r"
			: "{200}  [{078}D{200}] {078}[ OFF ]  {078}VT102 speed is set to SLOW.\n\r" );

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_HIGHLIGHT)
			? "{200}  [{078}E{200}] {178}[ ON  ]  {078}'YOU' highlighting is enabled.\n\r"
			: "{200}  [{078}E{200}] {078}[ OFF ]  {078}'YOU' highlighting is disabled.\n\r" );

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_BEEP)
			? "{200}  [{078}F{200}] {178}[ ON  ]  {078}VT100 Terminal Bell is enabled.\n\r"
			: "{200}  [{078}F{200}] {078}[ OFF ]  {078}VT100 Terminal Bell is disabled.\n\r");

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_ECMA48)
			? "{200}  [{078}G{200}] {178}[ ON  ]  {078}ISO48 is enabled.\n\r"
			: "{200}  [{078}G{200}] {078}[ OFF ]  {078}ISO48 is disabled.\n\r");

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_BOLD)
			? "{200}  [{078}H{200}] {178}[ ON  ]  {078}VT100 Bolded Text is enabled.\n\r"
			: "{200}  [{078}H{200}] {078}[ OFF ]  {078}VT100 Bolded Text is disabled.\n\r");

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_UNDERLINE)
			? "{200}  [{078}I{200}] {178}[ ON  ]  {078}VT100 Underlined Text is enabled.\n\r"
			: "{200}  [{078}I{200}] {078}[ OFF ]  {078}VT100 Underlined Text is disabled.\n\r");

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_FLASH)
			? "{200}  [{078}J{200}] {178}[ ON  ]  {078}VT100 Flashing Text is enabled.\n\r"
			: "{200}  [{078}J{200}] {078}[ OFF ]  {078}VT100 Flashing Text is disabled.\n\r");

		strcat( buf, IS_SET(ch->pcdata->vt100_flags, VT100_REVERSE)
			? "{200}  [{078}K{200}] {178}[ ON  ]  {078}VT100 Reversed Text is enabled.\n\r"
			: "{200}  [{078}K{200}] {078}[ OFF ]  {078}VT100 Reversed Text is disabled.\n\r");

		strcat( buf, "\n\r{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color( buf, ch );

		STRFREE( ch->pcdata->last_command );
		ch->pcdata->last_command = STRALLOC( "config B " );

		pop_call();
		return;
	}

	/* Tactical configuration */

	if (arg1[0] == 'c')
	{

		switch( *arg2 )
		{
			case 'a':
				cnt = URANGE(1, (ch->pcdata->vt100_type % 100) / 2, MAX_TACTICAL_ROW - 1);
				if (*arg3 == '\0')
				{
					ch_printf_color(ch, "\n\r{168}Enter amount of tactical lines: (1-%d)\n\r", cnt);

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config C A " );

					pop_call();
					return;
				}

				cnt2 = atol(arg3);

				if (cnt2 < 1 || cnt2 > cnt)
				{
					ch_printf_color(ch, "\n\r{118}Tactical rows must be between 1 and %d.\n\r", cnt );
					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config C A " );
					pop_call();
					return;
				}
				ch->pcdata->tactical_mode = (ch->pcdata->tactical_mode / 100) * 100 + cnt2;
				break;

			case 'b':
				if (*arg3 == '\0')
				{
					ch_printf_color(ch, "\n\r{168}Enter compass width: (5-15, or 0 for OFF)\n\r" );

					STRFREE( ch->pcdata->last_command );
					ch->pcdata->last_command = STRALLOC( "config C B " );

					pop_call();
					return;
				}
				if ((cnt2 = atol(arg3)) != 0)
				{
					if (cnt2 < 5 || cnt2 > 15)
					{
						ch_printf_color(ch, "\n\r{118}Compass width must be between 5 and 15, or 0 to turn off.\n\r");

						STRFREE( ch->pcdata->last_command );
						ch->pcdata->last_command = STRALLOC( "config C B " );

						pop_call();
						return;
					}
				}
				ch->pcdata->compass_width = cnt2;
				break;

			case 'c':
				if ((ch->pcdata->tactical_mode / 1000) % 10 == 0)
				{
					ch->pcdata->tactical_mode += 1000;
				}
				else
				{
					ch->pcdata->tactical_mode -= 1000;
				}
				break;

			case 'd':
				TOGGLE_BIT(ch->act, PLR_EXP_TO_LEVEL);
				break;

			case '\0':
				break;

			case 'x':
				do_config(ch, "x");
				pop_call();
				return;

			case '-':
				STRFREE( ch->pcdata->last_command );
				do_config( ch, "" );
				pop_call();
				return;

			default:
				ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );

				STRFREE( ch->pcdata->last_command );
				ch->pcdata->last_command = STRALLOC( "config C " );

				pop_call();
				return;
		}
		do_refresh(ch, "");

		sprintf( buf, "%sTactical Setup Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));

		cat_sprintf(buf, "{200}  [{078}A{200}] {178}[ %-3d ]  {078}Tactical terminal lines.\n\r", ch->pcdata->tactical_mode%100);

		if (ch->pcdata->compass_width < 4)
		{
			cat_sprintf(buf, "{200}  [{078}B{200}] {078}[ OFF ]  {078}Your tactical compass is off.\n\r" );
		}
		else
		{
			cat_sprintf(buf, "{200}  [{078}B{200}] {178}[ %-3d ]  {078}Tactical compass width.\n\r", ch->pcdata->compass_width);
		}

		strcat( buf, (ch->pcdata->tactical_mode/1000)%10==0
			? "{200}  [{078}C{200}] {178}[ ABOVE   ]  {078}Your top stat bar is above the tactical.\n\r"
			: "{200}  [{078}C{200}] {078}[ below   ]  {078}Your top stat bar is below the tactical.\n\r" );

		strcat(  buf, IS_SET(ch->act, PLR_EXP_TO_LEVEL)
			? "{200}  [{078}D{200}] {178}[ EXP2LVL ]  {078}Your tactical shows exp to level.\n\r"
			: "{200}  [{078}D{200}] {078}[ EXPTTL  ]  {078}Your tactical shows total exp.\n\r");

		strcat( buf, "\n\r{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color( buf, ch );

		STRFREE( ch->pcdata->last_command );
		ch->pcdata->last_command = STRALLOC( "config C " );

		pop_call();
		return;
	}

	/*
		Developer options
	*/

	if (*arg1 == 'd' || *arg1 == 'D')
	{
		if (!IS_GOD(ch) && !PLR_FUNCTION(ch, FUNCTION_BUILDER|FUNCTION_BETA_TESTER))
		{
			ch_printf_color(ch, "\n\r{200}You are not a builder.\n\r");
			pop_call();
			return;
		}

		switch (tolower(arg2[0]))
		{
			case 'a':
				TOGGLE_BIT(ch->act, PLR_WIZTIME);
				break;
			case 'b':
				TOGGLE_BIT(ch->act, PLR_BUILDWALK);
				break;
			case 'c':
				TOGGLE_BIT(ch->act, PLR_BUILDLIGHT);
				break;
			case 'd':
				TOGGLE_BIT(ch->act, PLR_HEARLOG);
				break;
			case '\0':
				break;
			case 'x':
				do_config(ch, "x");
				pop_call();
				return;
			case '-':
				STRFREE( ch->pcdata->last_command );
				do_config( ch, "" );
				pop_call();
				return;
			default:
				ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );

				RESTRING(ch->pcdata->last_command, "config D ");
				pop_call();
				return;
		}

		do_refresh(ch, "");

		sprintf( buf, "%sDeveloper Options Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));

		strcat( buf,  IS_SET(ch->act, PLR_WIZTIME)
			? "{200}  [{078}A{200}] {178}[TIMEMODE ] {078}You see playtesting messages and command times.\n\r"
			: "{200}  [{078}A{200}] {078}[timemode ] {078}You do not see playtesting messages and command times.\n\r");

		strcat( buf,  IS_SET(ch->act, PLR_BUILDWALK)
			? "{200}  [{078}B{200}] {178}[BUILDWALK] {078}You can create new rooms in the direction you walk.\n\r"
			: "{200}  [{078}B{200}] {078}[buildwalk] {078}You do not create new rooms in the direction you walk.\n\r");

		strcat( buf,  IS_SET(ch->act, PLR_BUILDLIGHT)
			? "{200}  [{078}C{200}] {178}[VNUM     ] {078}You see builder vnums in descs.\n\r"
			: "{200}  [{078}C{200}] {078}[vnum     ] {078}You do not see builder vnums in descs.\n\r");

		strcat( buf,  IS_SET(ch->act, PLR_HEARLOG)
			? "{200}  [{078}D{200}] {178}[HEARLOG  ] {078}You see logged alerts.\n\r"
			: "{200}  [{078}D{200}] {078}[hearlog  ] {078}You do not see logged alerts.\n\r");

		strcat( buf, "\n\r{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color(buf, ch);

		STRFREE(ch->pcdata->last_command);
		ch->pcdata->last_command = STRALLOC( "config D " );
		pop_call();
		return;
	}

	/*
		Combat Spamming configuration
	*/

	if (arg1[0] == 'e' || arg1[0] == 'E')
	{
		switch( *arg2 )
		{
			case 'a':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_YOU_HIT);
				break;
			case 'b':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_YOU_MISS);
				break;
			case 'c':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_THEY_HIT);
				break;
			case 'd':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_THEY_MISS);
				break;
			case 'e':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_PARTY_HIT);
				break;
			case 'f':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_PARTY_MISS);
				break;
			case 'g':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_THEY_HIT_PARTY);
				break;
			case 'h':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_THEY_MISS_PARTY);
				break;
			case 'i':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_OTHER_HIT);
				break;
			case 'j':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_OTHER_MISS);
				break;
			case 'k':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_DICE_ROLLS);
				break;
			case 'l':
				TOGGLE_BIT(ch->pcdata->spam, SPAM_PARTY_MOVE);
				break;
			case 'x':
				do_config(ch, "x");
				pop_call();
				return;
			case '-':
				STRFREE( ch->pcdata->last_command );
				do_config( ch, "" );
				pop_call();
				return;
			case '\0':
				break;
			default:
				ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );

				RESTRING(ch->pcdata->last_command, "config E ");
				pop_call();
				return;
		}

		do_refresh(ch, "");

		sprintf( buf, "%sCombat Spamming Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_YOU_HIT)
			? "{200}  [{078}A{200}] {178}[ ON  ] {078}You hit.\n\r"
			: "{200}  [{078}A{200}] {078}[ OFF ] {078}You hit.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_YOU_MISS)
			? "{200}  [{078}B{200}] {178}[ ON  ] {078}You miss.\n\r"
			: "{200}  [{078}B{200}] {078}[ OFF ] {078}You miss.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_THEY_HIT)
			? "{200}  [{078}C{200}] {178}[ ON  ] {078}They hit you.\n\r"
			: "{200}  [{078}C{200}] {078}[ OFF ] {078}They hit you.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_THEY_MISS)
			? "{200}  [{078}D{200}] {178}[ ON  ] {078}They miss you.\n\r"
			: "{200}  [{078}D{200}] {078}[ OFF ] {078}They miss you.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_PARTY_HIT)
			? "{200}  [{078}E{200}] {178}[ ON  ] {078}Party hits.\n\r"
			: "{200}  [{078}E{200}] {078}[ OFF ] {078}Party hits.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_PARTY_MISS)
			? "{200}  [{078}F{200}] {178}[ ON  ] {078}Party misses.\n\r"
			: "{200}  [{078}F{200}] {078}[ OFF ] {078}Party misses.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_THEY_HIT_PARTY)
			? "{200}  [{078}G{200}] {178}[ ON  ] {078}They hit party.\n\r"
			: "{200}  [{078}G{200}] {078}[ OFF ] {078}They hit party.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_THEY_MISS_PARTY)
			? "{200}  [{078}H{200}] {178}[ ON  ] {078}They miss party.\n\r"
			: "{200}  [{078}H{200}] {078}[ OFF ] {078}They miss party.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_OTHER_HIT)
			? "{200}  [{078}I{200}] {178}[ ON  ] {078}Other hit.\n\r"
			: "{200}  [{078}I{200}] {078}[ OFF ] {078}Other hit.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_OTHER_MISS)
			? "{200}  [{078}J{200}] {178}[ ON  ] {078}Other miss.\n\r"
			: "{200}  [{078}J{200}] {078}[ OFF ] {078}Other miss.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_DICE_ROLLS)
			? "{200}  [{078}K{200}] {178}[ ON  ] {078}See dice rolls when available.\n\r"
			: "{200}  [{078}K{200}] {078}[ OFF ] {078}See dice rolls when available.\n\r");

		strcat(  buf, !IS_SET(ch->pcdata->spam, SPAM_PARTY_MOVE)
			? "{200}  [{078}L{200}] {178}[ ON  ] {078}Hearing party members move.\n\r"
			: "{200}  [{078}L{200}] {078}[ OFF ] {078}Hearing party members move.\n\r");

		strcat( buf, "\n\r{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color(buf, ch);

		RESTRING(ch->pcdata->last_command, "config E ");
		pop_call();
		return;
	}

	if (*arg1 == 'f')
	{
		switch (arg2[0])
		{
			case 'a':
				TOGGLE_BIT(ch->pcdata->channel, CHANNEL_CHAT);
				break;
			case 'b':
				TOGGLE_BIT(ch->pcdata->channel, CHANNEL_ALERTS);
				break;
			case 'c':
				TOGGLE_BIT(ch->pcdata->channel, CHANNEL_ALERTS);
				break;
			case 'd':
				if (!IS_IMMORTAL(ch) && !PLR_FUNCTION(ch, FUNCTION_HELPER))
				{
					ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );
					RESTRING(ch->pcdata->last_command, "config F ");
					pop_call();
					return;
				}
				else
					TOGGLE_BIT(ch->pcdata->channel, CHANNEL_QUESTION);
				break;
			case 'e':
				if (!IS_IMMORTAL(ch))
				{
					ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );
					RESTRING(ch->pcdata->last_command, "config F ");
					pop_call();
					return;
				}
				else
					TOGGLE_BIT(ch->pcdata->channel, CHANNEL_IMMTALK);
				break;
			case 'f':
				if (!IS_IMMORTAL(ch))
				{
					ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );
					RESTRING(ch->pcdata->last_command, "config F ");
					pop_call();
					return;
				}
				else
					TOGGLE_BIT(ch->pcdata->channel, CHANNEL_LOGSPEECH);
				break;
			case '\0':
				break;
			case 'x':
				do_config(ch, "x");
				pop_call();
				return;
			case '-':
				STRFREE( ch->pcdata->last_command );
				do_config( ch, "" );
				pop_call();
				return;
			default:
				ch_printf_color(ch, "\n\r{118}'%c' Is not an option.\n\r", *arg2 );
				RESTRING(ch->pcdata->last_command, "config F ");
				pop_call();
				return;
		}

		do_refresh(ch, "");

		sprintf( buf, "%sCommunication Channels Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));

		strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_CHAT)
			? "{200}  [{078}A{200}] {178}[CHAT    ] {078}You hear the chat channel.\n\r"
			: "{200}  [{078}A{200}] {078}[chat    ] {078}You do not hear the chat channel.\n\r");

		strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_ALERTS)
			? "{200}  [{078}B{200}] {178}[ALERTS  ] {078}You hear system alert messages.\n\r"
			: "{200}  [{078}B{200}] {078}[alerts  ] {078}You do not hear system alert messages.\n\r");

		strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_PLAYER)
			? "{200}  [{078}C{200}] {178}[PLAYER  ] {078}You see player logins and events.\n\r"
			: "{200}  [{078}C{200}] {078}[player  ] {078}You do not see player logins and events.\n\r");

		if (IS_IMMORTAL(ch) || PLR_FUNCTION(ch, FUNCTION_HELPER))
		{
			strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_QUESTION)
				? "{200}  [{078}D{200}] {178}[QUESTION] {078}You hear the question channel.\n\r"
				: "{200}  [{078}D{200}] {078}[question] {078}You do not hear the question channel.\n\r");
		}
		
		if (IS_IMMORTAL(ch))
		{
			strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_IMMTALK)
				? "{200}  [{078}E{200}] {178}[IMMTALK ] {078}You hear the immtalk channel.\n\r"
				: "{200}  [{078}E{200}] {078}[immtalk ] {078}You do not hear the immtalk channel.\n\r");

			strcat( buf,  IS_SET(ch->pcdata->channel, CHANNEL_LOGSPEECH)
				? "{200}  [{078}F{200}] {178}[LOGSPCH ] {078}You hear logged player speech and thoughts.\n\r"
				: "{200}  [{078}F{200}] {078}[logspch ] {078}You do not hear logged player speech and thoughts.\n\r");
		}

		strcat( buf, "\n\r{200}  [{078}-{200}] {078}Return\n\r" );

		send_to_char_color(buf, ch);

		RESTRING(ch->pcdata->last_command, "config F ");
		pop_call();
		return;
	}
	do_refresh(ch, "");

	sprintf( buf, "\n\r%sConfiguration Main Menu\n\r\n\r", get_color_string(ch, COLOR_ACCENT, VT102_DIM));
	strcat( buf, "{200}  [{078}A{200}] {078}Display Setup\n\r" );
	strcat( buf, "{200}  [{078}B{200}] {078}Terminal Setup\n\r" );
	strcat( buf, "{200}  [{078}C{200}] {078}Tactical Setup\n\r" );
	strcat( buf, "{200}  [{078}D{200}] {078}Developer Options\n\r" );
	strcat( buf, "{200}  [{078}E{200}] {078}Combat Spamming\n\r" );
	strcat( buf, "{200}  [{078}F{200}] {078}Channel Setup\n\r\n\r" );
	strcat( buf, "{200}  [{078}X{200}] {078}Exit\n\r" );
	send_to_char_color(buf, ch );

	RESTRING( ch->pcdata->last_command, "config " );

	pop_call();
	return;
}

void do_spy( CHAR_DATA *ch, char *argument )
{
	push_call("do_spy(%p,%p)",ch,argument);

	send_to_char("This feature under construction!\n\r", ch);
	pop_call();
	return;
	
	pop_call();
	return;
}

int which_god( CHAR_DATA *ch )
{
	push_call("which_god(%p)",ch);

	if (ch->god != -1)
	{
		pop_call();
		return(ch->god);
	}

	ch->god = GOD_NEUTRAL;

	pop_call();
	return ch->god;
}

CHAR_DATA *lookup_char( char *name )
{
	PLAYER_GAME *gpl;

	push_call("lookup_char(%p)",name);

	for (gpl = mud->f_player ; gpl ; gpl = gpl->next)
	{
		if (!strcasecmp(gpl->ch->name, name))
		{
			pop_call();
			return( gpl->ch );
		}
	}
	pop_call();
	return( NULL);
}


void do_areas( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	char mainauth[32];
	AREA_DATA *pArea;
	int pcnt, mcnt, rcnt;
	CHAR_DATA *rch;
	int vnum;
	int lower_bound = 0;
	int upper_bound = MAX_LEVEL;

	push_call("do_areas(%p,%p)",ch,argument);

	if (argument != NULL && *argument != '\0')
	{
		if (isalpha(argument[0]))
		{
			for (pArea = mud->f_area ; pArea ; pArea = pArea->next)
			{
				if (!str_prefix(argument, pArea->name))
				{
					ch_printf_color(ch, "%s\n\r", pArea->name );
					ch_printf_color(ch, "Suggested levels:          %2d-%2d\n\r", pArea->low_soft_range, pArea->hi_soft_range);
					ch_printf_color(ch, "Min/Max levels:            %2d-%2d\n\r", pArea->low_hard_range, pArea->hi_hard_range);

					pcnt=0;
					mcnt=0;
					rcnt=0;

					for (vnum = pArea->low_r_vnum ; vnum <= pArea->hi_r_vnum ; vnum++)
					{
						if (room_index[vnum] && room_index[vnum]->area == pArea)
						{
							rcnt++;

							for (rch = room_index[vnum]->first_person ; rch ; rch = rch->next_in_room)
							{
								if (IS_NPC(rch) && can_see(ch, rch))
								{
									mcnt++;
								}
								else if (!IS_NPC(rch) && can_see_world(ch, rch))
								{
									pcnt++;
								}
							}
						}
					}
					ch_printf_color(ch, "Rooms in area:              %d\n\r", rcnt );
					ch_printf_color(ch, "Creatures in area:          %d\n\r", mcnt );
/*
					ch_printf_color(ch, "Players in area:            %d\n\r", pcnt );
*/
					ch_printf_color(ch, "Authors of area:            %s\n\r", pArea->authors ? pArea->authors : "none");

					pop_call();
					return;
				}
			}
			send_to_char( "That area was not found.\n\r", ch);
			pop_call();
			return;
		}
		else
		{
			argument = one_argument(argument, arg);

			if (arg[0] != '\0' && is_number(arg))
			{
				lower_bound = URANGE(0, atoi(arg), MAX_LEVEL);
			}
			else
			{
				send_to_char("That area wasn't found.\n\r", ch);
				pop_call();
				return;
			}

			if (argument[0] != '\0' && is_number(argument))
			{
				upper_bound = URANGE(0, atoi(argument), MAX_LEVEL);
			}
			else
			{
				send_to_char("Only two level numbers allowed.\n\r", ch);
				pop_call();
				return;
			}
			if (lower_bound > upper_bound)
			{
				upper_bound = upper_bound + lower_bound;
				lower_bound = upper_bound - lower_bound;
				upper_bound = upper_bound - lower_bound;
			}
		}
	}

	sprintf(buf, "{168} %-10s {178}%-28s  {178}Suggested  {178}Enforced\n\r",	"Author", "Area Name");
	strcat (buf,  "{178}-------------------------------------------------------------------------------\n\r");

	for (pArea = mud->f_area ; pArea ; pArea = pArea->next)
	{
		if (pArea->low_soft_range >= lower_bound
		&&  pArea->hi_soft_range  <= upper_bound)
		{
			one_argument_nolower(pArea->authors, mainauth);
			if (IS_NPC(ch) || !IS_IMMORTAL(ch) || !IS_GOD(ch))
			{
				cat_sprintf(buf, "{200} %-12s {078}%-30s{078}  %2d-%-2d      {078}%2d-%-2d\n\r",
					mainauth,
					pArea->name,
					pArea->low_soft_range,
					pArea->hi_soft_range,
					pArea->low_hard_range,
					pArea->hi_hard_range);
			}
			else
			{
				cat_sprintf(buf, "{200} %-12s {078}%-30s{078}  %2d-%-2d      {078}%2d-%-2d  {178}[%7d]\n\r",
					mainauth,
					pArea->name,
					pArea->low_soft_range,
					pArea->hi_soft_range,
					pArea->low_hard_range,
					pArea->hi_hard_range,
					pArea->low_r_vnum);
			}
		}
	}
	send_to_char_color(buf, ch);
	pop_call();
	return;
}

/*
	Returns a static {color} string - Scandum 29-05-2002
*/

char *get_color_code(CHAR_DATA *ch, int regist, int vt_code)
{
	static char buf[10];

	push_call("get_color_code(%p,%p,%p)",ch,regist,vt_code);

	if (!ch->desc)
	{
		pop_call();
		return "";
	}

	if (CH(ch->desc)->pcdata->ansi == 0 && CH(ch->desc)->pcdata->vt100_flags < VT100_ECMA48)
	{
		pop_call();
		return "";
	}

	sprintf(buf, "{%d%d%d}", vt_code, CH(ch->desc)->pcdata->color[regist] % 10, CH(ch->desc)->pcdata->color[regist] / 10);

	pop_call();
	return( buf );
}

/*
	Returns a static string -1 as register will ignore colors - Chaos 2/24/95
*/

char *get_color_string( CHAR_DATA *ch, int regist , int vt_code )
{
	static char buf[10];

	push_call("get_color_string(%p,%p,%p)",ch,regist,vt_code);

	if (!ch->desc)
	{
		if (regist == COLOR_SPEECH)
		{
			pop_call();
			return ansi_translate("{128}");
		}
		else
		{
			pop_call();
			return "";
		}
	}

	if (CH(ch->desc)->pcdata->ansi == 0 && CH(ch->desc)->pcdata->vt100_flags < VT100_ECMA48)
	{
		pop_call();
		return "";
	}

	if (regist >= 0 && regist < COLOR_MAX )
	{
		strcpy(buf, get_color_diff(ch, 8, 8, 8, CH(ch->desc)->pcdata->color[regist] % 10, CH(ch->desc)->pcdata->color[regist] / 10, vt_code));
	}
	else
	{
		strcpy(buf, get_color_diff(ch, 8, 8, 8, 8, 8, vt_code));
	}

	pop_call();
	return( buf );
}

ROOM_INDEX_DATA * RoomDir( CHAR_DATA *ch, ROOM_INDEX_DATA *in_room, int dir, int dist)
{
	ROOM_INDEX_DATA *room;
	EXIT_DATA *pExit;
	int cnt;

	push_call("RoomDir(%p,%p,%p,%p)",ch,in_room,dir,dist);

	cnt  = dist;
	room = in_room;

	while (cnt > 0)
	{
		if ((pExit = get_exit(room->vnum, dir)) == NULL)
		{
			pop_call();
			return NULL;
		}
		if (IS_SET(pExit->exit_info, EX_CLOSED))
		{
			pop_call();
			return NULL;
		}

		if (IS_SET(pExit->exit_info, EX_HIDDEN) && !can_see_hidden(ch,dir))
		{
			pop_call();
			return NULL;
		}

		if (!can_use_exit(ch, pExit))
		{
			pop_call();
			return NULL;
		}
		room = room_index[room->exit[dir]->to_room];
		cnt--;
	}
	pop_call();
	return( room );
}

void make_length( char *arg, int len )
{
	int leng, spt, cnt;
	char tbuf[MAX_INPUT_LENGTH];

	push_call("make_length(%p,%p)",arg,len);

	*(arg+len) = '\0';
	leng = strlen( arg );
	spt = (len-leng)/2;
	if( spt > 0 )
	{
		strcpy( tbuf, arg );
		for( cnt=0; cnt<spt; cnt++)
			*(arg+cnt) = ' ';
		strcpy( arg+spt, tbuf );
	}
	leng = strlen( arg );
	for( ; leng<len; leng++)
		*(arg+leng) = ' ';
	*(arg+len) = '\0';

	pop_call();
	return;
}

void strNameRoom( char *buf, ROOM_INDEX_DATA *room )
{
	int offset;
	char *buf3;

	push_call("strNameRoom(%p,%p)",buf,room);

	offset = 0;
	buf3 = get_dynamic_room_title(room);

	if( *buf3=='t' || *buf3=='T' )
	{
		if( *(buf3+1)=='h' || *(buf3+1)=='H' )
		{
			if( *(buf3+2)=='e' || *(buf3+2)=='E' )
			{
				if( *(buf3+3)==' ' )
				{
					offset = 4;
				}
			}
		}
	}
	if( *buf3=='a' || *buf3=='A' )
	{
		if( *(buf3+1)==' ' )
		{
			offset = 2;
		}
	}

	strcpy( buf, capitalize( buf3 + offset) );
	pop_call();
	return;
}

/*
 * Scan uses the compass code to drow multiple rooms
 * in all directions out to line of sight - Kregor
 */
void do_scan( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char tbuf1[MAX_INPUT_LENGTH];
	char tbuf3[MAX_INPUT_LENGTH];
	ROOM_INDEX_DATA *room1, *room2;
	int wwords, wrds;
	int col;
	int cnt;
	int map_range;

	push_call("do_scan(%p,%p)",ch,argument);

	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}

	if (IS_AFFECTED(ch, AFF_BLIND))
	{
		send_to_char("You can't see a thing!\n\r",ch);
		pop_call();
		return;
	}

	map_range = URANGE(0, argument[0] ? atoi(argument) : 4, 4);

	wrds = 1 + map_range * 2;
	wwords = (79 - 2*(1+map_range*2))/ wrds;
	wwords = UMIN( wwords, 12 );

	strcpy(buf, "{088}");
	*tbuf3 = '\0';
	room1 = NULL;
	room2 = NULL;

	/* The north and up directions */
	for( cnt = map_range ; cnt > 0 ; cnt--)
	{
		room1 = RoomDir( ch, ch->in_room, 0, cnt);
		room2 = RoomDir( ch, ch->in_room, 4, cnt);
		if( room1 == NULL && room2 == NULL )
			continue;

		/* Correct for centering */
		make_length( tbuf3, (79 - (map_range * 2 + 1) * (wwords + 2)) / 2);
		strcat( buf, tbuf3 );

		/* Top alignment */
		make_length( tbuf3, map_range * (wwords + 2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			make_length( tbuf3, 1 + wwords / 2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords / 2 - 1  );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}

		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			make_length( tbuf3, 1+wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 0 ) )
				strcat( buf, "|" );
			else
				if( is_valid_exit( ch, room2, 0 ) )
			strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2 - 1  );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 4 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room2, 4 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );

		/* Correct for centering */
		make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
		strcat( buf, tbuf3 );
		/* Center alignment */
		make_length( tbuf3, map_range*(wwords+2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			col = sector_table[room1->sector_type].color;
			cat_sprintf(buf, "{%d%d%d}", col / 128, col % 8, (col % 128) / 8);
			strNameRoom( tbuf1, room1 );
			make_length( tbuf1, wwords );
			strcat( buf, tbuf1 );
			strcat( buf, "{088}");
			if( is_valid_exit( ch, room1, 1 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room1, 1 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}
		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			if( is_valid_exit( ch, room2, 3 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room2, 3 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			col = sector_table[room2->sector_type].color;
			cat_sprintf(buf, "{%d%d%d}", col / 128, col % 8, (col % 128) / 8);
			strNameRoom( tbuf1, room2 );
			make_length( tbuf1, wwords );
			strcat( buf, tbuf1 );
			strcat( buf, "{088}");
			if( is_valid_exit( ch, room2, 1 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room2, 1 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );

		/* Correct for centering */
		make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
		strcat( buf, tbuf3 );

		/* Bottom alignment */
		make_length( tbuf3, map_range*(wwords+2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2  );
			strcat( buf, tbuf3 );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}
		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			if( is_valid_exit( ch, room2, 5 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room2, 5 ) )
				strcat( buf, "/" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 2 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room2, 2 ) )
				strcat( buf, "|" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );
	}

	/* The center row, top alignment */

	make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2  );
	strcat( buf, tbuf3 );
	for( cnt=map_range; cnt>=(0-map_range); cnt--)
	{
		if( cnt>0 )
			room1 = RoomDir( ch, ch->in_room, 3, cnt);
		else
		if( cnt==0 )
			room1 = ch->in_room;
		else
			room1 = RoomDir( ch, ch->in_room, 1, 0-cnt);
		if( room1 == NULL )
		{
			make_length( tbuf3, wwords + 2 );
			strcat( buf, tbuf3 );
		}
		else
		{
			make_length( tbuf3, wwords/2+1 );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2 - 1  );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
		}
	}
	strcat( buf, "\n\r" );

	/* The center row, center alignment */

	*tbuf3='\0';
	make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
	strcat( buf, tbuf3 );
	for( cnt=map_range; cnt>=(0-map_range); cnt--)
	{
		if( cnt>0 )
			room1 = RoomDir( ch, ch->in_room, 3, cnt);
		else
		if( cnt==0 )
			room1 = ch->in_room;
		else
			room1 = RoomDir( ch, ch->in_room, 1, 0-cnt);
		if( room1 == NULL )
		{
			make_length( tbuf3, wwords + 2 );
			strcat( buf, tbuf3 );
		}
		else
		{
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			col = sector_table[room1->sector_type].color;
			cat_sprintf(buf, "{%d%d%d}", col / 128, col % 8, (col % 128) / 8);
			if (ch->in_room == room1)
			{
				strcpy(tbuf1, "You are here");
			}
			else
			{
				strNameRoom(tbuf1, room1);
			}
			make_length( tbuf1, wwords );
			strcat( buf, tbuf1 );
			strcat( buf, "{088}");
			if (is_valid_exit(ch, room1, 1))
			{
				strcat( buf, "-" );
			}
			else if (is_valid_exit(ch, room1, 1))
			{
				strcat( buf, "+" );
			}
			else
			{
				strcat( buf, " " );
			}
		}
	}
	strcat( buf, "\n\r" );

	/* The center row, bottom alignment */

	*tbuf3='\0';
	make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
	strcat( buf, tbuf3 );
	for( cnt=map_range; cnt>=(0-map_range); cnt--)
	{
		if( cnt>0 )
			room1 = RoomDir( ch, ch->in_room, 3, cnt);
		else
		if( cnt==0 )
			room1 = ch->in_room;
		else
			room1 = RoomDir( ch, ch->in_room, 1, 0-cnt);
		if( room1 == NULL )
		{
			make_length( tbuf3, wwords + 2 );
			strcat( buf, tbuf3 );
		}
		else
		{
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords/2 );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2 );
			strcat( buf, tbuf3 );
		}
	}
	strcat( buf, "\n\r" );

	/* The Down and South directions */
	for( cnt=1; cnt<=map_range; cnt++)
	{
		room1 = RoomDir( ch, ch->in_room, 5, cnt);
		room2 = RoomDir( ch, ch->in_room, 2, cnt);
		if( room1==NULL && room2==NULL )
			continue;

		/* Correct for centering */
		make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
		strcat( buf, tbuf3 );

		/* Top alignment */
		make_length( tbuf3, (map_range-cnt)*(wwords+2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			make_length( tbuf3, 1+wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 0 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2 - 1  );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 4 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}

		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			make_length( tbuf3, 1+wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 0 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room2, 0 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2 - 1  );
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 4 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room2, 4 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );

		/* Correct for centering */
		make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
		strcat( buf, tbuf3 );

		/* Center alignment */
		make_length( tbuf3, (map_range-cnt)*(wwords+2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room1, 3 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			col = sector_table[room1->sector_type].color;
			cat_sprintf(buf, "{%d%d%d}", col / 128, col % 8, (col % 128) / 8);

			strNameRoom( tbuf1, room1 );
			make_length( tbuf1, wwords );
			strcat( buf, tbuf1 );
			strcat( buf, "{088}");
			if( is_valid_exit( ch, room1, 1 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room1, 1 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}

		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			if( is_valid_exit( ch, room2, 3 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room2, 3 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			col = sector_table[room2->sector_type].color;
			cat_sprintf(buf, "{%d%d%d}", col / 128, col % 8, (col % 128) / 8);
			strNameRoom( tbuf1, room2 );
			make_length( tbuf1, wwords );
			strcat( buf, tbuf1 );
			strcat( buf, "{088}");
			if( is_valid_exit( ch, room2, 1 ) )
				strcat( buf, "-" );
			else
			if( is_valid_exit( ch, room2, 1 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );

		/* Correct for centering */
		make_length( tbuf3, (79-(map_range*2+1)*(wwords+2))/2);
		strcat( buf, tbuf3 );

		/* Bottom alignment */
		make_length( tbuf3, (map_range-cnt)*(wwords+2));
		strcat( buf, tbuf3 );
		if( room1 != NULL )
		{
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room1, 5 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room1, 2 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords - wwords/2  );
				strcat( buf, tbuf3 );
		}
		else
		{
			make_length( tbuf3, wwords+2 );
			strcat( buf, tbuf3 );
		}

		make_length( tbuf3, (wwords+2)*( cnt-1 ) );
		strcat( buf, tbuf3 );
		if( room2 != NULL )
		{
			if( is_valid_exit( ch, room2, 5 ) )
				strcat( buf, "/" );
			else
			if( is_valid_exit( ch, room2, 5 ) )
				strcat( buf, "X" );
			else
				strcat( buf, " " );
			make_length( tbuf3, wwords/2);
			strcat( buf, tbuf3 );
			if( is_valid_exit( ch, room2, 2 ) )
				strcat( buf, "|" );
			else
			if( is_valid_exit( ch, room2, 2 ) )
				strcat( buf, "+" );
			else
				strcat( buf, " " );
		}
		strcat( buf, "\n\r" );
	}
	send_to_char_color( buf, ch );

	pop_call();
	return;
}

void do_afk( CHAR_DATA *ch, char *argument )
{
	push_call("do_afk(%p,%p)",ch,argument);

	if ( IS_NPC(ch) )
	{
		pop_call();
		return;
	}

	if IS_SET(ch->act, PLR_AFK)
	{
		REMOVE_BIT(ch->act, PLR_AFK);
		send_to_char( "You are no longer afk.\n\r", ch );
		act( "$n is no longer afk.", ch, NULL, NULL, TO_CAN_SEE);
		pop_call();
		return;
	}
	else
	{
		SET_BIT(ch->act, PLR_AFK);
		send_to_char( "You are now afk.\n\r", ch );
		act( "$n is now afk.", ch, NULL, NULL, TO_CAN_SEE);
		pop_call();
		return;
	}
	pop_call();
	return;
}

/*
 * Changes the name of a character if requested.
 * Immortals can change players' names - Kregor
 */
void do_rename (CHAR_DATA* ch, char* argument)
{
	char old_name[MAX_INPUT_LENGTH],
	new_name[MAX_INPUT_LENGTH],
	strsave [MAX_INPUT_LENGTH],
	log_buf [MAX_STRING_LENGTH];
	CHAR_DATA *victim;
	AUTH_LIST *auth = NULL;
	DESCRIPTOR_DATA *d;
	FILE* file;

	push_call("do_rename(%p,%p)",ch,argument);

	if (IS_IMMORTAL(ch))
	{
		argument = one_argument(argument, old_name);
		argument = one_argument(argument, new_name);
	
		if (old_name[0] == '\0')
		{
			send_to_char ("Rename who?\n\r",ch);
			pop_call();
			return;
		}
	
		if (new_name[0] == '\0')
		{
			send_to_char ("Rename them to what?\n\r",ch);
			pop_call();
			return;
		}
	
		if ((victim = get_player_world(ch, old_name)) == NULL)
		{
			send_to_char ("There is no such player online.\n\r",ch);
			pop_call();
			return;
		}

		if (victim != ch && (!IS_GOD(ch) || victim->level >= ch->level))
		{
			send_to_char ("You failed.\n\r", ch);
			pop_call();
			return;
		}
		auth = get_auth_name (victim->name);
	}
	else
	{
		if ((auth = get_auth_name (ch->name)) == NULL
		|| !IS_SET(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_NAME))
		{
			send_to_char("You have not been asked to change your name.\n\r", ch);
			pop_call();
			return;
		}
		argument = one_argument(argument, new_name);
		strcpy (old_name, ch->name);
	
		if (new_name[0] == '\0')
		{
			send_to_char ("Rename yourself to what?\n\r",ch);
			pop_call();
			return;
		}
		victim = ch;
	}

	if (!check_parse_name(new_name, TRUE))
	{
		send_to_char ("The new name is illegal.\n\r",ch);
		pop_call();
		return;
	}

	if (!strcasecmp (ch->name, new_name))
	{
		send_to_char ("That's already your name!\r\n", ch);
		return;
	}

	sprintf(strsave, "%s/%c/%s", PLAYER_DIR, tolower(new_name[0]), capitalize_name(new_name));

	close_reserve();

	file = my_fopen(strsave, "r", TRUE);

	if (file)
	{
		send_to_char ("A player with that name already exists!\n\r",ch);

		my_fclose(file);
		open_reserve();

		pop_call();
		return;
	}

	open_reserve();

	if (get_player_world(ch, new_name))
	{
		send_to_char ("A player with that name is currently playing.\n\r",ch);
		pop_call();
		return;
	}

	save_char_obj(victim, BACKUP_SAVE);

	sprintf(strsave, "%s/%c/%s", PLAYER_DIR, tolower(victim->name[0]), capitalize_name(victim->name));

	STRFREE(victim->name);
	victim->name = STRALLOC(capitalize_name(new_name));

	RESTRING(pvnum_index[victim->pcdata->pvnum]->name, victim->name);

	save_char_obj(victim, NORMAL_SAVE);

	unlink(strsave);

	if (victim != ch)
	{
		ch_printf_color(ch, "Character %s renamed to %s.\n\r", old_name, new_name);
		log_printf("[%s] renamed char %s to %s.\n\r", ch->name, old_name, new_name);
		ch_printf_color(victim, "The gods have renamed you to %s.\n\r", victim->name);
	}
	else
	{
		send_to_char ("Your name has been changed and is being submitted for approval.\r\n", ch);
	}

	if (auth != NULL)
	{
		if (auth->name)
			STRFREE (auth->name);
		auth->name = STRALLOC (capitalize_name(new_name));
		auth->state = AUTH_ONLINE;
		if (auth->change_by)
			STRFREE (auth->change_by);
		REMOVE_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_NAME);
		SET_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_AUTH_NAME);
		save_auth_list();
		save_players();
	}
	
	sprintf(log_buf, "{200}AUTH: {178}%s {078}has changed their name to {178}%s{078}.\n\r", capitalize_name(old_name), ch->name);
	for (d = mud->f_desc; d; d = d->next)
		if (d->connected == CON_PLAYING && d->character && IS_IMMORTAL(d->character))
			send_to_char_color(log_buf, d->character);

	pop_call();
	return;
}

/* 
 * This sets the adjective for a character, 
 * adjective must be authorized - Kregor
 */
void do_adjective(CHAR_DATA * ch, char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char log_buf[MAX_STRING_LENGTH];
	DESCRIPTOR_DATA *d;
	AUTH_LIST *auth;

	push_call("(do_adjective%p,%p)",ch,argument);
	
	if (IS_NPC(ch))
	{
		pop_call();
		return;
	}
	
	if (ch->pcdata->adjective && ch->pcdata->adjective[0] != '\0')
	{
		if ((auth = get_auth_name (ch->name)) == NULL
		|| !IS_SET(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_ADJ))
		{
			send_to_char("You have not been asked to change your adjective.\n\r", ch);
			pop_call();
			return;
		}
	}

	if (argument[0] == '\0')
	{
		send_to_char ("Set your adjective to what?\n\r", ch);
		pop_call();
		return;
	}

	if (strlen(argument) > 30)
	{
		send_to_char("Your adjective must be 30 characters or less.\n\r", ch);
		pop_call();
		return;
	}
		
	sprintf(buf, "%s", argument);
	smash_tilde(buf);
	if (ch->pcdata->adjective)
		STRFREE(ch->pcdata->adjective);
	ch->pcdata->adjective = STRALLOC(buf);
	ch_printf_color(ch, "Your adjective is set to %s and is awaiting approval.\n\r", buf);

	if ((auth = get_auth_name(ch->name)) != NULL)
	{
		if (auth->adjective)
			STRFREE (auth->adjective);
		auth->adjective = STRALLOC (buf);
		auth->state = AUTH_ONLINE;
		if (auth->change_by)
			STRFREE (auth->change_by);
		REMOVE_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_ADJ);
		SET_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_AUTH_ADJ);
		save_auth_list();
		save_players();
	}
	else
	{
		add_to_auth(ch);
		REMOVE_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_ADJ);
		SET_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_AUTH_ADJ);
		save_players();
	}

	sprintf(log_buf, "{200}AUTH: {078}%s has set their adjective to {178}%s{078}.\n\r", ch->name, buf);
	for (d = mud->f_desc; d; d = d->next)
		if (d->connected == CON_PLAYING && d->character && IS_IMMORTAL(d->character))
			send_to_char_color(log_buf, d->character);
}

void do_description( CHAR_DATA *ch, char *argument )
{
	AUTH_LIST *auth;
	DESCRIPTOR_DATA *d;
	char log_buf[MAX_INPUT_LENGTH];

	push_call("do_description(%p,%p)",ch,argument);

	CHECK_EDITMODE( ch );

	if (!ch->desc)
	{
		bug( "do_description: no descriptor", 0 );
		pop_call();
		return;
	}

	switch( ch->pcdata->editmode )
	{
		default:
			bug( "do_description: illegal editmode", 0 );
			pop_call();
			return;

		case MODE_RESTRICTED:
			send_to_char( "You cannot use this command from while editing something else.\n\r",ch);
			pop_call();
			return;

		case MODE_NONE:
			ch->pcdata->editmode = MODE_PERSONAL_DESC;
			ch->pcdata->tempmode  = MODE_NONE;
			start_editing( ch, ch->description );
			pop_call();
			return;

		case MODE_PERSONAL_DESC:
			STRFREE (ch->description );
			ch->description = copy_buffer( ch );
			stop_editing( ch );
			ch_printf_color(ch, "Your description is set and is awaiting approval.\n\r");
			if ((auth = get_auth_name(ch->name)) != NULL)
			{
				if (auth->descr)
					STRFREE (auth->descr);
				auth->descr = STRALLOC (ch->description);
				auth->state = AUTH_ONLINE;
				if (auth->change_by)
					STRFREE (auth->change_by);
				REMOVE_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_DESC);
				SET_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_AUTH_DESC);
				save_auth_list();
				save_players();
			}
			else
			{
				add_to_auth(ch);
				REMOVE_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_CHNG_DESC);
				SET_BIT(pvnum_index[ch->pcdata->pvnum]->flags, PVNUM_AUTH_DESC);
				save_players();
			}		
			sprintf(log_buf, "{200}AUTH: {078}%s has set their description for approval.\n\r", ch->name);
			for (d = mud->f_desc; d; d = d->next)
				if (d->connected == CON_PLAYING && d->character && IS_IMMORTAL(d->character))
					send_to_char_color(log_buf, d->character);
			pop_call();
			return;
	}
	pop_call();
	return;
}

void do_bio( CHAR_DATA *ch, char *argument )
{
	push_call("do_bio(%p,%p)",ch,argument);

	CHECK_EDITMODE( ch );

	if (!ch->desc)
	{
		bug( "do_bio: no descriptor", 0 );
		pop_call();
		return;
	}

	switch( ch->pcdata->editmode )
	{
		default:
			bug( "do_bio: illegal editmode", 0 );
			pop_call();
			return;

		case MODE_RESTRICTED:
			send_to_char( "You cannot use this command while editing something else.\n\r",ch);
			pop_call();
			return;

		case MODE_NONE:
			ch->pcdata->editmode = MODE_PERSONAL_BIO;
			ch->pcdata->tempmode  = MODE_NONE;
			start_editing( ch, ch->pcdata->bio );
			pop_call();
			return;

		case MODE_PERSONAL_BIO:
			STRFREE ( ch->pcdata->bio );
			ch->pcdata->bio = copy_buffer( ch );
			stop_editing( ch );
			pop_call();
			return;
	}
	pop_call();
	return;
}

void do_metamagic ( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	bool known = FALSE;

	push_call("do_metamagic(%p,%p)",ch,argument);
	
	if (!is_caster(ch))
	{
		send_to_char("You do not cast any spells.\n\r", ch);
		pop_call();
		return;
	}
	
	if (argument[0] == '\0')
	{
		sprintf(buf, "{200}%s's Metamagic Feats:\n\r", get_name(ch));
		
		if (learned(ch, gsn_disguise_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_DISGUISE))
				cat_sprintf(buf, "{178}  [DISGUISE] {078}You disguise your casting from normal detection.\n\r");
			else
				cat_sprintf(buf, "{078}  [disguise] {078}Your casting uses somatic and verbal components normally.\n\r");
		}
		if (learned(ch, gsn_empower_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_EMPOWER))
				cat_sprintf(buf, "{178}  [EMPOWER ] {078}Variable effect of spells is 1.5 times normal.\n\r");
			else
				cat_sprintf(buf, "{078}  [empower ] {078}Variable effect of spells is normal.\n\r");
		}
		if (learned(ch, gsn_enlarge_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_ENLARGE))
				cat_sprintf(buf, "{178}  [ENLARGE ] {078}You cast your ranged spells at double range.\n\r");
			else
				cat_sprintf(buf, "{078}  [enlarge ] {078}Your spells have their normal range.\n\r");
		}
		if (learned(ch, gsn_extend_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_EXTEND))
				cat_sprintf(buf, "{178}  [EXTEND  ] {078}Spells have double duration.\n\r");
			else
				cat_sprintf(buf, "{078}  [extend  ] {078}Spells have normal duration.\n\r");
		}
		if (learned(ch, gsn_maximize_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_MAXIMIZE))
				cat_sprintf(buf, "{178}  [MAXIMIZE] {078}Variable effect of spells is maximized.\n\r");
			else
				cat_sprintf(buf, "{078}  [maximize] {078}Variable effect of spells is normal.\n\r");
		}
		if (learned(ch, gsn_merciful_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_MERCIFUL))
				cat_sprintf(buf, "{178}  [MERCIFUL] {078}Your offensive spells do nonlethal damage.\n\r");
			else
				cat_sprintf(buf, "{078}  [merciful] {078}Your offensive spells do normal (lethal) damage.\n\r");
		}
		if (learned(ch, gsn_persistent_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_PERSIST))
				cat_sprintf(buf, "{178}  [PERSIST ] {078}Your personal spells last for 24 hours.\n\r");
			else
				cat_sprintf(buf, "{078}  [persist ] {078}Your personal spells last their normal duration.\n\r");
		}
		if (learned(ch, gsn_quicken_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_QUICKEN))
				cat_sprintf(buf, "{178}  [QUICKEN ] {078}Casting a spell is a swift action.\n\r");
			else
				cat_sprintf(buf, "{078}  [quicken ] {078}Casting a spell takes normal casting time.\n\r");
		}
		if (learned(ch, gsn_reach_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_REACH))
				cat_sprintf(buf, "{178}  [REACH   ] {078}Your touch spells are close range rays.\n\r");
			else
				cat_sprintf(buf, "{078}  [reach   ] {078}Your touch spells function as normal.\n\r");
		}
		if (learned(ch, gsn_repeat_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_REPEAT))
				cat_sprintf(buf, "{178}  [REPEAT  ] {078}Your ranged spells release twice per casting.\n\r");
			else
				cat_sprintf(buf, "{078}  [repeat  ] {078}Your spells release once per casting.\n\r");
		}
		if (learned(ch, gsn_sacred_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_SACRED))
				cat_sprintf(buf, "{178}  [SACRED  ] {078}Your divine spells deal divine damage.\n\r");
			else
				cat_sprintf(buf, "{078}  [sacred  ] {078}Your divine spells use their normal energy type.\n\r");
		}
		if (learned(ch, gsn_silent_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_SILENT))
				cat_sprintf(buf, "{178}  [SILENT  ] {078}Your spells need no verbal component.\n\r");
			else
				cat_sprintf(buf, "{078}  [silent  ] {078}You must speak verbal components.\n\r");
		}
		if (learned(ch, gsn_still_spell))
		{
			known = TRUE;
			if (IS_SET(ch->metamagic, METAMAGIC_STILL))
				cat_sprintf(buf, "{178}  [STILL   ] {078}You do not have to move to cast spells.\n\r");
			else
				cat_sprintf(buf, "{078}  [still   ] {078}You must move to gesture somatic components.\n\r");
		}
		if (!known)
				cat_sprintf(buf, "  {078}None.\n\r");		
		send_to_char_color(buf, ch);
		pop_call();
		return;
	}
	
	if (!strcasecmp(argument, "disguise"))
	{
		if (!learned(ch, gsn_disguise_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_DISGUISE);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "empower"))
	{
		if (!learned(ch, gsn_empower_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_EMPOWER);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "enlarge"))
 	{
 		if (!learned(ch, gsn_enlarge_spell))
 		{
 			send_to_char("You don't know that feat.\n\r", ch);
 			pop_call();
 			return;
 		}
 		TOGGLE_BIT(ch->metamagic, METAMAGIC_ENLARGE);
 		do_metamagic(ch, "");
 		pop_call();
 		return;
 	}
	if (!strcasecmp(argument, "extend"))
	{
		if (!learned(ch, gsn_extend_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_EXTEND);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "maximize"))
	{
		if (!learned(ch, gsn_maximize_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_MAXIMIZE);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "merciful"))
	{
		if (!learned(ch, gsn_merciful_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_MERCIFUL);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "persist"))
	{
		if (!learned(ch, gsn_persistent_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_PERSIST);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "quicken"))
	{
		if (!learned(ch, gsn_quicken_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_QUICKEN);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "reach"))
	{
		if (!learned(ch, gsn_reach_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_REACH);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "repeat"))
	{
		if (!learned(ch, gsn_repeat_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_REPEAT);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "sacred"))
	{
		if (!learned(ch, gsn_sacred_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_SACRED);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "silent"))
	{
		if (!learned(ch, gsn_silent_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_SILENT);
		do_metamagic(ch, "");
		pop_call();
		return;
	}
	if (!strcasecmp(argument, "still"))
	{
		if (!learned(ch, gsn_still_spell))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->metamagic, METAMAGIC_STILL);
		do_metamagic(ch, "");
		pop_call();
		return;
	}	
	do_metamagic(ch, "");
	pop_call();
	return;
}

void do_combatmode ( CHAR_DATA *ch, char *argument )
{
	char buf[MAX_STRING_LENGTH];
	char arg1[MAX_STRING_LENGTH];
	int value = 0;

	push_call("do_combatmode(%p,%p)",ch,argument);
	
	if (argument[0] == '\0')
	{
		sprintf(buf, "{200}%s's Combat Modes:\n\r", get_name(ch));
		
		if (learned(ch, gsn_arterial_strike))
		{
			if (IS_SET(ch->combatmode, COMBAT_ARTERIAL))
				cat_sprintf(buf, "{178}  [ARTERIAL ] {078}You sacrifice 1 die of sneak attack for 1 point bleeding damage.\n\r");
			else
				cat_sprintf(buf, "{078}  [arterial ] {078}You sneak attack with no bleeding damage for your full bonus.\n\r");
		}
		if (learned(ch, gsn_called_shot))
		{
			value = UMIN(stat_bonus(TRUE, ch, APPLY_DEX), base_attack(ch));
			if (IS_SET(ch->combatmode, COMBAT_CALLED))
				cat_sprintf(buf, "{178}  [CALLED  ] {078}You loose projectiles at a -%d penalty to hit for +%d damage.\n\r", value, value);
			else
				cat_sprintf(buf, "{078}  [called  ] {078}You loose missiles normally for normal damage.\n\r");
		}
		if (learned(ch, gsn_cripple))
		{
			if (IS_SET(ch->combatmode, COMBAT_CRIPPLE))
				cat_sprintf(buf, "{178}  [CRIPPLING] {078}You sacrifice 1 die of sneak attack for 2 points of STR damage.\n\r");
			else
				cat_sprintf(buf, "{078}  [crippling] {078}You sneak attack with no STR damage for your full damage bonus.\n\r");
		}
		if (learned(ch, gsn_combat_expertise))
			value = UMIN(stat_bonus(TRUE, ch, APPLY_DEX), base_attack(ch));
		else
			value = 4;
		if (IS_SET(ch->combatmode, COMBAT_DEFENSIVE))
			cat_sprintf(buf, "{178}  [DEFENSIVE] {078}You attack in melee at a -%d penalty to hit for a +%d dodge bonus.\n\r", value, learned(ch, gsn_combat_expertise) ? value : value / 2);
		else
			cat_sprintf(buf, "{078}  [defensive] {078}You are not fighting melee defensively.\n\r");
		if (learned(ch, gsn_flurry))
		{
			if (base_attack(ch) < 5)
				value = -2;
			else if (base_attack(ch) < 10)
				value = -1;
			else
				value = 0;
			if (IS_SET(ch->combatmode, COMBAT_FLURRY))
				cat_sprintf(buf, "{178}  [FLURRY   ] {078}You strike with a flurry of blows at a %d penalty.\n\r", value);
			else
				cat_sprintf(buf, "{078}  [flurry   ] {078}You do not strike with a flurry of blows.\n\r");
		}
		if (learned(ch, gsn_manyshot))
		{
			if (IS_SET(ch->combatmode, COMBAT_MANYSHOT))
				cat_sprintf(buf, "{178}  [MANYSHOT ] {078}You fire multiple arrows per shot, at a -2 penalty to hit.\n\r");
			else
				cat_sprintf(buf, "{078}  [manyshot ] {078}You fire a single arrow per attack.\n\r");
		}
		if (IS_SET(ch->combatmode, COMBAT_NONLETHAL))
			cat_sprintf(buf, "{178}  [NONLETHAL] {078}You deal nonlethal damage with your lethal weapons.\n\r");
		else
			cat_sprintf(buf, "{078}  [nonlethal] {078}You deal lethal damage with your leathal weapons.\n\r");
		if (learned(ch, gsn_power_attack))
		{
			value = UMIN(stat_bonus(TRUE, ch, APPLY_STR), base_attack(ch));
			if (IS_SET(ch->combatmode, COMBAT_POWER))
				cat_sprintf(buf, "{178}  [POWER    ] {078}You attack in melee at a -%d penalty to hit for +%d damage.\n\r", value, value);
			else
				cat_sprintf(buf, "{078}  [power    ] {078}You attack in melee normally for normal damage.\n\r");
		}
		if (learned(ch, gsn_rapid_shot))
		{
			if (IS_SET(ch->combatmode, COMBAT_RAPIDSHOT))
				cat_sprintf(buf, "{178}  [RAPIDSHOT] {078}You get an extra shot per round at a -2 to all attacks.\n\r");
			else
				cat_sprintf(buf, "{078}  [rapidshot] {078}You are not using your rapid shot feat.\n\r");
		}
		send_to_char_color(buf, ch);
		pop_call();
		return;
	}
	
	if (in_combat(ch) && !is_active(ch))
	{
		send_to_char("You can only change your combat modes during your turn.\n\r", ch);
		pop_call();
		return;
	}
	if (IS_SET(ch->attack, ATTACK_MELEE))
	{
		send_to_char("You cannot set your combat mode after making attacks.\n\r", ch);
		pop_call();
		return;
	}
		
	argument = one_argument(argument, arg1);
	
	int bs_dice = ROUNDUP(multi_skill_level(ch, gsn_backstab) / 2);

	if (!str_prefix(arg1, "flurry"))
	{
		if (!learned(ch, gsn_flurry))
		{
			send_to_char("You don't have that ability.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_FLURRY);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "manyshot"))
	{
		if (!learned(ch, gsn_manyshot))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_MANYSHOT);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "rapidshot"))
	{
		if (!learned(ch, gsn_rapid_shot))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_RAPIDSHOT);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "nonlethal"))
	{
		TOGGLE_BIT(ch->combatmode, COMBAT_NONLETHAL);
		do_combatmode(ch, "");
		pop_call();
		return;
	}	
	if (!str_prefix(arg1, "power"))
	{
		if (!learned(ch, gsn_power_attack))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_POWER);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "called"))
	{
		if (!learned(ch, gsn_called_shot))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_CALLED);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "defensive"))
	{
		TOGGLE_BIT(ch->combatmode, COMBAT_DEFENSIVE);
		do_combatmode(ch, "");
		pop_call();
		return;
	}	
	if (!str_prefix(arg1, "cripple"))
	{
		if (!learned(ch, gsn_cripple))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		if (!IS_SET(ch->combatmode, COMBAT_CRIPPLE) && bs_dice - 1 <= 0)
		{
			send_to_char("You do not have enough damage dice to sacrifice for this ability.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_CRIPPLE);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	if (!str_prefix(arg1, "arterial"))
	{
		if (!learned(ch, gsn_arterial_strike))
		{
			send_to_char("You don't know that feat.\n\r", ch);
			pop_call();
			return;
		}
		if (!IS_SET(ch->combatmode, COMBAT_ARTERIAL) && bs_dice - 1 <= 0)
		{
			send_to_char("You do not have enough damage dice to sacrifice for this ability.\n\r", ch);
			pop_call();
			return;
		}
		TOGGLE_BIT(ch->combatmode, COMBAT_ARTERIAL);
		do_combatmode(ch, "");
		pop_call();
		return;
	}
	do_combatmode(ch, "");
	pop_call();
	return;
}