1stMUD4.0/bin/
1stMUD4.0/doc/MPDocs/
1stMUD4.0/player/
1stMUD4.0/win32/
1stMUD4.0/win32/rom/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements 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.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*       1stMUD ROM Derivative (c) 2001-2003 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/

/*
 * 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 ---> d_write -> Write
 *
 * The OS-dependent functions are Read_from_descriptor and d_write.
 * -- Furey  26 Jan 1993
 */
#include "merc.h"
#include "interp.h"
#include "recycle.h"
#include "tables.h"
#include "webserver.h"
#include "olc.h"
#include "telnet.h"
#include "tablesave.h"
#include "signals.h"

/*
 * Global variables.
 */
DESCRIPTOR_DATA *d_next;		/* Next descriptor in loop  */
bool god;						/* All new chars are gods!  */

#if	defined( WIN32 )
#define WOULD_HAVE_BLOCKED ( WSAGetLastError() == WSAEWOULDBLOCK )
#else
#define WOULD_HAVE_BLOCKED ( errno == EWOULDBLOCK )
#endif

PROTOTYPE(void game_loop, (int));
PROTOTYPE(int init_socket, (int));
PROTOTYPE(void init_descriptor, (int));

/*
 * Other local functions (OS-independent).
 */
PROTOTYPE(int main, (int, char **));
PROTOTYPE(void nanny, (DESCRIPTOR_DATA *, const char *));
PROTOTYPE(bool process_output, (DESCRIPTOR_DATA *, bool));
PROTOTYPE(void read_from_buffer, (DESCRIPTOR_DATA *));
PROTOTYPE(void stop_idling, (CHAR_DATA *));
PROTOTYPE(void bust_a_prompt, (CHAR_DATA *));
PROTOTYPE(void set_game_levels, (int, int));
PROTOTYPE(void save_helps, (void));
PROTOTYPE(int count_mxp_tags, (DESCRIPTOR_DATA *, const char *, int));
PROTOTYPE(void convert_mxp_tags, (DESCRIPTOR_DATA *, char *, const char *,
								  int));
PROTOTYPE(bool check_directories, (void));

#if defined(WIN32)
void gettimeofday(struct timeval *t, void *tz)
{
	struct timeb timebuffer;
	ftime(&timebuffer);
	t->tv_sec = timebuffer.time;
	t->tv_usec = timebuffer.millitm * 1000;
}
#endif

const char *whoami(void)
{
#if defined(WIN32)
	static char username[UNLEN + 1];
	unsigned long szusername = UNLEN + 1;

	if (GetUserName(username, &szusername))
	{
		return (username);
	}

#else
	struct passwd *pwd;
	uid_t uid;

	uid = getuid();

	if ((pwd = getpwuid(uid)))
	{
		return (pwd->pw_name);
	}
#endif
	return "unknown";
}

bool verbose_log = FALSE;

void cycle_log(void)
{
	char buf[1024];
	FILE *fp;

	sprintf(buf, "%s%s", LOG_DIR, str_time(-1, -1, "%m-%d.log"));
	fp = file_open(buf, "a");
	if (!fp)
		log_error(buf);
	else
	{
		if (!verbose_log)
		{
			dup2(fileno(fp), STDERR_FILENO);
		}
	}
	file_close(fp);
}

int main(int argc, char **argv)
{
	struct timeval now_time;
#if !defined(NO_COPYOVER)
	bool fCopyOver = FALSE;
#endif
	int pos;
	char **args = argv;

	/*
	 * Init time.
	 */
	run_level = RUNLEVEL_INIT;
	tzset();
	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")) == NULL)
	{
		log_error(NULL_FILE);
		exit(1);
	}

#if defined(WIN32)
	{
		/* Initialise Windows sockets library */

		WORD wVersionRequested = MAKEWORD(2, 0);
		WSADATA wsadata;

		/* Need to include library: ws2_32.lib for Windows Sockets */
		if (WSAStartup(wVersionRequested, &wsadata))
		{
			log_string("Couldn't initialize winsock.");
			exit(1);
		}
	}
#endif

	if (gethostname(HOSTNAME, 1024) == -1 || IS_NULLSTR(HOSTNAME))
		strcpy(HOSTNAME, "localhost");

	if (!getcwd(CWDIR, 1024))
		strcpy(CWDIR, AREA_DIR);

	strcpy(UNAME, whoami());

	strcpy(EXE_FILE, argv[0]);

	/*
	 * Get the port number.
	 */
	for (pos = 1; pos < argc; pos++)
	{
		bool option = FALSE;

		if (IS_NULLSTR(args[pos]))
			break;

		if (*args[pos] == '-')
		{
			args[pos]++;
			if (*args[pos] == '-')
				args[pos]++;

			if (*args[pos] != '-')
				option = TRUE;
		}

		if (option)
		{
			if (!str_cmp(args[pos], "v"))
			{
				verbose_log = TRUE;
				log_string("Logging to stderr...");
			}
			else if (!str_cmp(args[pos], "version"))
			{
				log_string(MUD_NAME ": Compiled on " __DATE__ " at " __TIME__
						   ".");
				exit(0);
			}
#if !defined(NO_COPYOVER)
			else if (!str_cmp(args[pos], "c"))
			{
				fCopyOver = TRUE;
				control = atoi(args[++pos]);
			}
#endif
			else if (!str_cmp(args[pos], "levels"))
			{
				int oldlev, newlev;

				if (IS_NULLSTR(args[++pos])
					|| !is_number(args[pos])
					|| (oldlev = atoi(args[pos])) <= 0
					|| IS_NULLSTR(args[++pos])
					|| !is_number(args[pos]) || (newlev = atoi(args[pos])) <= 0)
				{
					logf("Usage: %s -levels [old max level] [new max level]",
						 argv[0]);
					exit(1);
				}

				boot_db();
				set_game_levels(oldlev, newlev);
				exit(0);
			}
			else
			{
				if (args[pos][0] != '?')
					logf("Invalid option '%s'.", args[pos]);
				logf("Valid options: %s -version (displays info)", argv[0]);
				logf("             : %s -v (disables log file)", argv[0]);
				logf
					("             : %s -levels [old max] [new max] (relevel the game)",
					 argv[0]);

				exit(1);
			}
		}
		else if (!IS_NULLSTR(args[pos]) && is_number(args[pos]))
		{
			if ((port = atoi(args[pos])) <= 1024)
			{
				log_string("Port number must be above 1024.");
				exit(1);
			}
		}
		else
		{
			logf("Invalid argument '%s'.", args[pos]);
			logf("Syntax: %s <port>", argv[0]);
			exit(1);
		}
	}
	// do directory layout checks
	if (check_directories())
	{
		log_string("There may have been problems creating the directories...");
		log_string("create them manually then try again.");
		exit(1);
	}

	cycle_log();

	/*
	 * Run the game.
	 */
#if !defined(NO_COPYOVER)
	if (!fCopyOver)
#endif
		control = init_socket(port);
#if !defined(NO_WEB)
	WebUP = init_web_server();
#endif
	log_string("Starting up " MUD_NAME "...");
	boot_db();
	set_signals();
	logf(MUD_NAME " is ready to rock on port %d.", port);
#if !defined(NO_COPYOVER)
	if (fCopyOver)
		copyover_recover();
#endif
	game_loop(control);
	close(control);
	/*
	 * That's all, folks.
	 */
	log_string("Normal termination of game.");
	exit(0);					// atexit will cleanup the mud hopefully
	return 0;
}

int init_socket(int prt)
{
	static struct sockaddr_in sa_zero;
	struct sockaddr_in sa;
	int x = 1;
	int fd;

	if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		log_error("Init_socket: socket");
		exit(-1);
	}

	if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof(x)) < 0)
	{
		log_error("Init_socket: SO_REUSEADDR");
		close(fd);
		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)
		{
			log_error("Init_socket: SO_DONTLINGER");
			close(fd);
			exit(-1);
		}
	}
#endif

	sa = sa_zero;
	sa.sin_family = AF_INET;
	sa.sin_port = htons(prt);

	if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
	{
		log_error("Init socket: bind");
		close(fd);
		exit(-1);
	}

	if (listen(fd, 3) < 0)
	{
		log_error("Init socket: listen");
		close(fd);
		exit(-1);
	}

	return fd;
}

