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.    *
 ****************************************************************************/
// nanny.cpp

#include	"stdafx.h"
#include	"smaug.h"
#include	"SysData.h"
#include	"language.h"
#include	"skill.h"
#include	"objects.h"
#include	"rooms.h"
#include	"races.h"
#include	"class.h"
#include	"SmaugWizDoc.h"
#include	"descriptor.h"
#include	"character.h"

BOOL	check_playing (CDescriptor *d, const char *name);
BOOL	check_reconnect (CDescriptor *d, BOOL bPwdValid);
void	SetNextErrorState (CDescriptor& Ds);
void	AskForClass (CDescriptor &Ds);
void	ExplainClassChange (CDescriptor &Ds);
void	ExplainRaceChange (CDescriptor &Ds);

BOOL	SetNewClass (char *argument, CDescriptor& Ds);
BOOL	SetNewRace (char *argument, CDescriptor& Ds);
void	show_title (CDescriptor &Ds);
void	GetNameForNanny (CDescriptor &Ds, char *argument,
			BOOL bNewPlayer = FALSE);
void	AskForRace (CDescriptor &Ds);


// Deal with sockets that haven't logged in yet.
void nanny (CDescriptor *d, char *argument)
{
	char		buf [MAX_STRING_LENGTH];
	CCharacter	*ch;
	CString		Npass;

	while (isspace (*argument))
		argument++;

	ch = d->m_pCharacter;

	switch (d->m_Connected) {
	  default:
		bug ("Nanny: bad d->m_Connected %d.", d->m_Connected);
		RemoveCharacter (*d);
		break;

	  case CON_GET_NAME:
	  case CON_GET_NEW_NAME:
		if (argument [0] == '\0')
			RemoveCharacter (*d);
		else
			GetNameForNanny (*d, argument,
				d->m_Connected == CON_GET_NEW_NAME);
		break;

	  case CON_GET_OLD_PASSWORD:
		d->WriteToBuffer ("\n\r");

		if (strcmp (Crypt (argument, ch->GetName ()),
		  ch->GetPcData ()->GetPassWord ())) {
			d->WriteToBuffer ("Wrong password.\n\r", 0);
		    RemoveCharacter (*d);
		    return;
		}

		d->WriteToBuffer (echo_on_str);

		if (check_playing (d, ch->GetName ()))
			return;

		if (check_reconnect (d, TRUE))
			return;

		sprintf (buf, ch->GetName ());
		d->m_pCharacter->SetDesc (NULL);
		delete d->m_pCharacter;
		load_char_obj (d, buf, FALSE);
		ch = d->m_pCharacter;

		// No ident so no user names
//		sprintf (log_buf, "%s%s (%s) has connected.", ch->GetName (),
//			d->m_pHost, d->user);

		sprintf (log_buf, "%s%s has connected.", ch->GetName (), d->m_pHost);
		if (ch->GetLevel () < LEVEL_DEMI) {
			// to_channel (log_buf, CHANNEL_MONITOR, "Monitor", ch->GetLevel ());
			gpDoc->LogString (log_buf, LOG_COMM, SysData.LogLevel);
		}
		else gpDoc->LogString (log_buf, LOG_COMM, ch->GetLevel ());

		SetNextErrorState (*d);
		break;

	  case CON_CONFIRM_NEW_NAME:
		switch (*argument) {
		  case 'y': case 'Y':
			sprintf (buf,
				"\n\rMake sure to use a password that won't be easily "
				"guessed by someone else.\n\rPick a good password for "
				"%s: %s", ch->GetName (), echo_off_str);
			d->WriteToBuffer (buf);
			d->m_Connected = CON_GET_NEW_PASSWORD;
			break;

		  case 'n': case 'N':
			d->WriteToBuffer ("Ok, what IS it, then? ");
			// clear descriptor pointer to get rid of bug message in log
			d->m_pCharacter->SetDesc (NULL);
			delete d->m_pCharacter;
			d->m_pCharacter = NULL;
			d->m_Connected = CON_GET_NEW_NAME;
			break;

		  default:
			d->WriteToBuffer ("Please type Yes or No? ");
			break;
		}
		break;

	  case CON_GET_NEW_PASSWORD:
		if (strlen (argument) < 5) {
			d->WriteToBuffer ("\n\rPassword must be at least five "
				"characters long.\n\rPassword: ");
			return;
		}

		Npass = Crypt (argument, ch->GetName ());
		if (Npass.Find ('~') >= 0) {
			d->WriteToBuffer ("\n\rNew password not acceptable, "
				"try again.\n\rPassword: ");
			return;
		}

		delete ch->GetPcData ()->GetPassWord ();
		ch->GetPcData ()->SetPassWord (str_dup (Npass));
		d->WriteToBuffer ("\n\rPlease retype the password to confirm: ");
		d->m_Connected = CON_CONFIRM_NEW_PASSWORD;
		break;

	  case CON_CONFIRM_NEW_PASSWORD:
		if (strcmp (Crypt (argument, ch->GetName ()),
		  ch->GetPcData ()->GetPassWord ())) {
			d->WriteToBuffer ("\n\rPasswords don't match.\n\rRetype password: ");
			d->m_Connected = CON_GET_NEW_PASSWORD;
			return;
		}

		d->WriteToBuffer (echo_on_str);
		d->WriteToBuffer ("\n\rWhat is your sex (M/F/N)? ");
		d->m_Connected = CON_GET_NEW_SEX;
		break;

	  case CON_GET_NEW_SEX:
		switch (argument [0]) {
		  case 'm': case 'M': ch->SetSex (SEX_MALE);    break;
		  case 'f': case 'F': ch->SetSex (SEX_FEMALE);  break;
		  case 'n': case 'N': ch->SetSex (SEX_NEUTRAL); break;
		  default:
			d->WriteToBuffer ("That's not a sex.\n\rWhat IS your sex? ");
			return;
		}

		AskForClass (*d);
		d->m_Connected = CON_GET_NEW_CLASS;
		break;

	  case CON_GET_NEW_CLASS:
		if (SetNewClass (argument, *d)) {
			AskForRace (*d);
			d->m_Connected = CON_GET_NEW_RACE;
		}
		break;

	  case CON_REDO_CLASS:
		if (SetNewClass (argument, *d)) {
			ch->ClrConnectError (CE_BADCLASS);
			SetNextErrorState (*d);
		}
		break;

	  case CON_GET_NEW_RACE:
		if (SetNewRace (argument, *d)) {
			d->WriteToBuffer ("\n\rWould you like RIP, ANSI or no "
				"graphic/color support, (R/A/N)? ");
			d->m_Connected = CON_GET_WANT_RIPANSI;
		}
		break;

	  case CON_REDO_RACE:
		if (SetNewRace (argument, *d)) {
			ch->ClrConnectError (CE_BADRACE);
			SetNextErrorState (*d);
		}
		break;


	  case CON_GET_WANT_RIPANSI:
		switch (argument [0]) {
		  case 'r': case 'R':
			ch->SetActBit (PLR_RIP);
			ch->SetActBit (PLR_ANSI);
			break;
		  case 'a': case 'A':
			ch->SetActBit (PLR_ANSI);
			break;
		  case 'n': case 'N':
			break;
		  default:
			d->WriteToBuffer ("Invalid selection.\n\rRIP, ANSI or NONE? ");
			return;
		}
	    sprintf (log_buf, "%s%s new %s %s.", ch->GetName (), d->m_pHost,
			RaceTable.GetName (ch->GetRace ()),
			ClassTable.GetName (ch->GetClass ()));
	    gpDoc->LogString (log_buf, LOG_COMM, SysData.LogLevel);
	    to_channel (log_buf, CHANNEL_MONITOR, "Monitor", LEVEL_IMMORTAL);
	    d->WriteToBuffer ("Press [ENTER] ");
	    show_title (*d);
	    ch->SetLevel (0);
	    ch->SetPosition (POS_STANDING);
	    d->m_Connected = CON_PRESS_ENTER;
	    break;

	  case CON_PRESS_ENTER:
		if (ch->IsRip ())
			send_rip_screen (ch);
		if (ch->IsAnsi ())
			send_to_pager ("\033[2J", ch);
		else send_to_pager ("\014", ch);
		set_pager_color (AT_LBLUE, ch);
		if (ch->IsImmortal ())
			do_help (ch, "imotd");
		if (ch->GetLevel () == 50)
			do_help (ch, "amotd");
		if (ch->GetLevel () < 50 && ch->GetLevel () > 0)
			do_help (ch, "motd");
		if (ch->GetLevel () == 0)
			do_help (ch, "nmotd");
		send_to_pager ("\n\rPress [ENTER] ", ch);
        d->m_Connected = CON_READ_MOTD;
        break;

	  case CON_READ_MOTD:
		sprintf (buf, "\n\rWelcome to %s...\n\r", SysData.GetLongTitle ());
		d->WriteToBuffer (buf);
		add_char (ch);
		d->m_Connected = CON_PLAYING;

		if (ch->GetLevel () == 0) {
			CObjData *obj;
			int iLang;

			ch->GetPcData ()->SetClanName (STRALLOC (""));
			ch->GetPcData ()->SetClan (NULL);
			switch (ClassTable.GetAttrPrime (ch->GetClass ())) {
				case APPLY_STR: ch->perm_str = 16; break;
				case APPLY_INT: ch->perm_int = 16; break;
				case APPLY_WIS: ch->perm_wis = 16; break;
				case APPLY_DEX: ch->perm_dex = 16; break;
				case APPLY_CON: ch->perm_con = 16; break;
				case APPLY_CHA: ch->perm_cha = 16; break;
				case APPLY_LCK: ch->perm_lck = 16; break;
			}

			CRaceData	&Ra = *RaceTable.GetRaceData (ch->GetRace ());
			ch->SetAffectFlags (Ra.GetAffectFlags ());
			ch->SetAttackFlags (Ra.GetAttackFlags ());
			ch->SetDefenseFlags (Ra.GetDefenseFlags ());

			ch->perm_str += Ra.str_plus;
			ch->perm_int += Ra.int_plus;
			ch->perm_wis += Ra.wis_plus;
			ch->perm_dex += Ra.dex_plus;
			ch->perm_con += Ra.con_plus;
			ch->perm_cha += Ra.cha_plus;
			ch->perm_lck += Ra.lck_plus;
	    
			ch->AddArmor (Ra.GetAcPlus ());
			ch->AddAlignment (Ra.GetAlignment ());
			ch->saving_poison_death = Ra.GetSavingPoisonDeath ();
			ch->saving_wand = Ra.GetSavingWand ();
			ch->saving_para_petri = Ra.GetSavingParaPetri ();
			ch->saving_breath = Ra.GetSavingBreath ();
			ch->saving_spell_staff = Ra.GetSavingSpellstaff ();

			ch->SetHeight (number_range ((int)(Ra.GetHeight () * .9),
					(int) (Ra.GetHeight () * 1.1)));
			ch->SetWeight (number_range ((int) (Ra.GetWeight () * .9),
					(int) (Ra.GetWeight () * 1.1)));

			if ((iLang = SkillTable.Lookup ("common")) < 0)
	    		bug ("Nanny: cannot find common language.");
			else ch->GetPcData ()->learned [iLang] = 100;
	    	
			for (int la=1; la < LanguageTable.GetCount (); ++la) {
				if (Ra.CanSpeak (la)) {
					int	sn = SkillTable.Lookup (LanguageTable.GetName (la));
					if (sn < 0)
						bug ("Nanny: cannot find racial language.");
					else ch->GetPcData ()->learned [sn] = 100;
				}
			}

			name_stamp_stats (ch);

			ch->SetLevel (1);
			ch->SetExp (0);
            ch->AddMaxHp (RaceTable.GetHp (ch->GetRace ()));
            ch->AddMaxMana (RaceTable.GetMana (ch->GetRace ()));
			ch->SetHp (ch->GetMaxHp ());
			ch->SetMana (ch->GetMaxMana ());
			ch->SetMove (ch->GetMaxMove ());
			sprintf (buf, "the %s", ClassTable.GetTitle (ch->GetClass (),
				ch->GetLevel (), ch->GetSex () == SEX_FEMALE ? 1 : 0));
			set_title (ch, buf);

            // Added by Narn.  Start new characters with autoexit and
            // autogold already turned on.  Very few people don't use those.
            ch->SetActBit (PLR_AUTOGOLD); 
            ch->SetActBit (PLR_AUTOEXIT); 

			// Added by Brittany, Nov 24/96.  The object is the adventurer's
			// guide to the realms of despair, part of Academy.are.
			CObjIndexData *obj_ind = OIdxTable.GetObj (SysData.m_ObjGuide);
			if (obj_ind != NULL) {
				obj = create_object (obj_ind, 0);
				obj_to_char (obj, ch);
				equip_char (ch, obj, WEAR_HOLD);
			}

			if (SysData.IsImmediateAuth ())
				ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomSchool));
			else {
				ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomAuthStart));
				ch->GetPcData ()->auth_state = 0;
				ch->SetUnauthed ();
			}
			// Display_prompt interprets blank as default
			ch->GetPcData ()->SetPrompt (STRALLOC (""));
		}
		else if (! ch->IsImmortal ()				// to hell with 'em
		  && ch->GetPcData ()->release_date > CurrentTime) {
			ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomHell));
		}
		else if (ch->GetInRoom () && (ch->IsImmortal () 
		  || ! ch->GetInRoom ()->IsPrototype ())) {
			ch->SendToRoom (ch->GetInRoom ());
		}
		else if (ch->IsImmortal ()) {
			ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomChat));
		}
		else {
			ch->SendToRoom (RoomTable.GetRoom (SysData.m_RoomTemple));
		}

		if (get_timer (ch, TIMER_SHOVEDRAG) > 0)
			remove_timer (ch, TIMER_SHOVEDRAG);

		if (get_timer (ch, TIMER_PKILLED) > 0)
			remove_timer (ch, TIMER_PKILLED);

		act (AT_ACTION, "$n has entered the game.", ch, NULL, NULL, TO_ROOM);

		if (ch->HasPet ()) {
			act (AT_ACTION, "$n returns to $s master from the Void.",
				ch->GetPet (), NULL, ch, TO_NOTVICT);
			act (AT_ACTION, "$N returns with you to the realms.",
				ch, NULL, ch->GetPet (), TO_CHAR);
			ch->GetPet ()->SendToRoom (ch->GetInRoom ());
		}         

		do_look (ch, "auto");
		mail_count (ch);

		if (! ch->GetWasInRoom ())
			ch->SetWasInRoom (ch->GetInRoom ());

		break;
    }
}


