SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * 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 Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 ****************************************************************************/
// character.cpp

#include	"stdafx.h"
#include	"smaug.h"
#include	"language.h"
#include	"skill.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"deity.h"
#include	"area.h"
#include	"menus.h"
#include	"editor.h"
#include	"races.h"
#include	"class.h"
#include	"descriptor.h"
#include	"character.h"
#include	"sysdata.h"
#include	"SmaugFiles.h"
#include	"Exits.h"

pc_data::~pc_data ()
{
	STRFREE (m_pClanName);
	STRFREE (m_pCouncilName);
	delete m_pPassWord;
	delete m_pBamfin;
	delete m_pBamfout;
	delete m_pRank;
	STRFREE (m_pTitle);
	STRFREE (bio); 
	delete m_pBestowments;
	delete m_pHomepage;
	STRFREE (authed_by);
	STRFREE (m_Prompt);
	STRFREE (m_Fprompt);
	STRFREE (m_Subprompt);
	
	STRFREE (helled_by);
	STRFREE (m_pDeityName);
}


// Clear a new character.
void CCharacter::InitChar ()
{
	Empty ();
	SetLogonTime (CurrentTime);
	SetArmor (100);
	SetPosition (POS_STANDING);
	SetHp (20);
	SetMaxHp (20);
	SetMana (100);
	SetMaxMana (100);
	SetMove (100);
	SetMaxMove (100);
	m_Height = 72;
	m_Weight = 180;
	SetClass (NPC_WARRIOR);
	SetSpeaking (LanguageTable.GetCommon ());
	SetSpeaks (LanguageTable.GetCommon ());
	barenumdie		= 1;
	baresizedie		= 4;
	perm_str		= 13;
	perm_dex		= 13;
	perm_int		= 13;
	perm_wis		= 13;
	perm_cha		= 13;
	perm_con		= 13;
	perm_lck		= 13;
}


int CCharacter::GetManaCost (int sn)
{
	return IsNpc () ? 0 : UMAX (SkillTable.GetMinMana (sn),
		100 / (2 + m_Level - SkillTable.GetClassLevel (sn, m_Class)));
}


// Retrieve a character's trusted level for permission checking.
int CCharacter::GetTrustLevel ()
{
	CCharacter	*ch = this;

	if (m_pDesc && m_pDesc->m_pOriginal) then
		ch = m_pDesc->m_pOriginal;

	if (ch->GetTrust ()) then return ch->GetTrust ();

	if (ch->IsNpc () && ch->GetLevel () >= LEVEL_AVATAR) then
		return LEVEL_AVATAR;

    if (ch->GetLevel () >= LEVEL_NEOPHYTE && ch->IsRetired ())
      return LEVEL_NEOPHYTE;

	return ch->GetLevel ();
}


// Retrieve a character's age.
int CCharacter::GetAge ()
{
	return 17 + (m_Played + (CurrentTime - m_LogTime).GetTotalSeconds ()) / 7200;

	// 12240 assumes 30 second hours, 24 hours a day, 20 day - Kahn
}


// Retrieve character's current strength.
int CCharacter::GetCurrentStrength ()
{
	short max;

	if (IsNpc () || ClassTable.GetAttrPrime (m_Class) == APPLY_STR) then 
		max = 25;
	else
		max = 20;

	return URANGE (3, perm_str + mod_str, max);
}


// Retrieve character's current intelligence.
int CCharacter::GetIntelligence ()
{
	short max;

	if (IsNpc () || ClassTable.GetAttrPrime (m_Class) == APPLY_INT) then
		max = 25;
	else max = 20;

    return URANGE (3, perm_int + mod_int, max);
}



// Retrieve character's current wisdom.
int CCharacter::GetWisdom ()
{
	short max;

	if (IsNpc () || ClassTable.GetAttrPrime (m_Class) == APPLY_WIS) then
		max = 25;
	else max = 22;

	return URANGE (3, perm_wis + mod_wis, max);
}



// Retrieve character's current dexterity.
int CCharacter::GetDexterity ()
{
	short max;

	if (IsNpc () || ClassTable.GetAttrPrime (m_Class) == APPLY_DEX) then
		max = 25;
	else max = 22;

	return URANGE (3, perm_dex + mod_dex, max);
}



// Retrieve character's current constitution.
int CCharacter::GetConstitution ()
{
	short max;

	if (IsNpc () || ClassTable.GetAttrPrime (m_Class) == APPLY_CON) then
		max = 25;
	else max = 22;

	return URANGE (3, perm_con + mod_con, max);
}


// Retrieve a character's item carrying capacity.
// Vastly reduced (finally) due to containers		-Thoric
int CCharacter::GetMaxItems ()
{
    int penalty = 0;

    if (! IsNpc () && GetLevel () >= LEVEL_IMMORTAL)
		return GetTrustLevel () * 200;

    if (IsNpc () && IsPet ())
		return 0;

    if (get_eq_char (this, WEAR_WIELD))
      ++penalty;
    if (get_eq_char (this, WEAR_DUAL_WIELD))
      ++penalty;
    if (get_eq_char (this, WEAR_MISSILE_WIELD))
      ++penalty;
    if (get_eq_char (this, WEAR_HOLD))
      ++penalty;
    if (get_eq_char (this, WEAR_SHIELD))
      ++penalty;
    return URANGE (5, (m_Level + 15) / 5 +
		GetDexterity () - 13 - penalty, 20);
}


// Move a char out of a room.
void CCharacter::RemoveFromRoom ()
{
	CObjData	*obj;

	if (! GetInRoom ()) {
		bug ("RemoveFromRoom: NULL room.");
		return;
	}

	if (! IsNpc ()) then --m_pInRoom->GetArea ()->nplayer;

	if ((obj = get_eq_char (this, WEAR_LIGHT)) != NULL
		&& obj->item_type == ITEM_LIGHT
		&& obj->value [2] != 0
		&& m_pInRoom->light > 0)
			--m_pInRoom->light;

	GetInRoom ()->UnLinkPerson (this);

	SetInRoom (NULL);
	SetNextInRoom (NULL);
	SetPrevInRoom (NULL);

	if (! IsNpc () && get_timer (this, TIMER_SHOVEDRAG) > 0)
		remove_timer (this, TIMER_SHOVEDRAG);
}


// Move a char into a room.
void CCharacter::SendToRoom (CRoomIndexData *pRoom)
{
	CObjData	*obj;

	if (! pRoom) {
		bug ("CCharacter::SendToRoom: %s -> NULL room!  Putting char in limbo (%d)",
			GetName (), SysData.m_RoomLimbo);
		// This used to just return, but there was a problem with crashing
		// and I saw no reason not to just put the char in limbo. -Narn
		pRoom = RoomTable.GetRoom (SysData.m_RoomLimbo);
	}

	SetInRoom (pRoom);
	pRoom->LinkPerson (this);

	if (! IsNpc ()) {
		CAreaData	&Area = *pRoom->GetArea ();
		if (++Area.nplayer > Area.max_players)
			Area.max_players = Area.nplayer;
	}

	if ((obj = get_eq_char (this, WEAR_LIGHT)) != NULL
	  && obj->item_type == ITEM_LIGHT
	  && obj->value [2] != 0)
		++pRoom->light;

	if (! IsNpc ()
		&& pRoom->IsSafe () && get_timer (this, TIMER_SHOVEDRAG) <= 0)
			add_timer (this, TIMER_SHOVEDRAG, 10, NULL, 0);	// -30 Seconds-

	// Delayed Teleport rooms					-Thoric
	// Should be the last thing checked in this function
	if (pRoom->IsTeleport () && pRoom->tele_delay > 0) {
		CTeleportData	*tele;

		for (tele = first_teleport; tele; tele = tele->GetNext ())
			if (tele->room == pRoom)
				return;

		tele = new CTeleportData;
		LINK (tele, first_teleport, last_teleport);
		tele->room = pRoom;
		tele->timer = pRoom->tele_delay;
	}
}