void show_greeting(DESCRIPTOR_DATA * dnew)
{
	if (help_greeting[0] == '.')
		d_print(dnew, help_greeting + 1, 0);
	else
		d_print(dnew, help_greeting, 0);
}

#define MS_PER_PULSE ( 1000 / PULSE_PER_SECOND )
// maintained by SyncGameClock; currently read only by WaitForPulse()
unsigned long last_pulse_millisec;

void SynchronizeClock(void)
{
#if defined( WIN32 )
	struct _timeb now_time;

	_ftime(&now_time);
	current_time = now_time.time;
	last_pulse_millisec = now_time.millitm;
#else
	struct timeval last_time;

	gettimeofday(&last_time, NULL);
	current_time = (time_t) last_time.tv_sec;
	last_pulse_millisec = (unsigned long) last_time.tv_usec / 1000;
#endif
}

#if !defined(WIN32)
void Sleep(time_t milliseconds)
{
	struct timeval nap_time;
	nap_time.tv_usec = 1000 * milliseconds;
	nap_time.tv_sec = 0;
	if (select(0, NULL, NULL, NULL, &nap_time) < 0)
	{
		switch (errno)
		{
		case EBADF:
			bug("Invalid file descriptor passed to Select()");
			break;
		case EINTR:			//bugf("A non-blocked signal was caught.", 0);
			break;
		case EINVAL:
			bug("Negative \'n\' descriptor passed to Select()");
			break;
		case ENOMEM:
			bug("Select() was unable to allocate memory for internal tables.");
			break;
		default:
			bug("Unknown error.");
			break;
		}
	}
}
#endif

void WaitForPulse()
{
	signed long pulse;
	signed long nap;
#if !defined(WIN32)
	struct timeval now_time;

	gettimeofday(&now_time, NULL);
	pulse = ((int) now_time.tv_sec) - ((int) current_time);
	// Careful here of signed versus unsigned arithmetic.
	pulse *= 1000;
	pulse += ((int) now_time.tv_usec / 1000) - ((int) last_pulse_millisec);
#else
	struct _timeb now_time;

	_ftime(&now_time);
	pulse = now_time.time - current_time;
	pulse *= 1000;
	pulse += now_time.millitm - last_pulse_millisec;
#endif
	nap = MS_PER_PULSE - pulse;

	if (nap > 0)
	{
		Sleep(nap);
	}
}

void game_loop(int ctrl)
{
	static struct timeval null_time = { 0, 0 };
#if !defined(NO_VTIMER)
	int vt_set = 0;
#endif

	SynchronizeClock();

	run_level = RUNLEVEL_MAIN_LOOP;

	/* Main loop */
	while (run_level == RUNLEVEL_MAIN_LOOP)
	{
		fd_set in_set;
		fd_set out_set;
		fd_set exc_set;
		DESCRIPTOR_DATA *d;
		int maxdesc;

		/*
		 * Poll all active descriptors.
		 */
		FD_ZERO(&in_set);
		FD_ZERO(&out_set);
		FD_ZERO(&exc_set);
		FD_SET(ctrl, &in_set);
		maxdesc = ctrl;
		for (d = descriptor_first; 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);
		}

		if (select(maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) < 0)
		{
			if (errno != EINTR)
			{
				log_error("Game_loop: select: poll");
				exit(1);
			}
		}

		/*
		 * New connection?
		 */
		if (FD_ISSET(ctrl, &in_set))
			init_descriptor(ctrl);

		/*
		 * Kick out the freaky folks.
		 */
		for (d = descriptor_first; d != NULL; 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->connected == CON_PLAYING)
					save_char_obj(d->character);
				d->outtop = 0;
				close_socket(d);
			}
		}

		/*
		 * Process input.
		 */
		for (d = descriptor_first; 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->connected == CON_PLAYING)
						save_char_obj(d->character);
					d->outtop = 0;
					close_socket(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 (!IS_NULLSTR(d->incomm))
			{
				d->fcommand = TRUE;
				stop_idling(d->character);

#if !defined(NO_VTIMER)
				vt_set = 0;
				set_vtimer(60);
#endif

				/* OLC */
				if (d->showstr_point)
					show_string(d, d->incomm);
				else if (d->pString)
					string_add(d->character, d->incomm);
				else
					switch (d->connected)
					{
					case CON_PLAYING:
						substitute_alias(d, d->incomm);
						break;
					default:
						nanny(d, d->incomm);
						break;
					}

				d->incomm[0] = '\0';
			}
		}

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

#if !defined(NO_WEB)
		if (WebUP)
			update_web_server();
#endif

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

			if ((d->fcommand || d->outtop > 0
#if !defined(NO_MCCP)
				 || d->out_compress) &&
#else
				) &&
#endif
				FD_ISSET(d->descriptor, &out_set))
			{
				bool ok = TRUE;

				if (d->fcommand || d->outtop > 0)
					ok = process_output(d, TRUE);

#if !defined(NO_MCCP)
				if (ok && d->out_compress)
					ok = processCompressed(d);
#endif

				if (!ok)
				{
					if (d->character != NULL && d->connected == CON_PLAYING)
						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.
		 */
		WaitForPulse();
		SynchronizeClock();
		if (current_time % (HOUR) == 0)
			cycle_log();
#if !defined(NO_VTIMER)
		if (++vt_set >= 35)
		{
			vt_set = 0;

			set_vtimer(60);
		}
#endif
	}

	return;
}

void init_descriptor(int ctrl)
{
	DESCRIPTOR_DATA *dnew;
	struct sockaddr_in sock;
	struct hostent *from;
	int desc;
	socklen_t size;
#if defined(WIN32)
	static unsigned long ARGP = 1;
#endif

	size = sizeof(sock);

	getsockname(ctrl, (struct sockaddr *) &sock, &size);

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

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

#if defined( WIN32 )
	if (ioctlsocket(desc, FIONBIO, &ARGP) != 0)
	{
		bugf("ioctlsocket returned error code %d", WSAGetLastError());
		return;
	}
#else
	if (fcntl(desc, F_SETFL, FNDELAY) == -1)
	{
		log_error("New_descriptor: fcntl: FNDELAY");
		return;
	}
#endif

	/*
	 * Cons a new descriptor.
	 */
	dnew = new_descriptor();	/* new_descriptor now also allocates things */
	dnew->descriptor = desc;

	size = sizeof(sock);
	if (getpeername(desc, (struct sockaddr *) &sock, &size) < 0)
	{
		log_error("New_descriptor: getpeername");
		dnew->host = str_dup("(unknown)");
	}
	else
	{
		/*
		 * Would be nice to use inet_ntoa here but it takes a struct arg,
		 * which ain't very compatible between gcc and system libraries.
		 * - who cares lets use it, Markanth 03/07/2003
		 */
		char *ip = inet_ntoa(sock.sin_addr);
		logf("Sock.sinaddr:  %s", ip);

		if (!IS_SET(mud_info.mud_flags, NO_DNS_LOOKUPS))
			from =
				gethostbyaddr((char *) &sock.sin_addr,
							  sizeof(sock.sin_addr), AF_INET);
		else
			from = NULL;

		dnew->host = str_dup(from ? from->h_name : ip);
	}

	/*
	 * 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(dnew->host, BAN_ALL))
	{
		d_write(dnew, "Your site has been banned from this mud.\n\r", 0);
		close(desc);
		free_descriptor(dnew);
		return;
	}
	/*
	 * Init descriptor data.
	 */
	LINK(dnew, descriptor_first, descriptor_last, next, prev);

	/*
	 * Send the greeting.
	 */
	init_telnet(dnew);
	d_println(dnew, "Autodetecting IMP...v1.30", 0);
	d_println(dnew, "This world is Pueblo 1.10 enhanced.", 0);
	d_println(dnew,
			  "Welcome, would you like ANSI color? (Y)es, (N)o, (T)est:", 0);
	return;
}

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

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

	if (dclose->snoop_by != NULL)
	{
		d_println(dclose->snoop_by, "Your victim has left the game.", 0);
	}

	{
		DESCRIPTOR_DATA *d;

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

	if ((ch = dclose->character) != NULL)
	{
		sprintf(log_buf, "Closing link to %s.", ch->name);
		log_string(log_buf);
		/* cut down on wiznet spam when rebooting */
		/* If ch is writing note or playing, just lose link otherwise clear char */
		if ((dclose->connected == CON_PLAYING && run_level != RUNLEVEL_SHUTDOWN)
			|| ((dclose->connected >= CON_NOTE_TO)
				&& (dclose->connected <= CON_NOTE_FINISH)))
		{
			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->original ? dclose->original : dclose->character);
		}
	}

	free_runbuf(dclose);

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

	UNLINK(dclose, descriptor_first, descriptor_last, next, prev);

