/*
 * $Id: comm.c,v 1.176 1999/04/17 10:12:47 fjoe Exp $
 */

/***************************************************************************
 *     ANATOLIA 2.1 is copyright 1996-1997 Serdar BULUT, Ibrahim CANPUNAR  *	
 *     ANATOLIA has been brought to you by ANATOLIA consortium		   *
 *	 Serdar BULUT {Chronos}		bulut@rorqual.cc.metu.edu.tr       *
 *	 Ibrahim Canpunar  {Asena}	canpunar@rorqual.cc.metu.edu.tr    *	
 *	 Murat BICER  {KIO}		mbicer@rorqual.cc.metu.edu.tr	   *	
 *	 D.Baris ACAR {Powerman}	dbacar@rorqual.cc.metu.edu.tr	   *	
 *     By using this code, you have agreed to follow the terms of the      *
 *     ANATOLIA license, in the file Anatolia/anatolia.licence             *	
 ***************************************************************************/

/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku vMud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Thanks to abaddon for proof-reading our comm.c and pointing out bugs.  *
 *  Any remaining bugs are, of course, our work, not his.  :)              *
 *                                                                         *
 *  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.                                                  *
 ***************************************************************************/

/***************************************************************************
*	ROM 2.4 is copyright 1993-1995 Russ Taylor			   *
*	ROM has been brought to you by the ROM consortium		   *
*	    Russ Taylor (rtaylor@pacinfo.com)				   *
*	    Gabrielle Taylor (gtaylor@pacinfo.com)			   *
*	    Brian Moore (rom@rom.efn.org)				   *
*	By using this code, you have agreed to follow the terms of the	   *
*	ROM license, in the file Rom24/doc/rom.license			   *
***************************************************************************/

/*
 * This file contains all of the OS-dependent stuff:
 *   startup, 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
 */

#include <sys/types.h>
#if	!defined(WIN32)
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <arpa/telnet.h>
#	include <arpa/inet.h>
#	include <unistd.h>
#	include <netdb.h>
#	include <sys/wait.h>
#else
#	include <winsock.h>
#	include <sys/timeb.h>
#endif

#include <sys/time.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <stdarg.h>   
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if defined(SUNOS) || defined(SVR4) || defined(LINUX)
#	include <crypt.h>
#endif

#include "merc.h"
#include "interp.h"
#include "quest.h"
#include "update.h"
#include "ban.h"
#include "charset.h"
#include "resolver.h"
#include "olc/olc.h"
#include "comm_info.h"
#include "comm_colors.h"
#include "db/lang.h"

DESCRIPTOR_DATA	*	new_descriptor	(void);
void			free_descriptor	(DESCRIPTOR_DATA *d);

bool class_ok(CHAR_DATA *ch , int class);

struct codepage {
	char* name;
	unsigned char* from;
	unsigned char* to;
};

struct codepage codepages[] = {
	{ "koi8-r",		koi8_koi8,	koi8_koi8	},
	{ "alt (cp866)",	alt_koi8,	koi8_alt	},
	{ "win (cp1251)",	win_koi8,	koi8_win	},
	{ "iso (ISO-8859-5)",	iso_koi8,	koi8_iso	},
	{ "mac",		mac_koi8,	koi8_mac	},
	{ "translit",		koi8_koi8,	koi8_vola	},
};
#define NCODEPAGES (sizeof(codepages) / sizeof(struct codepage))

/*
 * Socket and TCP/IP stuff.
 */

#if defined (WIN32)
#include <winsock.h>

void    gettimeofday    args( ( struct timeval *tp, void *tzp ) );

/*  Definitions for the TELNET protocol. Copied from telnet.h */

#define IAC		255	/* interpret as command: */
#define DONT	254	/* you are not to use option */
#define DO		253	/* please, you use option */
#define WONT	252	/* I won't use option */
#define WILL	251	/* I will use option */
#define SB		250	/* interpret as subnegotiation */
#define GA		249	/* you may reverse the line */
#define EL		248	/* erase the current line */
#define EC		247	/* erase the current character */
#define AYT		246	/* are you there */
#define AO		245	/* abort output--but let prog finish */
#define IP		244	/* interrupt process--permanently */
#define BREAK	243	/* break */
#define DM		242	/* data mark--for connect. cleaning */
#define NOP		241	/* nop */
#define SE		240	/* end sub negotiation */
#define EOR		239 /* end of record (transparent mode) */
#define SYNCH	242	/* for telfunc calls */

#define TELOPT_ECHO	1	/* echo */
#endif

char	echo_off_str	[] = { IAC, WILL, TELOPT_ECHO, '\0' };
char	echo_on_str	[] = { IAC, WONT, TELOPT_ECHO, '\0' };
char 	go_ahead_str	[] = { IAC, GA, '\0' };

/*
 * Global variables.
 */
DESCRIPTOR_DATA *   descriptor_list;	/* All open descriptors		*/
DESCRIPTOR_DATA *   d_next;		/* Next descriptor in loop	*/
bool		    merc_down;		/* Shutdown			*/
bool		    wizlock;		/* Game is wizlocked		*/
bool		    newlock;		/* Game is newlocked		*/
char		    str_boot_time[26];
time_t		    current_time;	/* time of this pulse */	
int                 iNumPlayers = 0; /* The number of players on */

extern int		max_on;

int	init_socket		(int port);
void	process_who		(int port);
void	init_descriptor		(int control);
void	close_descriptor	(DESCRIPTOR_DATA *d);
bool	read_from_descriptor	(DESCRIPTOR_DATA *d);
bool	write_to_descriptor	(int desc, char *txt, uint length);
void	game_loop_unix		(void);
#if !defined(WIN32)
void	resolv_done		(void);
#endif

/*
 * Other local functions (OS-independent).
 */
bool	check_reconnect		(DESCRIPTOR_DATA *d, const char *name,
				 bool fConn);
bool	check_playing		(DESCRIPTOR_DATA *d, const char *name);
int	main			(int argc, char **argv);
void	nanny			(DESCRIPTOR_DATA *d, const char *argument);
bool	process_output		(DESCRIPTOR_DATA *d, bool fPrompt);
void	read_from_buffer	(DESCRIPTOR_DATA *d);
void	stop_idling		(CHAR_DATA *ch);
void    bust_a_prompt           (CHAR_DATA *ch);
void 	log_area_popularity	(void);

varr 	control_sockets = { sizeof(int), 2 };
varr	info_sockets = { sizeof(int), 2 };
varr	info_trusted = { sizeof(struct in_addr), 2 };

static void usage(const char *name)
{
	fprintf(stderr, "Usage: %s [-p port...] [-i port...]\n"
			"Where:\n"
			"\t-p -- listen port\n"
			"\t-i -- info service port\n",
		get_filename(name));
	exit(1);
}

#define GETINT(v, i) (*(int*) VARR_GET(v, i))

static void open_sockets(varr *v, const char *logm)
{
	int i, j;

	for (i = 0, j = 0; i < v->nused; i++) {
		int port = GETINT(v, i);
		int sock;
		if ((sock = init_socket(port)) < 0)
			continue;
		log_printf(logm, port);
		GETINT(v, j++) = sock;
	}
	v->nused = j;
}

void close_sockets(varr *v)
{
	int i;

	for (i = 0; i < v->nused; i++) {
		int fd = GETINT(v, i);
#if defined (WIN32)
		closesocket(fd);
#else
		close(fd);
#endif
	}
}

int main(int argc, char **argv)
{
	struct timeval now_time;
	int ch;
	int check_info;

#if defined WIN32
	WORD	wVersionRequested = MAKEWORD(1, 1);
	WSADATA	wsaData;
	int err;
#endif

	/*
	 * Memory debugging if needed.
	 */
#if defined(MALLOC_DEBUG)
	malloc_debug(2);
#endif

	setlocale(LC_ALL, "");

	/*
	 * Init time.
	 */
	gettimeofday(&now_time, NULL);
	current_time 	= (time_t) now_time.tv_sec;
	strnzcpy(str_boot_time, sizeof(str_boot_time), strtime(current_time));

	/*
	 * Run the game.
	 */
	
#if defined (WIN32)
	srand((unsigned) time(NULL));
	err = WSAStartup(wVersionRequested, &wsaData); 
	if (err) {
		log_printf("winsock.dll: %s", strerror(errno));
		exit(1);
	}
#else
	resolver_init();
#endif

	boot_db_system();

	if (argc > 1) {
		/*
		 * command line parameters override configuration settings
		 */
		control_sockets.nused = 0;
		info_sockets.nused = 0;

		opterr = 0;
		while ((ch = getopt(argc, argv, "p:i:")) != -1) {
			int *p;

			switch (ch) {
			case 'p':
				if (!is_number(optarg))
					usage(argv[0]);
				p = varr_enew(&control_sockets);
				*p = atoi(optarg);
				break;

			case 'i':
				if (!is_number(optarg))
					usage(argv[0]);
				p = varr_enew(&info_sockets);
				*p = atoi(optarg);
				break;

			default:
				usage(argv[0]);
			}
		}
		argc -= optind;
		argv += optind;
	}

	if (!control_sockets.nused) {
		log_printf("no control sockets defined");
		exit(1);
	}
	check_info = (!!info_sockets.nused);

	boot_db();

	open_sockets(&control_sockets, "ready to rock on port %d");
	open_sockets(&info_sockets, "info service started on port %d");

	if (!control_sockets.nused) {
		log_printf("no control sockets could be opened.");
		exit(1);
	}

	if (check_info && !info_sockets.nused) {
		log_printf("no info service sockets could be opened.");
		exit(1);
	}

	game_loop_unix();

	close_sockets(&control_sockets);
	close_sockets(&info_sockets);

#if defined (WIN32)
	WSACleanup();
#else
	resolver_done();
#endif
	log_area_popularity();

	/*
	 * That's all, folks.
	 */
	log("Normal termination of game.");
	return 0;
}