void CCharacter::StopIdling ()
{
	ASSERT (this);

	SetTimer (0);
	if (GetConnectStatus () != CON_PLAYING
		|| m_pWasInRoom == NULL
		|| GetInRoom () != RoomTable.GetRoom (SysData.m_RoomLimbo))
			return;

	RemoveFromRoom ();
	SendToRoom (m_pWasInRoom);
	m_pWasInRoom	= NULL;
	act (AT_ACTION, "$n has returned from the void.", this, NULL, NULL, TO_ROOM);
}


//void ch_printf (CCharacter *ch, char *fmt, ...)
void CCharacter::SendTextf (char *fmt, ...)
{
	char	buf [MAX_STRING_LENGTH*2];	// better safe than sorry
	va_list	args;

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);

	SendText (buf);
}


void CCharacter::SendText (const char *txt)
{
	ASSERT (this);

	if (txt == NULL || GetDesc () == NULL)
		return;

	if (HasColor (txt))
		SendColor (txt);
	else
		GetDesc ()->WriteToBuffer (txt);
}


void CCharacter::SendColorf (char *fmt, ...)
{
	char	buf [MAX_STRING_LENGTH*2];	// better safe than sorry
	va_list	args;

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);

	SendColor (buf);
}


bool IsPushColor (const char*& col);
bool IsPopColor (const char*& col);


// Same as above, but converts &color codes to ANSI sequences..
void CCharacter::SendColor (const char *txt)
{
	ASSERT (this);

	CDescriptor		*d = GetDesc ();
	const char			*colstr;
	const char		*prevstr = txt;
	char			colbuf [32];
	int				len;
  
	if (! txt || ! d)
		return;

	BOOL	bWasStackingColors = IsStackColors ();
	if (! bWasStackingColors)
		StartColorStack ();

	while ((colstr = strpbrk (prevstr, "&^"))) {
		len = colstr - prevstr;
		if (len > 0)
			d->WriteToBuffer (prevstr, len);

		if (IsPopColor (colstr)) {
			if (! m_ColorStack.IsEmpty ()) {
				UCHAR	Prev = (UCHAR) m_ColorStack.RemoveTail ();
				len = MakeColorSequence (Prev, colbuf, d);
				if (len)
					d->WriteToBuffer (colbuf, len);
			}
			prevstr = colstr;
			continue;
		}
		else if (IsPushColor (colstr)) {
			m_ColorStack.AddTail ((void*) d->m_PrevColor);
			prevstr = colstr;
			continue;
		}
		else
			len = MakeColorSequence (colstr, colbuf, d);

		if (len > sizeof (colbuf)) {
			bug ("BIGBUG!: SendColor: Buffer overflow! (%d)", len);
			return;
		}
		if (len < 0) {
			prevstr = colstr+1;
			break;
		}

		if (len)
			d->WriteToBuffer (colbuf, len);

		prevstr = colstr + 2;
	}

	if (*prevstr)
		d->WriteToBuffer (prevstr);

	// If we were already stacking colors then leave the stack intact.
	if (! bWasStackingColors) {
		if (! m_ColorStack.IsEmpty ()) {
			UCHAR	Prev = (UCHAR) m_ColorStack.RemoveTail ();
			len = MakeColorSequence (Prev, colbuf, d);
			if (len)
				d->WriteToBuffer (colbuf, len);
		}
		EndColorStack ();
	}
}


bool IsPushColor (const char*& col)
{
	if (col [0] == '&' && col [1] == '[') {
		col += 2;
		return TRUE;
	}
	return FALSE;
}


bool IsPopColor (const char*& col)
{
	if (col [0] == '&' && col [1] == ']') {
		col += 2;
		return TRUE;
	}
	return FALSE;
}


void CCharacter::PageColorf (char *fmt, ...)
{
	char	buf [MAX_STRING_LENGTH*2];	// better safe than sorry
	va_list	args;

	va_start (args, fmt);
	vsprintf (buf, fmt, args);
	va_end (args);

	PageColor (buf);
}


void CCharacter::PageColor (const char *txt)
{
	const char		*colstr;
	const char	*prevstr = txt;
	char		colbuf [20];
	int			ln;
	CPtrList	List;

	CDescriptor &Ds = *GetDesc ();
	if (!txt || !&Ds)
		return;

	CCharacter	*ch = Ds.m_pOriginal ? Ds.m_pOriginal : Ds.m_pCharacter;
	if (ch->IsNpc () || ! ch->IsPagerOn ()) {
		Ds.m_pCharacter->SendColor (txt);
		return;
	}
	// Clear out old color stuff
	//  make_color_sequence (NULL, NULL, NULL);
	while ((colstr = strpbrk (prevstr, "&^")) != NULL) {
		if (colstr > prevstr)
			write_to_pager (&Ds, prevstr, (colstr - prevstr));

		if (IsPopColor (colstr)) {
			if (! List.IsEmpty ()) {
				UCHAR	Prev = (UCHAR) List.RemoveTail ();
				ln = MakeColorSequence (Prev, colbuf, &Ds);
				if (ln)
					Ds.WriteToBuffer (colbuf, ln);
			}
			prevstr = colstr;
			continue;
		}
		else if (IsPushColor (colstr)) {
			List.AddTail ((void*) Ds.m_PrevColor);
			prevstr = colstr;
			continue;
		}
		else
			ln = MakeColorSequence (colstr, colbuf, &Ds);

		if (ln > sizeof (colbuf)) {
			bug ("BIGBUG!: send_to_pager_color: Buffer overflow! (%d)", ln);
			return;
		}
		if (ln < 0) {
			prevstr = colstr+1;
			break;
		}

		if (ln)
			write_to_pager (&Ds, colbuf, ln);

		prevstr = colstr+2;
	}

	if (*prevstr)
		write_to_pager (&Ds, prevstr, 0);

	if (! List.IsEmpty ()) {
		UCHAR	Prev = (UCHAR) List.RemoveTail ();
		ln = MakeColorSequence (Prev, colbuf, &Ds);
		if (ln)
			Ds.WriteToBuffer (colbuf, ln);
	}
	List.RemoveAll ();
}


BOOL CCharacter::CheckSubrestricted ()
{
    if (GetSubstate () == SUB_RESTRICTED) {
		SendText (
			"You cannot use this command from within another command.\n\r");
		return TRUE;
	}
	return FALSE;
}



// Read in a char.

#if defined (KEY)
#undef KEY
#endif

#define KEY(literal,field,value)					\
				if (!str_cmp (word, literal)) {		\
				    field  = value;					\
				    fMatch = TRUE;					\
				    break;							\
				}
#define KEY2(literal, field, value)					\
				if (!str_cmp (word, literal)) {		\
				    field (value);					\
				    fMatch = TRUE;					\
				    break;							\
				}