#if !defined(NO_MCCP)
	if (dclose->out_compress)
	{
		deflateEnd(dclose->out_compress);
		free_mem(dclose->out_compress_buf);
		free_mem(dclose->out_compress);
	}
#endif

	close(dclose->descriptor);
	free_descriptor(dclose);
	return;
}

bool read_from_descriptor(DESCRIPTOR_DATA * d)
{
	unsigned int iStart, index;
	static unsigned char buf[sizeof(d->inbuf)];

	memset(buf, 0, sizeof(buf));

	/* Hold horses if pending command already. */
	if (!IS_NULLSTR(d->incomm))
		return TRUE;

	/* Check for overflow. */
	iStart = strlen(d->inbuf);
	index = 0;

	if (iStart >= sizeof(d->inbuf) - 10)
	{
		logf("%s input overflow!", d->host);
		d_write(d, "\n\r*** PUT A LID ON IT!!! ***\n\r", 0);
		d->inbuf[sizeof(d->inbuf) - 10] = '\0';
		return TRUE;
	}

	for (;;)
	{
		int nRead;

		nRead = read(d->descriptor, buf + index, sizeof(buf) - 10 - index);
		if (nRead > 0)
		{
			index += nRead;
			if (buf[index - 1] == '\n' || buf[index - 1] == '\r')
				break;
		}
		else if (nRead == 0)
		{
			logf("Read_from_descriptor: EOF encountered on read.");
			return FALSE;
		}
		else if (WOULD_HAVE_BLOCKED)
			break;
		else
		{
			log_error("Read_from_descriptor");
			return FALSE;
		}
	}

	/* process_telnet does the actual writing in d->inbuf now! */
	process_telnet(d, index, buf);

	/* If there is some telnet negotation going on, assume the client
	   supports colour... */
	if (d->connected == CON_GET_TERM &&
		(DESC_FLAGGED(d, DESC_TELOPT_EOR | DESC_TELOPT_ECHO | DESC_TELOPT_NAWS
					  | DESC_MXP | DESC_MSP | DESC_PUEBLO | DESC_TELOPT_TTYPE |
					  DESC_TELOPT_BINARY | DESC_PORTAL | DESC_IMP)
#if !defined(NO_MCCP)
		 || d->out_compress
#endif
		))
	{
		SET_BIT(d->d_flags, DESC_COLOUR);
		d_println(d, "{`Colour{x enabled Automatically...{x", 0);
		show_greeting(d);
		d->connected = CON_GET_NAME;
	}
	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 (!IS_NULLSTR(d->incomm))
		return;

	if (d->character && d->character->position == POS_FIGHTING && d->run_buf)
		free_runbuf(d);

	if (d->run_buf)
	{
		while (isdigit(*d->run_head) && *d->run_head != '\0')
		{
			char *s;
			char *e;

			s = (char *) d->run_head;
			while (isdigit(*s))
				s++;
			e = s;
			while (*(--s) == '0' && s != d->run_head);
			if (isdigit(*s) && *s != '0' && *e != 'o')
			{
				d->incomm[0] = *e;
				d->incomm[1] = '\0';
				s[0]--;
				while (isdigit(*(++s)))
					*s = '9';
				return;
			}
			if (*e == 'o')
				d->run_head = e;
			else
				d->run_head = ++e;
		}
		if (*d->run_head != '\0')
		{
			if (*d->run_head != 'o')
			{
				d->incomm[0] = *d->run_head++;
				d->incomm[1] = '\0';
				return;
			}
			else
			{
				char buf[MAX_INPUT_LENGTH];

				d->run_head++;

				strcpy(buf, "open ");
				switch (*d->run_head)
				{
				case 'n':
					strcat(buf, "north");
					break;
				case 's':
					strcat(buf, "south");
					break;
				case 'e':
					strcat(buf, "east");
					break;
				case 'w':
					strcat(buf, "west");
					break;
				case 'u':
					strcat(buf, "up");
					break;
				case 'd':
					strcat(buf, "down");
					break;
				default:
					return;
				}

				strcpy(d->incomm, buf);
				d->run_head++;
				return;
			}
		}
		free_runbuf(d);
	}

	/*
	 * 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)
		{
			d_write(d, "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 (isascii(d->inbuf[i]) && isprint(d->inbuf[i]))
			d->incomm[k++] = 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] != '!' && str_cmp(d->incomm, d->inlast))
		{
			d->repeat = 0;
		}
		else
		{
			if (++d->repeat >= 25 && d->character &&
				d->connected == CON_PLAYING)
			{
				sprintf(log_buf, "%s input spamming!", d->host);
				log_string(log_buf);
				wiznet
					("Spam spam spam $N spam spam spam spam spam!",
					 d->character, NULL, WIZ_SPAM, 0, get_trust(d->character));
				if (d->incomm[0] == '!')
					wiznet(d->inlast, d->character, NULL,
						   WIZ_SPAM, 0, get_trust(d->character));
				else
					wiznet(d->incomm, d->character, NULL,
						   WIZ_SPAM, 0, get_trust(d->character));

				d->repeat = 0;
/*
		d_write( d,
		    "\n\r*** PUT A LID ON IT!!! ***\n\r", 0 );
		strcpy( d->incomm, "quit" );
*/
			}
		}
	}

	/*
	 * Do '!' substitution.
	 */
	if (d->incomm[0] == '!')
		strcpy(d->incomm, d->inlast);
	else
		strcpy(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++)
		;
	return;
}

int col_attr, col_fore, col_back;

int random_attr(void)
{
	if (number_range(1, 50) < 25)
		return CL_CLEAR;
	else
		return CL_BRIGHT;
}

int random_fore(void)
{
	return number_range(FG_BLACK, FG_WHITE);
}

int random_back(void)
{
	return number_range(BG_BLACK, BG_WHITE);
}

char *make_colour(void)
{
	bool a, b;

	if (col_attr == CL_RANDOM)
		col_attr = random_attr();
	if (col_fore == FG_RANDOM)
		col_fore = random_fore();
	if (col_back == BG_RANDOM)
		col_back = random_back();

	a = (col_fore != FG_NONE);
	b = (col_back != BG_NONE);

	if (!a && !b)
		return FORMATF(CL_FORMAT1, col_attr);
	else if (a && !b)
		return FORMATF(CL_FORMAT2, col_attr, col_fore);
	else if (!a && b)
		return FORMATF(CL_FORMAT2, col_attr, col_back);
	else
		return FORMATF(CL_FORMAT3, col_attr, col_fore, col_back);
}

/* Taken from Gary McNickel's WOTmud 
   Updated by Markanth. */