void SetNextErrorState (CDescriptor& Ds)
{
	CCharacter	*ch = Ds.m_pCharacter;

	switch (ch->GetConnectError ()) {
	  case CE_BADCLASS:
		ExplainClassChange (Ds);
		AskForClass (Ds);
		Ds.m_Connected = CON_REDO_CLASS;
		break;
	  case CE_BADRACE:
		ExplainRaceChange (Ds);
		AskForRace (Ds);
		Ds.m_Connected = CON_REDO_RACE;
		break;
	  case CE_NONE:
	  default:
		ch->SetConnectError (CE_NONE);
		show_title (Ds);
		Ds.m_Connected = CON_PRESS_ENTER;
	}
}


void GetNameForNanny (CDescriptor& Ds, char* argument, BOOL bNewPlayer /* = FALSE */)
{
	Ds.GetConnectionInfo ();
	argument [0] = UPPER (argument [0]);
	if (! check_parse_name (argument, bNewPlayer)) {
		Ds.WriteToBuffer ("Illegal name, try another.\n\rName: ");
		return;
	}

	if (!str_cmp (argument, "New")) {
		if (Ds.newstate == 0) {
			// New player
			if (gpDoc->m_bDenyNewPlayers) {
				Ds.WriteToBuffer ("The mud is currently preparing for a reboot.\n\r");
				Ds.WriteToBuffer ("New players are not accepted during this time.\n\r");
				Ds.WriteToBuffer ("Please try again in a few minutes.\n\r");
				RemoveCharacter (Ds);
			}
			else if (SysData.IsDenyNew ()) {
				Ds.WriteToBuffer ("This mud is currently not accepting new players.\n\r");
				Ds.WriteToBuffer ("You may contact the mud adminsitrators to inquire\n\r");
				Ds.WriteToBuffer ("as to when the mud will reopen to new players.\n\r");
				RemoveCharacter (Ds);
			}
			else Ds.WriteToBuffer (
"\n\rChoosing a name is one of the most important parts of this game...\n\r"
"Make sure to pick a name appropriate to the character you are going\n\r"
"to role play, and be sure that it suits a medieval theme.\n\r"
"If the name you select is not acceptable, you will be asked to choose\n\r"
"another one.\n\r\n\rPlease choose a name for your character: ");
			Ds.newstate++;
			Ds.m_Connected = CON_GET_NEW_NAME;
			return;
		} else {
			Ds.WriteToBuffer ("Illegal name, try another.\n\rName: ");
			return;
		}
	}

	BOOL	bOld = load_char_obj (&Ds, argument, TRUE);
	if (! Ds.m_pCharacter) {
		sprintf (log_buf, "Bad player file %s%s.", argument, Ds.m_pHost);
		gpDoc->LogString (log_buf);
		Ds.WriteToBuffer (
			"Your playerfile is corrupt...Please notify an Imm.\n\r");
		RemoveCharacter (Ds);
		return;
	}

	CCharacter	*ch = Ds.m_pCharacter;
	CBanData	*pban;

	// Can't ban site if host name unknown
	if (Ds.IsHost ()) {
		for (pban = first_ban; pban; pban = pban->GetNext ()) {
			// This used to use str_suffix, but in order to do bans by the 
			// first part of the ip, ie "ban 207.136.25" str_prefix must
			// be used. -- Narn
			if (!str_prefix (pban->name, &Ds.m_pHost [1]) && 
			  pban->level >= ch->GetLevel ()) {
				Ds.WriteToBuffer (
					"Your site has been banned from this Mud.\n\r");
				RemoveCharacter (Ds);
				return;
			}
		}
	}

	if (ch->IsDenied ()) {
		sprintf (log_buf, "Denying access to %s%s.", argument, Ds.m_pHost);
		gpDoc->LogString (log_buf, LOG_COMM, SysData.LogLevel);
		if (Ds.newstate != 0) {
			Ds.WriteToBuffer (
				"That name is already taken.  Please choose another: ");
			Ds.m_Connected = CON_GET_NEW_NAME;
			return;
		}
		Ds.WriteToBuffer ("You are denied access.\n\r");
		RemoveCharacter (Ds);
		return;
	}

	if (check_reconnect (&Ds, FALSE))
		bOld = TRUE;
	else {
		if (wizlock &&  ch->IsMortal ()) {
			Ds.WriteToBuffer ("The game is wizlocked.  Only immortals can connect now.\n\r");
			Ds.WriteToBuffer ("Please try back later.\n\r");
			RemoveCharacter (Ds);
			return;
		}
	}

	if (bOld) {
		if (Ds.newstate != 0) {
			Ds.WriteToBuffer ("That name is already taken.  Please choose another: ");
			Ds.m_Connected = CON_GET_NEW_NAME;
			return;
		}
		// Previously existing player
		Ds.WriteToBuffer ("Password: ");
		Ds.WriteToBuffer (echo_off_str);
		Ds.m_Connected = CON_GET_OLD_PASSWORD;
		return;
	} else {
		if (Ds.newstate == 0) {
			// No such player
			Ds.WriteToBuffer ("\n\rNo such player exists.\n\r"
				"Please check your spelling, or type new to start a new "
				"player.\n\r\n\rName: ");
			Ds.m_Connected = CON_GET_NAME;
			return;
		}

		// New player
		CString	Msg;
		Msg.Format ("Did I get that right, %s (Y/N)? ", argument);
		Ds.WriteToBuffer (Msg);
		Ds.m_Connected = CON_CONFIRM_NEW_NAME;
	}
}