extern	int	file_ver;	// in save.cpp (move to CCharacterTable class when
						// that class is coded).

BOOL CCharacter::Read (FILE *fp, BOOL bPreload)
{
	char	buf [MAX_STRING_LENGTH];
	char	*word;
	int		x1, x2, x3, x4, x5, x6, x7;
	short	killcnt = 0;
	BOOL	fMatch;
	char	*pLine;

	file_ver = 0;

	pc_data	&Pc = *GetPcData ();

	for (;;) {
		fMatch = FALSE;
		if (! feof (fp)) {
			pLine = fread_line (fp);
			word = ParseWord (pLine);
		}
		else word = "End";

		switch (UPPER (word [0])) {
		  case '*':
			fMatch = TRUE;
			break;

		  case 'A':
			if (! str_cmp (word, "Act")) {
				m_ActionBits.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			if (! str_cmp (word, "AffectedBy")) {
				m_AffectBits.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			KEY2 ("Alignment",	SetAlignment,	ParseNumber (pLine));
			KEY2 ("Armor",		SetArmor,		ParseNumber (pLine));

			if (!str_cmp (word, "Affect") || !str_cmp (word, "AffectData")) {
				CAffectData	*paf;

				if (bPreload) {
					fMatch = TRUE;
					break;
				}
				paf = new CAffectData;
				if (! str_cmp (word, "Affect")) {
					paf->type = ParseNumber (pLine);
				} else {
					int		sn;
					char	*sname = ParseWord (pLine);

					if ((sn = SkillTable.Lookup (sname)) < 0) {
						if ((sn = HerbTable.Lookup (sname)) < 0)
							bug ("CCharacter::Read: unknown skill: %s", sname);
						else
							sn += TYPE_HERB;
					}
					paf->type = sn;
				}

				paf->duration	= ParseNumber (pLine);
				paf->modifier	= ParseNumber (pLine);
				paf->location	= ParseNumber (pLine);

				// 1.4add
				if (paf->location == APPLY_WEAPONSPELL
				  || paf->location == APPLY_WEARSPELL
				  || paf->location == APPLY_REMOVESPELL
				  || paf->location == APPLY_STRIPSN
				  || paf->location == APPLY_RECURRINGSPELL)
					paf->modifier = slot_lookup (paf->modifier);

				paf->SetVector (ParseWord (pLine));
				m_AffectList.AddTail (paf);
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "AttrMod" )) {
				x1 = x2 = x3 = x4 = x5 = x6 = x7 = 13;
				sscanf (pLine, "%d %d %d %d %d %d %d", &x1, &x2, &x3, &x4,
					&x5, &x6, &x7);
				mod_str = x1;
				mod_int = x2;
				mod_wis = x3;
				mod_dex = x4;
				mod_con = x5;
				mod_cha = x6;
				mod_lck = x7;
				fMatch = TRUE;
				break;
			}

			if (!str_cmp (word, "AttrPerm")) {
				x1 = x2 = x3 = x4 = x5 = x6 = x7 = 0;
				sscanf (pLine, "%d %d %d %d %d %d %d", &x1, &x2, &x3, &x4,
					&x5, &x6, &x7);
				perm_str = x1;
				perm_int = x2;
				perm_wis = x3;
				perm_dex = x4;
				perm_con = x5;
				perm_cha = x6;
				perm_lck = x7;
				if (!x7)
					perm_lck = 13;
				fMatch = TRUE;
				break;
			}
			KEY ("AuthedBy",	 Pc.authed_by,	ParseString (pLine, fp));
			break;

		  case 'B':
			KEY2("Bamfin",		Pc.SetBamfin,	ParseStringNohash (pLine, fp));
			KEY2("Bamfout",		Pc.SetBamfout,	ParseStringNohash (pLine, fp));
			KEY ("Bestowments", Pc.m_pBestowments, ParseStringNohash (pLine, fp));
			KEY ("Bio",			Pc.bio,			ParseString (pLine, fp));
			break;

		  case 'C':
			if (! str_cmp (word, "Clan")) {
				Pc.SetClanName (ParseString (pLine, fp));
				Pc.SetClan (ClanList.Find (Pc.GetClanName (), CLAN_ALL));

				if (! bPreload && Pc.HasClanName () && ! Pc.GetClan ()) {
					SendTextf ("Warning: the organization %s no longer "
						"exists, and therefore you no longer\n\rbelong to "
						"that organization.\n\r", Pc.GetClanName ());
					STRFREE (Pc.GetClanName ());
					Pc.SetClanName (STRALLOC (""));
				}
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Class")) {
				if (! bPreload) {
					int		c = ClassTable.GetClass (ParseCString (pLine, fp));
					if (ClassTable.IsValidClass (c))
						SetClass (c);
					else
						SetConnectError (CE_BADCLASS);
				}
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Condition")) {
				sscanf (pLine, "%d %d %d %d", &x1, &x2, &x3, &x4);
				Pc.condition [0] = x1;
				Pc.condition [1] = x2;
				Pc.condition [2] = x3;
				Pc.condition [3] = x4;
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Council")) {
				Pc.SetCouncilName (ParseString (pLine, fp));
				if (! bPreload && Pc.HasCouncilName () && (Pc.council =
				  get_council (Pc.GetCouncilName ())) == NULL) {
					SendTextf ("Warning: the council %s no longer exists,"
						" and herefore you no longer\n\r"
						"belong to a council.\n\r", Pc.GetCouncilName ());
					STRFREE (Pc.GetCouncilName ());
					Pc.SetCouncilName (STRALLOC (""));
				}
				fMatch = TRUE;
				break;
			}
			break;

		  case 'D':
			KEY2 ("Damroll",	SetDamroll,		ParseNumber (pLine));
			KEY ("Deaf",		deaf,			ParseNumber (pLine));

			if (! str_cmp (word, "Deity")) {
				Pc.SetDeityName (ParseString (pLine, fp));

				if (! bPreload && Pc.HasDeityName () && (Pc.deity =
				  get_deity (Pc.GetDeityName ())) == NULL) {
					SendTextf ("Warning: the deity %s no longer "
						"exists.\n\r", Pc.GetDeityName ());
					STRFREE (Pc.GetDeityName ());
					Pc.SetDeityName (STRALLOC (""));
					Pc.favor = 0;
				}
				fMatch = TRUE;
				break;
			}
			KEY2("Description", SetDescription, ParseCString (pLine, fp));
			break;

		  case 'E':
			if (! stricmp (word, "End")) {
				ValidateLoad ();
				return TRUE;
			}

			KEY2("Exp",		SetExp,			ParseNumber (pLine));
			break;

		  case 'F':
			KEY ("Favor",	Pc.favor,		ParseNumber (pLine));
			KEY ("Flags",	Pc.m_flags,		ParseNumber (pLine));
			KEY ("FPrompt",	Pc.m_Fprompt,	ParseString (pLine, fp));
			break;

		  case 'G':
			KEY ("Glory",	Pc.quest_curr,	ParseNumber (pLine));
			KEY2("Gold",	SetGold,		ParseNumber (pLine));
			// temporary measure
			if (!str_cmp (word, "Guild")) {
				Pc.SetClanName (ParseString (pLine, fp));
				Pc.SetClan (ClanList.Find (Pc.GetClanName (), CLAN_ALL));

				if (! bPreload && Pc.HasClanName () && ! Pc.GetClan ()) {
					SendTextf ("Warning: the organization %s no longer "
						"exists, and therefore you no longer\n\rbelong "
						"to that organization.\n\r", Pc.GetClanName ());
					STRFREE (Pc.GetClanName ());
					Pc.SetClanName (STRALLOC (""));
				}
				fMatch = TRUE;
				break;
			}
			break;

		  case 'H':
			KEY ("Height",		m_Height,		ParseNumber (pLine));

			if (!str_cmp (word, "Helled")) {
				Pc.release_date = CTime (ParseNumber (pLine));
				Pc.helled_by = ParseString (pLine, fp);
				if (Pc.release_date < CurrentTime) {
					STRFREE (Pc.helled_by);
					Pc.helled_by = NULL;
					Pc.release_date = CTime (0);
				}
				fMatch = TRUE;
				break;
			}

			KEY2("Hitroll",		SetHitroll,		ParseNumber (pLine));
			KEY2("Homepage",	Pc.SetHomepage,	ParseStringNohash (pLine, fp));

			if (!str_cmp (word, "HpManaMove")) {
				SetHp (ParseNumber (pLine));
				SetMaxHp (ParseNumber (pLine));
				SetMana (ParseNumber (pLine));
				SetMaxMana (ParseNumber (pLine));
				SetMove (ParseNumber (pLine));
				SetMaxMove (ParseNumber (pLine));
				fMatch = TRUE;
				break;
			}
			break;

		  case 'I':
			// 1.4add - read ignored list here
			if (! strcmp (word, "Ignored")) {
				CString	Name = ParseCString (pLine, fp);

				// If there's a pfile for the name then add it to the list
				if (FileTable.Exists (FileTable.PlayerName (Name)))
					Pc.m_IgnoreList.AddTail (Name);
				fMatch = TRUE;
				break;
			}

			KEY ("IllegalPK",	Pc.illegal_pk,	ParseNumber (pLine));
			KEY ("IMC",			Pc.imc_def,		ParseNumber (pLine));
			KEY ("IMCAllow",	Pc.imc_allow,	ParseNumber (pLine));
			KEY ("IMCDeny",		Pc.imc_deny,	ParseNumber (pLine));
			KEY ("ICEListen",	Pc.ice_listen,	ParseStringNohash (pLine, fp));
			KEY ("Immune",		m_Immune,		ParseNumber (pLine));
			break;

		  case 'K':
			if (!str_cmp (word, "Killed")) {
				fMatch = TRUE;
				if (killcnt >= MAX_KILLTRACK)
					bug ("CCharacter::Read: killcnt (%d) >= MAX_KILLTRACK",
						killcnt);
				else {
					Pc.killed [killcnt].vnum = ParseNumber (pLine);
					Pc.killed [killcnt++].count = ParseNumber (pLine);
				}
			}
			break;

		  case 'L':
			KEY2("Level",		SetLevel,		ParseNumber (pLine));
			KEY2("LongDescr",	SetLongDescr,	ParseCString (pLine, fp));
			break;

		  case 'M':
			KEY ("MDeaths",		Pc.mdeaths,			ParseNumber (pLine));
			KEY2("Mentalstate", SetMentalState,		ParseNumber (pLine));
			KEY ("MGlory",      Pc.quest_accum,		ParseNumber (pLine));
			KEY ("Minsnoop",	Pc.min_snoop,		ParseNumber (pLine));
			KEY ("MKills",		Pc.mkills,			ParseNumber (pLine));
			KEY ("Mobinvis",	m_MobInvis,			ParseNumber (pLine));
			if (!str_cmp (word, "MobRange")) {
				Pc.m_range_lo = ParseNumber (pLine);
				Pc.m_range_hi = ParseNumber (pLine);
				fMatch = TRUE;
			}
			break;

		  case 'N':
			if (! str_cmp (word, "Name")) {
				// Name already set externally.
				fMatch = TRUE;
				break;
			}
			if (! str_cmp (word, "NoAffectedBy")) {
				m_NoAffectBits.Parse (pLine);
				fMatch = TRUE;
				break;
			}
			KEY ("NoImmune",		m_NoImmune,		ParseNumber (pLine));
			KEY ("NoResistant",		m_NoResist,		ParseNumber (pLine));
			KEY ("NoSusceptible",	m_NoSuscept,	ParseNumber (pLine));
			break;

		  case 'O':
			KEY ("Outcast_time", Pc.outcast_time,	ParseNumber (pLine));
			if (!str_cmp (word, "ObjRange")) {
				Pc.o_range_lo = ParseNumber (pLine);
				Pc.o_range_hi = ParseNumber (pLine);
				fMatch = TRUE;
			}
			break;

		  case 'P':
			KEY ("Pagerlen",	Pc.pagerlen,	ParseNumber (pLine));
			KEY2("Password",	Pc.SetPassWord,	ParseStringNohash (pLine, fp));
			KEY ("PDeaths",		Pc.pdeaths,		ParseNumber (pLine));
			KEY ("PKills",		Pc.pkills,		ParseNumber (pLine));
			KEY2("Played",		SetPlayed,		ParseNumber (pLine));
			KEY2("Position",	SetPosition,	ParseNumber (pLine));
			KEY2("Practice",	SetPractices,	ParseNumber (pLine));
			KEY ("Prompt",		Pc.m_Prompt,	ParseString (pLine, fp));
			if (!str_cmp (word, "PTimer")) {
				add_timer (this, TIMER_PKILLED, ParseNumber (pLine), NULL, 0);	
				fMatch = TRUE;
				break;
			}
			break;

		  case 'R':
			if (! str_cmp (word, "Race")) {
				if (! bPreload) {
					int		r = RaceTable.GetRace (ParseCString (pLine, fp));
					if (RaceTable.IsValidRace (r))
						SetRace (r);
					else
						SetConnectError (CE_BADRACE);
				}
				fMatch = TRUE;
				break;
			}

			KEY2("Rank",        Pc.SetRank,	ParseStringNohash (pLine, fp));
			KEY ("Resistant",	m_Resist,	ParseNumber (pLine));
			KEY ("Restore_time",Pc.restore_time, CTime (ParseNumber (pLine)));

			if (!str_cmp (word, "Room")) {
				SetInRoom (RoomTable.GetRoom (ParseNumber (pLine)));
				if (!GetInRoom ())
					SetInRoom (RoomTable.GetRoom (SysData.m_RoomLimbo));
				fMatch = TRUE;
				break;
			}
			if (!str_cmp (word, "RoomRange")) {
				Pc.r_range_lo = ParseNumber (pLine);
				Pc.r_range_hi = ParseNumber (pLine);
				fMatch = TRUE;
			}
			break;

		  case 'S':
			KEY2("Sex",			SetSex,				ParseNumber (pLine));
			KEY2("ShortDescr",	SetShortDescr,		ParseCString (pLine, fp));
			KEY ("Style",		m_Style,			ParseNumber (pLine));
			KEY ("Susceptible",	m_Suscept,			ParseNumber (pLine));
			if (! str_cmp (word, "SavingThrow")) {
				saving_wand = ParseNumber (pLine);
				saving_poison_death = saving_wand;
				saving_para_petri = saving_wand;
				saving_breath = saving_wand;
				saving_spell_staff = saving_wand;
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "SavingThrows")) {
				saving_poison_death = ParseNumber (pLine);
				saving_wand = ParseNumber (pLine);
				saving_para_petri = ParseNumber (pLine);
				saving_breath = ParseNumber (pLine);
				saving_spell_staff = ParseNumber (pLine);
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Site")) {
				if (bPreload) {
					ValidateLoad ();
					return TRUE;
				}
				SendTextf ("Last connected from: %s\n\r", ParseLine (pLine));
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Speaking")) {
				m_Speaking = LanguageTable.GetLanguage (ParseWord (pLine));
				fMatch = TRUE;
			}

			if (! str_cmp (word, "Skill")) {
				if (bPreload) {
					ValidateLoad ();
					return TRUE;
				}
				int		sn;
				int		value;

				value = ParseNumber (pLine);
				sn = SkillTable.BsearchExact (ParseWord (pLine),
					gsn_first_skill, gsn_first_weapon-1);
				if (sn < 0)
					bug ("CCharacter::Read: unknown skill: %s", word);
				else {
					Pc.learned [sn] = value;
					// Take care of people who have stuff they shouldn't
					// Assumes class and level were loaded before. Altrag *
					// Assumes practices are loaded first too now. Altrag */
					if (GetLevel () < LEVEL_IMMORTAL) { 
						if (SkillTable.GetClassLevel (sn, m_Class)
						  >= LEVEL_IMMORTAL) {
							Pc.learned [sn] = 0;
							AddPractices (1);
						}
					}
				}
				fMatch = TRUE;
				break;
			}

			if (! str_cmp (word, "Spell")) {
				if (bPreload) {
					ValidateLoad ();
					return TRUE;
				}
				int		sn;
				int		value;

				value = ParseNumber (pLine);

				sn = SkillTable.BsearchExact (ParseWord (pLine),
					gsn_first_spell, gsn_first_skill-1);
				if (sn < 0)
					bug ("CCharacter::Read: unknown spell: %s", word);
				else {
					Pc.learned [sn] = value;
					if (GetLevel () < LEVEL_IMMORTAL)
					if (SkillTable.GetClassLevel (sn,
					  m_Class) >= LEVEL_IMMORTAL) {
						Pc.learned [sn] = 0;
						AddPractices (1);
					}
				}
				fMatch = TRUE;
				break;
			}
			break;

		  case 'T':
			if (! stricmp (word, "Tongue")) {
				if (IsMortal ()) {
					int		value = ParseNumber (pLine);
					int		sn = SkillTable.BsearchExact (ParseWord (pLine),
								gsn_first_tongue, gsn_top_sn-1);

					if (sn < 0)
						bug ("CCharacter::Read: unknown tongue: %s", word);
					else {
						if (SkillTable.GetClassLevel (sn, GetClass ()) >=
						  LEVEL_IMMORTAL) {
							Pc.learned [sn] = 0;
							AddPractices (1);
						} else {
							Pc.learned [sn] = value;
							SetSpeaks (LanguageTable.GetLanguage (word));
						}
					}
				}
				fMatch = TRUE;
				break;
			}
			
			KEY2("Trust", SetTrust, ParseNumber (pLine));
			// Let no character be trusted higher than
			// one below maxlevel -- Narn
			SetTrust (UMIN (GetTrust (), MAX_LEVEL - 1));

			if (! str_cmp (word, "Title")) {
				Pc.SetTitle (ParseString (pLine, fp));
				if (isalpha (Pc.GetTitle () [0])
				  || isdigit (Pc.GetTitle () [0])) {
					sprintf (buf, " %s", Pc.GetTitle ());
					if (Pc.GetTitle ())
						STRFREE (Pc.GetTitle ());
					Pc.SetTitle (STRALLOC (buf));
				}
				fMatch = TRUE;
				break;
			}
			break;

		  case 'V':
			if (! stricmp (word, "Version")) {
				file_ver = ParseNumber (pLine);
				if (file_ver != GetCurrentVersion ()) {
					bug ("Incompatible Player File: %s, Version %d "
						"(%d required)", GetName (), file_ver,
						GetCurrentVersion ());
					return FALSE;
				}
				fMatch = TRUE;
				break;
			}

			if (!str_cmp (word, "Vnum")) {
				SetMobIndex (MobTable.GetMob (ParseNumber (pLine)));
				fMatch = TRUE;
				break;
			}
			break;

		  case 'W':
			KEY ("Weight",		m_Weight,		ParseNumber (pLine));

			if (! str_cmp (word, "Weapon")) {
				if (bPreload) {
					ValidateLoad ();
					return TRUE;
				}
				int		sn;
				int		value;

				value = ParseNumber (pLine);

				sn = SkillTable.BsearchExact (ParseWord (pLine),
					gsn_first_weapon, gsn_first_tongue-1);
				if (sn < 0)
					bug ("CCharacter::Read: unknown weapon: %s", word);
				else {
					Pc.learned [sn] = value;
					if (GetLevel () < LEVEL_IMMORTAL)
						if (SkillTable.GetClassLevel (sn,
						  GetClass ()) >= LEVEL_IMMORTAL) {
							Pc.learned [sn] = 0;
							AddPractices (1);
						}
				}
				fMatch = TRUE;
				break;
			}

			KEY2("Wimpy",		SetWimpLevel,		ParseNumber (pLine));
			KEY ("WizInvis",	Pc.wizinvis,		ParseNumber (pLine));
			break;
		}
		if (! fMatch)
			bug ("CCharacter::Read: %s, no match: %s", GetName (), word);

		if (file_ver == 0) {
			bug ("CCharacter::Read: %s, Incompatible Player file, no Version.",
				GetName ());
			break;
		}
	}
	return FALSE;	// will never get here but VC++ likes to see a return!
}