bool process_ansi_output(DESCRIPTOR_DATA * d)
{
	CHAR_DATA *ch;
	char *counter;
	char output[MSL];
	char temp[MSL];
	char *work;
	bool success = TRUE;
	char *i;

	if (d == NULL)
		return FALSE;

	ch = (d->original ? d->original : d->character);

	memset(output, 0, sizeof(output));
	counter = output;
	work = d->outbuf;

	while (*work != '\0' && (work - d->outbuf) < d->outtop)
	{
		if ((long) (counter - output) >= MSL - 32)
		{

			*counter++ = '\0';

			if (!(success = d_write(d, output, strlen(output))))
				break;

			memset(output, 0, sizeof(output));
			counter = output;

		}

		if (*work == ANSI_KEY)
		{

			work++;

			temp[0] = '\0';

			if (DESC_FLAGGED(d, DESC_COLOUR) &&
				(!ch || !IS_SET(ch->comm, COMM_NOCOLOUR)))
			{
				switch (*work)
				{
				case '\0':
					break;
				case ' ':
					sprintf(temp, "%c ", ANSI_KEY);
					break;
				case '-':
					strcpy(temp, "~");
					break;
				case 'P':
				case 'p':
					sprintf(temp, "%c", 007);
					break;
				case ANSI_KEY:
					sprintf(temp, "%c", ANSI_KEY);
					break;
				default:
					{
						switch (*work)
						{
						case 'x':
						case 'X':
							strcpy(temp, char_colour(ch, _DEFAULT));
							break;
						case 'v':
						case 'V':
							col_attr = CL_REVERSE;
							break;
						case 'u':
						case 'U':
							col_attr = CL_UNDER;
							break;
						case 'f':
						case 'F':
							col_attr = CL_BLINK;
							break;
						case 'b':
							col_attr = CL_CLEAR;
							col_fore = FG_BLUE;
							break;
						case 'c':
							col_attr = CL_CLEAR;
							col_fore = FG_CYAN;
							break;
						case 'g':
							col_attr = CL_CLEAR;
							col_fore = FG_GREEN;
							break;
						case 'm':
							col_attr = CL_CLEAR;
							col_fore = FG_MAGENTA;
							break;
						case 'd':
							col_attr = CL_CLEAR;
							col_fore = FG_BLACK;
							break;
						case 'r':
							col_attr = CL_CLEAR;
							col_fore = FG_RED;
							break;
						case 'y':
							col_attr = CL_CLEAR;
							col_fore = FG_YELLOW;
							break;
						case 'w':
							col_attr = CL_CLEAR;
							col_fore = FG_WHITE;
							break;
						case 'B':
							col_attr = CL_BRIGHT;
							col_fore = FG_BLUE;
							break;
						case 'C':
							col_attr = CL_BRIGHT;
							col_fore = FG_CYAN;
							break;
						case 'G':
							col_attr = CL_BRIGHT;
							col_fore = FG_GREEN;
							break;
						case 'M':
							col_attr = CL_BRIGHT;
							col_fore = FG_MAGENTA;
							break;
						case 'D':
							col_attr = CL_BRIGHT;
							col_fore = FG_BLACK;
							break;
						case 'R':
							col_attr = CL_BRIGHT;
							col_fore = FG_RED;
							break;
						case 'W':
							col_attr = CL_BRIGHT;
							col_fore = FG_WHITE;
							break;
						case 'Y':
							col_attr = CL_BRIGHT;
							col_fore = FG_YELLOW;
							break;
						case '`':
							col_attr = CL_RANDOM;
							col_fore = FG_RANDOM;
							break;
						case '1':
							col_back = BG_RED;
							break;
						case '2':
							col_back = BG_BLUE;
							break;
						case '3':
							col_back = BG_GREEN;
							break;
						case '4':
							col_back = BG_BLACK;
							break;
						case '5':
							col_back = BG_WHITE;
							break;
						case '6':
							col_back = BG_MAGENTA;
							break;
						case '7':
							col_back = BG_YELLOW;
							break;
						case '8':
							col_back = BG_CYAN;
							break;
						case '+':
						case '=':
							col_attr = CL_RANDOM;
							col_back = BG_RANDOM;
							break;
						}
						strcpy(temp, make_colour());
						break;
					}
				}
			}
			work++;

			i = temp;
			while ((*counter = *i) != '\0')
				++counter, ++i;
		}
		/* Custom colour added by Markanth */
		else if (*work == ANSI_CUSTOM)
		{
			int slot = 0;

			work++;

			while (*work != ANSI_END)
			{
				if (isdigit(*work))
					slot = (slot * 10) + (*work - '0');
				work++;
			}

			work++;

			temp[0] = '\0';

			if (slot < 0 || slot >= MAX_CUSTOM_COLOUR)
			{
				bug("ansi_output: invalid custom color");
				strcpy(temp, CL_DEFAULT);
				col_attr = CL_CLEAR;
				col_fore = FG_NONE;
				col_back = BG_NONE;
			}
			else if (DESC_FLAGGED(d, DESC_COLOUR) &&
					 (!ch || !IS_SET(ch->comm, COMM_NOCOLOUR)))
			{
				strcpy(temp, char_colour(ch, slot));
			}

			i = temp;
			while ((*counter = *i) != '\0')
				++counter, ++i;
		}
		else
			*counter++ = *work++;
	}

	success = success && (d_write(d, output, strlen(output)));

	d->outtop = 0;
	return success;

}

/*
 * Low level output function.
 */
bool process_output(DESCRIPTOR_DATA * d, bool fPrompt)
{
	/*
	 * Bust a prompt.
	 */
	if (run_level != RUNLEVEL_SHUTDOWN)
	{
		if (d->showstr_point)
		{
			const char *ptr;
			int shown_lines = 0;
			int total_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++;

			d_printlnf(d,
					   "\n\r(%d%%) Please type (H)elp, (R)efresh, (B)ack, or (C)ontinue or hit ENTER.",
					   100 * shown_lines / total_lines);
		}
		else if (fPrompt && d->pString && d->connected == CON_PLAYING)
			d_print(d, "> ", 2);
		else if (fPrompt && d->connected == CON_NOTE_TEXT)
			d_print(d, "> ", 2);
		else if (fPrompt && d->connected == CON_PLAYING)
		{
			CHAR_DATA *ch;
			CHAR_DATA *victim;

			ch = d->character;

			/* battle prompt */
			if ((victim = ch->fighting) != NULL && can_see(ch, victim))
			{
				int percent;
				char wound[100];

				if (victim->max_hit > 0)
					percent = victim->hit * 100 / victim->max_hit;
				else
					percent = -1;

				if (percent >= 100)
					sprintf(wound, "is in excellent condition.");
				else if (percent >= 90)
					sprintf(wound, "has a few scratches.");
				else if (percent >= 75)
					sprintf(wound, "has some small wounds and bruises.");
				else if (percent >= 50)
					sprintf(wound, "has quite a few wounds.");
				else if (percent >= 30)
					sprintf(wound, "has some big nasty wounds and scratches.");
				else if (percent >= 15)
					sprintf(wound, "looks pretty hurt.");
				else if (percent >= 0)
					sprintf(wound, "is in awful condition.");
				else
					sprintf(wound, "is bleeding to death.");

				d_printlnf(d, "%s %s",
						   IS_NPC(victim) ? victim->short_descr :
						   victim->name, wound);
			}

			ch = d->original ? d->original : d->character;
			if (!IS_SET(ch->comm, COMM_COMPACT)
				&& ((!IS_NPC(ch) && IS_SET(ch->act, PLR_AUTOPROMPT))
					|| d->fcommand || ch->fighting || d->editor != ED_NONE))
				d_println(d, "", 0);

			if (IS_SET(ch->comm, COMM_PROMPT))
			{
				if ((!IS_NPC(ch) && IS_SET(ch->act, PLR_AUTOPROMPT))
					|| (d->fcommand && !d->run_buf) || ch->fighting)
					bust_a_prompt(d->character);
				else if (d->editor != ED_NONE)
				{
					chprintlnf(ch, "{cOLC %s : {W%s{x",
							   olc_ed_name(d), olc_ed_vnum(d));
				}
			}
			if (IS_SET(ch->comm, COMM_TELNET_GA))
			{
				d_write(d, go_ahead_str, 0);
			}
			else if (IS_SET(ch->comm, COMM_TELNET_EOR))
			{
				char eor_str[] = { IAC, EOR, '\0' };
				d_write(d, eor_str, 0);
			}
		}

	}

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

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

	/*
	 * OS-dependent output.
	 */
	return process_ansi_output(d);
}