void AskForClass (CDescriptor &Ds)
{
	char	buf [MAX_STRING_LENGTH];

	Ds.WriteToBuffer ("\n\rSelect a class, or type help [class] "
		"to learn more about that class.\n\r[");
	buf [0] = '\0';

	for (int iClass = 0; iClass < ClassTable.GetCount (); ++iClass) {
		char	*pName = ClassTable.GetName (iClass);
		if (iClass > 0) {
			if ((strlen (buf) + strlen (pName)) > 77) {
				strcat (buf, "\n\r");
				Ds.WriteToBuffer (buf);
				buf [0] = '\0';
			}
			else strcat (buf, " ");
		}
		strcat (buf, pName);
	}
	strcat (buf, "]\n\r: ");
	Ds.WriteToBuffer (buf);
}


BOOL SetNewClass (char *argument, CDescriptor& Ds)
{
	char	arg [MAX_STRING_LENGTH];

	CCharacter	*ch = Ds.m_pCharacter;
	argument = one_argument (argument, arg);

	if (! str_cmp (arg, "help")) {
		for (int cl = 0; cl < ClassTable.GetCount (); ++cl) {
			char	*pName = ClassTable.GetName (cl);
			if (toupper (argument [0]) == toupper (pName [0])
				&& ! str_prefix (argument, pName)) {
					do_help (ch, argument);
					Ds.WriteToBuffer ("Please choose a class: ");
					return FALSE;
			}
		}  
		Ds.WriteToBuffer ("No such help topic.  Please choose a class: ");
		return FALSE;
	}

	for (int cl = 0; cl < ClassTable.GetCount (); ++cl) {
		char	*pName = ClassTable.GetName (cl);
		if (toupper (arg [0]) == toupper (pName [0])
			&& ! str_prefix (arg, pName)) {
				ch->SetClass (cl);
				return TRUE;
		}
	}

	Ds.WriteToBuffer ("That's not a class.\n\rWhat IS your class? ");
	return FALSE;
}