void CCharacter::ValidateLoad ()
{
	pc_data	&Pc = *GetPcData ();

	if (!GetShortDescr ())
		SetShortDescr ("");
	if (!GetLongDescr ())
		SetLongDescr ("");
	if (!GetDescription ())
		SetDescription ("");
	if (!Pc.GetPassWord ())
		Pc.SetPassWord (str_dup (""));
	if (!Pc.GetBamfin ())
		Pc.SetBamfin (str_dup (""));
	if (!Pc.GetBamfout ())
		Pc.SetBamfout (str_dup (""));
	if (!Pc.bio)
		Pc.bio = STRALLOC ("");
	if (!Pc.GetRank ())
		Pc.SetRank (str_dup (""));
	if (!Pc.GetBestowments ())
		Pc.SetBestowments (str_dup (""));
	if (!Pc.GetTitle ())
		Pc.SetTitle (STRALLOC (""));
	if (!Pc.GetHomepage ())
		Pc.SetHomepage (str_dup (""));
	if (!Pc.authed_by)
		Pc.authed_by = STRALLOC ("");
	if (!Pc.m_Prompt)
		Pc.m_Prompt = STRALLOC ("");
	if (!Pc.m_Fprompt)
		Pc.m_Fprompt = STRALLOC ("");

	m_pEditor = NULL;
	int	killcnt = URANGE (2, ((GetLevel ()+3) * MAX_KILLTRACK)/LEVEL_AVATAR,
		MAX_KILLTRACK);
	if (killcnt < MAX_KILLTRACK)
		Pc.killed [killcnt].vnum = 0;

	if (! m_Speaking)
		SetSpeaking (LanguageTable.GetCommon ());

	if (IsImmortal ())
		SetSpeaks (LANG_ALL);
	else if (m_Speaks.IsEmpty ()) {
		SetSpeaksFlags (RaceTable.GetLanguages (GetRace ()));
		SetSpeaks (LanguageTable.GetCommon ());
	}

// 1.4add
//	ch->pcdata->tell_history = new char* [26];
//	for (i = 0; i < 26; i++)
//		ch->pcdata->tell_history [i] = NULL;

	// this disallows chars from being 6', 180lbs, but easier
	// than a flag
	CRaceData	&Ra = *RaceTable.GetRaceData (GetRace ());
	if (! m_Height || m_Height == 72)
		m_Height = number_range ((int)(Ra.GetHeight () * .9),
			(int)(Ra.GetHeight () * 1.1));
	if (! m_Weight || m_Weight == 180)
		m_Weight = number_range ((int)(Ra.GetWeight () * .9),
			(int)(Ra.GetWeight () * 1.1));
}