/*
 * Bust a prompt (player settable prompt)
 * coded by Morgenes for Aldara Mud
 */
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 doors[MAX_INPUT_LENGTH];
	EXIT_DATA *pexit;
	bool found;
	const char *dir_letter[] = { "N", "E", "S", "W", "U", "D" };
	int door;

	point = buf;
	str = ch->prompt;

	bust_a_portal(ch);

	if (IS_NULLSTR(str))
	{
		chprintlnf(ch, "<%ldhp %ldm %ldmv> %s", ch->hit, ch->mana,
				   ch->move, ch->prefix);
		return;
	}

	if (IS_SET(ch->comm, COMM_AFK))
	{
		chprint(ch, "<AFK> ");
		return;
	}

	chprint(ch, MXPTAG("Prompt"));

	while (*str != '\0')
	{
		if (*str != '%')
		{
			*point++ = *str++;
			continue;
		}
		++str;
		switch (*str)
		{
		default:
			i = " ";
			break;
		case 'e':
			found = FALSE;
			doors[0] = '\0';
			for (door = 0; door < 6; door++)
			{
				if ((pexit = ch->in_room->exit[door]) != NULL &&
					pexit->u1.to_room != NULL &&
					(can_see_room(ch, pexit->u1.to_room) ||
					 (IS_AFFECTED(ch, AFF_INFRARED) &&
					  !IS_AFFECTED(ch, AFF_BLIND))) &&
					!IS_SET(pexit->exit_info, EX_CLOSED))
				{
					found = TRUE;
					strcat(doors, MXPTAG("Ex"));
					strcat(doors, dir_letter[door]);
					strcat(doors, MXPTAG("/Ex"));
				}
			}
			if (!found)
				strcat(buf, "none");
			sprintf(buf2, MXPTAG("Rexits") "%s" MXPTAG("/Rexits"), doors);
			i = buf2;
			break;
		case 'c':
			sprintf(buf2, "%s", "\n\r");
			i = buf2;
			break;
		case 'h':
			sprintf(buf2, MXPTAG("Hp") "%ld" MXPTAG("/Hp"), ch->hit);
			i = buf2;
			break;
		case 'H':
			sprintf(buf2, MXPTAG("MaxHp") "%ld" MXPTAG("/MaxHp"), ch->max_hit);
			i = buf2;
			break;
		case 'm':
			sprintf(buf2, MXPTAG("Mana") "%ld" MXPTAG("/Mana"), ch->mana);
			i = buf2;
			break;
		case 'M':
			sprintf(buf2, MXPTAG("MaxMana") "%ld" MXPTAG("/MaxMana"),
					ch->max_mana);
			i = buf2;
			break;
		case 'v':
			sprintf(buf2, MXPTAG("Move") "%ld" MXPTAG("/Move"), ch->move);
			i = buf2;
			break;
		case 'V':
			sprintf(buf2, MXPTAG("MaxMove") "%ld" MXPTAG("/MaxMove"),
					ch->max_move);
			i = buf2;
			break;
		case 'x':
			sprintf(buf2, "%d", ch->exp);
			i = buf2;
			break;
		case 'X':
			sprintf(buf2, "%d",
					IS_NPC(ch) ? 0 : (ch->level +
									  1) * exp_per_level(ch,
														 ch->pcdata->points) -
					ch->exp);
			i = buf2;
			break;
		case 'g':
			sprintf(buf2, "%ld", ch->gold);
			i = buf2;
			break;
		case 's':
			sprintf(buf2, "%ld", ch->silver);
			i = buf2;
			break;
		case 'a':
			if (ch->level > 9)
				sprintf(buf2, "%d", ch->alignment);
			else
				sprintf(buf2, "%s",
						IS_GOOD(ch) ? "good" : IS_EVIL(ch) ?
						"evil" : "neutral");
			i = buf2;
			break;
		case 'r':
			if (ch->in_room != NULL)
				sprintf(buf2, "%s",
						((!IS_NPC(ch) &&
						  IS_SET(ch->act, PLR_HOLYLIGHT)) ||
						 (!IS_AFFECTED(ch, AFF_BLIND) &&
						  !room_is_dark(ch->in_room))) ? ch->
						in_room->name : "darkness");
			else
				sprintf(buf2, " ");
			i = buf2;
			break;
		case 'R':
			if (IS_IMMORTAL(ch) && ch->in_room != NULL)
				sprintf(buf2, "%ld", ch->in_room->vnum);
			else
				sprintf(buf2, " ");
			i = buf2;
			break;
		case 'Q':
			if (gquest_info.running != GQUEST_OFF)
				sprintf(buf2, "%d", gquest_info.timer);
			else
				sprintf(buf2, "%dn", gquest_info.timer);
			i = buf2;
			break;

		case 'z':
			if (IS_IMMORTAL(ch) && ch->in_room != NULL)
				sprintf(buf2, "%s", ch->in_room->area->name);
			else
				sprintf(buf2, " ");
			i = buf2;
			break;
		case '%':
			sprintf(buf2, "%%");
			i = buf2;
			break;
		case 'o':
			sprintf(buf2, MXPTAG("Olc") "%s" MXPTAG("/Olc"),
					olc_ed_name(ch->desc));
			i = buf2;
			break;
		case 'O':
			sprintf(buf2, "%s", olc_ed_vnum(ch->desc));
			i = buf2;
			break;
		case 'q':
			if (!IS_NPC(ch))
			{
				if (!IS_SET(ch->act, PLR_QUESTOR))
					sprintf(buf2, "%d", ch->pcdata->nextquest);
				else
					sprintf(buf2, "%d", ch->pcdata->countdown);
			}
			else
				strcpy(buf2, " ");

			i = buf2;
			break;
		}
		++str;
		while ((*point = *i) != '\0')
			++point, ++i;
	}
	d_print(ch->desc, buf, point - buf);
	d_print(ch->desc, MXPTAG("/Prompt"), 0);
	if (!IS_NULLSTR(ch->prefix))
		d_print(ch->desc, ch->prefix, 0);
	return;
}

/*
 * Append onto an output buffer.
 */
void d_print(DESCRIPTOR_DATA * d, const char *txt, int length)
{
	int origlength;
	bool noMXP = FALSE;

	if (!d)
		return;

	/*
	 * Find length in case caller didn't.
	 */
	if (length == 0)
		length = strlen(txt);
	else if (length <= -1)
	{
		/* if length is less than 0, disable MXP parsing,
		   good for sending strings EXACTLY the way they appear */
		noMXP = TRUE;
		length = strlen(txt);
	}

	origlength = length;

	/* work out how much we need to expand/contract it */
	if (!noMXP)
		length += count_mxp_tags(d, txt, length);

	/*
	 * 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.
	 */
	while (d->outtop + length >= d->outsize)
	{
		char *outbuf;

		if (d->outsize >= 32000)
		{
			bug("Buffer overflow. Closing.");
			close_socket(d);
			return;
		}
		alloc_mem(outbuf, char, 2 * d->outsize);
		strncpy(outbuf, d->outbuf, d->outtop);
		free_mem(d->outbuf);
		d->outbuf = outbuf;
		d->outsize *= 2;
	}

	/*
	 * Copy.
	 */
	strncpy(d->outbuf + d->outtop, txt, length);
	if (!noMXP)
		convert_mxp_tags(d, d->outbuf + d->outtop, txt, origlength);
	d->outtop += length;
	d->outbuf[d->outtop] = NUL;
	return;
}

void d_println(DESCRIPTOR_DATA * d, const char *txt, int length)
{
	if (!d)
		return;

	if (!IS_NULLSTR(txt))
		d_print(d, txt, length);
	d_print(d, "\n\r", 2);
}

void d_printf(DESCRIPTOR_DATA * d, const char *txt, ...)
{
	int length;
	va_list args;
	char buf[MSL * 2];

	if (!d || IS_NULLSTR(txt))
		return;

	va_start(args, txt);
	length = vsnprintf(buf, sizeof(buf), txt, args);
	va_end(args);

	d_print(d, buf, length);
}

void d_printlnf(DESCRIPTOR_DATA * d, const char *txt, ...)
{
	if (!d)
		return;

	if (!IS_NULLSTR(txt))
	{
		int length;
		va_list args;
		char buf[MSL * 2];

		va_start(args, txt);
		length = vsnprintf(buf, sizeof(buf), txt, args);
		va_end(args);

		d_print(d, buf, length);
	}
	d_print(d, "\n\r", 2);
}