/* stuff for recycling descriptors */
DESCRIPTOR_DATA *descriptor_free;

DESCRIPTOR_DATA *new_descriptor(void)
{
	DESCRIPTOR_DATA *d;

	if (descriptor_free == NULL)
		d = malloc(sizeof(*d));
	else {
		d = descriptor_free;
		descriptor_free = descriptor_free->next;
	}

	memset(d, 0, sizeof(*d));
	return d;
}

void free_descriptor(DESCRIPTOR_DATA *d)
{
	if (!d)
		return;

	free_string(d->host);
	free(d->outbuf);
	d->next = descriptor_free;
	descriptor_free = d;
}

int init_socket(int port)
{
	static struct sockaddr_in sa_zero;
	struct sockaddr_in sa;
	struct linger ld;
	int x = 1;
	int fd;

#if defined (WIN32)
	if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
#else
	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
#endif
		log_printf("init_socket(%d): socket: %s",
			   port, strerror(errno));
		return -1;
	}

	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
		       (char *) &x, sizeof(x)) < 0) {
		log_printf("init_socket(%d): setsockopt: SO_REUSEADDR: %s",
			   port, strerror(errno));
#if defined (WIN32)
		closesocket(fd);
#else
		close(fd);
#endif
		return -1;
	}

	ld.l_onoff  = 0;
	if (setsockopt(fd, SOL_SOCKET, SO_LINGER,
		       (char *) &ld, sizeof(ld)) < 0) {
		log_printf("init_socket(%d): setsockopt: SO_LINGER: %s",
			   port, strerror(errno));
#if defined (WIN32)
		closesocket(fd);
#else
		close(fd);
#endif
		return -1;
	}

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

	if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
		log_printf("init_socket(%d): bind: %s", port, strerror(errno));
#if defined (WIN32)
		closesocket(fd);
#else
		close(fd);
#endif
		return -1;
	}

	if (listen(fd, 3) < 0) {
		log_printf("init_socket(%d): listen: %s",
			   port, strerror(errno));
#if defined (WIN32)
		closesocket(fd);
#else
		close(fd);
#endif
		return -1;
	}

	return fd;
}

static void add_fds(varr *v, fd_set *in_set, int *maxdesc)
{
	int i;

	for (i = 0; i < v->nused; i++) {
		int fd = GETINT(v, i);
		FD_SET(fd, in_set);
		if (*maxdesc < fd) *maxdesc = fd;
	}
}

static void check_fds(varr *v, fd_set *in_set, void (*new_conn_cb)(int))
{
	int i;

	for (i = 0; i < v->nused; i++) {
		int fd = GETINT(v, i);
		if (FD_ISSET(fd, in_set))
			new_conn_cb(fd);
	}
}

void game_loop_unix(void)
{
	static struct timeval null_time;
	struct timeval last_time;
 
	gettimeofday(&last_time, NULL);
	current_time = (time_t) last_time.tv_sec;

	/* Main loop */
	while (!merc_down) {
		fd_set in_set;
		fd_set out_set;
		fd_set exc_set;
		DESCRIPTOR_DATA *d;
		INFO_DESC *id;
		INFO_DESC *id_next;
		int maxdesc;

#if defined(MALLOC_DEBUG)
		if (malloc_verify() != 1)
			abort();
#endif

		/*
		 * Poll all active descriptors.
		 */
		FD_ZERO(&in_set );
		FD_ZERO(&out_set);
		FD_ZERO(&exc_set);

		maxdesc = 0;
		add_fds(&control_sockets, &in_set, &maxdesc);
		add_fds(&info_sockets, &in_set, &maxdesc);

#if !defined (WIN32)
		FD_SET(fileno(rfin), &in_set);
		maxdesc = UMAX(maxdesc, fileno(rfin));
#endif

		for (d = descriptor_list; d; d = d->next) {
			maxdesc = UMAX(maxdesc, d->descriptor);
			FD_SET(d->descriptor, &in_set );
			FD_SET(d->descriptor, &out_set);
			FD_SET(d->descriptor, &exc_set);
		}

		for (id = id_list; id; id = id->next) {
			maxdesc = UMAX(maxdesc, id->fd);
			FD_SET(id->fd, &in_set);
		}

		if (select(maxdesc+1,
			   &in_set, &out_set, &exc_set, &null_time) < 0) {
			log_printf("game_loop: select: %s", strerror(errno));
			exit(1);
		}

#if !defined (WIN32)
		if (FD_ISSET(fileno(rfin), &in_set))
			resolv_done();
#endif

		check_fds(&control_sockets, &in_set, init_descriptor);
		check_fds(&info_sockets, &in_set, info_newconn);

		for (id = id_list; id; id = id_next) {
			id_next = id->next;

			if (FD_ISSET(id->fd, &in_set))
				info_process_cmd(id);
		}

		/*
		 * 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 && d->character->level > 1)
					save_char_obj(d->character, FALSE);
				d->outtop = 0;
				close_descriptor(d);
			}
		}

		/*
		 * Process input.
		 */
		for (d = descriptor_list; d != NULL; d = d_next) {
			d_next		= d->next;
			d->fcommand	= FALSE;

			if (FD_ISSET(d->descriptor, &in_set)) {
				if (d->character != NULL)
					d->character->timer = 0;

				if (!read_from_descriptor(d)) {
					FD_CLR(d->descriptor, &out_set);
					if (d->character != NULL
					&&  d->character->level > 1)
						save_char_obj(d->character,
							      FALSE);
					d->outtop = 0;
					close_descriptor(d);
					continue;
				}
			}

			if (d->character != NULL && d->character->daze > 0)
				--d->character->daze;

			if (d->character != NULL && d->character->wait > 0) {
				--d->character->wait;
				continue;
			}

			read_from_buffer(d);
			if (d->incomm[0] != '\0') {
				d->fcommand = TRUE;
				stop_idling(d->character);

				if (d->showstr_point)
					show_string(d, d->incomm);
				else if (d->pString)
					string_add(d->character, d->incomm);
				else if (d->connected == CON_PLAYING) {
					if (!run_olc_editor(d))
			    			substitute_alias(d, d->incomm);
				}
				else
					nanny(d, d->incomm);

				if (d->connected != CON_RESOLV)
					d->incomm[0]	= '\0';
			}
		}

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

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

			if ((d->fcommand || d->outtop > 0)
			&&  FD_ISSET(d->descriptor, &out_set)) {
				if (!process_output(d, TRUE)) {
					if (d->character != NULL
					&&  d->character->level > 1)
						save_char_obj(d->character, FALSE);
					d->outtop = 0;
					close_descriptor(d);
				}
			}
		}

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

	    gettimeofday(&now_time, NULL);
	    usecDelta	= ((int) last_time.tv_usec) - ((int) now_time.tv_usec)
			+ 1000000 / PULSE_PER_SCD;
	    secDelta	= ((int) last_time.tv_sec) - ((int) 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 (select(0, NULL, NULL, NULL, &stall_time) < 0) {
		    log_printf("game_loop: select: stall: %s", strerror(errno));
		    exit(1);
		}
	    }
	}
#else
	{
	    int times_up;
	    int nappy_time;
	    struct _timeb start_time;
	    struct _timeb end_time;
	    _ftime( &start_time );
	    times_up = 0;

	    while( times_up == 0 )
	    {
			_ftime( &end_time );
			if ( ( nappy_time = (int) ( 1000 * (double) ( ( end_time.time - start_time.time ) +
				       ( (double) ( end_time.millitm - start_time.millitm ) /
					1000.0 ) ) ) ) >= (double)( 1000 / PULSE_PER_SCD ) )
			  times_up = 1;
		else
		{
		    Sleep( (int) ( (double) ( 1000 / PULSE_PER_SECOND ) -
				  (double) nappy_time ) );
		    times_up = 1;
		}
	  }
	}
#endif
		gettimeofday(&last_time, NULL);
		current_time = (time_t) last_time.tv_sec;
	}
}

static void cp_print(DESCRIPTOR_DATA* d)
{
	char buf[MAX_STRING_LENGTH];
	int i;

	write_to_buffer(d, "\n\r", 0);
	for (i = 0; i < NCODEPAGES; i++) {
		snprintf(buf, sizeof(buf), "%s%d. %s",
			 i ? " " : "", i+1, codepages[i].name);
		write_to_buffer(d, buf, 0);
	}
	write_to_buffer(d, "\n\rSelect your codepage (non-russian players should choose translit): ", 0);
}

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

