daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(		    DalekenMUD 1.12 (C) 2000, 2001, 2002		)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael	 ||
    || Chastain, Michael Quan, and Mitchell Tse.			 ||
    || Original Diku Mud copyright (C) 1990, 1991			 ||
    || by Sebastian Hammer, Michael Seifert, Hans Henrik St{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  Much time and thought has gone into this software and	 ||
    || you are benefitting. We hope that you share your changes too.	 ||
    || What goes around, comes around.					 ||
    || ----------------------------------------------------------------- ||
    ||                           string.c                                ||
    || As distributed with OLC, based on The Isles 1.1 source code.      ||
    || The colour interpreter based on that distributed with CircleMUD.  ||
 *_/<>\_________________________________________________________________/<>\_*/


#include <stdarg.h>
#if defined( unix )
# include <unistd.h>
# include <sys/wait.h>
#endif
#include "mud.h"
#include "regexp.h"

#if defined( HAVE_VSNPRINTF )
int vsnprintf		args( ( char *str, size_t n,
				const char *format, va_list ap ) );
#endif

#if defined( HAVE_ISPELL )
void ispell_string	args( ( CHAR_DATA *ch, const char *str ) );
#endif


#define BUILDER_XP 17	/* 17 xp per 100 characters */

/*
 * Colour codes.
 */
int is_colour		args( ( char code ) );

/*
 * Normal.
 */
#define CNRM  "\x1B[0;0m"

/*
 * Normal Colours.
 */
#define CBLK  "\x1B[0;30m"
#define CRED  "\x1B[0;31m"
#define CGRN  "\x1B[0;32m"
#define CYEL  "\x1B[0;33m"
#define CBLU  "\x1B[0;34m"
#define CMAG  "\x1B[0;35m"
#define CCYN  "\x1B[0;36m"
#define CWHT  "\x1B[0;37m"
#define CNUL  ""

/*
 * Bolds.
 */
#define BRED  "\x1B[1;31m"
#define BGRN  "\x1B[1;32m"
#define BYEL  "\x1B[1;33m"
#define BBLU  "\x1B[1;34m"
#define BMAG  "\x1B[1;35m"
#define BCYN  "\x1B[1;36m"
#define BWHT  "\x1B[1;37m"
#define BBLK  "\x1B[1;30m"

/*
 * Backgrounds
 */
#define BKRED  "\x1B[41m"
#define BKGRN  "\x1B[42m"
#define BKYEL  "\x1B[43m"
#define BKBLU  "\x1B[44m"
#define BKMAG  "\x1B[45m"
#define BKCYN  "\x1B[46m"
#define BKWHT  "\x1B[47m"
#define BKBLK  "\x1B[40m"

/*
 * Special codes.
 */
#define CUDL  "\x1B[4m"		/* Underline ANSI code */
#define CFSH  "\x1B[5m"		/* Flashing ANSI code.	Change to "" if
				 * you want to disable flashing colour codes
				 */
#define CRVS  "\x1B[7m"		/* Reverse video ANSI code */

/*
 * Special characters.
 */
#define CAMP  "&"
#define CSLH  "\\"
#define CTIL  "~"
#define CCR   "\n"
#define BELL  "\a"
#define BARLN "|"

#define MAX_COLOURS 34

const char *COLOURLIST[] = {
    CNRM,
    CRED, CGRN, CYEL, CBLU, CMAG, CCYN, CWHT, CBLK,
    BRED, BGRN, BYEL, BBLU, BMAG, BCYN, BWHT, BBLK,
    BKRED, BKGRN, BKYEL, BKBLU, BKMAG, BKCYN, BKWHT, BKBLK,
    CFSH, CRVS, CUDL,
    CAMP, CSLH, CTIL, CCR, BELL, BARLN
};


/*****************************************************************************
 Name:		string_edit
 Purpose:	Puts player into append mode for given string.
 Called by:	(many)olc_act.c
 ****************************************************************************/
void string_edit( CHAR_DATA *ch, char **pString )
{
    int len;

    send_to_char( "  &w--<[====]>--<[ &bString Editor&w ]>--<[====]>--\n\r"
		  "     [ &g Type &r.h&g on a new line for help. &w ]\n\r"
		  "     [ &r@&g or &r~&g on a blank line to finish.&w ]\n\r"
		  "  --<[===================================]>--&n\n\r", ch );

    if( *pString == NULL )
    {
	*pString = str_dup( "" );
    }
    send_to_char( *pString, ch );

    len = strlen( *pString );
    if( *( *pString + len - 1 ) != '\r' )
	send_to_char( "\n\r", ch );

    if( len > 0 && ch->class == CLASS_BUILDER
	&& *pString != ch->description
	&& ( !ch->pcdata->note || *pString != ch->pcdata->note->text ) )
    {
	char buf[ 256 ];

	sprintf( buf, "&YYou lose %d.%2.2d experience points.&n\n\r",
		 ( BUILDER_XP * len ) / 100, ( BUILDER_XP * len ) % 100 );
	send_to_char( buf, ch );
	gain_exp( ch, -1 * BUILDER_XP * len );
    }

    ch->desc->pString = pString;
    return;
}


/*****************************************************************************
 Name:		string_replace
 Purpose:	Substitutes one string for another.
 Called by:	string_add (string.c) aedit_builder (olc_act.c).
 ****************************************************************************/
int string_replace( char **orig, const char *old, const char *new, bool fAll )
{
    char xbuf[MAX_STRING_LENGTH * 3];
    char temp, *substr, *start;
    int count = 0;

    xbuf[0] = '\0';
    start = substr = *orig;
    if( fAll )
    {
	while( ( substr = strstr( start, old ) ) )
	{
	    count++;
	    temp = *substr;
	    *substr = '\0';

	    if( strlen( xbuf ) + strlen( new ) + strlen( start )
		>= MAX_STRING_LENGTH * 3 - 4 )
	    {
		count = -1;
		break;
	    }

	    strcat( xbuf, start );
	    strcat( xbuf, new );
	    *substr = temp;

	    substr += strlen( old );
	    start = substr;
	}
	strcat( xbuf, start );
    }
    else
    {
	if( ( substr = strstr( start, old ) ) )
	{
	    if( strlen( start ) + strlen( new ) + strlen( start )
		>= MAX_STRING_LENGTH * 3 - 4 )
		return -1;

	    count++;
	    temp = *substr;
	    *substr = '\0';
	    strcat( xbuf, start );
	    strcat( xbuf, new );
	    strcat( xbuf, substr + strlen( old ) );
	    *substr = temp;
	}
    }
    if( count > 0 )
    {
	free_string( *orig );
	*orig = str_dup( xbuf );
    }

    return count;
}


/*****************************************************************************
 Name:		regex_replace
 Purpose:	Substitutes one string for another.
 Called by:	string_add (string.c) aedit_builder (olc_act.c).
 ****************************************************************************/
int regex_replace( char **orig, char *pat, char *new, bool fAll )
{
    int count = 0;
    regexp *preg;
    int len;
    char *origpt = *orig;
    char frombuf[MAX_STRING_LENGTH];
    char *frompt;
    char tobuf[MAX_STRING_LENGTH];
    char *topt = &tobuf[0];
    int space = MAX_STRING_LENGTH;

    if( strlen( *orig ) > MAX_STRING_LENGTH - 4 )
    {
	regerror( "String too long" );
	return -1;
    }

    if( !( preg = regcomp( pat, 1 ) ) )
	return -1;

    for( ;; )
    {
	len = strchr( origpt, '\n' ) - origpt;
	if( len < 0 )
	    break;
	strncpy( frombuf, origpt, len );
	frombuf[len] = '\0';
	origpt += len + 2;		/* to the next line. */

	frompt = &frombuf[0];

	if( regexec( preg, frompt ) )
	    do
	    {
		int diff;
		char *old;

		count++;
		/*
		 * Copy leading text.
		 */
		diff = preg->startp[0] - frompt;
		if( ( space -= diff ) < 0 )
		    return -1;
		strncpy( topt, frompt, diff );
		topt += diff;

		/* Substitution */
		old = topt;
		topt = regsub( preg, new, topt, space );
		if( !topt || ( space -= topt - old ) < 0 ) /* amylaar */
		    return -1;
		if( frompt == preg->endp[0])
		{	/* amylaar : prevent infinite loop */
		    if( !*frompt )
			break;
		    if( --space < 0 )
		    {
			regerror( "Ran out of space" );
			return -1;
		    }
		    *topt++ = *frompt++;
		}
		else
		    frompt = preg->endp[0];
	    }
	    while( fAll && !preg->reganch && regexec( preg, frompt ) );

	/*
	 * Add the end of the line.
	 */
	if( ( space -= strlen( frompt ) + 1 ) < 0 )
	{
	    regerror( "Ran out of space" );
	    return -1;
	}
	strcpy( topt, frompt );
	topt += strlen( frompt );
	*topt++ = '\n';
	*topt++ = '\r';
    }

    if( count )
    {
	free_string( *orig );
	*orig = str_dup( tobuf );
    }
    free( preg );
    return count;
}


/* OLC 1.1b */
/*****************************************************************************
  Name:		string_add
  Purpose:	Interpreter for string editing.
  Called by:	game_loop_xxxx (comm.c) .
  ****************************************************************************/
void string_add( CHAR_DATA *ch, const char *argument )
{
    char buf[MAX_STRING_LENGTH];

    if( *argument == '@' || *argument == '~' )
    {
	if( ch->class == CLASS_BUILDER
	    && *ch->desc->pString != ch->description
	    && ( !ch->pcdata->note || *ch->desc->pString != ch->pcdata->note->text ) )
	{
	    sprintf( buf, "&YYou receive %d.%2.2d experience points.&n\n\r",
		     ( BUILDER_XP * strlen( *ch->desc->pString ) ) / 100,
		     ( BUILDER_XP * strlen( *ch->desc->pString ) ) % 100 );
	    send_to_char( buf, ch );
	    gain_exp( ch, BUILDER_XP * strlen( *ch->desc->pString ) );
	}
	ch->desc->pString = NULL;
	return;
    }

    if( *argument == '.' )
    {
	char arg1[MAX_INPUT_LENGTH];

	argument = first_arg( argument, arg1, FALSE );

	switch( arg1[1] )
	{
	case 'c': case 'C':
	    send_to_char( "String cleared.\n\r", ch );
	    free_string( *ch->desc->pString );
	    *ch->desc->pString = str_dup( "" );
	    return;

	case 'e': case 'E':
	    send_to_char( "External Command:\n\r", ch );
	    interpret( ch, argument );
	    return;

	case 'f': case 'F':
	    *ch->desc->pString = format_string( *ch->desc->pString );
	    send_to_char( "String formatted.\n\r", ch );
	    return;

	case 'h': case 'H':
	    send_to_char(
		"StrEdit help (commands on blank line):\n\r"
		".c		 - clear string so far \n\r"
		".d<xnum>	 - delete line number <xnum>\n\r"
		".e <command>	 - external command\n\r"
		".f		 - format (word wrap) string\n\r"
		".h		 - get help (this info)\n\r"
		".i<num> <str>	 - insert string at line <num>\n\r"
		".r 'old' 'new'	 - replace a substring\n\r"
		".ra 'old' 'new' - replace ALL substrings\n\r"
		".rr 'pat' 'str' - replace using regular expressions\n\r"
		"		   (requires '' or \"\",\n\r"
		"		   use $^ for end of line)\n\r"
		".s[n]		 - show string so far\n\r"
		".l[n]		 - show with line numbers\n\r"
		"		   (.sn or .ln shows raw colour codes)\n\r"
#if defined( HAVE_ISPELL )
		".p              - ispell\n\r"
#endif
		"@		 - end string\n\r", ch );
	    return;

	case 's': case 'S':
	{
	    char buf[MAX_STRING_LENGTH];
	    char *p;
	    int i;

	    if( arg1[2] != 'n' && arg1[2] != 'N' )
	    {
		send_to_char( "String so far:\n\r", ch );
		send_to_char( *ch->desc->pString, ch );
		return;
	    }
	    i = 0;
	    p = *ch->desc->pString;
	    while( *p && i < MAX_STRING_LENGTH )
	    {
		if( *p == '&' )
		    buf[i++] = '&';
		buf[i++] = *p++;
	    }
	    buf[i] = '\0';
	    send_to_char( buf, ch );
	    return;
	}

	case 'i': case 'I':
	{
	    int num;
	    char *p1, chsave;
	    char buf[MAX_STRING_LENGTH];
	    char buf1[MAX_STRING_LENGTH];

	    if( !is_number( &arg1[2] ) )
	    {
		send_to_char( "Usage: .i<linenum> <string>\n\r", ch );
		return;
	    }
	    num = atoi( &arg1[2] );
	    strcpy( buf, *ch->desc->pString );
	    for( p1 = &buf[0]; p1 && *p1 && --num > 0; )
		p1 = strchr( p1 + 1, '\n' );
	    if( !p1 || !*p1 )
	    {
		send_to_char( "The string isn't that long.\n\r", ch );
		return;
	    }
	    if( p1 != &buf[0] )
	    {
		if( *p1 == '\n' )	p1++;
		if( *p1 == '\r' )	p1++;
	    }

	    chsave = *p1;
	    *p1 = '\0';
	    strcpy( buf1, buf );
	    *p1 = chsave;
	    strcat( buf1, argument );
	    strcat( buf1, "\n\r" );
	    strcat( buf1, p1 );
	    free_string( *ch->desc->pString );
	    *ch->desc->pString = str_dup( buf1 );
	    send_to_char( "Line Inserted.\n\r", ch );
	    return;
	}

	case 'd': case 'D':
	{
	    int num;
	    char *p1, *p2;
	    char buf[MAX_STRING_LENGTH * 2];

	    if( !is_number( &arg1[2] ) )
	    {
		send_to_char( "Usage: .d<xnumber>\n\r", ch );
		return;
	    }
	    num = atoi( &arg1[2] );
	    if( num < 0 )
	    {
		send_to_char( ".d<xnumber> : Try using a positive number.\n\r",
			      ch );
		return;
	    }
	    strcpy( buf, *ch->desc->pString );
	    for( p1 = &buf[0]; p1 && *p1 && --num > 0; )
		p1 = strchr( p1 + 1, '\n' );
	    if( !p1 || !*p1 )
	    {
		send_to_char( "The string isn't that long.\n\r", ch );
		return;
	    }
	    if( p1 != &buf[0] )
	    {
		if( *p1 == '\n' )	p1++;
		if( *p1 == '\r' )	p1++;
	    }
	    p2 = strchr( p1, '\n' );
	    if( !p2 )	p2 = strchr( buf, '\0' );
	    if( *p2 == '\n' )	p2++;
	    if( *p2 == '\r' )	p2++;
	    strcpy( p1, p2 );
	    free_string( *ch->desc->pString );
	    *ch->desc->pString = str_dup( buf );
	    send_to_char( "Line deleted.\n\r", ch );
	    return;
	}

	case 'l': case 'L':
	{
	    char buf1[MAX_STRING_LENGTH * 2];
	    char *p;
	    char *i;
	    int line = 1;

	    send_to_char( "String so far:\n\r", ch );
	    strcpy( buf1, "1  : " );
	    i = strchr( buf1, '\0' );
	    for( p = *ch->desc->pString; p && *p; p++ )
	    {
		if( *p == '\r' )
		{
		    /* ignore */
		}
		else if( *p == '\n' )
		{
		    *i++ = '\n';
		    *i++ = '\r';
		    line++;
		    sprintf( i, "%-3d: ", line );
		    i = strchr( i, '\0' );
		}
		else if( *p == '&' && ( arg1[2] == 'n' || arg1[2] == 'N' ) )
		{
		    *i++ = '&';
		    *i++ = '&';
		}
		else
		    *i++ = *p;
	    }
	    i = strrchr( buf1, '\r' );
	    if( i == NULL )
		strcpy( buf1, "\n\r" );
	    else
		strcpy( i, "\r&n" );

	    send_to_char( buf1, ch );
	    return;
	}

	case 'r': case 'R':
	{
	    bool fAll = FALSE;
	    bool fRegex = FALSE;
	    int replaced;
	    int i;
	    char *str;
	    char arg2[MAX_INPUT_LENGTH];
	    char arg3[MAX_INPUT_LENGTH];

	    argument = first_arg( argument, arg2, FALSE );
	    first_arg( argument, arg3, FALSE );
	    if( arg2[0] == '\0' )
	    {
		send_to_char(
		    "usage:  .r[a][r] \"old string\" \"new string\"\n\r", ch );
		return;
	    }
	    for( i = 2; i < 5 && arg1[i] != '\0'; ++i )
	    {
		if( UPPER( arg1[i] ) == 'A' )
		    fAll = TRUE;
		else if( UPPER( arg1[i] ) == 'R' )
		    fRegex = TRUE;
	    }

	    str = arg2;
	    while( ( str = strstr( str, "$^" ) ) )
	    {
		*str++ = '\n';
		*str++ = '\r';
	    }
	    str = arg3;
	    while( ( str = strstr( str, "$^" ) ) )
	    {
		*str++ = '\n';
		*str++ = '\r';
	    }
	    if( fRegex )
		replaced = regex_replace( ch->desc->pString,
					  arg2, arg3, fAll );
	    else
		replaced = string_replace( ch->desc->pString,
					   arg2, arg3, fAll );
	    if( replaced > 0 )
		sprintf( buf, "Replaced %d lot%s of '&g%s&n' with '&y%s&n'.\n\r",
			 replaced, ( replaced > 1 ) ? "s" : "", arg2, arg3 );
	    else if( replaced == 0 )
		strcpy( buf, "No pattern matching that was found.\n\r" );
	    else if( fRegex && regexp_error )
	    {
		sprintf( buf, "REGEX error: %s.\n\r", regexp_error );
		regexp_error = NULL;
	    }
	    else
		strcpy( buf, "The string became too long.\n\r" );
	    send_to_char( buf, ch );
	    return;
	}

	case 'p': case 'P':
#if defined( HAVE_ISPELL )
	    ispell_string( ch, *ch->desc->pString );
#else
	    send_to_char( "ISpell has been disabled on this server.\n\r", ch );
#endif
	    return;
	}
    }

    /*
     * Truncate strings to MAX_STRING_LENGTH.
     * --------------------------------------
     */
    if( strlen( buf ) + strlen( argument ) >= ( MAX_STRING_LENGTH - 4 ) )
    {
	send_to_char( "String too long, last line skipped.\n\r", ch );

	/* Force character out of editing mode. */
	ch->desc->pString = NULL;
	return;
    }

    strcpy( buf, *ch->desc->pString );
    strcat( buf, argument );
    strcat( buf, "\n\r" );
    free_string( *ch->desc->pString );
    *ch->desc->pString = str_dup( buf );
    return;
}


/*****************************************************************************
 Name:		format_string
 Purpose:	Special string formating and word-wrapping.
 Called by:	string_add (string.c) [many] (olc_act.c)
 ****************************************************************************/
char *format_string( char *oldstring /*, bool fSpace */ )
{
    const int MAX_LINE_LENGTH = 77;
    char xbuf[MAX_STRING_LENGTH];
    char *wptr = &xbuf[0];

    char *lastwordstart = NULL;
    int linelength = 0;
    bool newsentence = TRUE;
    int endoflines = 0;

    while( *oldstring )
    {
	if( *oldstring == '&' )
	{
	    *wptr++ = *oldstring++;
	    if( isprint( COLOURLIST[is_colour( *oldstring )][0] ) )
	    {
		linelength++;
	    }
	    *wptr++ = *oldstring;

	    endoflines = 0;
	}
	else if( isspace( *oldstring ) )
	{
	    if( *oldstring == '\r' )
		;
	    else if( *oldstring == '\n' && endoflines )
	    {
		strncpy( wptr, "\n\r\n\r", endoflines * 2 );
		wptr += endoflines * 2;

		endoflines = 1;
		linelength = 0;
		newsentence = TRUE;
	    }
	    else
	    {
		/* Only set this for the first '\n' so multiple '\n's don't
		 * cause extra end-of-line sequences to be added. */
		if( *oldstring == '\n' )
		    endoflines = 2;

		if( wptr == &xbuf[0] || !isspace( *(wptr - 1) ) )
		{
		    if( newsentence )
		    {
			*wptr++ = ' ';
			linelength++;
		    }
		    *wptr++ = ' ';
		    linelength++;
		}
	    }

	    lastwordstart = oldstring + 1;
	}
	else
	{
	    if( newsentence && isalpha( *oldstring ) )
	    {
		*wptr++ = UPPER( *oldstring );
		newsentence = FALSE;
	    }
	    else
	    {
		*wptr++ = *oldstring;
	    }
	    linelength++;

	    if( *oldstring == '.' || *oldstring == '?' || *oldstring == '!' )
		newsentence = TRUE;
	    endoflines = 0;
	}

	if( linelength >= MAX_LINE_LENGTH )
	{
	    if( lastwordstart )
	    {
		oldstring = lastwordstart;
		do
		{
		    --wptr;
		}
		while( !isspace( *wptr ) );

		if( wptr > &xbuf[0] && isspace( *(wptr - 1) ) )
		{
		    --wptr;
		}
	    }
	    else
	    {
		oldstring -= 2;
		wptr -= 2;
		*wptr++ = '-';
	    }
	    *wptr++ = '\n';
	    *wptr++ = '\r';

	    linelength = 0;
	    endoflines = 1;
	    lastwordstart = NULL;
	}
	else
	    oldstring++;
    }
    while( wptr > &xbuf[0] && isspace( *(wptr - 1) ) )
	wptr--;
    *wptr++ = '\n';
    *wptr++ = '\r';
    *wptr = '\0';

    free_string( oldstring );
    return ( str_dup( xbuf ) );
}


#if defined( HAVE_ISPELL )
/* Ispell, based on code by Erwin S. Andreasen, mostly just a straight copy. */
static FILE *ispell_out;
static int ispell_pid = -1;
static int to[2], from[2];

#define ISPELL_BUF_SIZE 1024

void ispell_init()
{
    char ignore_buf[1024];

    if( !IS_SET( SysInfo->flags, SYSINFO_ISPELL ) )
    {
	ispell_pid = -1;
	return;
    }

    pipe( to );
    pipe( from );

    ispell_pid = fork();
    if( ispell_pid < 0 )
    {
	bug( "ispell_init: fork." );
    }
    else if( ispell_pid == 0 ) /* child */
    {
	int i;

	dup2( to[0], 0 ); /* this is where we read commands from - (stdin) */
	close( to[0] );
	close( to[1] );
	dup2( from[1], 1 ); /* this is where we write stuff to - (stdout) */
	close( from[0] );
	close( from[1] );

	/* Close all the other files */
	for( i = 2; i < 255; i++ )
	    close( i );

	if( SysInfo->dictionary && SysInfo->dictionary[0] )
	{
	    sprintf( ignore_buf, "-p%s", SysInfo->dictionary );
	    execlp( "ispell", "ispell", "-a", ignore_buf,
		    (char*) NULL );
	}
	else
	    execlp( "ispell", "ispell", "-a", (char*) NULL );

	exit( 1 );
    }
    else /* ok !*/
    {
	close( to[0] );
	close( from[1] );
	ispell_out = fdopen( to[1], "w" );
	setbuf( ispell_out, NULL );
	fprintf( ispell_out, "+\n" ); /* terse output, less to worry about */
#if !defined( __sun ) /* that ispell on sun gives no (c) msg */
	read( from[0], ignore_buf, 1024 );
#endif
    }
}


void ispell_done()
{
    if( ispell_pid != -1 )
    {
	fprintf( ispell_out, "#\n" );
	fclose( ispell_out );
	close( from[0] );
	waitpid( ispell_pid, NULL, 0 );
	ispell_pid = -1;
    }
}


char *get_ispell_line( char *buf, const char *word )
{
    char buf2[MAX_INPUT_LENGTH + 10];
    int len;

    if( ispell_pid == -1 )
	return NULL;

    if( word )
    {
	fprintf( ispell_out, "^%s\n", word );
	fflush( ispell_out );
    }

    len = read (from[0], buf2, ISPELL_BUF_SIZE);
    buf2[len] = '\0';

    /* Read up to max 1024 characters here */
    if( sscanf( buf2, "%1024[^\n]\n\n", buf ) != 1 )
	return NULL;

    return buf;
}


void do_ispell( CHAR_DATA *ch, const char *argument )
{
    const char *pc;
    char buf[MAX_STRING_LENGTH];

    if( ispell_pid <= 0 )
    {
	send_to_char( "ispell is not running.\n\r", ch );
	return;
    }

    if( !argument[0] || strchr( argument, ' ' ) )
    {
	send_to_char( "Invalid input.\n\r", ch );
	return;
    }

    if( argument[0] == '+' )
    {
	if( !authorized( get_char( ch ), "ispell" ) )
	    return;
	for( pc = argument + 1; *pc; pc++ )
	    if( !isalpha( *pc ) )
	    {
		charprintf( ch, "'%c' is not a letter.\n\r", *pc );
		return;
	    }
	fprintf( ispell_out, "*%s\n", argument + 1 );
	fflush( ispell_out );
	charprintf( ch, "Word '%s' added to system dictionary.\n\r",
		    argument + 1 );
	send_to_char( "If you didn't mean to add that, please inform admin.\n\r", ch );
	log_string( "%s added the word '%s' to the dictionary.",
		   get_char( ch )->name, argument + 1 );
	return; /* we assume everything is OK.. better be so! */
    }

    pc = get_ispell_line( buf, argument );
    if( !pc )
    {
	send_to_char( "ispell: failed.\n\r", ch );
	return;
    }

    switch( pc[0] )
    {
    case '*':
    case '+': /* root */
    case '-': /* compound */
	send_to_char( "Correct.\n\r", ch );
	break;

    case '&': /* miss */
    case '?': /* guess */
	charprintf( ch, "Not found. Possible words: %s\n\r",
		    strchr( pc, ':' ) + 1 );
	break;

    case '#': /* none */
	send_to_char( "Unable to find anything that matches.\n\r", ch );
	break;

    default:
	charprintf( ch, "Weird output from ispell: %s\n\r", pc );
    }
}


void ispell_string( CHAR_DATA *ch, const char *str )
{
    char buf[MAX_STRING_LENGTH];
    char word[MAX_INPUT_LENGTH];
    char *wordp;
    const char *p, *q;
    int len, i;

    if( ispell_pid <= 0 )
    {
	send_to_char( "ispell isn't running.\n\r", ch );
	return;
    }

    for( q = str; ( p = strchr( q, '\n' ) ); q = p + 2 )
    {
	strncpy( buf, q, p - q );
	buf[p - q] = '\0';
	fprintf( ispell_out, "^%s\n", buf );
    }
    fflush( ispell_out );

    len = read( from[0], buf, MAX_STRING_LENGTH - 2 );
    buf[len] = '\0';
    for( i = 0; i < len; ++i )
    {
	if( buf[i] == '\n' )
	    buf[i] = '\0';
    }
    for( i = 0; i < len; ++i )
    {
	switch( buf[i] )
	{
	case '*': /* correct */
	case '+': /* root */
	case '-': /* compound */
	case '\0':
	    break;

	case '&': /* miss */
	case '?': /* guess */
	    i += 2;
	    wordp = &word[0];
	    while( !isspace( buf[i] ) )
		*wordp++ = buf[i++];
	    *wordp = '\0';
	    while( buf[i] != ':' )
		++i;
	    i += 2;
	    charprintf( ch, "'%s': %s.\n\r", word, &buf[i] );
	    break;

	case '#': /* none */
	    i += 2;
	    wordp = &word[0];
	    while( !isspace( buf[i] ) )
		*wordp++ = buf[i++];
	    *wordp = '\0';
	    charprintf( ch, "'%s': No suggestions.\n\r", word );
	    break;

	default:
	    charprintf( ch, "Weird output from ispell: %s\n\r", &buf[i] );
	    break;
	}
	while( buf[i] != '\0' )
	    ++i;
    }
    return;
}

#else	/* !defined( HAVE_ISPELL ) */

void do_ispell( CHAR_DATA *ch, const char *argument )
{
    send_to_char( "ISpell has been disabled on this system.\n\r", ch );
    return;
}

#endif


/*
 * Used above in string_add.  Because this function does not
 * modify case if fCase is FALSE and because it understands
 * parenthesis, it would probably make a nice replacement
 * for one_argument.
 */
/*****************************************************************************
 Name:		first_arg
 Purpose:	Pick off one argument from a string and return the rest.
		Understands quotes, parenthesis (barring) ('s) and
		percentages.
 Called by:	string_add( string.c )
 ****************************************************************************/
const char *first_arg( const char *argument, char *arg_first, bool fCase )
{
    char cEnd;

    while( *argument == ' ' )
	argument++;

    cEnd = ' ';
    if( strchr( "'\"%([{<", *argument ) )
    {
	switch( *argument )
	{
	    /* Careful here, we could run off the end by accident. */
	case '\0':
	    cEnd = ' ';
	    argument--;
	    break;

	case '(':	cEnd = ')';	break;
	case '[':	cEnd = ']';	break;
	case '{':	cEnd = '}';	break;
	case '<':	cEnd = '>';	break;

	default:
	    cEnd = *argument;
	    break;
	}
	argument++;
    }

    while( *argument != '\0' )
    {
	if( *argument == cEnd || ( cEnd == ' ' && isspace( *argument ) ) )
	{
	    argument++;
	    break;
	}
	if( fCase )
	    *arg_first = LOWER( *argument );
	else
	    *arg_first = *argument;
	arg_first++;
	argument++;
    }
    *arg_first = '\0';

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

    return argument;
}


/*
 * Used in olc_act.c for aedit_builders.
 */
char *string_unpad( char *argument )
{
    char buf[MAX_STRING_LENGTH];
    char *s;

    s = argument;

    while( *s == ' ' )
	s++;

    strcpy( buf, s );
    s = buf;

    if( *s != '\0' )
    {
	while( *s != '\0' )
	    s++;
	s--;

	while( *s == ' ' )
	    s--;
	s++;
	*s = '\0';
    }

    free_string( argument );
    return str_dup( buf );
}


/*
 * Same as capitalize but changes the pointer's data.
 * Used in olc_act.c in aedit_builder.
 */
char *string_proper( char *argument )
{
    char *s;

    s = argument;

    while( *s != '\0' )
    {
	if( *s != ' ' )
	{
	    *s = UPPER( *s );
	    while( *s != ' ' && *s != '\0' )
		s++;
	}
	else
	{
	    s++;
	}
    }

    return argument;
}


/*
 * Returns an all-caps string.		OLC 1.1b
 * Used for saving helps.
 */
char *all_capitalize( char *strcap, const char *str )
{
    int i;

    for( i = 0; str[i] != '\0'; i++ )
	strcap[i] = UPPER( str[i] );
    strcap[i] = '\0';
    return strcap;
}


/*
 * Takes a string of one line and splits it if it is too long
 */
char *multi_line( char *buf, const char *str, int length, int indent )
{
    int i, j;
    const char *p;
    char *q;

    p = str;
    j = 0;
    while( p < strchr( str, '\0' ) )
    {
	i = length;
	if( p > str )
	    i -= indent;
	while( i > 0 )
	{
	    if( isspace( p[i] ) )
		break;
	    i--;
	    if( p[i] == '-' )
		break;
	}

	if( indent && p > str )
	{
	    char *sp;
	    int k;

	    sp = strchr( buf, '\0' );
	    for( k = 0; k < indent; k++ )
	    {
		*sp++ = ' ';
	    }
	    j += indent;
	}
	strncpy( &buf[j], p, i );
	if( str[i] == '-' )
	{
	    strcpy( &buf[i + j], "-\n\r" );
	    j++;
	}
	else
	    strcpy( &buf[i + j], "\n\r" );
	p += i + 1;
	j += i + 2;
    }
    q = strchr( buf, '\0' ) - 1;
    while( isspace( *q ) )
    {
	*q-- = '\0';
    }
    ++q;
    *q++ = '\n';
    *q = '\r';

    return buf;
}


/*
  Implementation of a dynamically expanding buffer.

  Inspired by Russ Taylor's <rtaylor@efn.org> buffers in ROM 2.4b2.

  The buffer is primarily used for null-terminated character strings.

  A buffer is allocated with buffer_new, written to using buffer_strcat,
  cleared (if needed) using buffer_clear and free'ed using buffer_free.

  Erwin S. Andreasen <erwin@pip.dknet.dk>
  */
#define EMEM_SIZE -1 /* find_mem_size returns this when block is too large */
#define NUL '\0'
#define			MAX_MEM_LIST	14 /* from db.c */

extern const int rgSizeList [MAX_MEM_LIST];

/* Find in rgSizeList a memory size at least this long */
int find_mem_size( int min_size )
{
    int i;

    for( i = 0; i < MAX_MEM_LIST; i++ )
	if( rgSizeList[i] >= min_size )
	    return rgSizeList[ i ];

    /* min_size is bigger than biggest allowable size! */
    return EMEM_SIZE;
}


/* Create a new buffer, of at least size bytes */
BUFFER *buffer_new( int min_size )
{
    int size;
    BUFFER *buffer;
    char buf[200]; /* for the bug line */

    size = find_mem_size( min_size );

    if( size == EMEM_SIZE )
    {
	bug( buf );
	abort( );
    }

    buffer = alloc_mem( sizeof( BUFFER ) );

    buffer->size = size;
    buffer->data = alloc_mem( size );
    buffer->data[0] = '\0';
    buffer->overflowed = FALSE;
    buffer->len = 0;

    return buffer;
}


void buffer_strcat( BUFFER *buffer, const char *text )
{
    int new_size;
    int text_len;
    char *new_data;
    char buf[200];

    if( buffer->overflowed ) /* Do not attempt to add anymore if buffer is already overflowed */
	return;

    if( !text ) /* Adding NULL string ? */
	return;

    text_len = strlen( text );

    if( text_len == 0 ) /* Adding empty string ? */
	return;

    /* Will the combined len of the added text and the current text exceed our buffer? */

    if( ( text_len + buffer->len + 1 ) > buffer->size ) /* expand? */
    {
	new_size = find_mem_size( buffer->size + text_len + 1 );
	if( new_size == EMEM_SIZE ) /* New size too big ? */
	{
	    bug( buf );
	    buffer->overflowed = TRUE;
	    return;
	}

	/* Allocate the new buffer */
	new_data = alloc_mem( new_size );

	/* Copy the current buffer to the new buffer */
	memcpy( new_data, buffer->data, buffer->len );
	free_mem( buffer->data, buffer->size );
	buffer->data = new_data;
	buffer->size = new_size;

    }

    memcpy( buffer->data + buffer->len, text, text_len );    /* Start copying */
    buffer->len += text_len;	    /* Adjust length */
    buffer->data[ buffer->len ] = NUL; /* Null-terminate at new end */
}


/* Free a buffer */
void buffer_free( BUFFER *buffer )
{
    free_mem( buffer->data, buffer->size );
    free_mem( buffer, sizeof( BUFFER ) );
}


/* Clear a buffer's contents, but do not deallocate anything */
void buffer_clear( BUFFER *buffer )
{
    buffer->overflowed = FALSE;
    buffer->len = 0;
}


/* print stuff, append to buffer. safe-ish, a bit of a hack. */
#if !defined( HAVE_VSNPRINTF )
int bprintf( BUFFER *buffer, const char *fmt, ... )
{
    /* we have to allow for overflow, this is a bit of a hack */
    char buf[MAX_STRING_LENGTH + MAX_INPUT_LENGTH];
    va_list va;
    int res;

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

    res = strlen( buf );
    if( res >= MAX_STRING_LENGTH - 1 )
    {
	buf[0] = NUL;
	bug( "Overflow when printing string %s", fmt );
    }
    else
	buffer_strcat( buffer, buf );

    return res;
}
#else
int bprintf( BUFFER *buffer, const char *fmt, ... )
{
    char buf[MAX_STRING_LENGTH];
    va_list va;
    int res;

    va_start( va, fmt );
    res = vsnprintf( buf, MAX_STRING_LENGTH, fmt, va );
    va_end( va );

    if( res >= MAX_STRING_LENGTH - 1 )
    {
	buf[0] = NUL;
	bug( "Overflow when printing string %s", fmt );
    }
    else
	buffer_strcat( buffer, buf );

    return res;
}
#endif


int is_colour( char code )
{
    switch( code )
    {
	/* Normal */
    case 'n':
    case 'x':
	return 0;	break;

	/* Normal colours */
    case 'r':
	return 1;	break;		/* Red */
    case 'g':
	return 2;	break;		/* Green */
    case 'y':
	return 3;	break;		/* Yellow */
    case 'b':
	return 4;	break;		/* Blue */
    case 'm':
	return 5;	break;		/* Magenta */
    case 'c':
	return 6;	break;		/* Cyan */
    case 'w':
	return 7;	break;		/* White */
    case 'k':
	return 8;	break;		/* Black */

	/* Bold colours */
    case 'R':
	return 9;	break;		/* Bold red */
    case 'G':
	return 10;	break;		/* Bold green */
    case 'Y':
	return 11;	break;		/* Bold yellow */
    case 'B':
	return 12;	break;		/* Bold blue */
    case 'M':
	return 13;	break;		/* Bold magenta */
    case 'C':
	return 14;	break;		/* Bold cyan */
    case 'W':
	return 15;	break;		/* Bold white */
    case 'K':
	return 16;	break;		/* Bold black */

	/* Background colours */
    case '1':
	return 17;	break;		/* Red background */
    case '2':
	return 18;	break;		/* Green background */
    case '3':
	return 19;	break;		/* Yellow background */
    case '4':
	return 20;	break;		/* Blue background */
    case '5':
	return 21;	break;		/* Magenta background */
    case '6':
	return 22;	break;		/* Cyan background */
    case '7':
	return 23;	break;		/* White background */
    case '0':
	return 24;	break;		/* Black background */

	/* Special codes */
    case 'f':
	return 25;	break;		/* Flash */
    case 'v':
	return 26;	break;		/* Reverse video */
    case 'u':
	return 27;	break;		/* Underline (Only for mono screens) */

	/* Misc characters */
    case '&':
	return 28;	break;		/* The & character */
    case '\\':
	return 29;	break;		/* The \ character */
    case '-':
	return 30;	break;		/* tilde ~ */
    case '/':
	return 31;	break;		/* carriage return \n */
    case '*':
	return 32;	break;		/* bell */
    case '!':
	return 33;	break;		/* bar | */
    }
    return 0;
}


void proc_colour( char *outbuf, const char *inbuf, bool colour )
{
    register int j = 0, p = 0;
    int k, max, c = 0;

    if( inbuf[0] == '\0' )
	return;

    while( inbuf[j] != '\0' && p < MAX_STRING_LENGTH * 4 - 3 )
    {
	if( ( inbuf[j] == '&' ) )
	{
	    c = is_colour( inbuf[j + 1] );
	    j += 2;
	}
	else
	{
	    outbuf[p] = inbuf[j];
	    j++;
	    p++;
	    continue;
	}
	max = strlen( COLOURLIST[c] );
	if( colour || max == 1 )
	{
	    for( k = 0; k < max; k++ )
	    {
		outbuf[p] = COLOURLIST[c][k];
		p++;
	    }
	}
    }
    if( p >= MAX_STRING_LENGTH * 4 - 3 )
    {
	outbuf[p - 30] = '\0';	/* 30 back just to be safe */
	strcat( outbuf, "\n\r\n\r***Output Overflow***\n\r" );
    }
    else
    {
	outbuf[p] = '\0';
    }
    return;
}


/*
 * Length of the string as seen minus colour codes.
 */
int colour_strlen( const char *str )
{
    int i, j;

    j = 0;
    for( i = 0; str[i]; i++ )
    {
	if( str[i] == '&' )
	{
	    if( !isprint( COLOURLIST[is_colour( str[i + 1] )][0] ) )
		j--;
	}
	else
	    j++;
    }
    return j;
}


/*
 * Makes the string the correct length for format with colour.
 */
char *colour_strpad( char *outstr, const char *str, const int length )
{
    int i, j;

    j = 0;
    for( i = 0; str[i] && j < length; i++ )
    {
	outstr[i] = str[i];
	if( str[i] == '&' )
	{
	    if( !isprint( COLOURLIST[is_colour( str[i + 1] )][0] ) )
		j--;
	}
	else
	    j++;
    }
    outstr[i++] = '&';
    outstr[i++] = 'n';
    while( j < length )
    {
	outstr[i++] = ' ';
	j++;
    }
    outstr[i] = '\0';
    return outstr;
}


/*
 * Centres the string on a certain length line.
 */
char *colour_strcentre( char *outstr, const char *str, const int length )
{
    char *p = outstr;
    int pad, i;

    pad = length - colour_strlen( str );
    if( pad <= 0 )
	return colour_strpad( outstr, str, length );

    for( i = 0; i < pad / 2; ++i )
	*p++ = ' ';
    for( i = 0; str[i] != '\0'; ++i )
	*p++ = str[i];
    for( i = pad / 2; i < pad; ++i )
	*p++ = ' ';
    *p = '\0';

    return outstr;
}


/*
 * Limits the length of a colourised string.
 * Very similar to the above, except destructive to the string.
 */
void str_limit( char *str, const int length )
{
    int i, j;

    j = 0;
    for( i = 0; str[i] && j < length; i++ )
    {
	if( str[i] == '&' )
	{
	    if( !isprint( COLOURLIST[is_colour( str[i + 1] )][0] ) )
		j--;
	}
	else
	    j++;
    }
    str[i++] = '&';
    str[i++] = 'n';
    str[i] = '\0';
}


/*
 * Remove as much of the colour code as possible.
 */
char *kill_colour( char *outstr, const char *str )
{
    int i, j;

    j = 0;
    for( i = 0; str[i]; i++ )
    {
	if( str[i] == '&' )
	{
	    if( isprint( COLOURLIST[is_colour( str[++i] )][0] ) )
		outstr[j++] = COLOURLIST[is_colour( str[i] )][0];
	}
	else
	    outstr[j++] = str[i];
    }
    outstr[j] = '\0';
    return outstr;
}