void ExplainClassChange (CDescriptor& Ds)
{
	CString	s;

	s.Format ("\n\r  Your class is invalid.  This is probably\n\r"
		"  due to a change in the classes of %s.\n\r",
		NCCP SysData.GetLongTitle ());

	Ds.WriteToBuffer (s);
	Ds.WriteToBuffer ("Please Reselect your class:\n\r");
}


void AskForRace (CDescriptor &Ds)
{
	Ds.WriteToBuffer ("\n\rYou may choose from the following races, "
		"or type help [race] to learn more:\n\r[");
	
	CString		Msg;
	POSITION	pos = RaceTable.GetHeadPosition ();
	while (pos) {
		CRaceData	&Ra = *RaceTable.GetNext (pos);
		if (! Ra.IsVampire () && Ra.HasName ()
		  && ! Ra.IsClassRestricted (Ds.m_pCharacter->GetClass ())) {
			int		len = Msg.GetLength ();
			if (len) {
				if (len + strlen (Ra.GetName ()) > 77) {
					Msg += "\n\r";
					Ds.WriteToBuffer (Msg);
					Msg.Empty ();
				}
				else Msg += ' ';
			}
			Msg += Ra.GetName ();
		}
    }
	Msg += "]\n\r: ";
	Ds.WriteToBuffer (Msg);
}