/*
 * 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 d_write(DESCRIPTOR_DATA * d, const char *txt, int length)
{
	int iStart;
	int nWrite;
	int nBlock;

	if (!d)
		return FALSE;

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

	d->bytes_normal += length;

#if !defined(NO_MCCP)
	if (d->out_compress)
		return writeCompressed(d, txt, length);
	else
#endif
	{
		for (iStart = 0; iStart < length; iStart += nWrite)
		{
			nBlock = UMIN(length - iStart, 4096);
			if ((nWrite = write(d->descriptor, txt + iStart, nBlock)) < 0)
			{
				log_error("d_write");
				return FALSE;
			}
		}

		return TRUE;
	}
}

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

	/*
	 * Reserved words.
	 */
	if (is_exact_name
		(name, "all auto immortal self someone something the you loner none"))
	{
		return FALSE;
	}

	/* check clans */
	for (clan = clan_first; clan; clan = clan->next)
	{
		if (LOWER(name[0]) == LOWER(clan->name[0]) &&
			!str_cmp(name, clan->name))
			return FALSE;
	}

	if (str_cmp(capitalize(name), "Alander") &&
		(!str_prefix("Alan", name) || !str_suffix("Alander", name)))
		return FALSE;

	/*
	 * Length restrictions.
	 */

	if (strlen(name) < 2)
		return FALSE;

	if (strlen(name) > 12)
		return FALSE;

	/*
	 * Alphanumerics only.
	 * Lock out IllIll twits.
	 */
	{
		const char *pc;
		bool fIll, adjcaps = FALSE, cleancaps = FALSE;
		unsigned int total_caps = 0;

		fIll = TRUE;
		for (pc = name; *pc != '\0'; pc++)
		{
			if (!isalpha(*pc))
				return FALSE;

			if (isupper(*pc))	/* ugly anti-caps hack */
			{
				if (adjcaps)
					cleancaps = TRUE;
				total_caps++;
				adjcaps = TRUE;
			}
			else
				adjcaps = FALSE;

			if (LOWER(*pc) != 'i' && LOWER(*pc) != 'l')
				fIll = FALSE;
		}

		if (fIll)
			return FALSE;

		if (cleancaps || (total_caps > (strlen(name)) / 2 && strlen(name) < 3))
			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 != NULL;
				 pMobIndex = pMobIndex->next)
			{
				if (is_name(name, pMobIndex->player_name))
					return FALSE;
			}
		}
	}

	return TRUE;
}

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

	for (ch = char_first; ch != NULL; ch = ch->next)
	{
		if (!IS_NPC(ch) && (!fConn || ch->desc == NULL) &&
			!str_cmp(d->character->name, ch->name))
		{
			if (fConn == FALSE)
			{
				replace_string(d->character->pcdata->pwd, ch->pcdata->pwd);
			}
			else
			{
				free_char(d->character);
				d->character = ch;
				ch->desc = d;
				ch->timer = 0;
				chprintln(ch,
						  "Reconnecting. Welcome back, type replay to see missed tells.");
				act("$n has reconnected.", ch, NULL, NULL, TO_ROOM);

				sprintf(log_buf, "%s@%s reconnected.", ch->name, d->host);
				log_string(log_buf);
				wiznet("$N groks the fullness of $S link.", ch,
					   NULL, WIZ_LINKS, 0, 0);
				d->connected = CON_PLAYING;
				/* Inform the character of a note in progress and the possbility
				 * of continuation!
				 */
				if (ch->pcdata->in_progress)
					chprintln(ch,
							  "You have a note in progress. Type NOTE WRITE to continue it.");
			}
			return TRUE;
		}
	}

	return FALSE;
}

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

	for (dold = descriptor_first; dold; dold = dold->next)
	{
		if (dold != d && dold->character != NULL &&
			dold->connected != CON_GET_NAME &&
			dold->connected != CON_GET_OLD_PASSWORD &&
			!str_cmp(name,
					 dold->original ? dold->original->name : dold->
					 character->name))
		{
			d_println(d, "That character is already playing.", 0);
			d_print(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 == NULL ||
		ch->in_room != get_room_index(ROOM_VNUM_LIMBO))
		return;

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

/*
 * Write to one char.
 */
void chprint(CHAR_DATA * ch, const char *txt)
{
	if (!IS_NULLSTR(txt) && ch && ch->desc != NULL)
		d_print(ch->desc, txt, strlen(txt));
	return;
}

void chprintln(CHAR_DATA * ch, const char *txt)
{
	if (ch && ch->desc != NULL)
	{
		if (!IS_NULLSTR(txt))
			d_print(ch->desc, txt, strlen(txt));
		d_print(ch->desc, "\n\r", 2);
	}

	return;
}

/*
 * Send a page to one char.
 */
void sendpage(CHAR_DATA * ch, const char *txt)
{
	DESCRIPTOR_DATA *d;

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

	if (ch->lines <= 0)
	{
		d_print(d, txt, strlen(txt));
		return;
	}

	/*
	 * If there is already some data being "paged" for this descriptor,
	 * append the new string.
	 */
	if (!IS_NULLSTR(d->showstr_head))
	{
		char *fub;
		int i;
		int size_new = strlen(txt) + strlen(d->showstr_head) + 2;

		alloc_mem(fub, char, size_new);
		fub[0] = '\0';
		strncat(fub, d->showstr_head, size_new);
		i = strlen(fub) - strlen(d->showstr_point);
		strncat(fub, txt, size_new);

		replace_string(d->showstr_head, fub);
		d->showstr_point = d->showstr_head + i;
		free_mem(fub);
		return;
	}

	replace_string(d->showstr_head, txt);
	d->showstr_point = d->showstr_head;
	show_string(d, "");
	return;
}

/* string pager */
void show_string(DESCRIPTOR_DATA * d, char *input)
{
	char buffer[MAX_STRING_LENGTH * 3];
	char buf[MAX_INPUT_LENGTH];
	register const char *scan;
	register char *scan2;
	register const char *chk;
	int lines = 0;
	int maxlines = get_scr_lines(d->character);
	int toggle = 1;

	one_argument(input, buf);

	switch (UPPER(buf[0]))
	{
	case '\0':
	case 'C':
		lines = 0;
		break;

	case 'R':					/* refresh current page of text */
		lines = -1 - maxlines;
		break;

	case 'B':					/* scroll back a page of text */
		lines = -(2 * maxlines);
		break;

	case '?':
	case 'H':					/* Show some help */
		d_println(d, "Pager help:\n\r" "C or Enter     next page\n\r"
				  "R              refresh this page", 0);
		d_println(d,
				  "B              previous page\n\r"
				  "H or ?         help\n\r" "Any other keys exit.", 0);
		return;

	default:					/*otherwise, stop the text viewing */
		if (d->showstr_head)
		{
			replace_string(d->showstr_head, "");
		}
		d->showstr_point = NULL;
		return;

	}

	/* do any backing up necessary */
	if (lines < 0)
	{
		for (scan = d->showstr_point; scan > d->showstr_head; scan--)
			if ((*scan == '\n') || (*scan == '\r'))
			{
				toggle = -toggle;
				if (toggle < 0)
					if (!(++lines))
						break;
			}
		d->showstr_point = scan;
	}

	/* show a chunk */
	lines = 0;
	toggle = 1;
	for (scan2 = buffer;; scan2++, d->showstr_point++)
		if (((*scan2 = *d->showstr_point) == '\n' || *scan2 == '\r')
			&& (toggle = -toggle) < 0)
			lines++;
		else if (!*scan2
				 || (d->character && !IS_NPC(d->character)
					 && lines >= maxlines))
		{

			*scan2 = '\0';
			d_print(d, buffer, strlen(buffer));

			/* See if this is the end (or near the end) of the string */
			for (chk = d->showstr_point; isspace(*chk); chk++);
			if (!*chk)
			{
				if (d->showstr_head)
				{
					replace_string(d->showstr_head, "");
				}
				d->showstr_point = 0;
			}
			return;
		}

	return;
}

char *fname(const char *namelist)
{
	static char holder[256];
	char *point;

	for (point = holder; isalpha(*namelist); namelist++, point++)
		*point = *namelist;

	*point = '\0';

	return (holder);
}

void perform_act(const char *orig, CHAR_DATA * ch, const void *arg1,
				 const void *arg2, flag_t type, CHAR_DATA * to)
{
	static char *const he_she[] = { "it", "he", "she" };
	static char *const him_her[] = { "it", "him", "her" };
	static char *const his_her[] = { "its", "his", "her" };
	CHAR_DATA *vch = (CHAR_DATA *) arg2;
	OBJ_DATA *obj1 = (OBJ_DATA *) arg1;
	OBJ_DATA *obj2 = (OBJ_DATA *) arg2;
	const char *str, *i = NULL;
	char *point;
	char buf[MSL];

	point = buf;
	str = orig;

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

		++str;
		i = "<@@@>";
		if (!arg2 && *str >= 'A' && *str <= 'Z')
		{
			logf("perform_act:missing arg2 for code %d.", *str);
			i = " <@@@> ";
		}
		else
		{
			switch (*str)
			{
			default:
				logf("perform_act:bad code %c.", *str);
				i = " <@@@> ";
				break;

			case '$':
				i = "$";
				break;
			case 't':
				if (arg1)
				{
					if (IS_SET(type, TO_DAMAGE)
						&& (IS_NPC(to) || !IS_SET(to->act, PLR_AUTODAMAGE)))
						i = &str_empty[0];
					else
						i = (const char *) arg1;
				}
				else
					log_string("perform_act:bad code $t for 'arg1'");
				break;
			case 'T':
				if (arg2)
					i = (const char *) arg2;
				else
					log_string("perform_act:bad code $T for 'arg2'");
				break;
			case 'n':
				if (ch && to)
					i = PERS(ch, to);
				else
					log_string("perform_act:bad code $n for 'ch' or 'to'");
				break;
			case 'N':
				if (vch && to)
					i = PERS(vch, to);
				else
					log_string("perform_act:bad code $N for 'ch' or 'to'");
				break;
			case 'e':
				if (ch)
					i = he_she[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $e for 'ch'");
				break;
			case 'E':
				if (vch)
					i = he_she[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $E for 'vch'");
				break;
			case 'm':
				if (ch)
					i = him_her[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $m for 'ch'");
				break;
			case 'M':
				if (vch)
					i = him_her[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $M for 'vch'");
				break;
			case 's':
				if (ch)
					i = his_her[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $s for 'ch'");
				break;
			case 'S':
				if (vch)
					i = his_her[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $S for 'vch'");
				break;
			case 'g':
				if (ch && ch->deity != NULL)
					i = ch->deity->name;
				else
					log_string("perform_act:bad code $g for 'ch'");
				break;
			case 'G':
				if (vch && vch->deity != NULL)
					i = vch->deity->name;
				else
					log_string("perform_act:bad code $G for 'vch'");
				break;
			case 'c':
				if (ch && ch->clan != NULL)
					i = ch->clan->name;
				else
					log_string("perform_act:bad code $c for 'ch'");
				break;
			case 'C':
				if (vch && vch->clan != NULL)
					i = vch->clan->name;
				else
					log_string("perform_act:bad code $C for 'vch'");
				break;
			case 'o':
				if (to && obj1)
					i = can_see_obj(to, obj1) ? fname(obj1->name) : "something";
				else
					log_string("perform_act:bad code $o for 'to' and 'obj1'");
				break;

			case 'O':
				if (to && obj2)
					i = can_see_obj(to, obj2) ? fname(obj2->name) : "something";
				else
					log_string("perform_act:bad code $O for 'to' and 'obj2'");
				break;

			case 'p':
				if (to && obj1)
					i = can_see_obj(to, obj1) ? obj1->short_descr : "something";
				else
					log_string("perform_act:bad code $p for 'to' and 'obj1'");
				break;

			case 'P':
				if (to && obj2)
					i = can_see_obj(to, obj2) ? obj2->short_descr : "something";
				else
					log_string("perform_act:bad code $P for 'to' and 'obj2'");
				break;

			case 'd':
				if (arg2 == NULL || ((const char *) arg2)[0] == '\0')
				{
					i = "door";
				}
				else
				{
					char name[MIL];

					one_argument((const char *) arg2, name);
					i = name;
				}
				break;
			}
		}

		++str;
		while ((*point = *i) != '\0')
			++point, ++i;
	}

	*point++ = '{';
	*point++ = 'x';
	*point++ = '\n';
	*point++ = '\r';
	*point = '\0';
	buf[0] = UPPER(buf[0]);
	if (to->desc)
	{
		if (to->desc->connected == CON_PLAYING)
			d_print(to->desc, buf, point - buf);
	}
	else if (IS_NPC(to) && MOBtrigger && HAS_TRIGGER_MOB(to, TRIG_ACT))
		p_act_trigger(buf, to, NULL, NULL, ch, arg1, arg2, TRIG_ACT);

	if (ch && ch->in_room && IS_SET(type, TO_ROOM | TO_NOTVICT))
	{
		OBJ_DATA *obj, *obj_next;
		CHAR_DATA *tch, *tch_next;

		point = buf;
		str = orig;
		while (*str != '\0')
		{
			*point++ = *str++;
		}
		*point = '\0';

		for (obj = ch->in_room->first_content; obj; obj = obj_next)
		{
			obj_next = obj->next_content;
			if (HAS_TRIGGER_OBJ(obj, TRIG_ACT))
				p_act_trigger(buf, NULL, obj, NULL, ch, NULL, NULL, TRIG_ACT);
		}

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

			for (obj = tch->first_carrying; obj; obj = obj_next)
			{
				obj_next = obj->next_content;
				if (HAS_TRIGGER_OBJ(obj, TRIG_ACT))
					p_act_trigger(buf, NULL, obj, NULL, ch, NULL, NULL,
								  TRIG_ACT);
			}
		}

		if (HAS_TRIGGER_ROOM(ch->in_room, TRIG_ACT))
			p_act_trigger(buf, NULL, NULL, ch->in_room, ch, NULL, NULL,
						  TRIG_ACT);
	}

	return;
}

const char *perform_act_string(const char *orig, CHAR_DATA * ch,
							   const void *arg1, const void *arg2, bool cReturn)
{
	static char *const he_she[] = { "it", "he", "she" };
	static char *const him_her[] = { "it", "him", "her" };
	static char *const his_her[] = { "its", "his", "her" };
	CHAR_DATA *vch = (CHAR_DATA *) arg2;
	OBJ_DATA *obj1 = (OBJ_DATA *) arg1;
	OBJ_DATA *obj2 = (OBJ_DATA *) arg2;
	const char *str, *i = NULL;
	char *point;
	static char buf[MSL];

	point = buf;
	str = orig;

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

		++str;
		i = "<@@@>";
		if (!arg2 && *str >= 'A' && *str <= 'Z')
		{
			logf("perform_act:missing arg2 for code %d.", *str);
			i = " <@@@> ";
		}
		else
		{
			switch (*str)
			{
			default:
				logf("perform_act:bad code %c.", *str);
				i = " <@@@> ";
				break;

			case '$':
				i = "$";
				break;
			case 't':
				if (arg1)
				{
					i = (const char *) arg1;
				}
				else
					log_string("perform_act:bad code $t for 'arg1'");
				break;
			case 'T':
				if (arg2)
					i = (const char *) arg2;
				else
					log_string("perform_act:bad code $T for 'arg2'");
				break;
			case 'n':
				if (ch)
					i = IS_NPC(ch) ? ch->short_descr : ch->name;
				else
					log_string("perform_act:bad code $n for 'ch'");
				break;
			case 'N':
				if (vch)
					i = IS_NPC(vch) ? vch->short_descr : vch->name;
				else
					log_string("perform_act:bad code $N for 'vch'");
				break;
			case 'e':
				if (ch)
					i = he_she[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $e for 'ch'");
				break;
			case 'E':
				if (vch)
					i = he_she[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $E for 'vch'");
				break;
			case 'm':
				if (ch)
					i = him_her[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $m for 'ch'");
				break;
			case 'M':
				if (vch)
					i = him_her[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $M for 'vch'");
				break;
			case 's':
				if (ch)
					i = his_her[URANGE(0, ch->sex, 2)];
				else
					log_string("perform_act:bad code $s for 'ch'");
				break;
			case 'S':
				if (vch)
					i = his_her[URANGE(0, vch->sex, 2)];
				else
					log_string("perform_act:bad code $S for 'vch'");
				break;
			case 'g':
				if (ch && ch->deity != NULL)
					i = ch->deity->name;
				else
					log_string("perform_act:bad code $g for 'ch'");
				break;
			case 'G':
				if (vch && vch->deity != NULL)
					i = vch->deity->name;
				else
					log_string("perform_act:bad code $G for 'vch'");
				break;
			case 'c':
				if (ch && ch->clan != NULL)
					i = ch->clan->name;
				else
					log_string("perform_act:bad code $c for 'ch'");
				break;
			case 'C':
				if (vch && vch->clan != NULL)
					i = vch->clan->name;
				else
					log_string("perform_act:bad code $C for 'vch'");
				break;
			case 'o':
				if (obj1)
					i = fname(obj1->name);
				else
					log_string("perform_act:bad code $o for 'to' and 'obj1'");
				break;

			case 'O':
				if (obj2)
					i = fname(obj2->name);
				else
					log_string("perform_act:bad code $O for 'obj2'");
				break;

			case 'p':
				if (obj1)
					i = obj1->short_descr;
				else
					log_string("perform_act:bad code $p for 'obj1'");
				break;

			case 'P':
				if (obj2)
					i = obj2->short_descr;
				else
					log_string("perform_act:bad code $P for 'obj2'");
				break;

			case 'd':
				if (arg2 == NULL || ((const char *) arg2)[0] == '\0')
				{
					i = "door";
				}
				else
				{
					char name[MIL];

					one_argument((const char *) arg2, name);
					i = name;
				}
				break;
			}
		}

		++str;
		while ((*point = *i) != '\0')
			++point, ++i;
	}

	*point++ = '{';
	*point++ = 'x';
	if (cReturn)
	{
		*point++ = '\n';
		*point++ = '\r';
	}
	*point = '\0';
	buf[0] = UPPER(buf[0]);

	return buf;
}

#define SENDOK(ch, type)    ((IS_NPC(ch) || ((ch)->desc && (ch->desc->connected == CON_PLAYING))) \
            && ((ch)->position >= min_pos))

void act_new(const char *format, CHAR_DATA * ch, const void *arg1,
			 const void *arg2, flag_t type, position_t min_pos)
{
	DESCRIPTOR_DATA *d;
	ROOM_INDEX_DATA *room;
	CHAR_DATA *to = (CHAR_DATA *) arg2;

	if (!format || !*format)
		return;

	if (IS_SET(type, TO_CHAR))
	{
		if (ch && SENDOK(ch, type))
			perform_act(format, ch, arg1, arg2, type, ch);
	}

	if (IS_SET(type, TO_VICT))
	{
		if (to && SENDOK(to, type) && to != ch)
			perform_act(format, ch, arg1, arg2, type, to);
	}

	if (IS_SET(type, TO_ZONE | TO_ALL))
	{
		for (d = descriptor_first; d; d = d->next)
		{
			CHAR_DATA *vch = CH(d);

			if (vch && SENDOK(vch, type) && (vch != ch)
				&&
				((IS_SET
				  (type, TO_ALL) || (vch->in_room
									 && vch->in_room->area ==
									 ch->in_room->area))))
				perform_act(format, ch, arg1, arg2, type, vch);
		}
	}

	if (IS_SET(type, TO_ROOM | TO_NOTVICT))
	{
		OBJ_DATA *obj1 = (OBJ_DATA *) arg1;
		OBJ_DATA *obj2 = (OBJ_DATA *) arg2;

		if (ch && ch->in_room != NULL)
			room = ch->in_room;
		else if (obj1 && obj1->in_room != NULL)
			room = obj1->in_room;
		else if (obj2 && obj2->in_room != NULL)
			room = obj2->in_room;
		else
		{
			bugf("no valid target '%s'", format);
			return;
		}
		for (to = room->first_person; to; to = to->next_in_room)
		{
			if (SENDOK(to, type) && (to != ch)
				&& (IS_SET(type, TO_ROOM) || (to != (CHAR_DATA *) arg2)))
				perform_act(format, ch, arg1, arg2, type, to);
		}
	}
	return;
}

void chprintf(CHAR_DATA * ch, const char *fmt, ...)
{
	char buf[MAX_STRING_LENGTH * 2];
	va_list args;
	int length;

	if (IS_NULLSTR(fmt) || !ch->desc)
		return;

	va_start(args, fmt);
	length = vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	d_print(ch->desc, buf, length);
}

void chprintlnf(CHAR_DATA * ch, const char *fmt, ...)
{
	if (!ch || !ch->desc)
		return;

	if (!IS_NULLSTR(fmt))
	{
		char buf[MAX_STRING_LENGTH * 2];
		va_list args;
		int length;

		va_start(args, fmt);
		length = vsnprintf(buf, sizeof(buf), fmt, args);
		va_end(args);

		d_print(ch->desc, buf, length);
	}
	d_print(ch->desc, "\n\r", 2);
}

void bugf(const char *fmt, ...)
{
	char buf[2 * MSL];
	va_list args;

	if (IS_NULLSTR(fmt))
		return;

	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	bug(buf);
}

void logf(const char *fmt, ...)
{
	char buf[2 * MSL];
	va_list args;

	if (IS_NULLSTR(fmt))
		return;

	va_start(args, fmt);
	vsnprintf(buf, sizeof(buf), fmt, args);
	va_end(args);

	log_string(buf);
}

void set_game_levels(int Old, int New)
{
	CMD_DATA *cmd;
	HELP_DATA *pHelp;
	OBJ_INDEX_DATA *pObj;
	MOB_INDEX_DATA *pMob;
	BAN_DATA *ban;
	DISABLED_DATA *d;
	MBR_DATA *mbr;
	int hash, sn, x;
	int diff = MAX_LEVEL - LEVEL_IMMORTAL;
	int imm_level = Old - diff;
	int mod = New - Old;

	logf("Old Imm Level = %d, Old Max Level = %d.", imm_level, Old);
	logf("New Imm Level = %d, New Max Level = %d.", New - diff, New);

	for (cmd = cmd_first; cmd; cmd = cmd->next)
	{
		if (cmd->level >= imm_level)
			cmd->level += mod;
	}
	rw_commands(action_write);
	for (pHelp = help_first; pHelp; pHelp = pHelp->next)
	{
		if (pHelp->level >= imm_level)
			pHelp->level += mod;
	}
	save_helps();
	for (hash = 0; hash < MAX_KEY_HASH; hash++)
	{
		for (pMob = mob_index_hash[hash]; pMob; pMob = pMob->next)
		{
			if (pMob->level >= imm_level)
			{
				pMob->level += mod;
				SET_BIT(pMob->area->area_flags, AREA_CHANGED);
			}
		}
		for (pObj = obj_index_hash[hash]; pObj; pObj = pObj->next)
		{
			if (pObj->level >= imm_level)
			{
				pObj->level += mod;
				SET_BIT(pObj->area->area_flags, AREA_CHANGED);
			}
		}
	}
	do_asave(NULL, "changed");
	for (ban = ban_first; ban; ban = ban->next)
	{
		if (ban->level >= imm_level)
			ban->level += mod;
	}
	rw_bans(action_write);
	for (d = disabled_first; d; d = d->next)
	{
		if (d->level >= imm_level)
			d->level += mod;
	}
	save_disabled();
	for (sn = 0; sn < maxSkill; sn++)
	{
		for (x = 0; x < maxClass; x++)
		{
			if (skill_table[sn].skill_level[x] >= imm_level)
				skill_table[sn].skill_level[x] += mod;
		}
	}
	rw_skills(action_write);
	for (mbr = mbr_first; mbr; mbr = mbr->next)
	{
		if (mbr->level >= imm_level)
			mbr->level += mod;
	}
	rw_members(action_write);
}

/* wrap text to screen length.  Ignore's new lines. 
   Good for presenting long strings of text. */
#if defined(__cplusplus)
// a c++ example
void DESCRIPTOR_DATA::wrap(const char *buf)
{
	DESCRIPTOR_DATA *d = this;
#else
void dwrap(DESCRIPTOR_DATA * d, const char *buf)
{
#endif
	static char buffer[MSL * 5];
	static char out[MSL * 5];
	int width;
	unsigned int pos;
	char *p;

	if (!d)
		return;

	width = SCR_WIDTH(d);

	sprintf(buffer, "%s", buf);
	reformat_desc(buffer);

	p = &buffer[0];
	pos = 0;

	out[0] = NUL;

	do
	{
		pos = get_line(p, width);
		if (pos > 0)
		{
			strncat(out, p, pos);
			p += pos;
		}
		else
		{
			strcat(out, p);
			break;
		}
		strcat(out, "\n\r");
	}
	while (TRUE);

	d_print(d, out, 0);
}

void dwrapln(DESCRIPTOR_DATA * d, const char *buf)
{
	if (!d)
		return;

	dwrap(d, buf);
	d_print(d, "\n\r", 2);
}

void dwrapf(DESCRIPTOR_DATA * d, const char *buf, ...)
{
	va_list args;
	char format[MSL * 5];

	if (!d || IS_NULLSTR(buf))
		return;

	va_start(args, buf);
	vsnprintf(format, sizeof(format), buf, args);
	va_end(args);

	dwrap(d, format);
}

void dwraplnf(DESCRIPTOR_DATA * d, const char *buf, ...)
{
	if (!d)
		return;

	if (!IS_NULLSTR(buf))
	{
		va_list args;
		char format[MSL * 5];

		va_start(args, buf);
		vsnprintf(format, sizeof(format), buf, args);
		va_end(args);

		dwrap(d, format);
	}
	d_print(d, "\n\r", 2);
	return;
}