daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(			  DalekenMUD 1.12 (C) 2000			)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc 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{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  Much time and thought has gone into this software and	 ||
    || you are benefitting. We hope that you share your changes too.	 ||
    || What goes around, comes around.					 ||
    || ----------------------------------------------------------------- ||
    ||                               comm.c                              ||
    || Communication and system stuff.                                   ||
 *_/<>\_________________________________________________________________/<>\_*/


/***************************************************************************
 *  Thanks to abaddon for proof-reading our comm.c and pointing out bugs.  *
 *  Any remaining bugs are, of course, our work, not his.  :)		   *
 ***************************************************************************/

/*
 * This file contains all of the OS-dependent stuff:
 *   startup, signals, BSD sockets for tcp/ip, i/o, timing.
 *
 * The data flow for input is:
 *    Game_loop ---> Read_from_descriptor ---> Read
 *    Game_loop ---> Read_from_buffer
 *
 * The data flow for output is:
 *    Game_loop ---> Process_Output ---> Write_to_descriptor -> Write
 *
 * The OS-dependent functions are Read_from_descriptor and Write_to_descriptor.
 * -- Furey  26 Jan 1993
 */

#if defined( WIN32 )
char version_str[] = "$VER: DalekenMud 1.12 Windows 32 Bit Version";
/*
 * Provided by Mystro <http://www.cris.com/~Kendugas/mud.shtml>
 */
#else
char version_str[] = "$VER: DalekenMud 1.12 *NIX";
#endif

#include <errno.h>
#include <stdarg.h>
#include <math.h>
#include <signal.h>
#if defined( WIN32 )
# include <sys/timeb.h>		/* for _ftime( ), uses _timeb struct */
#else
# include <sys/time.h>
# include <sys/times.h>		/* for deadlock safety */
# include <sys/resource.h>
# include <unistd.h>		/* for unlink() and copyover/execl() */
#endif

#include "mud.h"

/*
 * Socket and TCP/IP stuff.
 */
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/telnet.h>
const char echo_off_str[] = { IAC, WILL, TELOPT_ECHO, '\0' };
const char echo_on_str[] = { IAC, WONT, TELOPT_ECHO, '\0' };
const char go_ahead_str[] = { IAC, GA, '\0' };
#if defined( MCCP )
const char compress_will[] = { IAC, WILL, TELOPT_COMPRESS2,
			       IAC, WILL, TELOPT_COMPRESS, '\0' };
#endif

/*
 * Global variables.
 */
DESCRIPTOR_DATA *descriptor_free;	/* Free list for descriptors	*/
DESCRIPTOR_DATA *descriptor_list;	/* All open descriptors		*/
DESCRIPTOR_DATA *d_next;		/* Next descriptor in loop	*/
FILE *fpReserve;			/* Reserved file handle		*/
bool merc_down;				/* Shutdown			*/
time_t boot_time;			/* Bootup time			*/
time_t current_time;			/* Time of this pulse		*/
unsigned short port;			/* Port number			*/
SOCKET control;				/* Control descriptor number	*/
unsigned long data_trans_inc;		/* small counter of data sent	*/
double data_trans;			/* for the overflow		*/
unsigned long data_recv;		/* # of bytes of data received	*/

IDENT_DATA *ident_free;			/* Used for ident information	*/
IDENT_DATA *ident_list;			/*	ditto			*/

/*
 * Local Function definitions.
 *
 * (as single player macintosh and MSDOG versions aren't supported
 *  all functions seem to be the same)
 */
void game_loop_standard	args( ( SOCKET control ) );
SOCKET init_socket	args( ( unsigned short port ) );
void new_descriptor	args( ( SOCKET control ) );
bool read_from_descriptor	args( ( DESCRIPTOR_DATA *d ) );
bool write_to_descriptor_nice	args( ( DESCRIPTOR_DATA *d ) );

bool check_reconnect	args( ( DESCRIPTOR_DATA *d, char *name,
				bool fConn ) );
bool check_playing	args( ( DESCRIPTOR_DATA *d, char *name ) );
int main		args( ( int argc, char **argv ) );
bool process_output	args( ( DESCRIPTOR_DATA *d ) );
void read_from_buffer	args( ( DESCRIPTOR_DATA *d ) );
void stop_idling	args( ( DESCRIPTOR_DATA *d ) );
void vt100_prompt	args( ( DESCRIPTOR_DATA *d ) );
void bust_a_prompt	args( ( DESCRIPTOR_DATA *d ) );
void init_descriptor	args( ( DESCRIPTOR_DATA *dnew, int desc ) );
#if defined( unix )
void signal_handler	args( ( int sig ) );
#endif
void init_signals	args( ( void ) );
void shutdown_wgrace	args( ( void ) );
void write_last_command args( ( void ) );
void reroll_stats	args( ( DESCRIPTOR_DATA *d ) );

/* Alarm for used processor time, based on code by Erwin S. Andreasen */
#if defined( unix )
void alarm_handler	args( ( int signo ) );
void install_alarm	args( ( void ) );
int get_user_time	args( ( void ) );
void update_alarm	args( ( void ) );

volatile int alarm_threshold = ALARM_BOOT_DB;
volatile int last_checkpoint;
#endif




/* Ident code by Dark@Igor, WIN32 modifications by Symposium */
void write_ident	args( ( IDENT_DATA *id ) );
void read_ident		args( ( IDENT_DATA *id ) );
void free_ident		args( ( IDENT_DATA *id ) );
IDENT_DATA *new_ident	args( ( DESCRIPTOR_DATA *desc ) );
void parse_ident	args( ( IDENT_DATA *id ) );


/*
 * Interpreter Prototypes
 */
INTERPRETER_FUN standard_interpret;
INTERPRETER_FUN aedit;
INTERPRETER_FUN redit;
INTERPRETER_FUN medit;
INTERPRETER_FUN oedit;
INTERPRETER_FUN sedit;
INTERPRETER_FUN hedit;
INTERPRETER_FUN reledit;
INTERPRETER_FUN cedit;
INTERPRETER_FUN poseedit;
INTERPRETER_FUN pledit;
INTERPRETER_FUN gmpedit;
INTERPRETER_FUN nanny_get_name;
INTERPRETER_FUN nanny_get_old_password;
INTERPRETER_FUN nanny_confirm_name;
INTERPRETER_FUN nanny_get_new_password;
INTERPRETER_FUN nanny_confirm_password;
INTERPRETER_FUN nanny_get_sex;
INTERPRETER_FUN nanny_display_class;
INTERPRETER_FUN nanny_get_class;
INTERPRETER_FUN nanny_confirm_class;
INTERPRETER_FUN nanny_display_race;
INTERPRETER_FUN nanny_get_race;
INTERPRETER_FUN nanny_confirm_race;
INTERPRETER_FUN nanny_reroll;
INTERPRETER_FUN nanny_magic;
INTERPRETER_FUN nanny_read_motd;

/* not in the table... */
INTERPRETER_FUN pager_interpret;

/*
 * Prompt functions.
 */
void standard_prompt	args( ( DESCRIPTOR_DATA *d ) );


const struct interpreter_type interpreter_table[] =
{
    {		/* -- 0 -- */
	"", standard_interpret, standard_prompt,
	INTERPRETER_STANDARD
    },
    {
	"AEdit", aedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"REdit", redit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"MEdit", medit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"OEdit", oedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {		/* -- 5 -- */
	"HEdit", hedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"SEdit", sedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"RelEdit", reledit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"CEdit", cedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"PoseEdit", poseedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {		/* -- 10 -- */
	"PlEdit", pledit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"GMPEdit", gmpedit, standard_prompt,
	INTERPRETER_LEADSPACE
    },
    {
	"getName", nanny_get_name, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"getOldPasswd", nanny_get_old_password, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"confirmName", nanny_confirm_name, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {		/* -- 15 -- */
	"getNewPasswd", nanny_get_new_password, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"confirmPasswd", nanny_confirm_password, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"getSex", nanny_get_sex, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"displayClass", nanny_display_class, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"getClass", nanny_get_class, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {		/* -- 20 -- */
	"confirmClass", nanny_confirm_class, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"displayRace", nanny_display_race, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"getRace", nanny_get_race, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"confirmRace", nanny_confirm_race, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"reroll", nanny_reroll, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {		/* -- 25 -- */
	"magic", nanny_magic, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"readMOTD", nanny_read_motd, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"copyoverRecover", NULL, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	"disconnect", NULL, NULL,
	INTERPRETER_SAFE|INTERPRETER_NOALIAS|INTERPRETER_NOMESSAGE
    },
    {
	NULL, NULL, NULL, 0
    }
};


#if defined( BCB )
int bcb_main( int argc, char **argv )
#else
int main( int argc, char **argv )
#endif
{
    struct timeval now_time;
    bool fCopyOver = FALSE;

    /*
     * Init time.
     */
    gettimeofday( &now_time, NULL );
    current_time = (time_t)now_time.tv_sec;
    boot_time = current_time;

    /*
     * Reserve one channel for our use.
     */
    if( !( fpReserve = fopen( NULL_FILE, "r" ) ) )
    {
	perror( NULL_FILE );
	exit( 1 );
    }

    /*
     * Get the port number.
     */
    port = 12345;
    if( argc > 1 )
    {
	if( !is_number( argv[1] ) )
	{
	    fprintf( stderr, "Usage: %s [port #]\n", argv[0] );
	    exit( 1 );
	}
	else if( ( port = atoi( argv[1] ) ) <= 1024 && port != 23 )
	{
	    fprintf( stderr, "Port number must be above 1024 or 23.\n" );
	    exit( 1 );
	}
    }

#if !defined( WIN32 )
    /* Are we recovering from a copyover? */
    if( argc == 4 && argv[2] && !str_cmp( argv[2], "copyover" ) )
    {
	fCopyOver = TRUE;
	control = (SOCKET)atoi( argv[3] );
    }
#endif

#if defined( unix )
    install_alarm();
#endif

    /*
     * Boot the server.
     */
    if( !fCopyOver )
	control = init_socket( port );
    boot_db( );

#if defined( unix )
    /* Install sigaction handler.
     * Gary Mcnickle <gary@dharvest.com> 10/98
     */
    init_signals();
#endif

    log_string( "DalekenMUD systems are on standby on port %d.", port );

    if( fCopyOver )
	copyover_recover( );

#if defined( unix )
    /* Update alarm times for normal operation. */
    update_alarm();
#endif

    /*
     * Run the game.
     */
    game_loop_standard( control );

#if !defined( WIN32 )
    close( control );
#else
    closesocket( control );
    WSACleanup( );
#endif

    /*
     * That's all, folks.
     */
    log_string( "Normal termination of game." );
#if defined( unix )
    log_string( "Total CPU time used: %d.%1.1d seconds.",
		get_user_time( ) / 10, get_user_time( ) % 10 );
#endif
    log_string( "Bytes sent: %.0f; bytes received: %lu.",
		data_trans + data_trans_inc, data_recv );
    exit( 0 );
    return 0;
}


SOCKET init_socket( unsigned short port )
{
    static struct sockaddr_in sa_zero;
    struct sockaddr_in sa;
    int x = 1;
    int fd;

#if !defined( WIN32 )
    system( "touch SHUTDOWN.TXT" );
    if( ( fd = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
	perror( "Init_socket: socket" );
	exit( 1 );
    }
#else
    WORD wVersionRequested = MAKEWORD( 1, 1 );
    WSADATA wsaData;
    int err = WSAStartup( wVersionRequested, &wsaData );

    if( err != 0 )
    {
	perror( "No useable WINSOCK.DLL" );
	exit( 1 );
    }

    if( ( fd = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
	perror( "Init_socket: socket" );
	exit( 1 );
    }
#endif

    if( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR,
		   (char *)&x, sizeof( x ) ) < 0 )
    {
	perror( "Init_socket: SO_REUSEADDR" );
#if !defined( WIN32 )
	close( fd );
#else
	closesocket( fd );
#endif
	exit( 1 );
    }

#if defined( SO_DONTLINGER ) && !defined( SYSV )
    {
	struct linger ld;

	ld.l_onoff = 1;
	ld.l_linger = 1000;

	if( setsockopt( fd, SOL_SOCKET, SO_DONTLINGER,
		       (char *)&ld, sizeof( ld ) ) < 0 )
	{
	    perror( "Init_socket: SO_DONTLINGER" );
#if !defined( WIN32 )
	    close( fd );
#else
	    closesocket( fd );
#endif
	    exit( 1 );
	}
    }
#endif

    sa = sa_zero;
#if !defined( WIN32 )
    sa.sin_family = AF_INET;
#else
    sa.sin_family = PF_INET;
#endif
    sa.sin_port = htons( port );

#if defined( BIND_SINGLE_IP )
    sa.sin_addr.s_addr = inet_addr( BIND_SINGLE_IP );
#else
    sa.sin_addr.s_addr = htonl( INADDR_ANY );
#endif

    if( bind( fd, (struct sockaddr *)&sa, sizeof( sa ) ) < 0 )
    {
	perror( "Init_socket: bind" );
#if !defined( WIN32 )
	close( fd );
#else
	closesocket( fd );
#endif
	exit( 1 );
    }

    if( listen( fd, 3 ) < 0 )
    {
	perror( "Init_socket: listen" );
#if !defined( WIN32 )
	close( fd );
#else
	closesocket( fd );
#endif
	exit( 1 );
    }

#if !defined( WIN32 )
    system( "rm SHUTDOWN.TXT" );
#endif
    return (SOCKET)fd;
}


void game_loop_standard( SOCKET control )
{
    static struct timeval null_time;
    struct timeval last_time;

    gettimeofday( &last_time, NULL );
    current_time = (time_t)last_time.tv_sec;

    SysInfo->pulse = 0;

    /* Main loop */
    while( !merc_down )
    {
	DESCRIPTOR_DATA *d;
	fd_set in_set;
	fd_set out_set;
	fd_set exc_set;
	SOCKET maxdesc;
	int i, j;
	IDENT_DATA *id, *id_next;

	/* Sleeping when noone is around, pinched from CircleMUD */
	if( descriptor_list == NULL && SysInfo->pulse > 0 )
	{
	    log_string( "No connections.  Having a nap." );
	    FD_ZERO( &in_set);
	    FD_SET( control, &in_set );
	    if( select( control + 1, &in_set, NULL, NULL, NULL ) < 0 )
	    {
		if( errno == EINTR )
		    log_string( "ZZZZ...wha?...Oh nothing, just a signal." );
		else
		    perror( "sleep select:" );
	    }
	    else
		log_string( "New connection, waking up." );
	    gettimeofday( &last_time, NULL );
	    current_time = (time_t)last_time.tv_sec;
	}

	SysInfo->pulse++;

	/*
	 * Poll all active descriptors.
	 */
	FD_ZERO( &in_set );
	FD_ZERO( &out_set );
	FD_ZERO( &exc_set );
	FD_SET( control, &in_set );
	maxdesc = control;
	for( d = descriptor_list; d; d = d->next )
	{
	    maxdesc = UMAX( (unsigned)maxdesc, d->descriptor );
	    FD_SET( d->descriptor, &in_set );
	    FD_SET( d->descriptor, &out_set );
	    FD_SET( d->descriptor, &exc_set );
	}

	/* Poll for ident information */
	for( id = ident_list; id != NULL; id = id->next )
	{
	    maxdesc = UMAX( maxdesc, id->descriptor );
	    if( id->connected )
		FD_SET( id->descriptor, &in_set );
	    else
		FD_SET( id->descriptor, &out_set );
	}

	if( select( maxdesc + 1, &in_set, &out_set, &exc_set, &null_time ) < 0 )
	{
	    perror( "Game_loop: select: poll" );
#if defined( unix )
	    raise( SIGABRT );
#endif
	    exit( 1 );
	}

	/*
	 * New connection?
	 */
	if( FD_ISSET( control, &in_set ) )
	    new_descriptor( control );

	/*
	 * Kick out the freaky folks.
	 */
	for( d = descriptor_list; d; d = d_next )
	{
	    d_next = d->next;
	    if( FD_ISSET( d->descriptor, &exc_set ) )
	    {
		FD_CLR( d->descriptor, &in_set );
		FD_CLR( d->descriptor, &out_set );
		if( d->character )
		    save_char_obj( d->character );
		d->outtop = 0;
		close_socket( d );
	    }
	}

	/* ident handling */
	for( id = ident_list; id != NULL; id = id_next )
	{
	    id_next = id->next;

	    if( !id->connected && FD_ISSET( id->descriptor, &out_set ) )
		write_ident( id );
	    else if( FD_ISSET( id->descriptor, &in_set ) )
		read_ident( id );
	    /* timed out */
	    else if( current_time > id->timeout )
		free_ident( id );
	}

	/*
	 * Process input.
	 */
	for( d = descriptor_list; d; d = d_next )
	{
	    d_next = d->next;
	    d->fcommand = FALSE;
	    if( FD_ISSET( d->descriptor, &in_set ) )
	    {
		if( d->character && d->character->pcdata )
		    d->character->pcdata->timer = 0;
		if( !read_from_descriptor( d ) )
		{
		    FD_CLR( d->descriptor, &out_set );
		    if( d->character )
			save_char_obj( d->character );
		    d->outtop = 0;
		    close_socket( d );
		    continue;
		}
	    }

	    if( d->character && d->character->wait > 0 )
	    {
		if( xIS_SET( CH( d )->act, PLR_NO_LAG ) )
		{
		    d->character->wait = 0;
		}
		else
		{
		    --d->character->wait;
		    continue;
		}
	    }

	    read_from_buffer( d );
	    for( i = 0; d->incomm[0] != '\0' && i < 10; i++ )
	    {
		d->fcommand = TRUE;
		stop_idling( d );

		/* look up aliases etc... */
		for( j = 0; j < 10 && d->incomm[0] != 0
			 && !IS_SET( d->interpreter->flags, INTERPRETER_NOALIAS )
			 && !d->pString
			 && perform_alias( d ); ++j )
		    read_from_buffer( d );

		/* interpret the command */
		if( d->showstr_point )
		    pager_interpret( d, d->incomm );
		else if( d->pString )
		    string_add( d->character, d->incomm );
		else if( d->interpreter->interpreter )
		    (*d->interpreter->interpreter)( d, d->incomm );

		d->incomm[0] = '\0';
		if( d == descriptor_free
		    || ( CH( d ) && !xIS_SET( CH(d)->act, PLR_NO_LAG ) ) )
		    break;
		read_from_buffer( d );
	    }
	}

	/*
	 * Autonomous game motion.
	 */
	update_handler( );

	/*
	 * Output.
	 */
	for( d = descriptor_list; d; d = d_next )
	{
	    d_next = d->next;

	    if( ( d->fcommand || d->outtop > 0 )
		&& FD_ISSET( d->descriptor, &out_set ) )
	    {
		if( !process_output( d ) )
		{
		    if( d->character )
			save_char_obj( d->character );
		    d->outtop = 0;
		    close_socket( d );
		}
	    }
	}

	/*
	 * Synchronize to a clock.
	 * Sleep( last_time + 1/PULSE_PER_SECOND - now ).
	 * Careful here of signed versus unsigned arithmetic.
	 */
	{
	    struct timeval now_time;
	    long secDelta;
	    long usecDelta;

	    gettimeofday( &now_time, NULL );
	    usecDelta = ( (long)last_time.tv_usec )
		- ( (long)now_time.tv_usec )
		+ 1000000 / PULSE_PER_SECOND;
	    secDelta = ( (long)last_time.tv_sec )
		- ( (long)now_time.tv_sec );
	    while( usecDelta < 0 )
	    {
		usecDelta += 1000000;
		secDelta -= 1;
	    }

	    while( usecDelta >= 1000000 )
	    {
		usecDelta -= 1000000;
		secDelta += 1;
	    }

	    if( secDelta > 0 || ( secDelta == 0 && usecDelta > 0 ) )
	    {
		struct timeval stall_time;

		stall_time.tv_usec = usecDelta;
		stall_time.tv_sec = secDelta;
#if defined( WIN32 )
		Sleep( stall_time.tv_sec * 1000 + stall_time.tv_usec / 1000 );
#else
		if( select( 0, NULL, NULL, NULL, &stall_time ) < 0 )
		{
		    perror( "Game_loop: select: stall" );
#if defined( unix )
		    raise( SIGABRT );
#endif
		    exit( 1 );
		}
#endif
	    }
	}

	gettimeofday( &last_time, NULL );
	current_time = (time_t)last_time.tv_sec;
    }

    return;
}


void init_descriptor( DESCRIPTOR_DATA *dnew, int desc )
{
    static DESCRIPTOR_DATA d_zero;

    *dnew = d_zero;
    dnew->descriptor = desc;
    dnew->character = NULL;
    dnew->interpreter = get_interpreter( "getName" );
    dnew->pEdit = NULL;		/* OLC */
    dnew->pString = NULL;	/* OLC */
    dnew->showstr_head = str_dup( "" );
    dnew->showstr_point = 0;
    dnew->fpromptok = TRUE;
}


void new_descriptor( SOCKET control )
{
    DESCRIPTOR_DATA *dnew;
    struct sockaddr_in sock;
    struct hostent *from;
    BAN_DATA *pban;
    char buf[MAX_STRING_LENGTH];
    int desc;
    size_t size;
    int addr;

    size = sizeof( sock );
    if( ( desc = accept( control, (struct sockaddr *)&sock, &size ) ) < 0 )
    {
	perror( "New_descriptor: accept" );
	return;
    }

#if !defined( FNDELAY )
#define FNDELAY O_NDELAY
#endif

#if !defined( WIN32 )
    if( fcntl( desc, F_SETFL, FNDELAY ) == -1 )
    {
	perror( "New_descriptor: fcntl: FNDELAY" );
	return;
    }
#endif

    /*
     * Cons a new descriptor.
     */
    if( !descriptor_free )
    {
	dnew = alloc_perm( sizeof( *dnew ) );
    }
    else
    {
	dnew = descriptor_free;
	descriptor_free = descriptor_free->next;
    }

    init_descriptor( dnew, desc );

    size = sizeof( sock );

    /*
     * Would be nice to use inet_ntoa here but it takes a struct arg,
     * which ain't very compatible between gcc and system libraries.
     */
    addr = ntohl( sock.sin_addr.s_addr );
    sprintf( buf, "%d.%d.%d.%d",
	     ( addr >> 24 ) & 0xFF, ( addr >> 16 ) & 0xFF,
	     ( addr >> 8 ) & 0xFF, ( addr ) & 0xFF
	 );
    log_string( "Sock.sinaddr: %s", buf );
    from = gethostbyaddr( (char *)&sock.sin_addr,
			 sizeof( sock.sin_addr ), AF_INET );
    dnew->host = str_dup( from ? from->h_name : buf );

    /*
     * Swiftest: I added the following to ban sites.  I don't
     * endorse banning of sites, but Copper has few descriptors now
     * and some people from certain sites keep abusing access by
     * using automated 'autodialers' and leaving connections hanging.
     *
     * Furey: added suffix check by request of Nickel of HiddenWorlds.
     */
    for( pban = ban_list; pban; pban = pban->next )
    {
	if( !str_suffix( pban->name, dnew->host ) )
	{
	    write_to_descriptor( desc,
		 "Your site has been banned from this Mud.\n\r", 0 );
#if !defined( WIN32 )
	    close( desc );
#else
	    closesocket( desc );
#endif
	    free_string( dnew->host );
	    if( dnew->ident )
		free_ident( dnew->ident );
	    dnew->next = descriptor_free;
	    descriptor_free = dnew;
	    return;
	}
    }

    /*
     * Init descriptor data.
     */
    dnew->ident = new_ident( dnew );	/* set ident */
    dnew->next = descriptor_list;
    descriptor_list = dnew;
    SysInfo->curr_descriptors++;
    if( SysInfo->curr_descriptors > SysInfo->max_descriptors )
	SysInfo->max_descriptors = SysInfo->curr_descriptors;

    /*
     * Send the greeting.
     */
#if defined( MCCP )
    write_to_buffer( dnew, compress_will );
#endif
    if( help_greeting[0] == '.' )
	write_to_buffer( dnew, help_greeting + 1 );
    else
	write_to_buffer( dnew, help_greeting );

    return;
}


void close_socket( DESCRIPTOR_DATA *dclose )
{
    CHAR_DATA *ch;

    if( dclose->outtop > 0 )
	process_output( dclose );

    if( dclose->snoop_by )
    {
	write_to_buffer( dclose->snoop_by,
			 "Your victim has left the game.\n\r" );
    }

    {
	DESCRIPTOR_DATA *d;

	for( d = descriptor_list; d; d = d->next )
	{
	    if( d->snoop_by == dclose )
		d->snoop_by = NULL;
	}
    }

    if( ( ch = dclose->character ) )
    {
	sprintf( log_buf, "Closing link to %s.", ch->name );
	log_string( log_buf );
	if( !IS_SET( dclose->interpreter->flags, INTERPRETER_SAFE ) )
	{
	    act( "$n has lost $s link.", ch, NULL, NULL, TO_ROOM );
	    ch->desc = NULL;
	}
	else
	{
	    dclose->interpreter = get_interpreter( "disconnect" );
	    free_char( dclose->character );
	}
    }
    SysInfo->curr_descriptors--;


    if( d_next == dclose )
	d_next = d_next->next;

    if( dclose == descriptor_list )
    {
	descriptor_list = descriptor_list->next;
    }
    else
    {
	DESCRIPTOR_DATA *d;

	for( d = descriptor_list; d && d->next != dclose; d = d->next )
	    ;
	if( d )
	    d->next = dclose->next;
	else
	    bug( "Close_socket: dclose not found." );
    }

#if defined( MCCP )
    if( dclose->out_compress )
    {
	deflateEnd( dclose->out_compress );
	free_mem( dclose->out_compress_buf, COMPRESS_BUF_SIZE );
	free_mem( dclose->out_compress, sizeof( z_stream ) );
    }
#endif

#if !defined( WIN32 )
    close( dclose->descriptor );
#else
    closesocket( dclose->descriptor );
#endif
    free_string( dclose->host );

    /* RT socket leak fix */
    if( dclose->ident )
	free_ident( dclose->ident );
    dclose->next = descriptor_free;
    descriptor_free = dclose;
    return;
}


/*
 * Create a new ident structure and connect it.
 */
IDENT_DATA *new_ident( DESCRIPTOR_DATA *desc )
{
    IDENT_DATA *id;
    static IDENT_DATA id_zero;
    int d;
    int localport, remoteport;
    struct sockaddr_in sin;
#if defined( WIN32 )
    int len;
#else
    size_t len;
#endif

    len = sizeof( sin );

    if( getsockname( desc->descriptor, (struct sockaddr *) &sin, &len ) < 0
	|| len < sizeof( sin ) )
	return NULL;
    localport = ntohs( sin.sin_port );

    len = sizeof( sin );
    /* This will also leave sin.sin_addr useful for connect() below. */
    if( getpeername( desc->descriptor, (struct sockaddr *)&sin, &len ) < 0
	|| len < sizeof( sin ) )
	return NULL;
    remoteport = ntohs( sin.sin_port );

    if( ( d = socket( PF_INET, SOCK_STREAM, 0 ) ) < 0 )
    {
	bug( "%s: Ident socket: socket", strerror( errno ) );
	return NULL;
    }

#if !defined( FNDELAY )
#define FNDELAY O_NDELAY
#endif

#if !defined( WIN32 )
    if( fcntl( d, F_SETFL, FNDELAY ) == -1 )
    {
	bug( "%s: Ident socket: fcntl: FNDELAY", strerror( errno ) );
	close( d );
	return NULL;
    }
#endif

    /* sin.sin_addr was set by getpeername() above. */
#if !defined( WIN32 )
    sin.sin_family = AF_INET;
#else
    sin.sin_family = PF_INET;
#endif
    sin.sin_port = htons( 113 );
    if( connect( d, (struct sockaddr *) &sin, len ) < 0 )
    {
#if !defined( WIN32 )
	bool dead = TRUE;

	/* A non-blocking connect can return EINPROGRESS but still succeed
	 * This section of code is suggested in the linux manuals for
	 * connect.  This way I can play it safe.
	 */
	if( errno == EINPROGRESS )
	{
	    fd_set w_set;
	    struct timeval tv;
	    int sopt;
          size_t soptlen;

	    FD_ZERO( &w_set );
	    FD_SET( d, &w_set );
	    tv.tv_sec = 0;
	    tv.tv_usec = 2;
	    soptlen = sizeof( sopt );

	    if( select( d + 1, NULL, &w_set, NULL, &tv ) >= 0
		&& getsockopt( d, SOL_SOCKET, SO_ERROR, &sopt, &soptlen ) >= 0
		&& sopt == 0 )
		    dead = FALSE;
	}
	if( dead )
	{
	    close( d );
	    return NULL;
	}
#else
	closesocket( d );
	return NULL;
#endif
    }

    if( ident_free == NULL )
    {
	id = alloc_perm( sizeof( *id ) );
    }
    else
    {
	id = ident_free;
	ident_free = ident_free->next;
    }
    *id = id_zero;
    id->desc = desc;
    id->descriptor = d;
    id->connected = FALSE;
    id->timeout = current_time + IDENT_TIMEOUT;
    id->remoteport = remoteport;
    id->localport = localport;
    id->next = ident_list;
    ident_list = id;
    return id;
}


/*
 * Write an ident request.
 */
void write_ident( IDENT_DATA *id )
{
    char querybuf[20];
    struct sockaddr sa;
    size_t salen = sizeof( sa );

    if( getpeername( id->descriptor, &sa, &salen ) < 0 )
    {
	free_ident( id );
	return;
    }

    sprintf( querybuf, "%d,%d\r\n", id->remoteport, id->localport );
#if !defined( WIN32 )
    if( write( id->descriptor, querybuf, strlen( querybuf ) ) < 0 )
#else
    if( send( id->descriptor, querybuf, strlen( querybuf ), 0 ) < 0 )
#endif
	free_ident( id );
    else
    {
	id->connected = TRUE;
	id->timeout = current_time + IDENT_TIMEOUT;
    }
}


/*
 * Read the reply, check that it is complete.
 */
void read_ident( IDENT_DATA *id )
{
    long size;
    char *p;
    bool complete = FALSE;

#if !defined( WIN32 )
    size = read( id->descriptor, id->reply_text + id->reply_pos,
		 MAX_INPUT_LENGTH - id->reply_pos );
#else
    size = recv( id->descriptor, id->reply_text + id->reply_pos,
		 MAX_INPUT_LENGTH - id->reply_pos, 0 );
#endif
    if( size < 0 )
    {
	free_ident( id );
	return;
    }

    id->reply_pos += size;
    if( id->reply_pos == MAX_INPUT_LENGTH )
    {
	complete = TRUE;
	id->reply_text[MAX_INPUT_LENGTH-2] = '\r';
	id->reply_text[MAX_INPUT_LENGTH-1] = '\n';
    }
    else
    {
	for( p = id->reply_text + id->reply_pos - 2;
	     p >= id->reply_text; p-- )
	{
	    if( *p == '\r' && *(p + 1) == '\n' )
	    {
		complete = TRUE;
		break;
	    }
	}
    }
    if( complete )
    {
	parse_ident( id );
	free_ident( id );
    }
}


/*
 * Parse the reply to the ident request.
 */
void parse_ident( IDENT_DATA *id )
{
    char *p;
    int remoteport, localport;
    char replytype[40];
    char foo;
    int tmp;
    const char *ident;
    char hostid[MAX_INPUT_LENGTH];
    const static char tokenchars[] =
	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"-, .!@#$%^&*()_=+.,<>/?\"'~`{}[];";

    if( !( p = strstr( id->reply_text, "\r\n" ) ) )
	return;

    *p = 0;
    /*
     * The extra argument "foo" is used because sscanf doesn't count
     * a bare %n.
     */
    if( sscanf( id->reply_text, "%d , %d : %39s : %n%c",
		&remoteport, &localport, replytype, &tmp, &foo ) < 4
	|| remoteport != id->remoteport
	|| localport != id->localport )
	return;

    p = id->reply_text + tmp;
    if( strcmp( replytype, "ERROR" ) == 0 )
    {
	tmp = strspn( p, tokenchars );
	if( p[tmp] )
	    return;

	if( p[0] == 'X' )
	    ident = p + 1;
	else if( strcmp( p, "NO-USER" ) == 0 )
	    ident = "<nobody>";
	else if( strcmp( p, "HIDDEN-USER" ) == 0 )
	    ident = "<hidden>";
	else
	    ident = "<error>";
    }
    else if( strcmp( replytype, "USERID" ) == 0 )
    {
	/* Skip straight to the userid field */
	p = strchr( p, ':' );
	if( p == NULL )
	    return;
	p++;
	/*
	 * The RFC says not to ignore leading whitespace here.
	 * Unfortunately, some servers get it wrong because of
	 * the example on line 109 of RFC1413.
	 */
	ident = p + strspn( p, " \t" );
	for( p = (char *)ident; *p; p++ )
	{
	    if( !isprint( *p ) )
		*p = '?';
	}
    }
    else
	return;

    if( id->desc->host )
    {
	sprintf( hostid, "%s@%s", ident, id->desc->host );
	free_string( id->desc->host );
    }
    else
    {
	sprintf( hostid, "%s@NULL", ident );
    }
    id->desc->host = str_dup( hostid );
}


/*
 * Free an ident structure.
 */
void free_ident( IDENT_DATA *id )
{
    if( id->descriptor > 0 )
    {
#if !defined( WIN32 )
	close( id->descriptor );
#else
	closesocket( id->descriptor );
#endif
    }

    id->desc->ident = NULL;

    if( ident_list == id )
	ident_list = ident_list->next;
    else
    {
	IDENT_DATA *idp;

	for( idp = ident_list; idp; idp = idp->next )
	{
	    if( idp->next == id )
	    {
		idp->next = id->next;
		break;
	    }
	}
    }

    id->next = ident_free;
    ident_free = id;
}


bool read_from_descriptor( DESCRIPTOR_DATA *d )
{
    int iStart;

    /* Hold horses if pending command already. */
    if( d->incomm[0] != '\0' )
	return TRUE;

    /* Check for overflow. */
    iStart = strlen( d->inbuf );
    if( iStart >= sizeof( d->inbuf ) - 10 )
    {
	log_string( "%s input overflow!", d->host );
#if defined( MCCP )
	/* They are gone anyway, so let's end it right here, it's easier. */
	compress_end( d, d->mccp_version );
#endif
	write_to_descriptor( d->descriptor, "\n\r*** PUT A LID ON IT!!! ***\n\r", 0 );
	return FALSE;
    }

    for( ;; )
    {
	int nRead;

#if !defined( WIN32 )
	nRead = read( d->descriptor, d->inbuf + iStart,
		     sizeof( d->inbuf ) - 10 - iStart );
#else
	nRead = recv( d->descriptor, d->inbuf + iStart,
		     sizeof( d->inbuf ) - 10 - iStart, 0 );
#endif
	if( nRead > 0 )
	{
	    data_recv += nRead;
	    iStart += nRead;
	    if( d->inbuf[iStart - 1] == '\n' || d->inbuf[iStart - 1] == '\r' )
		break;
	}
	else if( nRead == 0 )
	{
	    log_string( "EOF encountered on read." );
	    return FALSE;
	}
#if !defined( WIN32 )
	else if( errno == EWOULDBLOCK || errno == EAGAIN )
	    break;
#endif
#if defined( WIN32 ) && !defined( BCB )
	else if( WSAGetLastError( ) == WSAEWOULDBLOCK || errno == EAGAIN )
	    break;
#endif
	else
	{
	    perror( "Read_from_descriptor" );
	    return FALSE;
	}
    }

    d->inbuf[iStart] = '\0';
    return TRUE;
}


/*
 * Transfer one line from input buffer to input line.
 */
void read_from_buffer( DESCRIPTOR_DATA *d )
{
    int i;
    int j;
    int k;

    /*
     * Hold horses if pending command already.
     */
    if( d->incomm[0] != '\0' )
	return;

    /*
     * Look for at least one new line.
     */
    for( i = 0; d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++ )
    {
	if( d->inbuf[i] == '\0' )
	    return;
    }

    /*
     * Canonical input processing.
     */
    i = 0;
    while( !IS_SET( d->interpreter->flags, INTERPRETER_LEADSPACE )
	   && isspace( d->inbuf[i] )
	   && d->inbuf[i] != '\n' && d->inbuf[i] != '\r' )
	++i;
    for( k = 0; d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++ )
    {
	if( k >= MAX_INPUT_LENGTH - 2 )
	{
	    write_to_buffer( d, "Line too long.\n\r" );

	    /* skip the rest of the line */
	    for( ; d->inbuf[i] != '\0'; i++ )
	    {
		if( d->inbuf[i] == '\n' || d->inbuf[i] == '\r' )
		    break;
	    }
	    d->inbuf[i] = '\n';
	    d->inbuf[i + 1] = '\0';
	    break;
	}

	if( d->inbuf[i] == '\b' && k > 0 )
	    --k;
	else if( isascii( d->inbuf[i] ) && isprint( d->inbuf[i] ) )
	    d->incomm[k++] = d->inbuf[i];
#if defined( MCCP )
	else if( d->inbuf[i] == (char)IAC )
	{
	    if( d->inbuf[i + 1] == (char)DO )
	    {
		if( d->inbuf[i + 2] == (char)TELOPT_COMPRESS )
		    compress_start( d, 1 );
		else if( d->inbuf[i + 2] == (char)TELOPT_COMPRESS2 )
		    compress_start( d, 2 );
		i += 2;
	    }
	    else if( d->inbuf[i + 1] == (char)DONT )
	    {
		if( d->inbuf[i + 2] == (char)TELOPT_COMPRESS )
		    compress_end( d, 1 );
		else if( d->inbuf[i + 2] == (char)TELOPT_COMPRESS2 )
		    compress_end( d, 2 );
		i += 2;
	    }
	}
#endif
    }

    /*
     * Finish off the line.
     */
    if( k == 0 )
	d->incomm[k++] = ' ';
    d->incomm[k] = '\0';

    /*
     * Deal with bozos with #repeat 1000 ...
     */
    if( k > 1 || d->incomm[0] == '!' )
    {
	if( d->incomm[0] != '!' && strcmp( d->incomm, d->inlast ) )
	{
	    d->repeat = 0;
	}
	else
	{
	    if( ++d->repeat > 20 && !IS_IMMORTAL( d->character ) )
	    {
		sprintf( log_buf, "%s@%s input spamming!",
			 d->character->name, d->host );
		log_string( log_buf );
		write_to_buffer( d, "\n\r*** PUT A LID ON IT!!! ***\n\r" );
		strcpy( d->incomm, "quit" );
	    }
	}
    }

    /*
     * Do '!' substitution.
     */
    if( d->incomm[0] == '!' )
	strcpy( d->incomm, d->inlast );
    else
	strcpy( d->inlast, d->incomm );

    j = 0;

    /*
     * Deal with multiple command freaks.
     */
    if( *d->incomm == '#' )
    {
	int num;
	const char *ptr;
	char temp[MAX_INPUT_LENGTH];

	ptr = one_argument( d->incomm + 1, temp );
	if( is_number( temp ) && *ptr != '#' && temp[0] != '\0' )
	{
	    num = URANGE( 1, atoi( temp ), 50 ) - 1;
	    strcpy( d->incomm, ptr );
	    if( num > 1 )
		sprintf( d->inbuf, "#%d %s\n", num, d->incomm );
	    else
		sprintf( d->inbuf, "%s\n", d->incomm );
	    j = strchr( d->inbuf, '\0' ) - d->inbuf;
	}
	else
	    send_to_char( "Remember to use the '#' properly.\n\r",
			  d->character );
    }

    /*
     * Shift the input buffer.  Note that the fill backwards now takes into
     * consideration the command from the '#n command' parsing above.
     */
    while( d->inbuf[i] == '\n' || d->inbuf[i] == '\r' )
	i++;
    for( i = i - j; ( d->inbuf[j] = d->inbuf[i + j] ) != '\0'; j++ )
	;

    return;
}


void vt100_init( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch;
    char buf[30];

    if( !d || !xIS_SET( CH( d )->act, PLR_VT100 ) )
	return;
    ch = CH( d );
    if( ch->pcdata->pagelen < 19 )
	return;

    if( !process_output( d ) )
    {
	if( d->character )
	    save_char_obj( d->character );
	d->outtop = 0;
	close_socket( d );
	return;
    }
    /* save cursor position */
    sprintf( buf, "\033[%d;1H", ch->pcdata->pagelen + 2 );
    write_to_buffer( d, buf );
    write_to_buffer( d, VT_CLEAR_LINE VT_SAVECURSOR );
    /* clear screen */
    write_to_buffer( d, "\x1B[1;1H" VT_SETWIN_CLEAR VT_CLEAR_SCREEN );
    /* set scroll window */
    sprintf( buf, "\033[1;%dr", ch->pcdata->pagelen );
    write_to_buffer( d, buf );
    /* go back to input line */
    sprintf( buf, "\033[%d;1H", ch->pcdata->pagelen + 2 );
    write_to_buffer( d, buf );
}


/*
 * Low level output function.
 */
bool process_output( DESCRIPTOR_DATA *d )
{
    /*
     * Fighting: opponent condition.
     */
    if( d->character )
	show_opponent( d->character );

    /*
     * Snoop-o-rama.
     */
    if( d->snoop_by && d->outtop )
    {
	write_to_buffer( d->snoop_by, "&c<%SNOOP%>&n\n\r" );
	write_to_buffer( d->snoop_by, d->outbuf );
	write_to_buffer( d->snoop_by, "&c<%SNOOP%>&n\n\r" );
    }

    /*
     * Bust a prompt.
     * Below snoop because of vt100.
     */
    if( CH( d ) && xIS_SET( CH( d )->act, PLR_VT100 ) )
	vt100_prompt( d );
    else
	bust_a_prompt( d );

    /*
     * Short-circuit if nothing to write.
     */
    if( d->outtop == 0 )
	return TRUE;

    /*
     * OS-dependent output.
     */
    if( !write_to_descriptor_nice( d ) )
    {
	d->outtop = 0;
	return FALSE;
    }
    else
    {
	return TRUE;
    }
}


/*
 * vt100 enhanced prompter with status bar and pretty stuff.
 */
void vt100_prompt( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = CH( d );
    char buf[ MAX_INPUT_LENGTH ];
    char temp[ SMALL_OUTBUF_SIZE ];
    TEXT_BLOCK *block = NULL;

    if( !ch || !xIS_SET( ch->act, PLR_VT100 ) )
	return;

    /* prepend save_cursor and move to top to buffer, this means removing
       everything from the buffer first. */
    if( d->large_buffer )
    {
	block = d->large_buffer;
	d->large_buffer = NULL;
    }
    else
    {
	strncpy( temp, d->outbuf, d->outtop + 1 );
	temp[d->outtop] = '\0';	/* Just in case */
    }
    d->outtop = 0;

    sprintf( buf, VT_SAVECURSOR "\033[%d;1H" VT_CLEAR_LINE,
	     ch->pcdata->pagelen );
    write_to_buffer( d, buf );
    if( block )
    {
	write_to_buffer( d, block->str );
	free_text_block( block );
    }
    else
	write_to_buffer( d, temp );

    if( ( xIS_SET( ch->act, PLR_BLANK ) && !d->pString && !d->showstr_point ) )
	strcpy( buf, "\n\r" );
    else
	buf[0] = '\0';
    sprintf( strchr( buf, '\0' ),
	     "\033[0;36m--------------========(\033[35m DalekenMUD - %s \033[36m)========--------------",
	     ch->name );

    sprintf( buf + strlen( buf ), "\033[%d;1H" VT_CLEAR_LINE,
	     ch->pcdata->pagelen + 1 );
    write_to_buffer( d, buf );
    bust_a_prompt( d );
    sprintf( buf, "\033[%d;1H" VT_RESTORECURSOR "%s",
	     ch->pcdata->pagelen + 2,
	     d->fcommand ? VT_CLEAR_LINE : "" );
    write_to_buffer( d, buf );
}


/*
 * Multiple interpreter prompter.
 */
void bust_a_prompt( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = CH( d );

    if( merc_down || !ch || !d->fpromptok )
	return;

    if( !ch->pcdata->prompt || ch->pcdata->prompt[0] == '\0' )
	send_to_char( "\n\r", ch );
    else if( d->showstr_point )
    {
	char buf[MAX_INPUT_LENGTH];
	char *ptr;
	int total_lines, shown_lines = 0;

	for ( ptr = d->showstr_head; ptr != d->showstr_point; ptr++ )
	    if( *ptr == '\n' )
		shown_lines++;

	total_lines = shown_lines;
	for ( ptr = d->showstr_point; *ptr != '\0'; ptr++ )
	    if( *ptr == '\n' )
		total_lines++;

	sprintf( buf, "&b[%d%%]&g Please type &c(c)&gontinue, &c(r)&gefresh, "
		 "&c(h)&gelp, &c(q)&guit, or &cRETURN&g:&n ",
		 shown_lines * 100 / total_lines );
	write_to_buffer( d, buf );
    }
    else if( d->pString )
	write_to_buffer( d, "&n>" );
    else if( xIS_SET( ch->act, PLR_PROMPT ) && d->interpreter->prompt )
	(*d->interpreter->prompt)( d );

    if( xIS_SET( ch->act, PLR_TELNET_GA ) )
	write_to_buffer( d, go_ahead_str );
}


const INTERPRETER_DATA *get_interpreter( const char *name )
{
    int i;

    for( i = 0; interpreter_table[i].name; ++i )
    {
	if( !str_cmp( interpreter_table[i].name, name ) )
	    return interpreter_table + i;
    }
    return NULL;
}


/*
 * standard_interpret fits in with the interpreter system without
 * forcing changes to the code all over.
 */
void standard_interpret( DESCRIPTOR_DATA *d, const char *argument )
{
    interpret( d->character, argument );
}


/*
 * Bust a prompt (player settable prompt)
 * coded by Morgenes for Aldara Mud
 */
void standard_prompt( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = CH( d );
    CHAR_DATA *victim;
    const char *str;
    const char *i;
    char *point;
    char buf[MAX_STRING_LENGTH];
    char buf2[MAX_STRING_LENGTH];
    int percent, stat;

    if( xIS_SET( ch->act, PLR_BLANK ) && !xIS_SET( ch->act, PLR_VT100 ) )
	write_to_buffer( d, "\n\r" );

    point = buf;
    str = ch->pcdata->prompt;

    *point = '\0';
    if( xIS_SET( ch->act, PLR_AFK ) )
	strcat( point, "&w<AFK> " );
    if( xIS_SET( ch->act, PLR_BUSY ) )
	strcat( point, "&g[BUSY] " );
    else if( IS_SET( ch->deaf, CHANNEL_DEAF ) )
	strcat( point, "&m[[MUTED]] " );
    if( ch->pcdata->trade )
	strcat( point, "&y(TRADE) " );
    if( IS_IMMORTAL( ch ) && xIS_SET( ch->act, PLR_WIZINVIS ) )
	strcat( point, "&B^" );
    if( IS_AFFECTED( ch, AFF_SANCTUARY ) )
	strcat( point, "&W%" );
    if( IS_AFFECTED( ch, AFF_INVISIBLE ) )
	strcat( point, "&C!" );
    if( IS_AFFECTED( ch, AFF_SNEAK ) )
	strcat( point, "&y#" );
    if( IS_AFFECTED( ch, AFF_HIDE ) )
	strcat( point, "&m@" );
    strcat( point, "&g" );
    point = strchr( point, '\0' );

    while( *str != '\0' )
    {
	if( *str != '%' )
	{
	    *point++ = *str++;
	    continue;
	}
	stat = MAND;
	strcpy( buf2, " " );
	++str;
	switch( *str )
	{
	default:					break;
	case 'h':
	    stat = ch->hit;				break;
	case 'H':
	    stat = ch->max_hit;				break;
	case 'm':
	    switch( *(++str) )
	    {
	    case 'a': case 'A':
		sprintf( buf2, "&c%d&x", ch->mana[MAGIC_AIR] );
		break;
	    case 'e': case 'E':
		sprintf( buf2, "&y%d&x", ch->mana[MAGIC_EARTH] );
		break;
	    case 'f': case 'F':
		sprintf( buf2, "&r%d&x", ch->mana[MAGIC_FIRE] );
		break;
	    case 's': case 'S':
		sprintf( buf2, "&w%d&x", ch->mana[MAGIC_SPIRIT] );
		break;
	    case 'w': case 'W':
		sprintf( buf2, "&b%d&x", ch->mana[MAGIC_WATER] );
		break;
	    default:
		sprintf( buf2, "%d", total_mana( ch->mana ) );
		--str;	break;
	    }
	    break;
	case 'M':
	    switch( *(++str) )
	    {
	    case 'a': case 'A':
		sprintf( buf2, "&c%d&x", ch->max_mana[MAGIC_AIR] );
		break;
	    case 'e': case 'E':
		sprintf( buf2, "&y%d&x", ch->max_mana[MAGIC_EARTH] );
		break;
	    case 'f': case 'F':
		sprintf( buf2, "&r%d&x", ch->max_mana[MAGIC_FIRE] );
		break;
	    case 's': case 'S':
		sprintf( buf2, "&w%d&x", ch->max_mana[MAGIC_SPIRIT] );
		break;
	    case 'w': case 'W':
		sprintf( buf2, "&b%d&x", ch->max_mana[MAGIC_WATER] );
		break;
	    default:
		sprintf( buf2, "%d", total_mana( ch->max_mana ) );
		--str;	break;
	    }
	    break;
	case 'v':
	    stat = ch->move;				break;
	case 'V':
	    stat = ch->max_move;			break;
	case 'x':
	    if( ch->exp < 10000 )
		sprintf( buf2, "%d.%2.2d", ch->exp / 100, ch->exp % 100 );
	    else
		sprintf( buf2, "%d", ch->exp / 100 );
	    break;
	case 'X':
	    sprintf( buf2, "%d", ( ch->fighting )
		     ? ch->fighting->hit * 100 / ch->fighting->max_hit : 0 );
	    break;
	case 'f':
	    if( ch->pcdata && ch->pcdata->familiar )
		stat = ch->pcdata->familiar;
	    break;
	case 'g':
	    strcpy( buf2, int_to_str_special( ch->gold ) );
	    break;
	case 'w':
	    stat = ch->wait;				break;
	case 'a':
	    if( ch->level > 10 )
		stat = ch->alignment;
	    else
		sprintf( buf2, "%s", IS_GOOD( ch ) ? "good"
			 : IS_EVIL( ch ) ? "evil" : "neutral" );
	    break;
	case 'r':
	    if( ch->in_room )
		sprintf( buf2, "%s", ch->in_room->name );
	    break;
	case 'R':
	    if( IS_IMMORTAL( ch ) && ch->in_room )
		stat = ch->in_room->vnum;
	    break;
	case 't':
	    if( ch->leader )	stat = ch->leader->hit;
	    break;
	case 'T':
	    if( ch->leader )	stat = ch->leader->max_hit;
	    break;
	case 'z':
	    if( IS_IMMORTAL( ch ) && ch->in_room )
		sprintf( buf2, "%s", ch->in_room->area->name );
	    break;
	case 'q':
	    if( ch->pcdata && ch->pcdata->quest )
		stat = ch->pcdata->quest->time;
	    break;
	case '%':
	    sprintf( buf2, "%%" );			break;
	case 'C':		/* OLC */
	    strcpy( buf2, olc_ed_vnum( ch ) );		break;
	case 'c':		/* OLC */
	    strcpy( buf2, olc_ed_string( ch ) );	break;
	case 'i':		/* Semi-OLC */
	    strcpy( buf2, d->interpreter->name );
	    break;
	    /*
	     *	<- Gothar's Battle Prompts ->
	     * Heavily modified, Symposium.
	     */
	case 'b':
	    if( ( victim = ch->fighting ) != NULL )
	    {
		if( victim->max_hit > 0 )
		    percent = victim->hit * 100 / victim->max_hit;
		else
		    percent = -1;
		if( percent >= 100)
		    sprintf( buf2, "&r+++&Y+++&g++++" );
		else if( percent >= 90)
		    sprintf( buf2, "&r+++&Y+++&g+++ " );
		else if( percent >= 80)
		    sprintf( buf2, "&r+++&Y+++&g++  " );
		else if( percent >= 70)
		    sprintf( buf2, "&r+++&Y+++&g+   " );
		else if( percent >= 58)
		    sprintf( buf2, "&r+++&Y+++	  &g" );
		else if( percent >= 45)
		    sprintf( buf2, "&r+++&Y++	  &g" );
		else if( percent >= 30)
		    sprintf( buf2, "&r+++&Y+	  &g" );
		else if( percent >= 28)
		    sprintf( buf2, "&r+++	&g" );
		else if( percent >= 15)
		    sprintf( buf2, "&r++	&g" );
		else if( percent >= 8)
		    sprintf( buf2, "&r+		&g" );
		else
		    sprintf( buf2, "	      &g" );
	    }
	    break;
	case 'B':
	    if( ( victim = ch->fighting ) != NULL )
	    {
		if( victim->max_hit > 0 )
		    percent = victim->hit * 100 / victim->max_hit;
		else
		    percent = 0;
		if( percent >= 65 )
		    i = "&g";
		else if( percent >= 25 )
		    i = "&Y";
		else
		    i = "&r";
		sprintf( buf2, "%s%d&g%%", i, percent );
	    }
	    break;
	case 'F':
	    if( ( victim = ch->fighting ) != NULL )
		sprintf( buf2, "%s", PERS( victim, ch ) );
	    break;
	case 'Y':
	    if( ch->in_room->area->plane->time.hour <  5 )
		strcpy( buf2, "night" );
	    else if( ch->in_room->area->plane->time.hour <  6 )
		strcpy( buf2, "dawn" );
	    else if( ch->in_room->area->plane->time.hour < 19 )
		strcpy( buf2, "day" );
	    else if( ch->in_room->area->plane->time.hour < 21 )
		strcpy( buf2, "dusk" );
	    else
		strcpy( buf2, "night" );
	    break;
	}
	if( stat != MAND )
	    sprintf( buf2, "%d", stat );
	i = buf2;
	++str;
	while( ( *point = *i ) != '\0' )
	    ++point, ++i;
    }
    *point++ = '&';
    *point++ = 'n';
    *point = '\0';
    write_to_buffer( d, buf );
    return;
}


/*
 * Append onto an output buffer.
 */
void write_to_buffer( DESCRIPTOR_DATA *d, const char *txt )
{
    CHAR_DATA *ch;
    char buffer[MAX_STRING_LENGTH * 4];
    int length;

    /*
     * Colour processing.
     */
    ch = CH( d );

    if( !ch || xIS_SET( ch->act, PLR_COLOUR ) )
	proc_colour( buffer, txt, TRUE );
    else
	proc_colour( buffer, txt, FALSE );

    length = strlen( buffer );

    /*
     * Initial \n\r if needed.
     */
    if( d->outtop == 0 && !d->fcommand )
    {
	d->outbuf[0] = '\n';
	d->outbuf[1] = '\r';
	d->outtop = 2;
    }

    /*
     * Expand the buffer as needed.
     */
    if( d->large_buffer && d->outtop + length + 1 >= d->large_buffer->size )
    {
	TEXT_BLOCK *block;

	block = new_text_block( d->outtop + length + 3 );
	strncpy( block->str, d->large_buffer->str, d->outtop );
	free_text_block( d->large_buffer );
	d->large_buffer = block;
    }
    else if( !d->large_buffer && d->outtop + length + 1 >= SMALL_OUTBUF_SIZE )
    {
	d->large_buffer = new_text_block( d->outtop + length + 3 );
	strncpy( d->large_buffer->str, d->outbuf, d->outtop );
    }

    /*
     * Copy.  Modifications sent in by Zavod.
     */
    if( d->large_buffer )
	strncpy( d->large_buffer->str + d->outtop, buffer, length + 1 );
    else
	strncpy( d->outbuf + d->outtop, buffer, length + 1 );
    d->outtop += length;
    return;
}


/*
 * Lowest level output function.
 * Write a block of text to the file descriptor.
 * If this gives errors on very long blocks (like 'ofind all'),
 *   try lowering the max block size.
 * This is not very nice when it comes to writing large blocks, it just keeps
 * on going...
 *
 *
 * MCCP Note: This function does not compress, use write_to_buffer
 * whereever possible.
 */
bool write_to_descriptor( int desc, const char *txt, int length )
{
    int iStart;
    int nWrite;
    int nBlock;

    if( length <= 0 )
	length = strlen( txt );

    for( iStart = 0; iStart < length; iStart += nWrite )
    {
	nBlock = UMIN( length - iStart, 2048 );
#if !defined( WIN32 )
	if( ( nWrite = write( desc, txt + iStart, nBlock ) ) < 0 )
#else
	if( ( nWrite = send( desc, txt + iStart, nBlock, 0 ) ) < 0 )
#endif
	{
#if !defined( WIN32 )
	    if( errno == EWOULDBLOCK || errno == EAGAIN )
		break;
#elif defined( WIN32 ) && !defined( BCB )
	    if( WSAGetLastError( ) == WSAEWOULDBLOCK || errno == EAGAIN )
		break;
#endif
	    perror( "Write_to_descriptor" );
	    return FALSE;
	}

	data_trans_inc += nWrite;
	if( data_trans_inc > (1<<30) )
	{
	    data_trans += data_trans_inc;
	    data_trans_inc = 0;
	}
    }

    return TRUE;
}


/*
 * A helper function that clears the output buffer.
 * Supports compression if enabled.
 */
bool write_to_descriptor_nice( DESCRIPTOR_DATA *d )
{
    char *writebuf;
    int nWrite, nBlock;

#if defined( MCCP )
    if( d->out_compress )
    {
	process_compressed( d );

	nBlock = d->out_compress->next_out - d->out_compress_buf;
	writebuf = d->out_compress_buf;
    }
    else
#endif
    {
	if( d->large_buffer )
	    writebuf = &d->large_buffer->str[0];
	else
	    writebuf = &d->outbuf[0];
	nBlock = d->outtop;
    }

    nBlock = UMIN( nBlock, 4096 );

#if !defined( WIN32 )
    if( ( nWrite = write( d->descriptor, writebuf, nBlock ) ) < 0 )
#else
    if( ( nWrite = send( d->descriptor, writebuf, nBlock, 0 ) ) < 0 )
#endif
    {
	perror( "write_to_descriptor_nice" );
#if defined( MCCP )
	d->out_compress->next_out = d->out_compress_buf;
#endif
	d->outtop = 0;
	return FALSE;
    }

    data_trans_inc += nWrite;
    if( data_trans_inc > (1<<30) )	/* stop the overflow */
    {
	data_trans += data_trans_inc;
	data_trans_inc = 0;
    }

#if defined( MCCP )
    if( d->out_compress )
    {
	if( d->out_compress->next_out - d->out_compress_buf > nWrite )
	{
	    memmove( d->out_compress_buf, d->out_compress_buf + nWrite,
		     d->out_compress->next_out - d->out_compress_buf - nWrite );
	    d->out_compress->next_out -= nWrite;
	    d->fpromptok = FALSE;
	}
	else
	{
	    d->out_compress->next_out = d->out_compress_buf;
	    d->fpromptok = TRUE;
	}
    }
    else
#endif
    {
	if( d->outtop > nWrite )
	{
	    if( d->large_buffer )
	    {
		if( d->outtop - nWrite >= SMALL_OUTBUF_SIZE - 1 )
		    memmove( d->large_buffer->str,
			     d->large_buffer->str + nWrite,
			     d->outtop - nWrite + 1 );
		else
		{
		    memmove( d->outbuf, d->large_buffer->str + nWrite,
			     d->outtop - nWrite + 1 );
		    free_text_block( d->large_buffer );
		    d->large_buffer = NULL;
		}
	    }
	    else
		memmove( d->outbuf, d->outbuf + nWrite, d->outtop - nWrite + 1 );
	    d->outtop -= nWrite;
	    d->fpromptok = FALSE;
	}
	else
	{
	    if( d->large_buffer )
	    {
		free_text_block( d->large_buffer );
		d->large_buffer = NULL;
	    }
	    d->outtop = 0;
	    d->fpromptok = TRUE;
	}
    }

/*    packet[current_time%60] += nWrite; */
    return TRUE;
}


void nanny_get_name( DESCRIPTOR_DATA *d, const char *argument )
{
    bool fOld;
    CHAR_DATA *ch;
    char name[MAX_INPUT_LENGTH];
    char buf[MAX_STRING_LENGTH];

    if( argument[0] == '\0' )
    {
	close_socket( d );
	return;
    }

    strcpy( name, argument );
    name[0] = UPPER( name[0] );

    if( !check_parse_name( name ) )
    {
	write_to_buffer( d, "&rThat name is not good, try another.&c\n\rYour name sir?&n " );
	return;
    }
    fOld = load_char_obj( d, name );
    ch = d->character;

    if( xIS_SET( ch->act, PLR_DENY ) )
    {
	sprintf( log_buf, "Denying access to %s (%s).", name, d->host );
	log_string( log_buf );
	write_to_buffer( d, "&rYou are denied access.\n\r" );
	write_to_buffer( d, "Log on with a new character and see an immortal\n\r" );
	write_to_buffer( d, "and they will help you.&n\n\r" );
	close_socket( d );
	return;
    }
    vt100_init( d );

    if( check_reconnect( d, name, FALSE ) )
    {
	fOld = TRUE;
    }
    else
    {
	/* Must be immortal with wizbit in order to get in */
	if( IS_SET( SysInfo->flags, SYSINFO_WIZLOCK )
	    && !IS_HERO( ch ) && !xIS_SET( ch->act, PLR_WIZBIT ) )
	{
	    write_to_buffer( d, "&rThe game is wizlocked.&n\n\r" );
	    close_socket( d );
	    return;
	}
	if( ch->level <= SysInfo->numlock && !xIS_SET( ch->act, PLR_WIZBIT )
	    && SysInfo->numlock != 0 )
	{
	    write_to_buffer( d,
			     "The game is locked to your level character.\n\r\n\r" );
	    if( ch->level == 0 )
	    {
		write_to_buffer(
		    d, "New characters are now temporarily in email " );
		write_to_buffer( d, "registration mode.\n\r\n\r" );
		write_to_buffer(
		    d, "Please email <implementor addr here> to " );
		write_to_buffer( d, "register your character.\n\r\n\r" );
		write_to_buffer(
		    d, "One email address per character please.\n\r" );
		write_to_buffer( d, "Thank you, DalekenMud Staff.\n\r\n\r" );
	    }
	    close_socket( d );
	    return;
	}
    }

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

    if( fOld )
    {
	/* Old player */
	write_to_buffer( d, "&cWhat's the magic word?&n " );
	write_to_buffer( d, echo_off_str );
	d->interpreter = get_interpreter( "getOldPasswd" );
    }
    else
    {
	/* New player */
	sprintf( buf, "&BNew Player.&g\n\rDid I get that right, %s [&cYes&g/&cNo&g]?&n ", name );
	write_to_buffer( d, buf );
	d->interpreter = get_interpreter( "confirmName" );
    }
    return;
}


void nanny_get_old_password( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    int lines;

#if defined( unix )
    write_to_buffer( d, "\n\r" );
#endif

    if( strcmp( crypt( argument, ch->pcdata->pwd ), ch->pcdata->pwd ) )
    {
	write_to_buffer( d, "&rWrong password.&n\n\r" );
	sprintf( log_buf, "Failed login for name '%s' from %s.",
		    ch->name, d->host );
	log_string( log_buf );
	wiznet( ch, WIZ_LOGINS, L_SEN, log_buf );
	close_socket( d );
	return;
    }

    write_to_buffer( d, echo_on_str );

    if( check_reconnect( d, ch->name, TRUE ) )
	return;

    sprintf( log_buf, "%s (%s) has connected.", ch->name, d->host );
    log_string( log_buf );
    lines = ch->pcdata->pagelen;
    ch->pcdata->pagelen = 20;
    if( IS_HERO( ch ) )
	do_help( ch, "imotd" );
    else
	do_help( ch, "motd" );
    ch->pcdata->pagelen = lines;
    d->interpreter = get_interpreter( "readMOTD" );
}


void nanny_confirm_name( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    char buf[MAX_INPUT_LENGTH];

    switch( *argument )
    {
    case 'y':
    case 'Y':
	sprintf( buf, "&gGive me a password for %s:&n %s",
		 ch->name, echo_off_str );
	write_to_buffer( d, buf );
	d->interpreter = get_interpreter( "getNewPasswd" );
	break;

    case 'n':
    case 'N':
	write_to_buffer( d, "Ok, what IS it, then? " );
	free_char( d->character );
	d->character = NULL;
	d->interpreter = get_interpreter( "getName" );
	break;

    default:
	write_to_buffer( d, "&rPlease type Yes or No?&n " );
	break;
    }
}


void nanny_get_new_password( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    char *pwdnew, *p;

#if defined( unix )
    write_to_buffer( d, "\n\r" );
#endif

    if( strlen( argument ) < 5 )
    {
	write_to_buffer( d, "&rPassword must be at least five characters "
			 "long.&g\n\rPassword: " );
	return;
    }

    pwdnew = crypt( argument, ch->name );
    for( p = pwdnew; *p != '\0'; p++ )
    {
	if( *p == '~' )
	{
	    write_to_buffer( d, "&rNew password not acceptable, try again.\n\r"
			     "&gPassword: " );
	    return;
	}
    }

    free_string( ch->pcdata->pwd );
    ch->pcdata->pwd = str_dup( pwdnew );
    write_to_buffer( d, "&gPlease retype password:&n " );
    d->interpreter = get_interpreter( "confirmPasswd" );
}


void nanny_confirm_password( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;

#if defined( unix )
    write_to_buffer( d, "\n\r" );
#endif

    if( strcmp( crypt( argument, ch->pcdata->pwd ), ch->pcdata->pwd ) )
    {
	write_to_buffer( d, "Passwords don't match.\n\rRetype password: " );
	d->interpreter = get_interpreter( "getNewPasswd" );
	return;
    }

    write_to_buffer( d, echo_on_str );
    write_to_buffer( d, "\n\r&gWhat sex would you prefer to be:\n\r"
		     "(&yMale&g/&yFemale&g/&yNeutral&g) ? " );
    d->interpreter = get_interpreter( "getSex" );
}


void nanny_get_sex( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;

    switch( argument[0] )
    {
    case 'b':	case 'B':
	if( !str_prefix( argument, "back" ) )
	{
	    write_to_buffer( d, "Please enter your new password:" );
	    d->interpreter = get_interpreter( "getNewPasswd" );
	    return;
	}
	break;
    case 'h':	case 'H':
	write_to_buffer( d, "It's all aesthetic, what would you prefer "
			 "to be?\n\rIt's up to you (M/F/N): " );
	return;
    case 'm': case 'M':
	ch->sex = SEX_MALE;
	break;
    case 'f': case 'F':
	ch->sex = SEX_FEMALE;
	break;
    case 'n': case 'N':
	ch->sex = SEX_NEUTRAL;
	break;
    default:
	write_to_buffer( d, "That's not a sex.\n\rWhat IS your sex? " );
	return;
    }

    d->interpreter = get_interpreter( "displayClass" );
    write_to_buffer( d, "\n\r&cPress Return to continue:&n\n\r" );
}


void nanny_display_class( DESCRIPTOR_DATA *d, const char *argument )
{
    char buf[MAX_STRING_LENGTH];
    int iClass;

    strcpy( buf, "&gSelect a class [&y" );
    for( iClass = 0; iClass < AVAIL_CLASS; iClass++ )
    {
	if( iClass > 0 )
	    strcat( buf, " " );
	strcat( buf, class_table[iClass].who_name );
    }
    strcat( buf, "&g]:&n " );
    write_to_buffer( d, buf );
    d->interpreter = get_interpreter( "getClass" );
}


void nanny_get_class( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    char buf[MAX_STRING_LENGTH];
    int iClass;

    if( argument[0] == '\0' )
    {
	nanny_display_class( d, argument );
	return;
    }
    if( !str_cmp( argument, "back" ) )
    {
	nanny_display_race( d, argument );
	return;
    }
    if( !str_cmp( argument, "help" ) )
    {
	write_to_buffer(
	    d, "Here you choose your class, just type the name,\n\r"
	    "of the class you want to choose.  If you want to\n\r"
	    "choose another sex, type 'back'\n\r"
	    "Type 'help <subject>' to get help on any subject.\n\r"
	    "Press anter on an empty line to show a list of races.\n\r" );
	nanny_display_class( d, argument );
	return;
    }
    if( !str_prefix( "help ", argument ) )
    {
	do_help( ch, one_argument( argument, buf ) );
	write_to_buffer( d, "Your class now? " );
	return;
    }

    for( iClass = 0; iClass < AVAIL_CLASS; iClass++ )
    {
	if( !str_prefix( argument, class_table[iClass].who_name ) )
	{
	    ch->class = iClass;
	    break;
	}
    }

    if( iClass == AVAIL_CLASS )
    {
	write_to_buffer( d, "That's not a class.\n\rWhat IS your class? " );
	return;
    }

    write_to_buffer( d, "\n\r" );

    strcpy( buf, class_table[ch->class].name );
    do_help( ch, buf );

    write_to_buffer( d, "&gAre you sure you want this class?&n " );
    d->interpreter = get_interpreter( "confirmClass" );
}


void nanny_confirm_class( DESCRIPTOR_DATA *d, const char *argument )
{
    switch( argument[0] )
    {
    case 'y':
    case 'Y':
	break;
    default:
	write_to_buffer( d, "\n\r&cPress Return to continue:&n\n\r" );
	d->interpreter = get_interpreter( "displayClass" );
	return;
    }

    write_to_buffer( d, "\n\r&cPress Return to continue:&n\n\r" );
    d->interpreter = get_interpreter( "displayRace" );
}


void nanny_display_race( DESCRIPTOR_DATA *d, const char *argument )
{
    char buf[MAX_STRING_LENGTH];
    char buf1[MAX_STRING_LENGTH];
    int col = 0;
    int iRace;

    strcpy( buf1, "&gSelect a race from the following\n\r&y" );
    for( iRace = 0; iRace < MAX_RACE; iRace++ )
    {
	if( IS_SET( race_table[iRace].race_abilities, RACE_NPC_ONLY ) )
	    continue;
	if( ++col % 4 == 1 )
	    strcat( buf1, "    " );
	sprintf( buf, "%-18s", race_table[iRace].name );
	strcat( buf1, buf );
	if( col % 4 == 0 )
	    strcat( buf1, "\n\r" );
    }
    strcat( buf1, "\n\r&gYour choice:&n " );
    write_to_buffer( d, buf1 );
    d->interpreter = get_interpreter( "getRace" );
}


void nanny_get_race( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    int iRace;
    char buf[MAX_INPUT_LENGTH];

    if( argument[0] == '\0' )
    {
	nanny_display_race( d, argument );
	return;
    }
    if( !str_cmp( argument, "back" ) )
    {
	nanny_display_class( d, argument );
	return;
    }
    if( !str_cmp( argument, "help" ) )
    {
	write_to_buffer(
	    d, "This is where you choose which race your character will be."
	    "\n\rChoose carefully this is an important choice."
	    "\n\rType 'help <subject>' to get help on any subject."
	    "\n\rPress anter on an empty line to show a list of races."
	    "\n\rSo, what will it be then?" );
	return;
    }
    if( !str_prefix( "help ", argument ) )
    {
	do_help( ch, one_argument( argument, buf ) );
	write_to_buffer( d, "Your race again? " );
	return;
    }

    for( iRace = 0; iRace < MAX_RACE; iRace++ )
	if( !str_prefix( argument, race_table[iRace].name )
	    && !IS_SET( race_table[iRace].race_abilities,
			RACE_NPC_ONLY ) )
	{
	    ch->race = iRace;
	    break;
	}

    if( iRace == MAX_RACE )
    {
	write_to_buffer(
	    d, "That is not a race.\n\rWhat IS your race? " );
	return;
    }

    write_to_buffer( d, "\n\r" );
    strcpy( buf, race_table[ch->race].name );
    do_help( ch, buf );
    write_to_buffer( d, "\n\r" );
    do_raceinfo( ch, buf );

    write_to_buffer( d, "&gAre you sure you want this race?&n " );
    d->interpreter = get_interpreter( "confirmRace" );
}


void nanny_confirm_race( DESCRIPTOR_DATA *d, const char *argument )
{
    switch( argument[0] )
    {
    case 'y':
    case 'Y':
	break;
    default:
	write_to_buffer( d, "\n\r&cPress Return to continue:&n\n\r" );
	d->interpreter = get_interpreter( "displayRace" );
	return;
    }

    reroll_stats( d );
    d->interpreter = get_interpreter( "reroll" );
    return;
}


void reroll_stats( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = d->character;
    char buf[MAX_INPUT_LENGTH];
    int i;

    write_to_buffer( d, "\n\r&cRerolling character stats...&n\n\r" );
    ch->pcdata->perm_str = 15 + race_table[ch->race].str_mod;
    ch->pcdata->perm_int = 15 + race_table[ch->race].int_mod;
    ch->pcdata->perm_wis = 15 + race_table[ch->race].wis_mod;
    ch->pcdata->perm_dex = 15 + race_table[ch->race].dex_mod;
    ch->pcdata->perm_con = 15 + race_table[ch->race].con_mod;
    switch( class_table[ch->class].attr_prime )
    {
    case APPLY_STR:	ch->pcdata->perm_str++;		break;
    case APPLY_INT:	ch->pcdata->perm_int++;		break;
    case APPLY_WIS:	ch->pcdata->perm_wis++;		break;
    case APPLY_DEX:	ch->pcdata->perm_dex++;		break;
    case APPLY_CON:	ch->pcdata->perm_con++;		break;
    }
    for( i = 0; i < 5; ++i )
    {
	switch( number_range( 0, 4 ) )
	{
	case 0:	++ch->pcdata->perm_str;	break;
	case 1:	++ch->pcdata->perm_int;	break;
	case 2:	++ch->pcdata->perm_wis;	break;
	case 3:	++ch->pcdata->perm_dex;	break;
	case 4:	++ch->pcdata->perm_con;	break;
	default:
	    bug( "Bad number in rerolling character %s.", ch->name );
	    break;
	}
	switch( number_range( 0, 4 ) )
	{
	case 0:	--ch->pcdata->perm_str;	break;
	case 1:	--ch->pcdata->perm_int;	break;
	case 2:	--ch->pcdata->perm_wis;	break;
	case 3:	--ch->pcdata->perm_dex;	break;
	case 4:	--ch->pcdata->perm_con;	break;
	default:
	    bug( "Bad number in rerolling character %s.", ch->name );
	    break;
	}
    }
    sprintf( buf, "&bStrength       %s%2d (racial normal %2d)&n\n\r",
	     (ch->pcdata->perm_str < 15 + race_table[ch->race].str_mod)
	     ? "&r" : "&g", ch->pcdata->perm_str,
	     race_table[ch->race].str_mod + 15 );
    write_to_buffer( d, buf );
    sprintf( buf, "&bIntelligence   %s%2d (racial normal %2d)&n\n\r",
	     (ch->pcdata->perm_int < 15 + race_table[ch->race].int_mod)
	     ? "&r" : "&g", ch->pcdata->perm_int,
	     race_table[ch->race].int_mod + 15 );
    write_to_buffer( d, buf );
    sprintf( buf, "&bWisdom         %s%2d (racial normal %2d)&n\n\r",
	     (ch->pcdata->perm_wis < 15 + race_table[ch->race].wis_mod)
	     ? "&r" : "&g", ch->pcdata->perm_wis,
	     race_table[ch->race].wis_mod + 15 );
    write_to_buffer( d, buf );
    sprintf( buf, "&bDexterity      %s%2d (racial normal %2d)&n\n\r",
	     (ch->pcdata->perm_dex < 15 + race_table[ch->race].dex_mod)
	     ? "&r" : "&g", ch->pcdata->perm_dex,
	     race_table[ch->race].dex_mod + 15 );
    write_to_buffer( d, buf );
    sprintf( buf, "&bConstitution   %s%2d (racial normal %2d)&n\n\r",
	     (ch->pcdata->perm_con < 15 + race_table[ch->race].con_mod)
	     ? "&r" : "&g", ch->pcdata->perm_con,
	     race_table[ch->race].con_mod + 15 );
    write_to_buffer( d, buf );

    write_to_buffer( d, "&gAre you happy with these stats?&n " );
}


void display_magic( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = d->character;
    char buf[MAX_INPUT_LENGTH];
    int i;

    write_to_buffer( d, "&gYour current magic statistics are:\n\r" );
    write_to_buffer( d, "Sphere    Skill  Mana\n\r" );

    for( i = 0; i < MAGIC_MAX; ++i )
    {
	sprintf( buf, "%s%-9s %3d %6d\n\r",
		 magic_colour[i], magic_name[i],
		 get_magic( ch, i ), ch->max_mana[i] );
	write_to_buffer( d, buf );
    }

    write_to_buffer( d, "\n\r&cCommands: list, add, subtract, help, done.\n\r" );
    sprintf( buf, "&gYou have %d point%s left to spend:&n ", ch->practice,
	     ( ch->practice == 1 ) ? "" : "s" );
    write_to_buffer( d, buf );
}


void intro_new_player( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = d->character;

    SysInfo->new_characters++;
    sprintf( log_buf, "%s (%s) new player.", ch->name, d->host );
    log_string( log_buf );
    wiznet( ch, WIZ_LOGINS, 0, log_buf );
    sprintf( log_buf, "Please welcome %s to the game!", ch->name );
    talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );

    write_to_buffer( d, "\n\r" );
    ch->pcdata->pagelen = 20;
    do_help( ch, "motd" );
    d->interpreter = get_interpreter( "readMOTD" );
}


void nanny_reroll( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    int i;
    char buf[MAX_INPUT_LENGTH];

    switch( argument[0] )
    {
    case 'b': case 'B':
	if( !str_cmp( argument, "back" ) )
	    d->interpreter = get_interpreter( "displayClass" );
	break;
    case 'h': case 'H':
	if( !str_cmp( argument, "help" ) )
	{
	    write_to_buffer(
		d, "Here you get to decide on a little randomisation for"
		" your character.\n\rJust say yes when you like the stats"
		" you see.\n\rIf you don't know what they are try the helps"
		" and just choose it\n\rwhen the left number (actual) is about"
		" the same as the right number (average)\n\rAre they ok? " );
	    return;
	}
	if( !str_prefix( "help ", argument ) )
	{
	    do_help( ch, one_argument( argument, buf ) );
	    write_to_buffer( d, "Are you happy with these? " );
	    return;
	}
	break;
    case 'y':	 case 'Y':
	break;
    default:
	reroll_stats( d );
	d->interpreter = get_interpreter( "reroll" );
	return;
    }

    for( i = 0; i < MAGIC_MAX; ++i )
    {
	ch->max_mana[i] = ch->mana[i] = 50;
	ch->pcdata->perm_magic[i] = 2;
	ch->pcdata->mod_magic[i] = 0;
	if( race_table[ch->race].mana_gain[i] > 5 )
	    ch->pcdata->perm_magic[i] += log( race_table[ch->race].mana_gain[i] / 2 );
	else if( race_table[ch->race].mana_gain[i] < 0 )
	    ch->pcdata->perm_magic[i] += UMAX( -2, ch->pcdata->perm_magic[i] / 10 );
    }

    if( class_table[ch->class].fMana )
    {
	ch->practice = class_table[ch->class].fMana;
	display_magic( d );
	d->interpreter = get_interpreter( "magic" );
    }
    else
	intro_new_player( d );
}


void nanny_magic( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    char arg[MAX_INPUT_LENGTH];
    int sphere;

    argument = one_argument( argument, arg );
    if( !str_prefix( arg, "list" ) )
    {
	display_magic( d );
	return;
    }
    else if( !str_prefix( arg, "add" ) )
    {
	if( !str_cmp( argument, "all" ) )
	{
	    if( ch->practice < 5 )
		write_to_buffer( d, "You don't have 5 points to spend.\n\r" );
	    else
	    {
		ch->practice -= 5;
		for( sphere = 0; sphere < MAGIC_MAX; ++sphere )
		{
		    ch->pcdata->mod_magic[sphere]++;
		    ch->max_mana[sphere] += 25;
		    ch->mana[sphere] = ch->max_mana[sphere];
		}
		write_to_buffer( d, "You increase all spheres by 1.\n\r" );
	    }
	}
	else
	{
	    sphere = flag_value( NULL, magic_flags, argument );
	    if( sphere < 0 || sphere >= MAGIC_MAX )
	    {
		write_to_buffer( d, "You must choose the sphere or 'all':\n\r"
				 "    air, earth, fire, spirit, water.\n\r" );
	    }
	    else if( ch->practice > 0 )
	    {
		ch->pcdata->mod_magic[sphere]++;
		ch->max_mana[sphere] += 25;
		ch->mana[sphere] = ch->max_mana[sphere];
		ch->practice--;
		sprintf( arg, "You increase your %s skill by 1.\n\r",
			 magic_name[sphere] );
		write_to_buffer( d, arg );
	    }
	    else
	    {
		write_to_buffer(
		    d, "You have used up all your available points.\n\r"
		    "Type 'done' to go on.\n\r" );
	    }
	}
    }
    else if( !str_prefix( arg, "subtract" ) )
    {
	sphere = flag_value( NULL, magic_flags, argument );
	if( sphere < 0 || sphere >= MAGIC_MAX )
	{
	    write_to_buffer( d, "You must choose the sphere:\n\r"
			     "    air, earth, fire, spirit or water.\n\r" );
	}
	else if( ch->pcdata->mod_magic[sphere] > 0 )
	{
	    ch->pcdata->mod_magic[sphere]--;
	    ch->max_mana[sphere] -= 25;
	    ch->mana[sphere] = ch->max_mana[sphere];
	    ch->practice++;
	    sprintf( arg, "You decrease your %s skill by 1.\n\r",
		     magic_name[sphere] );
	    write_to_buffer( d, arg );
	}
	else
	{
	    write_to_buffer(
		d, "You are at the minimum value for that sphere.\n\r" );
	}
    }
    else if( !str_prefix( arg, "help" ) )
    {
	if( argument[0] == '\0' )
	    write_to_buffer(
		d, "This section allows you to customise your magic ability.\n\r"
		"You can add or subtract from your skill in each sphere.\n\r"
		"Use 'add <sphere>' to add a magic point to a sphere.\n\r"
		"You can also use 'add all' to add to all spheres.\n\r"
		"Use 'subtract <sphere>' to take a point back.\n\r"
		"'list' shows your current magic, 'help' shows this help.\n\r"
		"Type 'done' when you are finished.\n\r" );
	else
	    do_help( ch, argument );
    }
    else if( !str_prefix( arg, "done" ) )
    {
	if( ch->practice > 0 )
	{
	    write_to_buffer( d, "But you have more points to spend!\n\r" );
	}
	else
	{
	    int i;
	    for( i = 0; i < MAGIC_MAX; ++i )
	    {
		ch->pcdata->perm_magic[i] += ch->pcdata->mod_magic[i];
		ch->pcdata->mod_magic[i] = 0;
	    }
	    intro_new_player( d );
	    return;
	}
    }
    else if( !str_cmp( arg, "back" ) )
    {
	reroll_stats( d );
	d->interpreter = get_interpreter( "reroll" );
	return;
    }
    sprintf( arg, "&gYou have %d point%s left to spend:&n ", ch->practice,
	     ( ch->practice == 1 ) ? "" : "s" );
    write_to_buffer( d, arg );
}


void nanny_read_motd( DESCRIPTOR_DATA *d, const char *argument )
{
    CHAR_DATA *ch = d->character;
    OBJ_DATA *obj;
    int i, notes;
    char buf[MAX_STRING_LENGTH];
    bool new_note = FALSE;

    ch->next = char_list;
    char_list = ch;
    d->interpreter = get_interpreter( "" );

    SysInfo->total_logins++;
    send_to_char(
	"\n\r&gWelcome to &mDalekenMUD&g.  May your visit here be ... "
	"&cDifferent&g.&n\n\r",	ch );

    if( ch->level == 0 )
    {
	char_to_room( ch, get_room_index( class_table[ch->class].mudschool ) );

	switch( ch->class )
	{
	default:
	    break;
	case CLASS_THIEF:	/* thief */
	    ch->pcdata->learned[skill_lookup( "dagger pro" )] = 75;
	    break;
	case CLASS_WARRIOR:	/* warrior */
	    ch->pcdata->learned[skill_lookup( "short sword pro" )] = 75;
	    break;
	}
	ch->level = 1;
	ch->exp = race_tnl( ch->race ) * 100;
	ch->body_parts = race_table[ch->race].body_parts;
	ch->gold = 5500 + number_fuzzy( 3 )
	    * number_fuzzy( 4 ) * number_fuzzy( 5 ) * 9;
	ch->hit = ch->max_hit = 20;
	for( i = 0; i < MAGIC_MAX; ++i )
	    ch->mana[i] = ch->max_mana[i];
	ch->move = ch->max_move = 100;
	ch->practice = 400;
	ch->alignment = race_table[ch->race].natural_align;
	sprintf( buf, "the %s %s", race_table[ch->race].name,
		 class_table[ch->class].name );
	set_title( ch, buf );

	free_string( ch->pcdata->prompt );
	ch->pcdata->prompt = str_dup( "<%h/%Hhp %m/%Mm %v/%Vmv %xtnl> " );
	ch->pcdata->condition[COND_FULL] = 10000;
	ch->pcdata->condition[COND_THIRST] = 10000;

	obj = create_object( get_obj_index( OBJ_VNUM_SCHOOL_LIGHT ), 0 );
	obj_to_char( obj, ch );
	equip_char( ch, obj, WEAR_FLOAT_L );

	if( class_table[ch->class].fMana )
	    obj = create_object( get_obj_index( OBJ_VNUM_SCHOOL_VEST ), 0 );
	else
	    obj = create_object( get_obj_index( OBJ_VNUM_SCHOOL_ARMOUR ), 0 );
	obj_to_char( obj, ch );
	equip_char( ch, obj, WEAR_BODY );

	obj = create_object( get_obj_index( OBJ_VNUM_SCHOOL_SHIELD ), 0 );
	obj_to_char( obj, ch );
	equip_char( ch, obj, WEAR_SHIELD );

	obj = create_object( get_obj_index( class_table[ch->class].weapon ), 1 );
	if( obj )
	{
	    obj_to_char( obj, ch );
	    equip_char( ch, obj, WEAR_WIELD_R );
	}

	advance_level( ch, FALSE );
    }
    else if( ch->in_room )
    {
	char_to_room( ch, ch->in_room );
    }
    else if( IS_IMMORTAL( ch ) )
    {
	char_to_room( ch, get_room_index( ROOM_VNUM_IMMPLANE ) );
    }
    else
    {
	char_to_room( ch, get_room_index( ROOM_VNUM_TEMPLE ) );
    }

    if( !xIS_SET( ch->act, PLR_WIZINVIS )
	&& !IS_AFFECTED( ch, AFF_INVISIBLE ) )
	act( "&b###$n has entered the game.", ch, NULL, NULL, TO_ROOM );
    sprintf( buf, "%s has entered the game.", ch->name );
    wiznet( ch, WIZ_LOGINS, get_trust( ch ), buf );
    if( get_trust( ch ) < L_JUN )
	talk_channel( NULL, buf, CHANNEL_INFO, "INFO" );

    do_look( ch, AUTOLOOK );
    send_to_char( "\n\r", ch );
    update_highest_list( ch );

    for( i = 0; i < MAX_BOARD; i++ )
    {
	notes = unread_notes( ch, &board_table[i] );

	if( notes > 0 )
	{
	    charprintf( ch, "&mYou have %d new %s note%s.&n\n\r",
			notes, board_table[i].short_name,
			( notes == 1 ) ? "" : "s" );
	    new_note = TRUE;
	}
    }
    if( !new_note )
	send_to_char( "&mYou have no new notes.&n\n\r", ch );
}


/*
 * Parse a name for acceptability.
 */
bool check_parse_name( const char *name )
{
    BAN_DATA *pbad;

    /*
     * Length restrictions.
     */
    if( strlen( name ) < 3 )
	return FALSE;

    /*
     * All the words reserved by immortals.
     */
    for( pbad = badname_list; pbad; pbad = pbad->next )
    {
	if( !str_cmp( name, pbad->name ) )
	    return FALSE;
    }

#if defined( unix )
    if( strlen( name ) > 12 )
	return FALSE;
#endif

    /*
     * Alphanumerics only.
     * Lock out IllIll twits.
     */
    {
	const char *pc;
	bool fIll;

	fIll = TRUE;
	for( pc = name; *pc != '\0'; pc++ )
	{
	    if( !isalpha( *pc ) && ( pc == name
				     || ( *pc != '\'' && *pc != '-' ) ) )
		return FALSE;
	    if( LOWER( *pc ) != 'i' && LOWER( *pc ) != 'l' )
		fIll = FALSE;
	}

	if( fIll )
	    return FALSE;
    }

    /*
     * Prevent players from naming themselves after mobs.
     */
    {
	MOB_INDEX_DATA *pMobIndex;
	int iHash;

	for( iHash = 0; iHash < MAX_KEY_HASH; iHash++ )
	{
	    for( pMobIndex = mob_index_hash[iHash];
		 pMobIndex;
		 pMobIndex = pMobIndex->next )
	    {
		if( !str_cmp( name, pMobIndex->name ) )
		    return FALSE;
	    }
	}
    }

    return TRUE;
}


/*
 * Look for link-dead player to reconnect.
 */
bool check_reconnect( DESCRIPTOR_DATA *d, char *name, bool fConn )
{
    CHAR_DATA *ch;

    for( ch = char_list; ch; ch = ch->next )
    {
	if( ch->deleted || IS_NPC( ch )
	    || ( !fConn && ch->desc )
	    || str_cmp( d->character->name, ch->name ) )
	    continue;

	if( fConn == FALSE )
	{
	    free_string( d->character->pcdata->pwd );
	    d->character->pcdata->pwd = str_dup( ch->pcdata->pwd );
	}
	else
	{
	    /* An Immortal is switched into them. */
	    if( ch->desc && ch->desc != d
		&& CH( ch->desc )->pcdata->switched )
	    {
		write_to_buffer(
		    ch->desc, "Someone wants their character back.\n\r" );
		do_return( ch, "" );
	    }

	    free_char( d->character );
	    d->character = ch;
	    ch->desc = d;
	    ch->pcdata->timer = 0;
	    send_to_char( "&gReconnecting.&n\n\r", ch );
	    act( "&b$n has reconnected.", ch, NULL, NULL, TO_ROOM );
	    sprintf( log_buf, "%s (%s) reconnected.", ch->name, d->host );
	    log_string( log_buf );
	    d->interpreter = get_interpreter( "" );
	}
	return TRUE;
    }

    return FALSE;
}


/*
 * Check if already playing.
 */
bool check_playing( DESCRIPTOR_DATA *d, char *name )
{
    DESCRIPTOR_DATA *dold;

    for( dold = descriptor_list; dold; dold = dold->next )
    {
	if( dold != d
	    && dold->character
	    && str_cmp( dold->interpreter->name, "getName" )
	    && !str_cmp( name, CH( dold )->name )
	    && !dold->character->deleted )
	{
	    write_to_buffer( d, "They are allready playing.\n\rName: " );
	    d->interpreter = get_interpreter( "getName" );
	    if( d->character )
	    {
		free_char( d->character );
		d->character = NULL;
	    }
	    return TRUE;
	}
    }

    return FALSE;
}


void stop_idling( DESCRIPTOR_DATA *d )
{
    CHAR_DATA *ch = d->character;

    if( ch && xIS_SET( CH( d )->act, PLR_KEYLOCK ) )
    {
	if( str_prefix( "keylock ", d->incomm ) )
	    send_to_char( "You are keylocked, type keylock <password> to "
			  "unlock input.\n\r", ch );
	else if( !strcmp( crypt( &d->incomm[8], CH( d )->pcdata->pwd ),
			  CH( d )->pcdata->pwd ) )
	{
	    log_string( "Log %s: removing keylock.", CH( d )->name );
	    send_to_char( "Keylock removed.\n\r", ch );
	    xREMOVE_BIT( CH( d )->act, PLR_KEYLOCK );
	    strcpy( &d->incomm[8], "###############################" );
	}
	else
	{
	    log_string( "Log %s: incorrect password, remaining keylocked.",
		       CH( d )->name );
	    send_to_char( "Incorrect password, character will remain keylocked.\n\r", ch );
	}

	d->incomm[0] = '\0';
    }
    if( !ch || !ch->desc
	|| str_cmp( ch->desc->interpreter->name, "" )
	|| !ch->was_in_room
	|| ch->in_room != get_room_index( ROOM_VNUM_LIMBO ) )
	return;

    ch->pcdata->timer = 0;
    char_from_room( ch );
    char_to_room( ch, ch->was_in_room );
    ch->was_in_room = NULL;
    act( "&b$n has returned from the void.", ch, NULL, NULL, TO_ROOM );
    return;
}


/*
 * Write to all in the room.
 */
void send_to_room( const char *txt, ROOM_INDEX_DATA *room )
{
    CHAR_DATA *target;

    for( target = room->people; target; target = target->next_in_room )
    {
	if( !target->deleted && target->desc )
	    act( txt, target, NULL, NULL, TO_CHAR );
    }
}


/*
 * Write to all characters.
 */
void send_to_all_char( const char *text )
{
    DESCRIPTOR_DATA *d;

    if( !text )
	return;
    for( d = descriptor_list; d; d = d->next )
	if( !IS_SET( d->interpreter->flags, INTERPRETER_NOMESSAGE ) )
	    send_to_char( text, d->character );

    return;
}


/*
 * Write to one char.
 */
void send_to_char( const char *txt, CHAR_DATA *ch )
{
    if( !txt || !txt[0] || !ch || !ch->desc )
	return;

    /*
     * Bypass the paging procedure if the text output is small.
     * Saves process time.
     */
    if( strlen( txt ) < 600 || CH( ch->desc )->pcdata->pagelen < 0 )
	write_to_buffer( ch->desc, txt );
    else
    {
	free_string( ch->desc->showstr_head );
	ch->desc->showstr_head = str_dup( txt );
	ch->desc->showstr_point = ch->desc->showstr_head;
	pager_interpret( ch->desc, "" );
    }

    return;
}


/* OLC, new pager for editing long descriptions. */
/* ========================================================================= */
/* -The heart of the pager.  Thanks to N'Atas-Ha, ThePrincedom for porting   */
/*  this SillyMud code for MERC 2.0 and laying down the groundwork.	     */
/* -Thanks to Blackstar, hopper.cs.uiowa.edu 4000 for which the improvements */
/*  to the pager was modeled from.  - Kahn				     */
/* -Safer, allows very large pagelen now, and allows to page while switched  */
/*  Zavod of jcowan.reslife.okstate.edu 4000.				     */
/* ========================================================================= */
void pager_interpret( DESCRIPTOR_DATA *d, const char *input )
{
    char *start, *end, chsave;
    int lines, pagelen;

    /* Set the page length */
    /* ------------------- */
    pagelen = d->original ? d->original->pcdata->pagelen
	: d->character->pcdata->pagelen;

    /* Check for the command entered */
    /* ----------------------------- */

    switch( UPPER( *input ) )
    {
	/* Show the next page */
    case '\0':	case ' ':
    case 'C':
	lines = 0;
	break;

	/* Scroll back a page */
    case 'B':
	lines = -2 * pagelen;
	break;

	/* Up X lines, down X lines */
    case 'U': case 'D':
	chsave = UPPER( *input );
	do
	{
	    input++;
	}
	while( *input != '\0' && !isdigit( *input ) );
	lines = 0;
	while( *input != '\0' && isdigit( *input ) )
	{
	    lines = *input++ - '0' + lines * 10;
	}
	if( chsave == 'U' )
	    lines *= -1;
	if( lines )
	{
	    lines -= pagelen;
	    break;
	}

	/* Help for show page */
    case 'H':
	write_to_buffer( d, "B\t- Scroll back one page.\n\r" );
	write_to_buffer( d, "C\t- Continue scrolling.\n\r" );
	write_to_buffer( d, "H\t- This help menu.\n\r" );
	write_to_buffer( d, "R\t- Refresh the current page.\n\r" );
	write_to_buffer( d, "D #\t- Page down '#' lines.\n\r" );
	write_to_buffer( d, "U #\t- Page back up '#' lines.\n\r" );
	write_to_buffer( d, "Enter\t- Continue Scrolling.\n\r" );
	return;

	/* refresh the current page */
    case 'R':
	lines = -1 - pagelen;
	break;

	/* stop viewing */
    default:
	free_string( d->showstr_head );
	d->showstr_head = NULL;
	d->showstr_point = NULL;
	return;
    }

    /* do any backing up necessary to find the starting point */
    /* ------------------------------------------------------ */

    if( lines < 0 )
    {
	for( start = d->showstr_point; start > d->showstr_head && lines < 0;
	     start-- )
	    if( *start == '\r' )
		lines++;
    }
    else
	start = d->showstr_point;

    /* Find the ending point based on the page length */
    /* ---------------------------------------------- */
    lines = 0;

    for( end = start; *end && lines < pagelen; end++ )
	if( *end == '\r' )
	    lines++;

    d->showstr_point = end;

    chsave = *end;
    *end = '\0';
    if( end - start )
	write_to_buffer( d, start );
    *end = chsave;

    /* See if this is the end (or near the end) of the string */
    /* ------------------------------------------------------ */
    for( ; isspace( *end ); end++ )
	;

    if( !*end )
    {
	free_string( d->showstr_head );
	d->showstr_head = NULL;
	d->showstr_point = NULL;
    }

    return;
}

#undef PERS
#define PERS( ch, looker )  ( ( !looker || can_see( (looker), (ch) ) )	\
				? ( ( (ch)->short_descr			\
				    && (ch)->short_descr[0] )		\
				  ? (ch)->short_descr : (ch)->name )	\
				: ( IS_IMMORTAL( ch )			\
				  ? "an immortal" : "someone" ) )
char *act_string( char *buf, const char *format, CHAR_DATA *to, CHAR_DATA *ch,
		  const void *arg1, const void *arg2, int type )
{
    char fname[MAX_INPUT_LENGTH];
    char buf1[MAX_STRING_LENGTH];
    static const char *const he_she[] = {"it", "he", "she"};
    static const char *const him_her[] = {"it", "him", "her"};
    static const char *const his_her[] = {"its", "his", "her"};
    const char *str = format;
    const char *i;
    char *point = &buf[0];
    char lcol = 'n';
    OBJ_DATA *obj1 = (OBJ_DATA *)arg1;
    OBJ_DATA *obj2 = (OBJ_DATA *)arg2;
    CHAR_DATA *vch = (CHAR_DATA *)arg2;

    while( *str != '\0' )
    {
	if( *str == '&' )	/* save colour codes */
	{
	    *point++ = *str++;
	    lcol = *str;
	    continue;
	}
	else if( *str != '$' )
	{
	    *point++ = *str++;
	    continue;
	}
	++str;

	if( !arg2 && *str >= 'A' && *str <= 'Z' )
	{
	    bug( "Act: missing arg2 for code %d.", *str );
	    bug( "Bad act string:  %s", format );
	    i = " <@@@> ";
	}
	else
	{
	    switch( *str )
	    {
	    default:
		bug( "Act: bad code %d.", *str );
		bug( "Bad act string:      %s", format );
		i = " <@@@> ";
		break;
	    case '$':
		i = "$";
		break;
		/* Thx alex for 't' idea */
	    case 't':
		i = (char *)arg1;
		break;
	    case 'T':
		i = (char *)arg2;
		break;
	    case 'n':
		i = PERS( ch, to );
		if(ch == to && type != TO_CHAR)
		    i = "you";
		break;
	    case 'N':
		i = PERS( vch, to );
		if( vch == to && type != TO_VICT )
		    i = "you";
		break;
	    case 'o':
		sprintf( buf1, "%s's", PERS( ch, to ) );
		if( ch == to )
		    i = "your";
		else
		    i = buf1;
		break;
	    case 'O':
		sprintf( buf1, "%s's", PERS( vch, to ) );
		if( vch == to )
		    i = "your";
		else
		    i = buf1;
		break;
	    case 'g':
		if( IS_NPC( ch ) || !ch->pcdata->religion )
		    i = "God";
		else
		    i = capitalize( ch->pcdata->religion->god_name );
		break;
	    case 'G':
		if( IS_NPC( vch ) || !vch->pcdata->religion )
		    i = "God";
		else
		    i = capitalize( vch->pcdata->religion->god_name );
		break;
	    case 'e':
		i = he_she[URANGE( 0, ch->sex, 2 )];
		if( ch == to )
		    i = "you";
		break;
	    case 'E':
		i = he_she[URANGE( 0, vch->sex, 2 )];
		if( vch == to )
		    i = "you";
		break;
	    case 'm':
		i = him_her[URANGE( 0, ch->sex, 2 )];
		if( ch == to )
		    i = "you";
		break;
	    case 'M':
		i = him_her[URANGE( 0, vch->sex, 2 )];
		if( vch == to )
		    i = "you";
		break;
	    case 's':
		i = his_her[URANGE( 0, ch->sex, 2 )];
		if( ch == to )
		    i = "your";
		break;
	    case 'S':
		i = his_her[URANGE( 0, vch->sex, 2 )];
		if( vch == to )
		    i = "your";
		break;
	    case '%':	/* Third person */
		if( ch == to )
		    i = "";
		else
		    i = "s";
		break;

	    case 'p':
		i = ( !to || !obj1 || can_see_obj( to, obj1 ) )
		    ? obj1->short_descr : "something";
		break;
	    case 'P':
		i = ( !to || !obj2 || can_see_obj( to, obj2 ) )
		    ? obj2->short_descr : "something";
		break;

	    case 'd':
		if( !arg2 || ( (char *)arg2 )[0] == '\0' )
		{
		    i = "door";
		}
		else
		{
		    one_argument( (char *)arg2, fname );
		    i = fname;
		}
		break;
	    }
	}

	++str;
	while( ( *point = *i ) != '\0' )
	{
	    ++point, ++i;
	    if( *( point - 1 ) == '&' && *i == 'x' )
	    {
		*point = lcol;
		++point, ++i;
	    }
	}
	*point++ = '&';
	*point++ = lcol;
    }

    strcpy( point, "&n\n\r" );
    point = &buf[0];
    while( *point == '&' )
	point += 2;
    *point = UPPER( *point );
    return buf;
}


/*
 * The primary output interface for formatted output.
 */
void act( const char *format, CHAR_DATA *ch, const void *arg1,
	 const void *arg2, int type )
{
    OBJ_DATA *obj1 = (OBJ_DATA *)arg1;
    CHAR_DATA *to;
    CHAR_DATA *vch = (CHAR_DATA *)arg2;
    char msg[MAX_STRING_LENGTH];

    /*
     * Discard null and zero-length messages.
     */
    if( !format || format[0] == '\0' )
	return;

    if( !ch->in_room )
    {
	bug( "Act: null ch->in_room: %s", format );
	return;
    }

    /* Crash protection using magic numbers, suggested by Erwin A. */
    if( vch && vch->magic == MAGIC_NUM_CHAR )
	obj1 = NULL;
    else
	vch = NULL;

    to = ch->in_room->people;
    if( type == TO_VICT )
    {
	if( !vch )
	{
	    bug( "Bad act string (NULL vch with TO_VICT): %s", format );
	    return;
	}
	to = vch->in_room->people;
    }

    if( IS_SET( SysInfo->flags, SYSINFO_ACT_TRIGGER )
	&& type != TO_CHAR && type != TO_VICT && to
	&& ( xIS_SET( ch->in_room->progtypes, ACT_PROG )
	     || ch->in_room->contents ) )
    {
	act_string( msg, format, NULL, ch, arg1, arg2, TO_ALL );
	roomobj_act_trigger( msg, ch->in_room, ch, obj1, (void *)arg2 );
    }

    for( ; to; to = to->next_in_room )
    {
	if( to->deleted || ( !IS_AWAKE( to ) && ( type == TO_ROOM
						  || type == TO_NOTVICT
						  || type == TO_ALL
						  || type == TO_CANSEE ) )
	    || ( !to->desc && !IS_SET( SysInfo->flags, SYSINFO_ACT_TRIGGER ) ) )
	    continue;

	if( type == TO_CHAR && to != ch )
	    continue;
	if( type == TO_VICT && ( to != vch || to == ch ) )
	    continue;
	if( type == TO_ROOM && to == ch )
	    continue;
	if( type == TO_NOTVICT && ( to == ch || to == vch ) )
	    continue;
	if( type == TO_CANSEE && ( to == ch || !can_see( to, ch ) ) )
	    continue;

	act_string( msg, format, to, ch, arg1, arg2, type );
	if( IS_SET( SysInfo->flags, SYSINFO_ACT_TRIGGER ) )
	    mob_act_trigger( msg, to, ch, obj1, vch );
	if( to->desc )
	    write_to_buffer( to->desc, msg );
    }

    SET_BIT( SysInfo->flags, SYSINFO_ACT_TRIGGER );
    return;
}

/*
 * Windows 95 and Windows NT support functions
 */
#if defined( WIN32 )
void gettimeofday( struct timeval *tp, void *tzp )
{
  DWORD millisec = GetTickCount();

  tp->tv_sec = (int)( millisec / 1000 );
  tp->tv_usec = ( millisec % 1000 ) * 1000;
/*
  #if defined( BCB )
    struct timeb curr_time;

    ftime( &curr_time );
#else
    struct timeb curr_time;

    ftime( &curr_time );
#endif

    tp->tv_sec = curr_time.time;
    tp->tv_usec = curr_time.millitm * 1000;
*/
}
#endif


/*
 * This looks soo much better than the standard ctime, plus it
 * doesn't put that nasty trailing '\n' on.
 */
char *myctime( time_t *current_time )
{
    struct tm *tmtime;
    char strtime[50];
    static char final[50];
    int hour;

    tmtime = localtime( current_time );
    strftime( strtime, 50, "%a %%d/%b/%Y %%d:%M%p %Z", tmtime );
    hour = tmtime->tm_hour % 12;
    if( !hour )
	hour = 12;
    sprintf( final, strtime, tmtime->tm_mday, hour );
    return final;
}


void charprintf( CHAR_DATA *ch, const char *fmt, ... )
{
    va_list args;
    char buf [MAX_STRING_LENGTH];

    if( fmt == NULL || fmt[0] == '\0' || ch->desc == NULL )
	return;

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

    send_to_char( buf, ch );
}


/* Recover from a copyover - load players */
void copyover_recover( )
{
    DESCRIPTOR_DATA *d;
    FILE *fp;
    char name[100];
    char host[MAX_STRING_LENGTH];
    int desc;
    bool fOld;

    log_string( "Copyover recovery initiated" );

    fp = open_file( COPYOVER_FILE, "r", FALSE );

    if( !fp )
	/* there are some descriptors open which will hang forever then ? */
    {
	perror( "copyover_recover: open_file" );
	log_string( "Copyover file not found. Exitting.\n\r" );
	exit( 1 );
    }

#if !defined( WIN32 )
    /* In case something crashes - doesn't prevent reading */
    unlink( COPYOVER_FILE );
#else
    remove( COPYOVER_FILE );
#endif

    for( ;; )
    {
	fscanf( fp, "%d %s %s\n", &desc, name, host );
	if( desc == -1 )
	    break;

	/* Write something, and check if it goes error-free */
	if( !write_to_descriptor( desc, "\n\rRestoring from copyover...\n\r", 0 )
#if defined( MCCP )
	    || !write_to_descriptor( desc, compress_will, strlen( compress_will ) )
#endif
	    )
	{
#if !defined( WIN32 )
	    close( desc );
#else
	    closesocket( desc );
#endif
	    continue;
	}

	if( !descriptor_free )
	{
	    d = alloc_perm( sizeof( *d ) );
	}
	else
	{
	    d = descriptor_free;
	    descriptor_free = descriptor_free->next;
	}
	init_descriptor( d, desc );

	d->host = str_dup( host );
	d->next = descriptor_list;
	descriptor_list = d;
	d->interpreter = get_interpreter( "disconnect" );
	/* Now, find the pfile */

	fOld = load_char_obj( d, name );

	if( !fOld )		/* Player file not found?! */
	{
	    write_to_descriptor( desc, "\n\rSomehow, your character was lost "
				"in the copyover. Sorry.\n\r ", 0 );
	    close_socket( d );
	}
	else			/* ok! */
	{
	    SysInfo->total_logins++;
	    SysInfo->curr_descriptors++;
	    if( SysInfo->curr_descriptors > SysInfo->max_descriptors )
		SysInfo->max_descriptors = SysInfo->curr_descriptors;
	    write_to_descriptor( desc, "\n\rCopyover recovery complete.\n\r", 0 );

	    /* sanity check */
	    if( !d->character->in_room )
		d->character->in_room = get_room_index( ROOM_VNUM_TEMPLE );

	    /* Insert in the char_list */
	    d->character->next = char_list;
	    char_list = d->character;
	    char_to_room( d->character, d->character->in_room );
	    do_look( d->character, AUTOLOOK );
	    act( "$n materializes!", d->character, NULL, NULL, TO_ROOM );
	    d->interpreter = get_interpreter( "" );
	}
    }
    close_file( fp );
    return;
}


/* -----------------------------------------------------------------------
   Wheel of Time (WoT) Mud System v0.2a
   Copyright (c) 1997-1998, by Gary McNickle <gary@dharvest.com>
   and Dark Harvest Systems <admin@dharvest.com>
   Based on ROM Mud, v2.4b4 by Russ Taylor

   We don't require that you inform of us if you use this code, only that
   you keep this header with the code, in whatever format you use. ie: if
   you move these functions into another code file, this header goes with
   them.

   [File: signals.c|version 1.0|Author: Gary Mcnickle|Updated on: 11/03/98]

   Updates for this, and other publicly available WoT code can be found at
   our web site: http://www.dharvest.com/wotmud/

   NOTE: was "signal.c"
   ------------------------------------------------------------------------ */

/*
 * Code from signal.c allows a graceful departure from a crash.
 * but only on *NIX, sorry
 */
#if defined( unix )

/* Core dumps aren't created when the signal handler is used.
 * Thus as a debug tool these are removed.
 */
#if defined( DEBUG )
void init_signals()
{
    signal( SIGPIPE, SIG_IGN );
}
#else
volatile sig_atomic_t fatal_error_in_process = 0;

/* Copyover note:
 * Whenever a program is executed from exec (as in the case of copyover)
 * all signals from the previous execution of the mud return to their
 * SIG_DEF (default) signal handler, since copyover starts the mud
 * process all over again, init_signals will get called and properly
 * reset the signal handler.
 */

/** Function: signal_handler
 * Descr   : Our signal handler, to shutdown gracefully (if possible)
 *	   : upon reciept of a signal signifying we should shutdown.
 * Returns : void
 * Syntax  : (n/a)
 * Written : v1.0 11/98
 * Author  : Gary McNickle <gary@dharvest.com>
 */
void signal_handler( int sig )
{
    /* Since our 'shutdown_wgrace' is not reentrant, and may (likely) access
     * non-volatile global variables, be damn sure it's not called twice.
     */
    if( fatal_error_in_process )
	raise( sig );

    switch( sig )
    {
    case SIGBUS:
    case SIGTERM:
    case SIGABRT:
    case SIGSEGV:
    {
	fatal_error_in_process = 1;	 /* Yes, this IS a fatal error	*/
	  /* Log signal to bug log file	 */
	bug( "Caught deadly signal: %s.",
	     (sig == SIGBUS)  ? "SIGBUS" : (sig == SIGTERM) ? "SIGTERM"
	     : (sig == SIGABRT) ? "SIGABRT" : (sig == SIGXCPU) ? "SIGXCPU"
	     : "SIGSEGV" );

	shutdown_wgrace();		    /* Attempt a graceful shutdown  */
	raise( sig );			    /* set return status of process */
	break;
    }
    break;
    }
}


/** Function: init_signals
 * Descr   : Initialize signals that we trap, setting up a handler for them.
 * Returns : void
 * Syntax  : void
 * Written : v1.0 11/98
 * Author  : Gary McNickle <gary@dharvest.com>
 * Note	   : By default, signals sent to sighandler are blocked until the
 *	   : handler returns, but other signals are not blocked. We need
 *	   : to block any other signal we recieve that we normally trap,
 *	   : until we are done trying to be graceful...
 */
void init_signals()
{
    struct sigaction sigact;
    sigset_t mask;

    signal( SIGPIPE, SIG_IGN );

    /* NOTE: We inherit any current signal actions by default.
     * Don't install a signal handler for any ignored signals.
     */
    sigaction( SIGBUS, NULL, &sigact );

    if( sigact.sa_handler != SIG_IGN )
    {
	sigact.sa_handler = signal_handler;
	sigemptyset( &mask );
	/* block these signals to the handler while it's running */
	sigaddset( &mask, SIGTERM );
	sigaddset( &mask, SIGABRT );
	sigaddset( &mask, SIGSEGV );
	sigaddset( &mask, SIGPIPE );
	sigaddset( &mask, SIGXCPU );
	sigprocmask( SIG_BLOCK, &mask, NULL);  /*JLR*/
	sigact.sa_mask = mask;
	sigact.sa_flags = SA_RESETHAND;
	sigaction( SIGBUS, &sigact, NULL );
    }
    else
	log_string( "Init: Signal SIGBUS ignored." );

    sigaction( SIGTERM, NULL, &sigact );

    if( sigact.sa_handler != SIG_IGN )
    {
	sigact.sa_handler = signal_handler;
	sigemptyset( &mask );
	/* block these signals to the handler while it's running */
	sigaddset( &mask, SIGBUS );
	sigaddset( &mask, SIGABRT );
	sigaddset( &mask, SIGSEGV );
	sigaddset( &mask, SIGPIPE );
	sigaddset( &mask, SIGXCPU );
	sigprocmask( SIG_BLOCK, &mask, NULL);  /*JLR*/
	sigact.sa_mask = mask;
	sigact.sa_flags = SA_RESETHAND;
	sigaction( SIGTERM, &sigact, NULL );
    }
    else
	log_string( "Init: Signal SIGTERM ignored." );

    sigaction( SIGABRT, NULL, &sigact );

    if( sigact.sa_handler != SIG_IGN )
    {
	sigact.sa_handler = signal_handler;
	sigemptyset( &mask );
	/* block these signals to the handler while it's running */
	sigaddset( &mask, SIGBUS );
	sigaddset( &mask, SIGTERM );
	sigaddset( &mask, SIGSEGV );
	sigaddset( &mask, SIGPIPE );
	sigaddset( &mask, SIGXCPU );
	sigprocmask( SIG_BLOCK, &mask, NULL);  /*JLR*/
	sigact.sa_mask = mask;
	sigact.sa_flags = SA_RESETHAND;
	sigaction ( SIGABRT, &sigact, NULL );
    }
    else
	log_string( "Init: Signal SIGABRT ignored." );

    sigaction ( SIGSEGV, NULL, &sigact );
    if( sigact.sa_handler != SIG_IGN )
    {
	sigact.sa_handler = signal_handler;
	sigemptyset( &mask );
	/* block these signals to the handler while it's running */
	sigaddset( &mask, SIGBUS );
	sigaddset( &mask, SIGTERM );
	sigaddset( &mask, SIGABRT );
	sigaddset( &mask, SIGPIPE );
	sigaddset( &mask, SIGXCPU );
	sigprocmask( SIG_BLOCK, &mask, NULL);  /*JLR*/
	sigact.sa_mask = mask;
	sigact.sa_flags = SA_RESETHAND;
	sigaction ( SIGSEGV, &sigact, NULL );
    }
    else
	log_string("Init: Signal SIGSEGV ignored.");

    sigaction( SIGXCPU, NULL, &sigact );

    if( sigact.sa_handler != SIG_IGN )
    {
	sigact.sa_handler = signal_handler;
	sigemptyset( &mask );
	/* block these signals to the handler while it's running */
	sigaddset( &mask, SIGBUS );
	sigaddset( &mask, SIGTERM );
	sigaddset( &mask, SIGABRT );
	sigaddset( &mask, SIGSEGV );
	sigaddset( &mask, SIGPIPE );
	sigprocmask( SIG_BLOCK, &mask, NULL );  /*JLR*/
	sigact.sa_mask = mask;
	sigact.sa_flags = SA_RESETHAND;
	sigaction( SIGXCPU, &sigact, NULL );
    }
    else
	log_string( "Init: Signal SIGXCPU ignored." );

    log_string( "Init: Signals initialized." );
}


/** Function: shutdown_wgrace
 * Descr   : Upon receipt of a fatal signal, attempt a graceful shutdown.
 * Returns : void
 * Syntax  : void
 * Written : v1.0 11/98
 * Author  : Gary McNickle <gary@dharvest.com>
 */
void shutdown_wgrace()
{
    DESCRIPTOR_DATA *d,*d_next;

    write_last_command();
    db_dump( );

    /* Notify players of impending crash, and save all pfiles	*/
    for ( d = descriptor_list; d != NULL; d = d_next )
    {
	if( d->character )
	{
	    if( !IS_SET( d->interpreter->flags, INTERPRETER_NOMESSAGE ) )
		send_to_char( "\n\r-*- System Crash... Saving game data. -*-\n\r",
			      d->character);

	    do_save ( d->character, "" );
	}

	d_next = d->next;
	close_socket( d );
    }
}


/* Write last command */
void write_last_command()
{
    FILE *fp;

    /* Return if no last command - set before normal exit */
    if( !last_command[0] )
	return;

    fp = open_file( LAST_COMM_FILE, "a", FALSE );

    if( !fp )
	return;

    fprintf( fp, "%s\n", last_command );
    close_file( fp );
}
#endif

/**************************************************************************
 * CPU time safety checker.
 * This is based on code by Erwin S. Andreasen.
 * An alarm is set to call alarm_handler every ALARM_FREQUENCY seconds.
 * At this time, if the CPU usage exceeds the threshold value:
 * alarm_threshold, then the MUD will halt.
 * When booting, the threshold value is much larger.
 */


/* Returns the current amount of time used. */
int get_user_time( void )
{
    struct tms tx;

    times( &tx );
    return (int)( tx.tms_utime * 10 / CLK_TCK );
}


/*
 * This signal handler is called when the user timer triggers.  Note that for
 * debugging purposes the exit is not called.  This allows me to find where
 * the code is stuck in the debugger.
 */
void alarm_handler( int signo )
{
    int usage_now = get_user_time();

    /* Has there gone alarm_threshold CPU seconds without alarm_update? */
    if( !IS_SET( SysInfo->flags, SYSINFO_NOCPULIMIT )
	&& ( usage_now - last_checkpoint > alarm_threshold ) )
    {
	bug( "User CPU time limit exceeded." );
	signal( signo, SIG_DFL );

#if !defined( DEBUG )
	exit( 1 );
#endif
    }
    last_checkpoint = usage_now;
}

/*
 * Install handler for the virtual timer.
 */
void install_alarm( void )
{
    struct sigaction sigact;
    struct itimerval itimer;

    last_checkpoint = get_user_time();

    /*
     * Install the signal handler.
     */
    sigact.sa_handler = alarm_handler;
#if !defined( SA_RESTART )
    sigact.sa_flags = 0;	/* Cygwin does not handle this. */
#else
    sigact.sa_flags = SA_RESTART;	/* Restart interrupted system calls */
#endif
    sigemptyset( &sigact.sa_mask );

    if( sigaction( SIGVTALRM, &sigact, NULL ) < 0 )
    {
	perror( "init_alarm_handler:sigaction" );
	exit( 1 );
    }

    /*
     * Start the timer,
     * the handler is called every ALARM_FREQUENCY CPU seconds.
     */
    itimer.it_interval.tv_usec = 0;
    itimer.it_interval.tv_sec  = ALARM_FREQUENCY;
    itimer.it_value.tv_usec = 0;
    itimer.it_value.tv_sec = ALARM_FREQUENCY;

    /* start the timer - in that many CPU seconds, alarm_handler will be called */
    if( setitimer( ITIMER_VIRTUAL, &itimer, NULL ) < 0)
    {
	perror( "reset_itimer:setitimer" );
	exit( 1 );
    }

    log_string( "Init: The alarm has been set." );
}


/*
 * This is called after boot_db to change the allowed amount of CPU time.
 */
void update_alarm( void )
{
    int now_time = get_user_time();
    alarm_threshold = ALARM_RUNNING;
    log_string( "Init: Boot took %d.%d user CPU seconds.",
		( now_time - last_checkpoint ) / 10,
		( now_time - last_checkpoint ) % 10 );
    last_checkpoint = now_time;
}

#endif