void init_descriptor(int control)
{
	DESCRIPTOR_DATA *dnew;
	struct sockaddr_in sock;
	int desc;
	int size;
	HELP_DATA *greeting;

	size = sizeof(sock);
	getsockname(control, (struct sockaddr *) &sock, &size);
	if ((desc = accept(control, (struct sockaddr *) &sock, &size)) < 0) {
		log_printf("init_descriptor: accept: %s", strerror(errno));
		return;
	}

#if !defined (WIN32)
	if (fcntl(desc, F_SETFL, FNDELAY) < 0) {
		log_printf("init_descriptor: fcntl: FNDELAY: %s",
			   strerror(errno));
		return;
	}
#endif

	/*
	 * Cons a new descriptor.
	 */
	dnew = new_descriptor();

	dnew->descriptor	= desc;
	dnew->connected		= CON_GET_CODEPAGE;
	dnew->showstr_head	= NULL;
	dnew->showstr_point	= NULL;
	dnew->pString		= NULL;
	dnew->olced		= NULL;
	dnew->pEdit		= NULL;
	dnew->pEdit2		= NULL;
	dnew->outsize		= 2000;
	dnew->outbuf		= malloc(dnew->outsize);
	dnew->wait_for_se	= 0;
	dnew->codepage		= codepages;
	dnew->host		= NULL;

	size = sizeof(sock);
	if (getpeername(desc, (struct sockaddr *) &sock, &size) < 0) {
		log_printf("init_descriptor: getpeername: %s",
			   strerror(errno));
		return;
	}
#if defined (WIN32)
	else {
		/* Copying from ROM 2.4b6 */
		int addr;
		struct hostent *from;

		addr = ntohl(sock.sin_addr.s_addr);
		from = gethostbyaddr((char *) &sock.sin_addr,
				     sizeof(sock.sin_addr), AF_INET);
		dnew->host = str_dup(from ? from->h_name : "unknown");
	}
#endif

	log_printf("sock.sinaddr: %s", inet_ntoa(sock.sin_addr));

	dnew->next		= descriptor_list;
	descriptor_list		= dnew;

	/*
	 * Send the greeting.
	 */
	if ((greeting = help_lookup(1, "GREETING"))) {
		char buf[MAX_STRING_LENGTH];
		parse_colors(mlstr_mval(greeting->text), buf, sizeof(buf),
			     FORMAT_DUMB);
		write_to_buffer(dnew, buf + (buf[0] == '.'), 0);
	}
	cp_print(dnew);
}

void close_descriptor(DESCRIPTOR_DATA *dclose)
{
	CHAR_DATA *ch;
	DESCRIPTOR_DATA *d;

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

	if (dclose->snoop_by != NULL) 
		write_to_buffer(dclose->snoop_by,
				"Your victim has left the game.\n\r", 0);

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

	if ((ch = dclose->character) != NULL) {
		log_printf("Closing link to %s.", ch->name);
		if (dclose->connected == CON_PLAYING) {
			act("$n has lost $s link.", ch, NULL, NULL, TO_ROOM);
			wiznet("Net death has claimed $N.", ch, NULL,
			       WIZ_LINKS, 0, 0);
			ch->desc = NULL;
		}
		else
	    		free_char(dclose->character);
	}

	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 != NULL)
			d->next = dclose->next;
		else
			bug("Close_socket: dclose not found.", 0);
	}

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

bool read_from_descriptor(DESCRIPTOR_DATA *d)
{
	int iOld;
	int iStart;
	unsigned char *p, *q;

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

	/* Check for overflow. */
	iOld = iStart = strlen(d->inbuf);
	if (iStart >= sizeof(d->inbuf) - 10) {
		log_printf("%s input overflow!", d->host);
		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) {
			iStart += nRead;
			if (d->inbuf[iStart-1] == '\n'
			||  d->inbuf[iStart-1] == '\r')
				break;
		}
		else if (nRead == 0) {
			log("EOF encountered on read.");
			return FALSE;
			break;
		}
#if !defined (WIN32)
		else if (errno == EWOULDBLOCK)
			break;
#else
        else if ( WSAGetLastError() == WSAEWOULDBLOCK)
	    break;
#endif
		else {
			log_printf("read_from_descriptor: %s", strerror(errno));
			return FALSE;
		}
	}

	d->inbuf[iStart] = '\0';
	if (iOld == iStart)
		return TRUE;

	for (p = d->inbuf+iOld; *p;) {
		unsigned char *r;

		if (*p != IAC
		||  (d->connected == CON_PLAYING &&
		     d->character &&
		     IS_SET(d->character->comm, COMM_NOTELNET))) {
			p++;
			continue;
		}

		if (d->wait_for_se)
			goto wse;

		switch (p[1]) {
		case DONT:
		case DO:
		case WONT:
		case WILL:
			q = p+3;
			break;

		wse:
		case SB:   
			q = strchr(p, SE);
			if (q == NULL) {
				q = strchr(p, '\0');
				d->wait_for_se = 1; 
			}
			else {
				q++; 
				d->wait_for_se = 0; 
			}
			break;

		case IAC:
			memmove(p, p+1, strlen(p));
			p++;
			continue;
			/* NOTREACHED */

		default:
			q = p+2;
			break;
		}
		if ((r = strchr(p, '\0')) < q)
			q = r;
		memmove(p, q, strlen(q)+1);
	} 

	return TRUE;
}

/*
 * Transfer one line from input buffer to input line.
 */