BOOL SetNewRace (char *argument, CDescriptor& Ds)
{
	char	arg [MAX_STRING_LENGTH];

	CCharacter	*ch = Ds.m_pCharacter;
	argument = one_argument (argument, arg);
 
    if (! str_cmp (arg, "help")) {
		CRaceData	&Ra = *RaceTable.Find (argument);
		if (&Ra) {
			CString name = Ra.GetName ();
			do_help (ch, NCCP name);
		}
		else
			Ds.WriteToBuffer (
				"No help on that topic.  Please choose a race:");
		AskForRace (Ds);
		return FALSE;
	}

	CRaceData	&Ra = *RaceTable.Find (arg);
	if (! &Ra || ! Ra.HasName () || Ra.IsVampire ()
	  || Ra.IsClassRestricted (ch->GetClass ())) {
		Ds.WriteToBuffer ("That's not a race.\n\rWhat IS your race? ");
		return FALSE;
	}
	ch->SetRace (Ra.GetRace ());

	return TRUE;
}


void ExplainRaceChange (CDescriptor& Ds)
{
	CString	s;

	s.Format ("\n\r  Your race is invalid.  This is probably\n\r"
		"  due to a change in the races of %s.\n\r",
		NCCP SysData.GetLongTitle ());

	Ds.WriteToBuffer (s);
	Ds.WriteToBuffer ("Please Reselect your race:\n\r");
}