void CCharacter::ReadMob (FILE* fp)
{
	char	*pLine;
	char	*word;
	BOOL	fMatch;

	for (;;) {
		fMatch = FALSE;
		if (! feof (fp)) {
			pLine = fread_line (fp);
			word = ParseWord (pLine);
		}
		else word = "EndMobile";

		switch (UPPER (word [0])) {
		  case '*':
			fMatch = TRUE;
			break;

		  case '#':
			if (! strcmp (word, "#OBJECT"))
				fread_obj (this, fp, OS_CARRY);
			break;

		  case 'D':
			KEY2("Description", SetDescription, ParseCString (pLine, fp));
			break;

		  case 'E':
			if (! strcmp (word, "EndMobile"))
				return;
			break;

		  case 'F':
			if (! str_cmp (word, "Flags")) {
				m_ActionBits.Parse (pLine);
				fMatch = TRUE;
				break;
			}

		  case 'L':
			KEY2("LongDescr",	SetLongDescr,	ParseCString (pLine, fp));
			break;
		  case 'N':
			KEY2("Name",		SetName,		ParseCString (pLine, fp));
			break;
		  case 'P':
			KEY2("Position",	SetPosition,	ParseNumber (pLine));
			break;
		  case 'R':
			KEY ("Room",		tempnum,		ParseNumber (pLine));
			break;
		  case 'S':
			KEY2("ShortDescr",	SetShortDescr,	ParseCString (pLine, fp));
			break;
		}
		if (! fMatch)
			bug ( "ReadMob: no match: %s", word);
	}
}