void read_from_buffer(DESCRIPTOR_DATA *d)
{
	int i, j, 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.
	 */
	for (i = 0, k = 0; d->inbuf[i] != '\n' && d->inbuf[i] != '\r'; i++) {
		if (k >= MAX_INPUT_LENGTH - 2) {
			write_to_descriptor(d->descriptor,
					    "Line too long.\n\r", 0);

			/* 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 ((unsigned)d->inbuf[i] >= ' ')
			d->incomm[k++] =
				d->codepage->from[(unsigned char) d->inbuf[i]];
	}

	/*
	 * 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 {
			CHAR_DATA *ch = d->original ? d->original :
						      d->character;
			if (ch && ++d->repeat >= 100) {
				char buf[MAX_STRING_LENGTH];

				log_printf("%s input spamming!", d->host);
				snprintf(buf, sizeof(buf),
					 "Inlast:[%s] Incomm:[%s]!",
					 d->inlast, d->incomm);
					
				wiznet("SPAM SPAM SPAM $N spamming, and OUT!",
					ch, NULL, WIZ_SPAM, 0, ch->level);
				wiznet("[$N]'s $t!",
					ch, buf, WIZ_SPAM, 0, ch->level);

				write_to_descriptor(d->descriptor, "\n\r*** PUT A LID ON IT!!! ***\n\r", 0);
				d->repeat = 0;
				if (d->showstr_point) {
					if (d->showstr_head) {
						free_string(d->showstr_head);
						d->showstr_head = NULL;
					}
					d->showstr_point = NULL;
				}
				if (d->pString) {
					free_string(*d->pString);
					*d->pString = d->backup;
					d->pString = NULL;
				}
				strnzcpy(d->incomm, sizeof(d->incomm), "quit");
			}
		}
	}

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

	/*
	 * Shift the input buffer.
	 */
	while (d->inbuf[i] == '\n' || d->inbuf[i] == '\r')
		i++;
	for (j = 0; (d->inbuf[j] = d->inbuf[i+j]) != '\0'; j++)
		;
}

/*
 * Low level output function.
 */
void battle_prompt(CHAR_DATA *ch, CHAR_DATA *victim)
{
	int percent;
	char* msg;
 
        if (victim->max_hit > 0)
		percent = victim->hit * 100 / victim->max_hit;
        else
		percent = -1;
  
        if (percent >= 100)
		msg = "{Cis in perfect health{x.";
        else if (percent >= 90)
		msg = "{bhas a few scratches{x.";
        else if (percent >= 75)
		msg = "{Bhas some small but disgusting cuts{x.";
        else if (percent >= 50)
		msg = "{Gis covered with bleeding wounds{x.";
        else if (percent >= 30)
		msg = "{Yis gushing blood{x.";
        else if (percent >= 15)
		msg = "{Mis writhing in agony{x.";
        else if (percent >= 0)
		msg = "{Ris convulsing on the ground{x.";
        else
		msg = "{Ris nearly dead{x.";

	act_puts("$N $t", ch, msg, victim, TO_CHAR | ACT_TRANS, POS_DEAD);
}

/*
 * Some specials added by KIO 
 */
bool process_output(DESCRIPTOR_DATA *d, bool fPrompt)
{
	extern bool merc_down;
	bool ga = FALSE;

	/*
	 * Bust a prompt.
	 */
	if (!merc_down) {
		CHAR_DATA *ch = d->character;

		if (d->showstr_point) {
			write_to_buffer(d, "[Hit Return to continue]\n\r", 0);
			ga = TRUE;
		}
		else if (fPrompt && d->connected == CON_PLAYING) {
			if (d->pString) {
				write_to_buffer(d, "  > ", 0);
				ga = TRUE;
			}
			else if (ch) {
				CHAR_DATA *victim;

				/* battle prompt */
				if ((victim = ch->fighting) != NULL
				&&  can_see(ch,victim))
					battle_prompt(ch, victim);

				if (!IS_SET(ch->comm, COMM_COMPACT))
					write_to_buffer(d, "\n\r", 2);

				if (IS_SET(ch->comm, COMM_PROMPT))
					bust_a_prompt(d->character);
				ga = TRUE;
			}
		}

		if (ch && ga && !IS_SET(ch->comm, COMM_TELNET_GA))
			ga = FALSE;
	}

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

	/*
	 * Snoop-o-rama.
	 */
	if (d->snoop_by) {
		if (d->character)
			write_to_buffer(d->snoop_by, d->character->name, 0);
		write_to_buffer(d->snoop_by, "> ", 2);
		write_to_buffer(d->snoop_by, d->outbuf, d->outtop);
	}

	/*
	 * OS-dependent output.
	 */
	if (!write_to_descriptor(d->descriptor, d->outbuf, d->outtop)) {
		d->outtop = 0;
		return FALSE;
	}
	else {
		if (ga)
			write_to_descriptor(d->descriptor, go_ahead_str, 0);
		d->outtop = 0;
		return TRUE;
	}
}

void percent_hp(CHAR_DATA *ch, char buf[MAX_STRING_LENGTH])
{
	if (ch->hit >= 0)
		snprintf(buf, sizeof(buf), "%d%%",
			 ((100 * ch->hit) / UMAX(1,ch->max_hit)));
	else
		strnzcpy(buf, sizeof(buf), "BAD!");
}

/*
 * Bust a prompt (player settable prompt)
 * coded by Morgenes for Aldara Mud
 * bust
 */
void bust_a_prompt(CHAR_DATA *ch)
{
	char buf[MAX_STRING_LENGTH];
	char buf2[MAX_STRING_LENGTH];
	const char *str;
	const char *i;
	char *point;
	CHAR_DATA *victim;
	EXIT_DATA *pexit;
	bool found;
	const char *dir_name[] = {"N","E","S","W","U","D"};
	int door;
 
	if (IS_SET(ch->comm, COMM_AFK)) {
		char_printf(ch, "{c<AFK>{x %s", ch->prefix);
		return;
	}

	point = buf;
	str = ch->prompt;
	if (IS_NULLSTR(str))
		str = DEFAULT_PROMPT;

	while (*str != '\0') {
		if (*str != '%') {
			*point++ = *str++;
			continue;
		}

		switch(*++str) {
		default:
	        	i = "";
			break;

		case 't':
			snprintf(buf2, sizeof(buf2),
				 "%d%s", (time_info.hour % 12 == 0) ? 
				 12 : time_info.hour % 12, 
				 time_info.hour >= 12 ? "pm" : "am");
			i = buf2;
			break;

		case 'e':
			found = FALSE;
			buf2[0] = '\0';
			for (door = 0; door < 6; door++)
				if ((pexit = ch->in_room->exit[door])
				&&  pexit->to_room.r
				&&  can_see_room(ch, pexit->to_room.r)
				&&  check_blind_raw(ch)
				&&  (!IS_SET(pexit->exit_info, EX_CLOSED) ||
				     IS_IMMORTAL(ch))) {
					found = TRUE;
					strnzcat(buf2, sizeof(buf2),
						 dir_name[door]);
					if (IS_SET(pexit->exit_info, EX_CLOSED))
						strnzcat(buf2, sizeof(buf2),
							 "*");
				}
			if (buf2[0])
				strnzcat(buf2, sizeof(buf2), " ");
			i = buf2;
			break;

		case 'n':
			i = ch->name;
			break;

		case 'S':
			i = ch->sex == SEX_MALE ? "Male" :
			    ch->sex == SEX_FEMALE ? "Female" :
			    "None";
			break;

		case 'y':
			percent_hp(ch, buf2);
			i = buf2;
			break;

		case 'o':
			if ((victim = ch->fighting) != NULL) {
				if (can_see(ch, victim)) {
					percent_hp(victim, buf2);
					i = buf2;
				}
				else
					i = "???";
			}
			else
				i = "None";
			break;

		case 'h':
			snprintf(buf2, sizeof(buf2), "%d", ch->hit);
			i = buf2;
			break;

		case 'H':
			snprintf(buf2, sizeof(buf2), "%d", ch->max_hit);
			i = buf2;
			break;

		case 'm':
			snprintf(buf2, sizeof(buf2), "%d", ch->mana);
			i = buf2;
			break;

		case 'M':
			snprintf(buf2, sizeof(buf2), "%d", ch->max_mana);
			i = buf2;
			break;

		case 'v':
			snprintf(buf2, sizeof(buf2), "%d", ch->move);
			i = buf2;
			break;

		case 'V':
			snprintf(buf2, sizeof(buf2), "%d", ch->max_move);
			i = buf2;
			break;

		case 'x':
			snprintf(buf2, sizeof(buf2), "%d", ch->exp);
			i = buf2;
			break;

		case 'X':
			if (!IS_NPC(ch)) {
				snprintf(buf2, sizeof(buf2), "%d",
					 exp_to_level(ch));
				i = buf2;
			}
			else
				i = "";
			break;

		case 'g':
			snprintf(buf2, sizeof(buf2), "%d", ch->gold);
			i = buf2;
			break;

		case 's':
			snprintf(buf2, sizeof(buf2), "%d", ch->silver);
			i = buf2;
			break;

		case 'a':
			i = IS_GOOD(ch) ? "good" :
			    IS_EVIL(ch) ? "evil" :
			    "neutral";
			break;

		case 'r':
			if (ch->in_room)
				i = (check_blind_raw(ch) && !room_is_dark(ch)) ?
				     mlstr_cval(ch->in_room->name, ch) :
				     "darkness";
			else
				i = "";
			break;

		case 'R':
			if (IS_IMMORTAL(ch) && ch->in_room) {
				snprintf(buf2, sizeof(buf2), "%d",
					 ch->in_room->vnum);
				i = buf2;
			}
			else
				i = "";
			break;

		case 'z':
			if (IS_IMMORTAL(ch) && ch->in_room != NULL)
				i = ch->in_room->area->name;
			else
				i = "";
			break;

		case '%':
			i = "%%";
			break;

		case 'E':
			i = OLCED(ch) ? OLCED(ch)->name : str_empty;
			if (!IS_NULLSTR(i)) {
				snprintf(buf2, sizeof(buf2), "%s ", i);
				i = buf2;
			}
			break;

		case 'W':
			if (ch->invis_level) {
				snprintf(buf2, sizeof(buf2), "Wizi %d ",
					 ch->invis_level);
				i = buf2;
			}
			else
				i = "";
			break;

		case 'I':
			if (ch->incog_level) {
				snprintf(buf2, sizeof(buf2), "Incog %d ",
					 ch->incog_level);
				i = buf2;
			}
			else
				i = "";
			break;
		}
		++str;
		while((*point = *i) != '\0')
			++point, ++i;
	}

	*point = '\0';
	send_to_char(buf, ch);

	if (ch->prefix[0] != '\0')
		write_to_buffer(ch->desc, ch->prefix, 0);
}

/*
 * Append onto an output buffer.
 */
void write_to_buffer(DESCRIPTOR_DATA *d, const char *txt, uint length)
{
	uint size;
	int i;
	bool noiac = (d->connected == CON_PLAYING &&
		      d->character != NULL &&
		      IS_SET(d->character->comm, COMM_NOIAC));
	
	/*
	 * Find length in case caller didn't.
	 */
	if (length <= 0)
		length = strlen(txt);

	/*
	 * Adjust size in case of IACs (they will be doubled)
	 */
	size = length;
	if (!noiac)
		for (i = 0; i < length; i++)
			if (d->codepage->to[(unsigned char) txt[i]] == IAC)
				size++;

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

	/*
	 * Expand the buffer as needed.
	 */
	while (d->outtop + size >= d->outsize) {
		char *outbuf;

		if (d->outsize >= 32000) {
			bug("Buffer overflow. Closing.\n\r",0);
			close_descriptor(d);
			return;
 		}
		outbuf = malloc(2 * d->outsize);
		strncpy(outbuf, d->outbuf, d->outtop);
		free(d->outbuf);
		d->outbuf = outbuf;
		d->outsize *= 2;
	}

	/*
	 * Copy.
	 */
	while (length--) {
		unsigned char c;

		c = d->codepage->to[(unsigned char) *txt++];
		d->outbuf[d->outtop] = c;
		if (c == IAC)
			if (noiac)
				d->outbuf[d->outtop] = IAC_REPL;
			else 
				d->outbuf[++d->outtop] = IAC;
		d->outtop++;
	}
	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.
 */
bool write_to_descriptor(int desc, char *txt, uint length)
{
	uint iStart;
	uint nWrite;
	uint nBlock;

	if (!length)
		length = strlen(txt);

	for (iStart = 0; iStart < length; iStart += nWrite) {
		nBlock = UMIN(length - iStart, 4096);
#if !defined( WIN32 )
		if ((nWrite = write(desc, txt + iStart, nBlock)) < 0) {
#else
		if ((nWrite = send(desc, txt + iStart, nBlock, 0)) < 0) {
#endif
			log_printf("write_to_descriptor: %s", strerror(errno));
			return FALSE;
		}
	} 
	return TRUE;
}

int search_sockets(DESCRIPTOR_DATA *inp)
{
	DESCRIPTOR_DATA *d;

	if (IS_IMMORTAL(inp->character))
		return 0;

	for(d = descriptor_list; d; d = d->next) {
		if(!strcmp(inp->host, d->host)) {
			if (d->character && inp->character
			&&  !strcmp(inp->character->name, d->character->name)) 
				continue;
			return 1;
		}
	}
	return 0;
}
  
int align_restrict(CHAR_DATA *ch);
int ethos_check(CHAR_DATA *ch);

void advance(CHAR_DATA *victim, int level);

static void print_hometown(CHAR_DATA *ch)
{
	race_t *r;
	class_t *cl;
	int htn;

	if ((r = race_lookup(ORG_RACE(ch))) == NULL
	||  !r->pcdata
	||  (cl = class_lookup(ch->class)) == NULL) {
		char_puts("You should create your character anew.\n", ch);
		close_descriptor(ch->desc);
		return;
	}

	if ((htn = hometown_permanent(ch)) >= 0) {
		ch->hometown = htn;
		char_printf(ch, "\nYour hometown is %s, permanently.\n"
				"[Hit Return to continue]\n",
			    hometown_name(htn));

/* XXX */
		ch->endur = 100;
		ch->desc->connected = CON_GET_ETHOS;
		return;
	}

	char_puts("\n", ch);
	do_help(ch, "HOMETOWN");
	hometown_print_avail(ch);
	char_puts("? ", ch);
	ch->desc->connected = CON_PICK_HOMETOWN;
}

/*
 * Deal with sockets that haven't logged in yet.
 */
void nanny(DESCRIPTOR_DATA *d, const char *argument)
{
	char buf[MAX_STRING_LENGTH];
	DESCRIPTOR_DATA *d_old, *d_next;
	char buf1[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *ch;
	char *pwdnew;
	int iClass,race,i;
	int nextquest = 0;
	struct sockaddr_in sock;
	int size;
	race_t *r;

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

	ch = d->character;

	switch (d->connected) {
	default:
		bug("Nanny: bad d->connected %d.", d->connected);
		close_descriptor(d);
		return;

	case CON_GET_CODEPAGE: {
		int num;

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

		if (argument[1] != '\0'
		||  (num = argument[0] - '1') < 0
		||  num >= NCODEPAGES) {
			cp_print(d);
			break;
		}

		d->codepage = codepages+num;
		log_printf("'%s' codepage selected", d->codepage->name);
		d->connected = CON_GET_NAME;
		write_to_buffer(d, "By which name do you wish to be known? ", 0);
		break;
	}

	case CON_GET_NAME:
		if (argument[0] == '\0') {
			close_descriptor(d);
			return;
		}

		if (!pc_name_ok(argument)) {
			write_to_buffer(d, "Illegal name, try another.\n\r"
					   "Name: ", 0);
			return;
		}

		load_char_obj(d, argument);
		ch   = d->character;

		if (d->host == NULL) {
			size = sizeof(sock);
			if (getpeername(d->descriptor,
					(struct sockaddr *) &sock, &size) < 0)
				d->host = str_dup("(unknown)");
			else {
#if defined (WIN32)
				printf("%s@%s\n", ch->name, inet_ntoa(sock.sin_addr));
#else
				fprintf(rfout, "%s@%s\n",
					ch->name, inet_ntoa(sock.sin_addr));
#endif
				d->connected = CON_RESOLV;
/* wait until sock.sin_addr gets resolved */
				break;
			}
		}

		/* FALLTHRU */

	case CON_RESOLV:
		if (d->host == NULL)
			break;

	/*
	 * 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.
	 */
	if (check_ban(d->host,BAN_ALL)) {
		write_to_buffer(d, "Your site has been banned from this mud.\n\r", 0);
		close_descriptor(d);
		return;
	}

	if (!IS_IMMORTAL(ch)) {
		if (check_ban(d->host,BAN_PLAYER)) {
			write_to_buffer(d,"Your site has been banned for players.\n\r",0);
			close_descriptor(d);
			return;
	        }

#undef NO_PLAYING_TWICE
#ifdef NO_PLAYING_TWICE
		if(search_sockets(d)) {
			write_to_buffer(d, "Playing twice is restricted...\n\r", 0);
			close_descriptor(d);
			return;
		} 
#endif
	  if (iNumPlayers > MAX_OLDIES && !IS_SET(ch->plr_flags, PLR_NEW))  {
	     snprintf(buf, sizeof(buf),
	   "\n\rThere are currently %i players mudding out of a maximum of %i.\n\rPlease try again soon.\n\r",iNumPlayers - 1, MAX_OLDIES);
	     write_to_buffer(d, buf, 0);
	     close_descriptor(d);
	     return;
	  }
	  if (iNumPlayers > MAX_NEWBIES && IS_SET(ch->plr_flags, PLR_NEW))  {
	     snprintf(buf, sizeof(buf),
	   "\n\rThere are currently %i players mudding. New player creation is \n\rlimited to when there are less than %i players. Please try again soon.\n\r",
		     iNumPlayers - 1, MAX_NEWBIES);
	     write_to_buffer(d, buf, 0);
	     close_descriptor(d);
	     return;
	  }
	   }
	     
	if (IS_SET(ch->plr_flags, PLR_DENY))
	{
	    log_printf("Denying access to %s@%s.", argument, d->host);
	    write_to_buffer(d, "You are denied access.\n\r", 0);
	    close_descriptor(d);
	    return;
	}

		if (check_reconnect(d, argument, FALSE))
			REMOVE_BIT(ch->plr_flags, PLR_NEW);
		else if (wizlock && !IS_HERO(ch)) {
			write_to_buffer(d, "The game is wizlocked.\n\r", 0);
			close_descriptor(d);
			return;
		}

		if (!IS_SET(ch->plr_flags, PLR_NEW)) {
			/* Old player */
			write_to_descriptor(d->descriptor, echo_off_str, 0);
 			write_to_buffer(d, "Password: ", 0);
			d->connected = CON_GET_OLD_PASSWORD;
			return;
		}
		else {
			/* New player */
 			if (newlock) {
				write_to_buffer(d, "The game is newlocked.\n\r", 0);
				close_descriptor(d);
				return;
			}

			if (check_ban(d->host, BAN_NEWBIES)) {
				write_to_buffer(d, "New players are not allowed from your site.\n\r", 0);
				close_descriptor(d);
				return;
			}
 	    
 			do_help(ch, "NAME");
			d->connected = CON_CONFIRM_NEW_NAME;
			return;
		}
		break;

/* RT code for breaking link */
	case CON_BREAK_CONNECT:
		switch(*argument) {
		case 'y' : case 'Y':
			for (d_old = descriptor_list; d_old; d_old = d_next) {
				CHAR_DATA *rch;

				d_next = d_old->next;
				if (d_old == d || d_old->character == NULL)
					continue;

				rch = d_old->original ? d_old->original :
							d_old->character;
				if (str_cmp(ch->name, rch->name))
					continue;

				if (d_old->original)
					do_return(d_old->character, str_empty);
				close_descriptor(d_old);
			}

			if (check_reconnect(d, ch->name, TRUE))
				return;
			write_to_buffer(d,"Reconnect attempt failed.\n\r",0);

			/* FALLTHRU */

		case 'n' : case 'N':
	 		write_to_buffer(d,"Name: ",0);
			if (d->character != NULL) {
				free_char(d->character);
				d->character = NULL;
			}
			d->connected = CON_GET_NAME;
			break;

		default:
			write_to_buffer(d, "Please type Y or N? ", 0);
			break;
		}
		break;

	case CON_CONFIRM_NEW_NAME:
		switch (*argument) {
		case 'y': case 'Y':
			snprintf(buf, sizeof(buf),
				 "New character.\n\r"
				 "Give me a password for %s: ", ch->name);
			write_to_buffer(d, buf, 0);
			write_to_descriptor(d->descriptor, echo_off_str, 0);
			d->connected = CON_GET_NEW_PASSWORD;
			break;

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

		default:
			write_to_buffer(d, "Please type Yes or No? ", 0);
			break;
		}
		break;

	case CON_GET_NEW_PASSWORD:
#if defined(unix)
		write_to_buffer( d, "\n\r", 2 );
#endif

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

		pwdnew = crypt(argument, ch->name);
		free_string(ch->pcdata->pwd);
		ch->pcdata->pwd	= str_dup(pwdnew);
		write_to_buffer(d, "Please retype password: ", 0);
		d->connected = CON_CONFIRM_NEW_PASSWORD;
		break;

	case CON_CONFIRM_NEW_PASSWORD:
#if defined(unix)
	write_to_buffer( d, "\n\r", 2 );
#endif

		if (strcmp(crypt(argument, ch->pcdata->pwd), ch->pcdata->pwd)) {
			write_to_buffer(d, "Passwords don't match.\n\r"
					   "Retype password: ", 0);
			d->connected = CON_GET_NEW_PASSWORD;
			return;
		}

		write_to_descriptor(d->descriptor, (char *) echo_on_str, 0);
		write_to_buffer(d, "The Shades of Gray is home for the following races:\n\r", 0);
		do_help(ch, "RACETABLE");
		d->connected = CON_GET_NEW_RACE;
		break;

	case CON_GET_NEW_RACE:
		one_argument(argument, arg, sizeof(arg));

		if (!str_cmp(arg, "help")) {
			argument = one_argument(argument, arg, sizeof(arg));
			if (argument[0] == '\0') {
				write_to_buffer(d, "The Shades of Gray is the home for the following races:\n\r", 0);
	  			do_help(ch,"RACETABLE");
			}
			else {
				do_help(ch, argument);
				write_to_buffer(d, "What is your race? ('help <race>' for more information) ",0);
			}	
			break;
		}

		race = rn_lookup(argument);
		r = RACE(race);

		if (race == 0 || !r->pcdata) {
			write_to_buffer(d, "That is not a valid race.\n\r", 0);
			write_to_buffer(d, "The following races are available:\n\r  ", 0);
			for (race = 1; race < races.nused; race++) {
				r = RACE(race);
		        	if (!r->pcdata)
	        	        	break;
				if (race == 8 || race == 14)
					write_to_buffer(d,"\n\r  ",0);
				write_to_buffer(d,"(",0);
				write_to_buffer(d, r->name, 0);
				write_to_buffer(d,") ",0);
	        	}
			write_to_buffer(d, "\n\r", 0);
			write_to_buffer(d, "What is your race? ('help <race>' for more information) ", 0);
			break;
		}

		SET_ORG_RACE(ch, race);
		ch->race = race;
		for (i=0; i < MAX_STATS;i++)
			ch->mod_stat[i] = 0;

		/* Add race stat modifiers 
		for (i = 0; i < MAX_STATS; i++)
			ch->mod_stat[i] += r->pcdata->stats[i];	*/

		/* Add race modifiers */
		ch->max_hit += r->pcdata->hp_bonus;
		ch->hit = ch->max_hit;
		ch->max_mana += r->pcdata->mana_bonus;
		ch->mana = ch->max_mana;
		ch->practice = r->pcdata->prac_bonus;

		ch->affected_by = ch->affected_by| r->aff;
		ch->imm_flags	= ch->imm_flags| r->imm;
		ch->res_flags	= ch->res_flags| r->res;
		ch->vuln_flags	= ch->vuln_flags| r->vuln;
		ch->form	= r->form;
		ch->parts	= r->parts;

		/* add cost */
		ch->pcdata->points = r->pcdata->points;
		ch->size = r->pcdata->size;

		write_to_buffer(d, "What is your sex (M/F)? ", 0);
		d->connected = CON_GET_NEW_SEX;
		break;

	case CON_GET_NEW_SEX:
	switch (argument[0])
	{
	case 'm': case 'M': ch->sex = SEX_MALE;    
			    ch->pcdata->true_sex = SEX_MALE;
			    break;
	case 'f': case 'F': ch->sex = SEX_FEMALE; 
			    ch->pcdata->true_sex = SEX_FEMALE;
			    break;
	default:
	    write_to_buffer(d, "That's not a sex.\n\rWhat IS your sex? ", 0);
	    return;
	}
	
	do_help(ch,"class help");

	strnzcpy(buf, sizeof(buf), "Select a class:\n\r[ ");
	snprintf(buf1, sizeof(buf), "  (Continuing:) ");
	for (iClass = 0; iClass < classes.nused; iClass++)
	{
	  if (class_ok(ch,iClass))
	    {
	     if (iClass < 7)
	      {
	      	strnzcat(buf, sizeof(buf), CLASS(iClass)->name);
	      	strnzcat(buf, sizeof(buf), " ");
	      }
	     else
	      {
	      	strnzcat(buf1, sizeof(buf), CLASS(iClass)->name);
	      	strnzcat(buf1, sizeof(buf), " ");
	      }
	    }
	}
	strnzcat(buf, sizeof(buf), "\n\r");
	strnzcat(buf1, sizeof(buf), "]:\n\r");
	write_to_buffer(d, buf, 0);
	write_to_buffer(d, buf1, 0);
	        write_to_buffer(d,
		"What is your class ('help <class>' for more information)? ",0);
	    d->connected = CON_GET_NEW_CLASS;
	    break;

	case CON_GET_NEW_CLASS:
	iClass = cn_lookup(argument);
	argument = one_argument(argument, arg, sizeof(arg));

	if (!str_cmp(arg,"help"))
	  {
	    if (argument[0] == '\0')
		do_help(ch,"class help");
	    else
		do_help(ch,argument);
	        write_to_buffer(d,
		"What is your class ('help <class>' for more information)? ",0);
	    return;
	  }

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

	if (!class_ok(ch,iClass))
	  {
	    write_to_buffer(d, 
	    "That class is not available for your race or sex.\n\rChoose again: ",0);
	    return;
	  }

		ch->class = iClass;
		ch->pcdata->points += CLASS(iClass)->points;
		act("You are now $t.", ch, CLASS(iClass)->name, NULL, TO_CHAR);

		for (i = 0; i < MAX_STATS; i++)
			ch->perm_stat[i] = number_range(10, get_max_train(ch, i));

		snprintf(buf, sizeof(buf),
			 "Str:%s  Int:%s  Wis:%s  Dex:%s  Con:%s  Cha:%s\n\rAccept (Y/N)? ",
			 get_stat_alias(ch, STAT_STR),
			 get_stat_alias(ch, STAT_INT),
			 get_stat_alias(ch, STAT_WIS),
			 get_stat_alias(ch, STAT_DEX),
			 get_stat_alias(ch, STAT_CON),
			 get_stat_alias(ch, STAT_CHA));


		do_help(ch, "stats");
		write_to_buffer(d, "\n\rNow rolling for your stats (10-20+).\n\r", 0);
		write_to_buffer(d, "You don't get many trains, so choose well.\n\r", 0);
		write_to_buffer(d, buf, 0);
		d->connected = CON_ACCEPT_STATS;
		break;

	case CON_ACCEPT_STATS:
	switch(argument[0])
	  {
	  case 'H': case 'h': case '?':
	    do_help(ch,"stats");
	    break;
	  case 'y': case 'Y':	
	    for (i=0; i < MAX_STATS;i++)
	      ch->mod_stat[i] = 0;
	    write_to_buffer(d, "\n\r", 2);
	    if (!align_restrict(ch))
	    {
	    write_to_buffer(d, "You may be good, neutral, or evil.\n\r",0);
	    write_to_buffer(d, "Which alignment (G/N/E)? ",0);
	    d->connected = CON_GET_ALIGNMENT;
	    }
	    else {
		write_to_buffer(d, "[Hit Return to Continue]\n\r",0);
		print_hometown(ch);
	    }
	    break;
	    
		case 'n': case 'N':
			for (i = 0; i < MAX_STATS; i++)
				ch->perm_stat[i] = number_range(10, get_max_train(ch, i));

			snprintf(buf, sizeof(buf),
				 "Str:%s  Int:%s  Wis:%s  Dex:%s  Con:%s  Cha:%s\n\rAccept (Y/N)? ",
				 get_stat_alias(ch, STAT_STR),
				 get_stat_alias(ch, STAT_INT),
				 get_stat_alias(ch, STAT_WIS),
				 get_stat_alias(ch, STAT_DEX),
				 get_stat_alias(ch, STAT_CON),
				 get_stat_alias(ch, STAT_CHA));

			write_to_buffer(d, buf,0);
			d->connected = CON_ACCEPT_STATS;
			break;

		default:
			write_to_buffer(d,"Please answer (Y/N)? ",0);
			break;
		}
		break;
	    
	case CON_GET_ALIGNMENT:
	switch(argument[0])
	  {
	  case 'g' : case 'G' : 
		ch->alignment = 1000; 
	        write_to_buffer(d, "Now your character is good.\n\r",0);
		break;
	  case 'n' : case 'N' : 
		ch->alignment = 0;	
	        write_to_buffer(d, "Now your character is neutral.\n\r",0);
		break;
	  case 'e' : case 'E' : 
		ch->alignment = -1000; 
	        write_to_buffer(d, "Now your character is evil.\n\r",0);
		break;
	  default:
	    write_to_buffer(d,"That's not a valid alignment.\n\r",0);
	    write_to_buffer(d,"Which alignment (G/N/E)? ",0);
	    return;
	  }
		write_to_buffer(d, "\n\r[Hit Return to Continue]\n\r", 0);
		print_hometown(ch);
		break;
	
	case CON_PICK_HOMETOWN: {
		int htn;

		if (argument[0] == '\0'
		||  (htn = htn_lookup(argument)) < 0
		||  hometown_restrict(HOMETOWN(htn), ch)) {
			char_puts("That's not a valid hometown.\n", ch);
			print_hometown(ch);
			return;
		}

		ch->hometown = htn; 
		char_printf(ch, "\nNow your hometown is %s.\n"
				"[Hit Return to continue]\n",
			    hometown_name(htn));
		ch->endur = 100;
		d->connected = CON_GET_ETHOS;
		break;
	}
	
	  case CON_GET_ETHOS:
	if (!ch->endur)
	 {
	  switch(argument[0]) 
	      {
	   case 'H': case 'h': case '?': 
		do_help(ch, "alignment"); return; break;
	   case 'L': case 'l': 
	 	snprintf(buf, sizeof(buf), "\n\rNow you are lawful-%s.\n\r",
		   IS_GOOD(ch) ? "good" : IS_EVIL(ch) ? "evil" : "neutral");
	        write_to_buffer(d, buf, 0);
		ch->ethos = ETHOS_LAWFUL; 
		break;
	   case 'N': case 'n': 
	 	snprintf(buf, sizeof(buf), "\n\rNow you are neutral-%s.\n\r",
		   IS_GOOD(ch) ? "good" : IS_EVIL(ch) ? "evil" : "neutral");
	        write_to_buffer(d, buf, 0);
		ch->ethos = ETHOS_NEUTRAL; 
		break;
	   case 'C': case 'c': 
	 	snprintf(buf, sizeof(buf), "\n\rNow you are chaotic-%s.\n\r",
		   IS_GOOD(ch) ? "good" : IS_EVIL(ch) ? "evil" : "neutral");
	        write_to_buffer(d, buf, 0);
		ch->ethos = ETHOS_CHAOTIC; 
		break;
	   default:
	    write_to_buffer(d, "\n\rThat is not a valid ethos.\n\r", 0);
	    write_to_buffer(d, "What ethos do you want, (L/N/C) <type help for more info>? ",0);
	    return;
	   }
	} else {
	  ch->endur = 0;
	  if (!ethos_check(ch)) {
		write_to_buffer(d, "What ethos do you want, (L/N/C) "
				   "<type help for more info> ?", 0);
		d->connected = CON_GET_ETHOS;
		return;
	   } else
		ch->ethos = 1;
	 }
	     write_to_buffer(d, "\n\r[Hit Return to Continue]\n\r",0);
	     d->connected = CON_CREATE_DONE;
	     break;

	case CON_CREATE_DONE:
		log_printf("%s@%s new player.", ch->name, d->host);
		write_to_buffer(d, "\n\r", 2);
		do_help(ch, "motd");
		char_puts("[Press Enter to continue]", ch);
		d->connected = CON_READ_MOTD;
		break;

	case CON_GET_OLD_PASSWORD:
		write_to_buffer(d, "\n\r", 2);

		if (strcmp(crypt(argument, ch->pcdata->pwd), ch->pcdata->pwd)) {
			write_to_buffer(d, "Wrong password.\n\r", 0);
			log_printf("Wrong password by %s@%s",
				   ch->name, d->host);
			if (ch->endur == 2)
				close_descriptor(d);
			else {
				write_to_descriptor(d->descriptor,
						    (char *) echo_off_str, 0);
				write_to_buffer(d, "Password: ", 0);
				d->connected = CON_GET_OLD_PASSWORD;
				ch->endur++;
			}
			return;
		}
 
		if (ch->pcdata->pwd[0] == '\0') {
			write_to_buffer(d, "Warning! Null password!\n\r"
					   "Type 'password null <new password>'"
					   " to fix.\n\r", 0);
		}

		write_to_descriptor(d->descriptor, (char *) echo_on_str, 0);

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

		log_printf("%s@%s has connected.", ch->name, d->host);
		d->connected = CON_READ_IMOTD;

		/* FALL THRU */

	case CON_READ_IMOTD:
		if (IS_HERO(ch))
			do_help(ch, "imotd");
		write_to_buffer(d,"\n\r",2);
		do_help(ch, "motd");
		d->connected = CON_READ_MOTD;

		/* FALL THRU */

	case CON_READ_MOTD:
		update_skills(ch);
		write_to_buffer(d, 
		"\n\rWelcome to Shades of Gray Multi User Dungeon. Enjoy!!...\n\r",
		    0);
		ch->next	= char_list;
		char_list	= ch;
		if (!char_list_lastpc)
			char_list_lastpc = ch;
		d->connected	= CON_PLAYING;
		{
			int count;
			FILE *max_on_file;
			int tmp = 0;
			count = 0;
			for (d = descriptor_list; d != NULL; d = d->next)
				if (d->connected == CON_PLAYING)
			       		count++;
			max_on = UMAX(count, max_on);
			if ((max_on_file = dfopen(TMP_PATH, MAXON_FILE, "r"))) {
				fscanf(max_on_file, "%d", &tmp);
				fclose(max_on_file);
			}
			if (tmp < max_on
			&&  (max_on_file = dfopen(TMP_PATH, MAXON_FILE, "w"))) {
				fprintf(max_on_file, "%d", max_on);
				log("Global max_on changed.");
				fclose(max_on_file);
			}
		}

		reset_char(ch);

		/* quest code */
		nextquest = -abs(ch->pcdata->questtime);
		quest_cancel(ch);
		ch->pcdata->questtime = nextquest;
		/* !quest code */

		wiznet("{W$N{x has left real life behind.",
			ch, NULL, WIZ_LOGINS, 0, ch->level);

		for (i = 0; i < MAX_STATS; i++) {
			int max_stat = get_max_train(ch, i);

			if (ch->perm_stat[i] > max_stat) {
				ch->train += ch->perm_stat[i] - max_stat;
				ch->perm_stat[i] = max_stat;
			}
		}

		if (ch->gold > 6000 && !IS_IMMORTAL(ch)) {
			char_printf(ch, "You are taxed %d gold to pay for the Mayor's bar.\n\r", (ch->gold - 6000) / 2);
			ch->gold -= (ch->gold - 6000) / 2;
		}
	
		if (!IS_IMMORTAL(ch)) {
			for (i = 2; exp_for_level(ch, i) < ch->exp; i++)
				;

			if (i < ch->level) {
				int con;
				int wis;
				int inte;
				int dex;

				con = ch->perm_stat[STAT_CON];
				wis = ch->perm_stat[STAT_WIS];
				inte = ch->perm_stat[STAT_INT];
				dex = ch->perm_stat[STAT_DEX];
				ch->perm_stat[STAT_CON] = get_max_train(ch, STAT_CON);
				ch->perm_stat[STAT_WIS] = get_max_train(ch, STAT_WIS);
				ch->perm_stat[STAT_INT] = get_max_train(ch, STAT_INT);
				ch->perm_stat[STAT_DEX] = get_max_train(ch, STAT_DEX);
				do_remove(ch, "all");
				advance(ch, i-1);
		 		ch->perm_stat[STAT_CON] = con;
		 		ch->perm_stat[STAT_WIS] = wis;
		 		ch->perm_stat[STAT_INT] = inte;
		 		ch->perm_stat[STAT_DEX] = dex;
			}
		}

		if (ch->level == 0) {
			OBJ_DATA *wield;
			OBJ_INDEX_DATA *map;

			ch->level	= 1;
			ch->exp		= base_exp(ch);
			ch->hit		= ch->max_hit;
			ch->mana	= ch->max_mana;
			ch->move	= ch->max_move;
			ch->train	= 3;
			ch->practice	+= 5;
			ch->pcdata->death = 0;

			set_title(ch, title_lookup(ch));

			do_outfit(ch, str_empty);

			obj_to_char(create_obj(get_obj_index(OBJ_VNUM_MAP), 0), ch);
			obj_to_char(create_obj(get_obj_index(OBJ_VNUM_NMAP1), 0), ch);
			obj_to_char(create_obj(get_obj_index(OBJ_VNUM_NMAP2), 0), ch);

			if ((map = get_map(ch)) != NULL)
				obj_to_char(create_obj(map, 0), ch);

			if ((wield = get_eq_char(ch, WEAR_WIELD)))
				set_skill_raw(ch, get_weapon_sn(wield),
					      40, FALSE);

			char_puts("\n", ch);
			do_help(ch, "NEWBIE INFO");
			char_puts("\n", ch);
			char_to_room(ch, get_room_index(ROOM_VNUM_SCHOOL));
		}
		else {
			CHAR_DATA *pet;
			ROOM_INDEX_DATA *to_room;

			if (ch->in_room
			&&  (room_is_private(ch->in_room) ||
			     (ch->in_room->area->clan &&
			      ch->in_room->area->clan != ch->clan)))
				ch->in_room = NULL;

			if (ch->in_room) 
				to_room = ch->in_room;
			else if (IS_IMMORTAL(ch))
				to_room = get_room_index(ROOM_VNUM_CHAT);
			else
				to_room = get_room_index(ROOM_VNUM_TEMPLE);

			pet = ch->pet;
			act("$N has entered the game.",
			    to_room->people, NULL, ch, TO_ALL);
			char_to_room(ch, to_room);

			if (pet) {
				act("$N has entered the game.",
				    to_room->people, NULL, pet, TO_ROOM);
				char_to_room(pet, to_room);
			}
		}

		if (!JUST_KILLED(ch)) {
			do_look(ch, "auto");
			do_unread(ch, "login");  
		}

		break;
	}
}

/*
 * look for link-dead player to reconnect.
 *
 * when fConn == FALSE then
 * simple copy password for newly [re]connected character
 * authentication
 *
 * otherwise reconnect attempt is made
 */
bool check_reconnect(DESCRIPTOR_DATA *d, const char *name, bool fConn)
{
	CHAR_DATA *ch;
	DESCRIPTOR_DATA *d2;

	if (!fConn) {
		for (d2 = descriptor_list; d2; d2 = d2->next) {
			if (d2 == d)
				continue;
			ch = d2->original ? d2->original : d2->character;
			if (ch && !str_cmp(d->character->name, ch->name)) {
				free_string(d->character->pcdata->pwd);
				d->character->pcdata->pwd = str_qdup(ch->pcdata->pwd);
				return TRUE;
			}
		}
	}

	for (ch = char_list; ch && !IS_NPC(ch); ch = ch->next) {
		if ((!fConn || ch->desc == NULL)
		&&  !str_cmp(d->character->name, ch->name)) {
			if (!fConn) {
				free_string(d->character->pcdata->pwd);
				d->character->pcdata->pwd = str_qdup(ch->pcdata->pwd);
			}
			else {
				free_char(d->character);
				d->character	= ch;
				ch->desc	= d;
				ch->timer	= 0;
				char_puts("Reconnecting. Type replay to see missed tells.\n", ch);
				act("$n has reconnected.",
				    ch, NULL, NULL, TO_ROOM);

				log_printf("%s@%s reconnected.",
					   ch->name, d->host);
				wiznet("$N groks the fullness of $S link.",
				       ch, NULL, WIZ_LINKS, 0, 0);
				d->connected = CON_PLAYING;
			}
			return TRUE;
		}
	}

	return FALSE;
}

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

	for (dold = descriptor_list; dold; dold = dold->next) {
		if (dold != d
		&&  dold->character != NULL
		&&  dold->connected != CON_GET_CODEPAGE
		&&  dold->connected != CON_GET_NAME
		&&  dold->connected != CON_RESOLV
		&&  dold->connected != CON_GET_OLD_PASSWORD
		&&  !str_cmp(name, dold->original ?  dold->original->name :
						     dold->character->name)) {
			write_to_buffer(d, "That character is already playing.\n\r",0);
			write_to_buffer(d, "Do you wish to connect anyway (Y/N)?",0);
			d->connected = CON_BREAK_CONNECT;
			return TRUE;
		}
	}

	return FALSE;
}

void stop_idling(CHAR_DATA *ch)
{
	if (ch == NULL
	||  ch->desc == NULL
	||  ch->desc->connected != CON_PLAYING
	||  !ch->was_in_room
	||  ch->in_room->vnum != ROOM_VNUM_LIMBO)
		return;

	ch->timer = 0;
	char_from_room(ch);
	act("$N has returned from the void.",
	    ch->was_in_room->people, NULL, ch, TO_ALL);
	char_to_room(ch, ch->was_in_room);
	ch->was_in_room	= NULL;
}

void char_puts(const char *txt, CHAR_DATA *ch)
{
	send_to_char(GETMSG(txt, ch->lang), ch);
}

void char_printf(CHAR_DATA *ch, const char *format, ...)
{
	char buf[MAX_STRING_LENGTH];
	va_list ap;

	va_start(ap, format);
	vsnprintf(buf, sizeof(buf), GETMSG(format, ch->lang), ap);
	va_end(ap);
	send_to_char(buf, ch);
}

/*
 * Write to one char.
 */
void send_to_char(const char *txt, CHAR_DATA *ch)
{
	char buf[MAX_STRING_LENGTH*4];

	if (txt == NULL || ch->desc == NULL)
		return;

	parse_colors(txt, buf, sizeof(buf), OUTPUT_FORMAT(ch));
	write_to_buffer(ch->desc, buf, 0);
}

/*
 * Send a page to one char.
 */
void page_to_char(const char *txt, CHAR_DATA *ch)
{
	if (txt == NULL || ch->desc == NULL)
		return; /* ben yazdim ibrahim */

	if (ch->lines == 0) {
		send_to_char(txt, ch);
		return;
	}
	
	ch->desc->showstr_head = str_dup(txt);
	ch->desc->showstr_point = ch->desc->showstr_head;
	show_string(ch->desc, str_empty);
}

/* string pager */
void show_string(struct descriptor_data *d, char *input)
{
	char buffer[4*MAX_STRING_LENGTH];
	char buf[MAX_INPUT_LENGTH];
	char *scan;
	int lines = 0;
	int show_lines;

	one_argument(input, buf, sizeof(buf));
	if (buf[0] != '\0') {
		if (d->showstr_head) {
			free_string(d->showstr_head);
			d->showstr_head = NULL;
		}
		d->showstr_point  = NULL;
		return;
	}

	if (d->character)
		show_lines = d->character->lines;
	else
		show_lines = 0;

	for (scan = buffer; scan - buffer < sizeof(buffer)-2;
						scan++, d->showstr_point++) {
		/*
		 * simple copy if not eos and not eol
		 */
		if ((*scan = *d->showstr_point) && (*scan) != '\n') 
			continue;

		/*
		 * bamf out buffer if we reached eos or show_lines limit
		 */
		if (!*scan || (show_lines > 0 && ++lines >= show_lines)) {
			const char *chk;

			if (*scan)
				*++scan = '\0';
			send_to_char(buffer, d->character);

			for (chk = d->showstr_point; isspace(*chk); chk++)
				;
			if (!*chk) {
				if (d->showstr_head) {
					free_string(d->showstr_head);
					d->showstr_head = NULL;
				}
				d->showstr_point  = NULL;
			}
			return;
		}
	}
}

void log_area_popularity(void)
{
	FILE *fp;
	AREA_DATA *area;
	extern AREA_DATA *area_first;

	if ((fp = dfopen(TMP_PATH, AREASTAT_FILE, "w")) == NULL)
		return;
	fprintf(fp,"\nBooted %sArea popularity statistics (in char * ticks)\n",
	        str_boot_time);

	for (area = area_first; area != NULL; area = area->next)
		if (area->count >= 5000000)
			fprintf(fp,"%-60s overflow\n",area->name);
		else
			fprintf(fp,"%-60s %u\n",area->name,area->count);

	fclose(fp);
}

bool class_ok(CHAR_DATA *ch, int class)
{
	race_t *r;
	class_t *cl;

	if ((cl = class_lookup(class)) == NULL
	||  (r = race_lookup(ORG_RACE(ch))) == NULL
	||  !r->pcdata)
		return FALSE;

	if (rclass_lookup(r, cl->name) == NULL
	||  (cl->restrict_sex >= 0 && cl->restrict_sex != ch->sex))
		return FALSE;

	return TRUE;
}

int align_restrict(CHAR_DATA *ch)
{
	DESCRIPTOR_DATA *d = ch->desc;
	race_t *r;

	if ((r = race_lookup(ORG_RACE(ch))) == NULL
	||  !r->pcdata)
		return RA_NONE;

	if (r->pcdata->restrict_align == RA_GOOD
	||  CLASS(ch->class)->restrict_align == RA_GOOD) {
		write_to_buffer(d, "Your character has good tendencies.\n\r",0);
		ch->alignment = 1000;
		return RA_GOOD;
	}

	if (r->pcdata->restrict_align == RA_NEUTRAL
	||  CLASS(ch->class)->restrict_align == RA_NEUTRAL) {
		write_to_buffer(d, "Your character has neutral tendencies.\n\r",0);
		ch->alignment = 0;
		return RA_NEUTRAL;
	}

	if (r->pcdata->restrict_align == RA_EVIL
	||  CLASS(ch->class)->restrict_align == RA_EVIL) {
		write_to_buffer(d, "Your character has evil tendencies.\n\r",0);
		ch->alignment = -1000;
		return RA_EVIL;
	}		

	return RA_NONE;
}

int ethos_check(CHAR_DATA *ch)
{
	DESCRIPTOR_DATA *d = ch->desc;
	class_t *cl;

	if ((cl = class_lookup(ch->class))) {
		/*
		 * temporary workaround for paladins
		 */
		if (IS_SET(cl->restrict_ethos, ETHOS_LAWFUL)) {
			write_to_buffer(d, "You are Lawful.\n\r", 0);
			return 1;
		}
	}
	return 0;
}

#if !defined (WIN32)
void resolv_done()
{
	char *host;
	char buf[MAX_STRING_LENGTH];
	char *p;
	DESCRIPTOR_DATA *d;

	while (fgets(buf, sizeof(buf), rfin)) {
		if ((p = strchr(buf, '\n')) == NULL) {
			log_printf("rfin: line too long, skipping to '\\n'");
			while(fgetc(rfin) != '\n')
				;
			continue;
		}
		*p = '\0';

		if ((host = strchr(buf, '@')) == NULL)
			continue;
		*host++ = '\0';

		log_printf("resolv_done: %s@%s", buf, host);

		for (d = descriptor_list; d; d = d->next) {
			if (d->host
			||  d->character == NULL
			||  str_cmp(buf, d->character->name))
				continue;
			d->host = str_dup(host);
		}
	}
}
#endif

/* Windows 95 and Windows NT support functions (copied from Envy) */
#if defined (WIN32)
void gettimeofday (struct timeval *tp, void *tzp)
{
    tp->tv_sec  = time( NULL );
    tp->tv_usec = 0;
}
#endif