void show_title (CDescriptor &Ds)
{
	CCharacter	*ch;

	ch = Ds.m_pCharacter;

	if (! ch->IsNoIntro ()) {
		if (ch->IsRip ())
			send_rip_title (ch);
		else if (ch->IsAnsi ())
			send_ansi_title (ch);
		else send_ascii_title (ch);
	}
	else Ds.WriteToBuffer ("Press enter...\n\r");
}


// Check if already playing.
BOOL check_playing (CDescriptor *d, const char *name)
{
	POSITION	pos = DList.GetHeadPosition ();
	while (pos) {
		CDescriptor	&Dold = *DList.GetNext (pos);
		if (Dold.IsDisconnecting ())
			continue;

		if (&Dold != d && Dold.m_pCharacter &&
		  Dold.m_Connected != CON_GET_NAME &&
		  Dold.m_Connected != CON_GET_NEW_NAME &&
		  Dold.m_Connected != CON_GET_OLD_PASSWORD &&
		  ! str_cmp (name, Dold.m_pOriginal ?
			  Dold.m_pOriginal->GetName () : Dold.m_pCharacter->GetName ())){

			delete d->m_pCharacter;
			CCharacter	*ch = Dold.m_pCharacter;
			d->m_pCharacter = ch;
			Dold.m_pCharacter = NULL;
			RemoveCharacter (Dold);

			ch->SetDesc (d);
			ch->SetTimer (0);
			ch->SendText ("Reconnecting.\n\r");
			act (AT_ACTION, "$n has lost $s link.", ch,
				NULL, NULL, TO_ROOM);
			act (AT_ACTION, "$n has reconnected, kicking off "
				"old connection.", ch, NULL, NULL, TO_ROOM);

			// Do not check for ident - Zircon
			// sprintf (log_buf, "%s%s (%s) reconnected.", ch->GetName(),
			//	d->m_pHost, d->user);
			sprintf (log_buf, "%s%s has reconnected, kicking off old "
				"connection.", ch->GetName(), d->m_pHost);
			gpDoc->LogString (log_buf, LOG_COMM,
				UMAX ((int) SysData.LogLevel, (int) ch->GetLevel()));
			d->m_Connected = CON_PLAYING;

			return TRUE;
		}
    }

    return FALSE;
}