// Write the char.
void CCharacter::Write (FILE *fp)
{
	CAffectData	*paf;
	int			sn, track;
	CSkill		*skill = NULL;

	if (IsNpc ()) {
		WriteMob (fp);
		return;
	}
	fprintf (fp, "#PLAYER\n");
	fprintf (fp, "Version     %d           SmaugWiz V2\n\n",
		GetCurrentVersion ());

	fprintf (fp, "Name         %s~\n", GetName ());

	if (HasShortDescription ())
		fprintf (fp, "ShortDescr   %s~\n", GetShortDescr ());
	if (HasLongDescription ())
		fprintf (fp, "LongDescr    %s~\n", GetLongDescr ());
	if (HasDescription ())
		fprintf (fp, "Description  %s~\n", GetDescription ());

	fprintf (fp, "Sex          %d\n", GetSex ());
	fprintf (fp, "Class        %s~\n", ClassTable.GetName (GetClass ()));
	fprintf (fp, "Race         %s~\n", RaceTable.GetName (GetRace ()));
	fprintf (fp, "Level        %d\n", GetLevel ());
	fprintf (fp, "Speaking    '%s'\n",
		NCCP LanguageTable.GetName (m_Speaking));

	fprintf (fp, "Played       %d\n",
		GetPlayed () + (CurrentTime - GetLogonTime ()).GetTotalSeconds ());
	fprintf (fp, "Room         %d\n",
		(GetInRoom () == RoomTable.GetRoom (SysData.m_RoomLimbo)
		&& GetWasInRoom ()) ? GetWasInRoom ()->vnum : GetInRoom ()->vnum);

	fprintf (fp, "HpManaMove   %d %d %d %d %d %d\n",
		GetHp (), GetMaxHp (), GetMana (), GetMaxMana (), GetMove (),
		GetMaxMove ());
	fprintf (fp, "Gold         %d\n", GetGold ());
	fprintf (fp, "Exp          %d\n", GetExp ());
	fprintf (fp, "Height       %d\n", GetHeight ());
	fprintf (fp, "Weight       %d\n", GetWeight ());
	if (! GetActFlags ().IsEmpty ())
		fprintf (fp, "Act          %s\n", NCCP GetActFlags ().PrintVector ());
	if (! GetAffectFlags ().IsEmpty ())
		fprintf (fp, "AffectedBy   %s\n", NCCP GetAffectFlags ().PrintVector ());
	if (! GetNoAffectFlags ().IsEmpty ())
		fprintf (fp, "NoAffectedBy   %s\n", NCCP GetNoAffectFlags ().PrintVector ());

	fprintf (fp, "Position     %d\n",
		IsFightPosition () ? POS_STANDING : GetPosition ());
	fprintf (fp, "Style        %d\n", GetStyle ());
	fprintf (fp, "Practice     %d\n", GetPractices ());
	fprintf (fp, "SavingThrows %d %d %d %d %d\n", saving_poison_death,
		saving_wand, saving_para_petri, saving_breath, saving_spell_staff);
	fprintf (fp, "Alignment    %d\n", GetAlignment ());
	fprintf (fp, "Hitroll      %d\n", GetHitroll ());
	fprintf (fp, "Damroll      %d\n", GetDamroll ());
	fprintf (fp, "Armor        %d\n", GetArmor ());

	if (IsWimpy ())
		fprintf (fp, "Wimpy        %d\n", GetWimpLevel ());
	if (deaf)
		fprintf (fp, "Deaf         %d\n", deaf);

	if (m_Resist)
		fprintf (fp, "Resistant    %d\n", m_Resist);
	if (m_NoResist)
		fprintf (fp, "NoResistant  %d\n", m_NoResist);
	if (m_Immune)
		fprintf (fp, "Immune       %d\n", m_Immune);
	if (m_NoImmune)
		fprintf (fp, "NoImmune     %d\n", m_NoImmune);
	if (m_Suscept)
		fprintf (fp, "Susceptible  %d\n", m_Suscept);
	if (m_NoSuscept)
		fprintf (fp, "NoSusceptible %d\n", m_NoSuscept);
	if (GetMentalState ())
		fprintf (fp, "Mentalstate  %d\n", GetMentalState ());

	if (IsNpc ()) {
		fprintf (fp, "Vnum         %d\n", GetMobIndex ()->vnum);
		fprintf (fp, "Mobinvis     %d\n", m_MobInvis);
	} else {
		pc_data		&Pc = *m_pPcdata;
		fprintf (fp, "Password     %s~\n", Pc.GetPassWord ());
		if (Pc.HasRank ())
			fprintf (fp, "Rank         %s~\n", Pc.GetRank ());
		if (Pc.HasBestowments ())
			fprintf (fp, "Bestowments  %s~\n", Pc.GetBestowments ());
		fprintf (fp, "Title        %s~\n", Pc.GetTitle ());
		fprintf (fp, "Favor	       %d\n", Pc.favor);
		if (Pc.quest_curr)
			fprintf (fp, "Glory        %d\n", Pc.quest_curr);
		if (Pc.quest_accum)
			fprintf (fp, "MGlory       %d\n", Pc.quest_accum);

		if (Pc.HasHomepage ())
			fprintf (fp, "Homepage     %s~\n", Pc.GetHomepage ());

		if (Pc.imc_def)
			fprintf (fp, "IMC          %d\n", Pc.imc_def);
		if (Pc.imc_allow)
			fprintf (fp, "IMCAllow     %d\n", Pc.imc_allow);
		if (Pc.imc_deny)
			fprintf (fp, "IMCDeny      %d\n", Pc.imc_deny);
		fprintf (fp, "ICEListen %s~\n", Pc.ice_listen);

		if (Pc.HasBio ())
			fprintf (fp, "Bio          %s~\n", Pc.bio);
		if (Pc.HasAuthedBy ())
			fprintf (fp, "AuthedBy     %s~\n", Pc.authed_by);
		if (Pc.min_snoop)
			fprintf (fp, "Minsnoop     %d\n", Pc.min_snoop);
		if (Pc.HasPrompt ())
			fprintf (fp, "Prompt       %s~\n", Pc.m_Prompt);
		if (Pc.HasFightPrompt ())
			fprintf (fp, "FPrompt	   %s~\n", Pc.m_Fprompt);
		if (Pc.pagerlen)
			fprintf (fp, "Pagerlen     %d\n", Pc.pagerlen);

// 1.4add
		// If ch is ignoring players then store those players
		POSITION	pos = Pc.m_IgnoreList.GetHeadPosition ();
		while (pos)
			fprintf (fp, "Ignored      %s~\n",
				NCCP Pc.m_IgnoreList.GetNext (pos));

		if (IsImmortal ()) {
			if (Pc.HasBamfin ())
				fprintf (fp, "Bamfin       %s~\n", Pc.GetBamfin ());
			if (Pc.HasBamfout ())
				fprintf (fp, "Bamfout      %s~\n", Pc.GetBamfout ());
			if (GetTrust ())
				fprintf (fp, "Trust        %d\n", GetTrust ());
			if (! Pc.restore_time.IsZero ())
				fprintf (fp, "Restore_time %d\n",
					Pc.restore_time.GetCtime ());
			fprintf (fp, "WizInvis     %d\n", Pc.wizinvis);
			if (Pc.r_range_lo && Pc.r_range_hi)
				fprintf (fp, "RoomRange    %d %d\n", Pc.r_range_lo,
					Pc.r_range_hi);
			if (Pc.o_range_lo && Pc.o_range_hi)
				fprintf (fp, "ObjRange     %d %d\n", Pc.o_range_lo,
					Pc.o_range_hi);
			if (Pc.m_range_lo && Pc.m_range_hi)
				fprintf (fp, "MobRange     %d %d\n", Pc.m_range_lo,
					Pc.m_range_hi);
		}
		if (Pc.HasCouncilName ())
			fprintf (fp, "Council      %s~\n", Pc.GetCouncilName ());
		if (Pc.HasDeityName ())
			fprintf (fp, "Deity	     %s~\n", Pc.GetDeityName ());
		if (Pc.HasClanName ())
			fprintf (fp, "Clan         %s~\n", Pc.GetClanName ());
		fprintf (fp, "Flags        %d\n", Pc.m_flags);
		if (Pc.release_date > CurrentTime)
			fprintf (fp, "Helled       %d %s~\n",
				Pc.release_date.GetCtime (), Pc.helled_by);
		if (Pc.pkills)
			fprintf (fp, "PKills       %d\n", Pc.pkills);
		if (Pc.pdeaths)
			fprintf (fp, "PDeaths      %d\n", Pc.pdeaths);

		if (get_timer (this, TIMER_PKILLED)       
		  && (get_timer (this, TIMER_PKILLED) > 0))
			fprintf (fp, "PTimer       %d\n", get_timer (this, TIMER_PKILLED));
		fprintf (fp, "MKills       %d\n", Pc.mkills);
		fprintf (fp, "MDeaths      %d\n", Pc.mdeaths);
		if (Pc.illegal_pk)
			fprintf (fp, "IllegalPK    %d\n", Pc.illegal_pk);
		fprintf (fp, "AttrPerm     %d %d %d %d %d %d %d\n", perm_str,
			perm_int, perm_wis, perm_dex, perm_con, perm_cha, perm_lck);

		fprintf (fp, "AttrMod      %d %d %d %d %d %d %d\n", mod_str, 
			mod_int, mod_wis, mod_dex, mod_con, mod_cha, mod_lck);

		if (Pc.outcast_time)
			fprintf (fp, "Outcast_time %ld\n", Pc.outcast_time);

		fprintf (fp, "Condition    %d %d %d %d\n", Pc.condition [0],
			Pc.condition [1], Pc.condition [2], Pc.condition [3]);
		if (GetDesc () && GetDesc ()->IsHost ())
			fprintf (fp, "Site         %s\n", GetDesc ()->m_pHost);
		else
			fprintf (fp, "Site         (Link-Dead)\n");

		for (sn = 1; sn < SkillTable.GetCount (); sn++) {
			if (SkillTable.GetName (sn) && Pc.learned [sn] > 0)
				switch (SkillTable.GetType (sn)) {
				  default:
					fprintf (fp, "Skill        %d '%s'\n",
						Pc.learned [sn], SkillTable.GetName (sn));
					break;
				  case SKILL_SPELL:
					fprintf (fp, "Spell        %d '%s'\n",
						Pc.learned [sn], SkillTable.GetName (sn));
					break;
				  case SKILL_WEAPON:
					fprintf (fp, "Weapon       %d '%s'\n",
						Pc.learned [sn], SkillTable.GetName (sn));
					break;
				  case SKILL_TONGUE:
					if (IsMortal ())
						fprintf (fp, "Tongue       %d '%s'\n",
							Pc.learned [sn], SkillTable.GetName (sn));
					break;
				}
		}
	}

	POSITION	pos = m_AffectList.GetHeadPosition ();
	while (pos) {
		paf = m_AffectList.GetNext (pos);
		if (paf->type >= 0
		  && (skill = SkillTable.GetValidSkill (paf->type)) == NULL)
			continue;

		if (paf->type >= 0 && paf->type < TYPE_PERSONAL)
			fprintf (fp, "AffectData   '%s' %3d %3d %3d '%s'\n",
				skill->GetName (), paf->duration, paf->modifier,
				paf->location, paf->GetVectorName ());
		else
			fprintf (fp, "Affect       %3d %3d %3d %3d '%s'\n",
				paf->type, paf->duration, paf->modifier, paf->location,
				paf->GetVectorName ());
	}

	track = URANGE (2,
		((GetLevel ()+3) * MAX_KILLTRACK)/LEVEL_AVATAR, MAX_KILLTRACK);
	for (sn = 0; sn < track; sn++) {
		if (m_pPcdata->killed[sn].vnum == 0)
			break;
		fprintf (fp, "Killed       %d %d\n", m_pPcdata->killed [sn].vnum,
			m_pPcdata->killed [sn].count);
	}

	fprintf (fp, "End\n\n");
}


void CCharacter::WriteMob (FILE *fp)
{
	fprintf (fp, "#MOBILE\n");
	fprintf (fp, "Vnum        %d\n", m_pIndexData->vnum );

	if (GetInRoom ())
		fprintf (fp, "Room        %d\n",
			(GetInRoom () == RoomTable.GetRoom (SysData.m_RoomLimbo)
			&& GetWasInRoom ()) ? GetWasInRoom ()->vnum : GetInRoom ()->vnum);

	if (QUICKMATCH (GetName (), m_pIndexData->GetPlayerName ()) == 0)
		fprintf (fp, "Name        %s~\n", GetName ());
	if (QUICKMATCH (GetShortDescr (), m_pIndexData->GetShortDescr ()) == 0)
		fprintf (fp, "Short       %s~\n", GetShortDescr ());
	if (QUICKMATCH (GetLongDescr (), m_pIndexData->GetLongDescr ()) == 0)
		fprintf (fp, "Long        %s~\n", GetLongDescr ());
	if (QUICKMATCH (GetDescription (), m_pIndexData->GetDescription ()) == 0)
		fprintf (fp, "Description %s~\n", GetDescription ());

	fprintf (fp, "Position    %d\n", GetPosition ());

	if (! GetActFlags ().IsEmpty ())
		fprintf (fp, "Flags       %s\n", GetActFlags ().PrintVector ());

	if (! GetCarryList ().IsEmpty ())
		fwrite_obj (GetCarryList (), fp, 0, OS_CARRY);

	fprintf (fp, "EndMobile\n");
}