// Look for link-dead player to reconnect.
// The descriptor has been loaded with the player data from
// the reconnect, we look for a matching character in the character
// list.
BOOL check_reconnect (CDescriptor *d, BOOL bPwdValid)
{
	CCharacter	*ch;
	CObjData	*obj;
	const char	*name = d->m_pCharacter->GetName ();

	for (ch = first_char; ch != NULL; ch = ch->GetNext ()) {
		if (! ch->IsNpc () && (!bPwdValid || ch->GetDesc () == NULL) &&
		  ch->GetName () && !str_cmp (name, ch->GetName ())) {
			// Look at smaug code in comm.c to see about handling
			// switched characters here.
			if (bPwdValid == FALSE) {
				free_string (d->m_pCharacter->GetPcData ()->GetPassWord ());
				d->m_pCharacter->GetPcData ()->SetPassWord (
					str_dup (ch->GetPcData ()->GetPassWord ()));
			} else {
				delete d->m_pCharacter;
				d->m_pCharacter = ch;
				ch->SetDesc (d);
				ch->SetTimer (0);
				ch->SendText ("Reconnecting.\n\r");
				act (AT_ACTION, "$n has reconnected.", ch, NULL, NULL, TO_ROOM);
				// No user id yet
//				sprintf (log_buf, "%s%s (%s) reconnected.", ch->GetName (),
//					d->m_pHost, d->user);
				sprintf (log_buf, "%s%s reconnected.", ch->GetName (),
					d->m_pHost);
				gpDoc->LogString (log_buf, LOG_COMM,
					UMAX ((int)SysData.LogLevel, (int)ch->GetLevel ()));
				d->m_Connected = CON_PLAYING;

				// Contributed by Gene Choi
				// { If the reconnecting player has a light, add
				//   it to the count of lights in the room. (RCP) }
				if ((obj = get_eq_char (ch, WEAR_LIGHT)) != NULL &&
					obj->item_type == ITEM_LIGHT && obj->value [2] != 0 &&
					ch->GetInRoom () != NULL)
						++ch->GetInRoom ()->light;
			}
			return TRUE;
		}
	}
	return FALSE;
}