// Note: these functions cannot be inline due to not wanting to have to
// include menus.h everywhere character.h is needed.
void CCharacter::DisplayMenu (char menu)
{
	m_pEditMenu->Display (*this, menu);
}


void CCharacter::RemoveMenu ()
{
	ASSERT (m_pEditMenu);

	SendText ("\x1b[2J");					// clear screen
	SendTextf ("&gChanges to %s:%u Abandoned.\r\n",
		m_pEditMenu->GetName (), m_pEditMenu->m_Vnum);

	// for now just delete it.  Later we can ask for save, etc.
	delete m_pEditMenu;
	m_pEditMenu = NULL;
}


BOOL CCharacter::EditMenu (char* arg, const char* cmd)
{
	ASSERT (m_pEditMenu);
	
	if (! stricmp (cmd, "quitmenu") || ! stricmp (cmd, "qm")) {
		RemoveMenu ();
		return TRUE;
	}

	else if (! stricmp (cmd, "savemenu") || ! stricmp (cmd, "sm")) {
		SaveMenu ();
		return TRUE;
	}

	else return m_pEditMenu->Edit (*this, arg, cmd);
}


void CCharacter::SaveMenu ()
{
	ASSERT (m_pEditMenu);

	SendColorf ("\x1b[2J&YSaving %s %s:%u.\r\n",
		m_pEditMenu->m_bProto ? "Prototype" : "Non-Prototype",
		NCCP m_pEditMenu->GetName (), m_pEditMenu->m_Vnum);
	m_pEditMenu->Save (*this);
	delete m_pEditMenu;
	m_pEditMenu = NULL;
}


void CCharacter::ReturnToMenu (BOOL b)
{
	m_pEditMenu->ReturnToMenu (this, b);
}


void CCharacter::Edit (char* arg)
{
	if (m_pEditor)
		m_pEditor->Edit (*this, arg);
	else
		SetConnectStatus (CON_PLAYING);
}


char* CCharacter::GetEditBuffer ()
{
	if (m_pEditor)
		return m_pEditor->CopyToBuffer ();
	else
		return STRALLOC ("");
}


void CCharacter::StopEditing ()
{
	if (m_pEditor)
		m_pEditor->StopEditing (*this);
	else
		SetConnectStatus (CON_PLAYING);
}


BOOL CCharacter::IsPermanentAffect (int bit)
{
	return bit < 0 ? FALSE : RaceTable.IsAffected (GetRace (), bit);
}


int CCharacter::GetConnectStatus ()
{
	return m_pDesc ? m_pDesc->m_Connected : 0;
}


void CCharacter::SetConnectStatus (int st)
{
	if (m_pDesc) m_pDesc->m_Connected = st;
}


const char *CCharacter::GetRaceName ()
{
	return IsNpc () ?
		RaceTable.GetNpcRaceName (GetRace ()) :
			RaceTable.GetName (GetRace ());
}


const char *CCharacter::GetClassName ()
{
	const char	*pName = "Unknown";

	if (IsNpc ()) {
		if (GetClass () < MAX_NPC_CLASS && GetClass () >= 0)
			return ClassTable.GetNpcClassName (GetClass ());
	}
	else pName = ClassTable.GetName (GetClass ());

	return pName;
}


BOOL CCharacter::IsVampire ()
{
	return ! IsNpc ()
		&& (RaceTable.IsVampire (m_Race) || ClassTable.IsVampire (m_Class));
}


BOOL CCharacter::IsFightPosition ()
{
	return m_Position == POS_FIGHTING || m_Position == POS_EVASIVE
		|| m_Position == POS_DEFENSIVE || m_Position == POS_AGGRESSIVE
		|| m_Position == POS_BERSERK;
}


BOOL CCharacter::HasValidRace ()
{
	return RaceTable.IsValidRace (GetRace ());
}


ConnectErrorTypes CCharacter::GetConnectError ()
{
	for (int i=0; i < CE_MAX; ++i)
		if (m_ConnectError.IsSet (i))
			return (ConnectErrorTypes) i;

	return CE_NONE;
}


void CCharacter::SetConnectError (ConnectErrorTypes e)
{
	if (e == CE_NONE)
		m_ConnectError.Empty ();

	else m_ConnectError.SetBit (e);
}


BOOL CCharacter::CanGo (short door)
{
	CExitData	&Ex = *GetExit (door);

	if (! &Ex)
		return FALSE;

	return Ex.GetToRoom () && ! Ex.IsDisabled () && ! Ex.IsClosed ();
}


// This function checks to see if ch is ignoring ich - Fireblade.
BOOL CCharacter::IsIgnoring (CCharacter* ich)
{
	if (IsNpc () || ich->IsNpc ())
		return FALSE;

	CStringList	&List = GetPcData ()->m_IgnoreList;

	POSITION	pos = List.GetHeadPosition ();
	while (pos)
		if (nifty_is_name (List.GetNext (pos), ich->GetName ()))
			return TRUE;

	return FALSE;
}


CCharacter::~CCharacter ()
{
	ASSERT (this);

	m_AffectList.RemoveAll ();

    CTimerData	*pNtimer, *pTimer = first_timer;
	while (pTimer) {
		pNtimer = pTimer->GetNext ();
		delete pTimer;
		pTimer = pNtimer;
	}
	
    delete m_pEditor;
    delete hunting;
    delete hating;
    delete fearing;
    delete m_pFighting;

	CNoteData	*pNnote, *pNote = m_pNote;
	while (pNote) {
		pNnote = pNote->GetNext ();
		delete pNote;
		pNote = pNnote;
	}

    delete m_pPcdata;

    CMobProgActList *pNpact, *pPact = m_pMobPact;
    while (pPact) {
		pNpact = pPact->GetNext ();
		delete pPact;
		pPact = pNpact;
    }

    CNoteData	*pNcomm, *pComments = comments;
    while (pComments) {
		pNcomm = pComments->GetNext ();
		delete pComments;
		pComments = pNcomm;
    }
}