atd/area/
atd/build/
atd/clans/
atd/log/
atd/player/store/
atd/site/
atd/src/bin/
/***************************************************************************
 *  File: string.c                                                         *
 *  Usage: various functions for new NOTE system                           *
 *                                                                         *
 *  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.                                                  *
 *                                                                         *
 *  Much of this code came from CircleMUD.                                 *
 *  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
 *                                                                         *
 *  Revised for Merc 2.1 by Jason Dinkel.                                  *
 *  Revised for Envy 1.0 by Jason Dinkel.				   *
 ***************************************************************************/

/***************************************************************************
 *  God Wars Mud originally written by KaVir aka Richard Woolcock.         *
 *  Changes done to the code done by Sage aka Walter Howard, this mud is   *
 *  for the public, however if you run this code it means you agree        *
 *  to the license.low, license.gw, and license.merc have fun. :)          *
***************************************************************************/

#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"

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

/*
 * Special codes.
 */
#define C_UDL  "\x1B[4m"		/* Underline ANSI code */
#define C_NRM   CLEAR

#define MAX_COLOURS 17
const char *COLOURLIST[] = {
    C_NRM,
    C_RED, C_GREEN, C_YELLOW, C_BLUE, C_MAGENTA, C_CYAN, C_WHITE, C_D_GREY,
    C_B_RED, C_B_GREEN, C_B_YELLOW, C_B_BLUE, C_B_MAGENTA, C_B_CYAN,
    C_B_WHITE,
    C_UDL
};

void string_edit( CHAR_DATA *ch, char **pString )
{
    send_to_char( "  --<[====]>--<[ String Editor ]>--<[====]>--\n\r"
		  "     [  Type .h on a new line for help.  ]\n\r"
		  "     [    @ on a blank line to finish.   ]\n\r"
		  "  --<[===================================]>--\n\r", ch );

    if ( *pString == NULL )
    {
        *pString = str_dup( "" );
    }
    else
    {
        **pString = '\0';
    }

    ch->desc->pString = pString;

    return;
}



void string_append( CHAR_DATA *ch, char **pString )
{
    send_to_char( "  --<[====]>--<[ String Editor ]>--<[====]>--\n\r"
		  "     [  Type .h on a new line for help.  ]\n\r"
		  "     [    @ on a blank line to finish.   ]\n\r"
		  "  --<[===================================]>--\n\r", ch );

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



    ch->desc->pString = pString;

    return;
}



char * string_replace( char * orig, char * old, char * new )
{
    char xbuf[MAX_STRING_LENGTH];
    int i;

    xbuf[0] = '\0';
    strcpy( xbuf, orig );
    if ( strstr( orig, old ) != NULL )
    {
        i = strlen( orig ) - strlen( strstr( orig, old ) );
        xbuf[i] = '\0';
        strcat( xbuf, new );
        strcat( xbuf, &orig[i+strlen( old )] );
        free_string( orig );
    }

    return str_dup( xbuf );
}



char   *string_replace2 (CHAR_DATA * ch, char *orig, int line, char *new)
{
        char   *rdesc;
        char    xbuf[MAX_STRING_LENGTH];
        int     current_line = 1;
        int     i;
        bool    fReplaced = FALSE;

        xbuf[0] = '\0';
        strcpy (xbuf, orig);

        i = 0;

        for (rdesc = orig; *rdesc; rdesc++)
        {
                if (current_line == line && !fReplaced)
                {
                        xbuf[i] = '\0';

                        if (*new)
                                strcat (xbuf, new);
                        strcat (xbuf, "\n\r");
                        fReplaced = TRUE;
                }

                if (current_line == line + 1)
                {
                        strcat (xbuf, &orig[i]);
                        free_string (orig);

                        send_to_char ("Line replaced.\n\r", ch);

                        return str_dup (xbuf);
                }

                i++;

                if (*rdesc == '\r')
                        current_line++;
        }

        if (current_line - 1 != line)
        {
                send_to_char ("That line does not exist.\n\r", ch);
                return str_dup (xbuf);
        }

        free_string (orig);
        send_to_char ("Line replaced.\n\r", ch);

        return str_dup (xbuf);
}

/*****************************************************************************
 Name:          string_insertline
 Purpose:       Inserts a line, blank or containing text.
 Called by:     string_add(string.c) (aedit_builder)olc_act.c.
 ****************************************************************************/
char   *string_insertline (CHAR_DATA * ch, char *orig, int line, char *addstring)
{
        char   *rdesc;
        char    xbuf[MAX_STRING_LENGTH];
        int     current_line = 1;
        int     i;

        xbuf[0] = '\0';
        strcpy (xbuf, orig);

        i = 0;

        for (rdesc = orig; *rdesc; rdesc++)
        {
                if (current_line == line)
                        break;

                i++;

                if (*rdesc == '\r')
                        current_line++;
        }

        if (!*rdesc)
        {
                send_to_char ("That line does not exist.\n\r", ch);
                return str_dup (xbuf);
        }

        xbuf[i] = '\0';

        if (*addstring)
                strcat (xbuf, addstring);
        strcat (xbuf, "\n\r");

        strcat (xbuf, &orig[i]);
        free_string (orig);

        send_to_char ("Line inserted.\n\r", ch);

        return str_dup (xbuf);
}


/*****************************************************************************
 Name:          string_deleteline
 Purpose:       Deletes a specified line of the string.
 Called by:     string_add(string.c) (aedit_builder)olc_act.c.
 ****************************************************************************/
char   *string_deleteline (char *orig, int line)
{
        char   *rdesc;
        char    xbuf[MAX_STRING_LENGTH];
        int     current_line = 1;
        int     i = 0;

        xbuf[0] = '\0';

        for (rdesc = orig; *rdesc; rdesc++)
        {
                if (current_line != line)
                {
                        xbuf[i] = *rdesc;
                        i++;
                }

                if (*rdesc == '\r')
                        current_line++;
        }

        free_string (orig);
        xbuf[i] = 0;

        return str_dup (xbuf);
}



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

        /*
         * Thanks to James Seng
         */
        smash_tilde (argument);

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

                argument = one_argument (argument, arg1);
                
                if (!str_cmp (arg1, "./"))
                {
                        interpret(ch, argument);
                        send_to_char ("Command performed.\n\r", ch);
                        return;
                }
                
                argument = first_arg (argument, arg2, FALSE);
                argument = first_arg (argument, arg3, FALSE);

                if (!str_cmp (arg1, ".c"))
                {
                        send_to_char ("String cleared.\n\r", ch);
                        **ch->desc->pString = '\0';
                        return;
                }

                if (!str_cmp (arg1, ".s"))
                {
                        char   *rdesc;
                        int     i = 1;

                        ch_printf (ch, "`5%2d`` ", i);

                        for (rdesc = *ch->desc->pString; *rdesc; rdesc++)
                        {
                                if (*rdesc != '{') /* ` */
                                        ch_printf (ch, "%c", rdesc[0]);
                                else
                                {
                                        if (rdesc[1] == 'Z')
                                                send_to_char ("{Z}", ch);
                                        else
                                                ch_printf (ch, "%c%c", 
							rdesc[0], 
							rdesc[1]);
                                        rdesc++;
                                }

                                if (*rdesc == '\r' && *(rdesc + 1))
                                {
                                        i++;
                                        ch_printf (ch, "`5%2d`` ", i);
                                }
                        }
/*            send_to_char( *ch->desc->pString, ch ); */

                        return;
                }

#ifdef ISPELL
                if (!str_cmp (arg1, ".sp"))
                {
                        spell_check (ch, *ch->desc->pString);
                        return;
                }
#endif
                if (!str_cmp (arg1, ".r"))
                {
                        if (arg2[0] == '\0')
                        {
                                send_to_char (
                                   "usage:  .r \"old string\" \"new string\"\n\r", ch);
                                return;
                        }

                        smash_tilde (arg3);     /* Just to be sure -- Hugin */
                        *ch->desc->pString =
                           string_replace (*ch->desc->pString, arg2, arg3);
                        sprintf (buf, "'%s' replaced with '%s'.\n\r", arg2, arg3);
                        send_to_char (buf, ch);
                        return;
                }

                if (!str_cmp (arg1, ".rl"))
                {
                        if (arg2[0] == '\0' || !is_number (arg2))
                        {
                                send_to_char (
                                   "usage:  .rl <line> <text>\n\r", ch);
                                return;
                        }

                        smash_tilde (arg3);     /* Just to be sure -- Hugin */
                        *ch->desc->pString =
                           string_replace2 (ch, *ch->desc->pString, atoi (arg2), arg3);
                        return;
                }


                if (!str_cmp (arg1, ".i"))
                {
                        if (arg2[0] == '\0' || !is_number (arg2))
                        {
                                send_to_char (
                                   "usage:  .i <line> {text}\n\r", ch);
                                return;
                        }

                        smash_tilde (arg3);     /* Just to be sure -- Hugin */
                        *ch->desc->pString =
                           string_insertline (ch, *ch->desc->pString, atoi (arg2), arg3);
                        return;
                }


                if (!str_cmp (arg1, ".d"))
                {
                        if (arg2[0] == '\0' || !is_number (arg2))
                        {
                                send_to_char (
                                   "usage:  .d <line>\n\r", ch);
                                return;
                        }

                        *ch->desc->pString =
                           string_deleteline (*ch->desc->pString, atoi (arg2));
                        sprintf (buf, "Line %d deleted.\n\r", atoi (arg2));
                        send_to_char (buf, ch);
                        return;
                }


                if (!str_cmp (arg1, ".f"))
                {
                        *ch->desc->pString = format_string (*ch->desc->pString);
                        send_to_char ("String formatted.\n\r", ch);
                        return;
                }

                if (!str_cmp (arg1, ".h"))
                {
                        send_to_char ("Sedit help (commands on blank line):   \n\r", ch);
                        send_to_char (".r 'old' 'new'   - replace a substring \n\r", ch);
                        send_to_char ("                   (requires '', \"\") \n\r", ch);
                        send_to_char (".rl <line> <text> - replaces a line    \n\r", ch);
                        send_to_char (".h               - get help (this info)\n\r", ch);
                        send_to_char (".s               - show string so far  \n\r", ch);
#ifdef ISPELL
                        send_to_char (".sp              - spell check string  \n\r", ch);
#endif
                        send_to_char (".f               - (word wrap) string  \n\r", ch);
                        send_to_char (".c               - clear string so far \n\r", ch);
                        send_to_char (".d <line>        - deletes a line      \n\r", ch);
                        send_to_char (".i <line> {{text} - inserts a line \n\r", ch);
                        send_to_char ("./ <command>     - do a regular command\n\r", ch);
                        send_to_char ("@                - end string          \n\r", ch);
                        return;
                }


                send_to_char ("SEdit:  Invalid dot command.\n\r", ch);
                return;
        }

        if (*argument == '~' || *argument == '@')
        {
/*              if (ch->desc->editor == ED_NOTE_AJUST)
                        write_all_notes (); */

                ch->desc->pString = NULL;
                ch->desc->editor = 0;
                return;
        }

        strcpy (buf, *ch->desc->pString);

        /*
         * 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;
        }

        /*
         * Ensure no tilde's inside string.
         * --------------------------------
         */
        smash_tilde (argument);

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



/*
 * Thanks to Kalgen for the new procedure (no more bug!)
 * Original wordwrap() written by Surreality.
 */
char *format_string( char *oldstring /*, bool fSpace */)
{
  char xbuf[MAX_STRING_LENGTH];
  char xbuf2[MAX_STRING_LENGTH];
  char *rdesc;
  int i=0;
  bool cap=TRUE;
  
  xbuf[0]=xbuf2[0]=0;
  
  i=0;
  
  for (rdesc = oldstring; *rdesc; rdesc++)
  {
    if (*rdesc=='\n')
    {
      if (xbuf[i-1] != ' ')
      {
        xbuf[i]=' ';
        i++;
      }
    }
    else if (*rdesc=='\r') ;
    else if (*rdesc==' ')
    {
      if (xbuf[i-1] != ' ')
      {
        xbuf[i]=' ';
        i++;
      }
    }
    else if (*rdesc==')')
    {
      if (xbuf[i-1]==' ' && xbuf[i-2]==' ' && 
          (xbuf[i-3]=='.' || xbuf[i-3]=='?' || xbuf[i-3]=='!'))
      {
        xbuf[i-2]=*rdesc;
        xbuf[i-1]=' ';
        xbuf[i]=' ';
        i++;
      }
      else
      {
        xbuf[i]=*rdesc;
        i++;
      }
    }
    else if (*rdesc=='.' || *rdesc=='?' || *rdesc=='!') {
      if (xbuf[i-1]==' ' && xbuf[i-2]==' ' && 
          (xbuf[i-3]=='.' || xbuf[i-3]=='?' || xbuf[i-3]=='!')) {
        xbuf[i-2]=*rdesc;
        if (*(rdesc+1) != '\"')
        {
          xbuf[i-1]=' ';
          xbuf[i]=' ';
          i++;
        }
        else
        {
          xbuf[i-1]='\"';
          xbuf[i]=' ';
          xbuf[i+1]=' ';
          i+=2;
          rdesc++;
        }
      }
      else
      {
        xbuf[i]=*rdesc;
        if (*(rdesc+1) != '\"')
        {
          xbuf[i+1]=' ';
          xbuf[i+2]=' ';
          i += 3;
        }
        else
        {
          xbuf[i+1]='\"';
          xbuf[i+2]=' ';
          xbuf[i+3]=' ';
          i += 4;
          rdesc++;
        }
      }
      cap = TRUE;
    }
    else
    {
      xbuf[i]=*rdesc;
      if ( cap )
        {
          cap = FALSE;
          xbuf[i] = UPPER( xbuf[i] );
        }
      i++;
    }
  }
  xbuf[i]=0;
  strcpy(xbuf2,xbuf);
  
  rdesc=xbuf2;
  
  xbuf[0]=0;
  
  for ( ; ; )
  {
    for (i=0; i<77; i++)
    {
      if (!*(rdesc+i)) break;
    }
    if (i<77)
    {
      break;
    }
    for (i=(xbuf[0]?76:73) ; i ; i--)
    {
      if (*(rdesc+i)==' ') break;
    }
    if (i)
    {
      *(rdesc+i)=0;
      strcat(xbuf,rdesc);
      strcat(xbuf,"\n\r");
      rdesc += i+1;
      while (*rdesc == ' ') rdesc++;
    }
    else
    {
      bug ("No spaces", 0);
      *(rdesc+75)=0;
      strcat(xbuf,rdesc);
      strcat(xbuf,"-\n\r");
      rdesc += 76;
    }
  }
  while (*(rdesc+i) && (*(rdesc+i)==' '||
                        *(rdesc+i)=='\n'||
                        *(rdesc+i)=='\r'))
    i--;
  *(rdesc+i+1)=0;
  strcat(xbuf,rdesc);
  if (xbuf[strlen(xbuf)-2] != '\n')
    strcat(xbuf,"\n\r");

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



/*
 * Pick off one argument from a string and return the rest.
 * Understands quotes, parenthesis (barring ) ('s) and percentages.
 */
char *first_arg( char *argument, char *arg_first, bool fCase )
{
    char cEnd;

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

    cEnd = ' ';
    if ( *argument == '\'' || *argument == '"'
      || *argument == '%'  || *argument == '(' )
    {
        if ( *argument == '(' )
        {
            cEnd = ')';
            argument++;
        }
        else cEnd = *argument++;
    }

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

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

    return argument;
}



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 );
}



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;
}



int arg_count( char *argument )
{
    int total;
    char *s;

    total = 0;
    s = argument;

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

    return total;
}

char * current_date( )
{
    static char buf [ 128 ];
    struct tm * datetime;

    datetime = localtime( &current_time );
    strftime( buf, sizeof( buf ), "%x", datetime );
    return buf;
}
/*
 * This operation draws an 80 character line with a word in the centre
 */
void text_bar( CHAR_DATA *ch, char *argument, bool pager )
{
    char bar_buf[MAX_INPUT_LENGTH];             /* Buffer to store text bar */
    int text_length = strlen( argument );       /* Length of the text */
    int word_start;                             /* Start of text word */
    int loop;                                   /* Loop counter */

    word_start = 37 - ((text_length+1) >> 1);   /* Initialise word start */

    /* If the text is too big, just display it as is */
    if ( text_length > 70 )
    {
        send_to_char(argument,ch);
        send_to_char("\n\r",ch);
        return;
    }

    sprintf( bar_buf, "-%s", capitalize(ch->name) );
    /* Set the left half of the bar to spaces */
    for ( loop = strlen(bar_buf); loop < word_start; loop++ )
    {
        bar_buf[loop] = '-';
    }
    /* Terminate the bar string so that strcat can be used to add the word */
    bar_buf[word_start] = '\0';

    strcat( bar_buf, "[ ");
    strcat( bar_buf, argument );
    strcat( bar_buf, " ]" );

    for( loop = strlen(bar_buf)-1; loop < SCREEN_WIDTH-10; loop++ )
        strcat( bar_buf, "-");

    strcat( bar_buf, current_date());
    strcat( bar_buf, "-" );
    strcat( bar_buf, "\n\r" );

    
    
    /* Display the text bar */
    if( pager )
	page_to_char( bar_buf, ch );
    else
        send_to_char( bar_buf, ch );

    return;
}

/*
 * Take a number like 43 and make it forty-three.
 */
char *   const  ones_numerals [10] =
{
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine"
};

char *   const  tens_numerals [10] =
{
    "-",
    "-",
    "twenty",
    "thirty",
    "forty",
    "fifty",
    "sixty",
    "seventy",
    "eighty",
    "ninety"
};

char *   const  meta_numerals [4] =
{
    "hundred",
    "thousand",
    "million",
    "billion"
};

char *   const  special_numbers [10] =
{
    "ten",
    "eleven",
    "twelve",
    "thirteen",
    "fourteen",
    "fifteen",
    "sixteen",
    "seventeen",
    "eighteen",
    "nineteen"
};

char *numberize( int n )
{
    static char buf[MAX_STRING_LENGTH];
    sh_int    digits[3];
    int t = abs(n);

    buf[0] = '\0';

    /*
     * Special cases (10-19)
     */
    if ( n >= 10 && n <= 19 )
    {
        sprintf( buf, "%s", special_numbers[n-10] );
        return buf;
    }

    if ( n < 10 && n >= 0 )
    {
        sprintf( buf, "%s", ones_numerals[n] );
        return buf;
    }

    /*
     * Cha.
     */
    if ( n >= 10000 || n < 0 )
    {
        sprintf( buf, "%d", n );
        return buf;
    }


    digits[3] = t / 1000;
    t -= 1000*digits[3];
    digits[2] = t / 100;
    t -= 100*digits[2];
    digits[1] = t / 10;
    t -= 10*digits[1];
    digits[0] = t;

    if ( digits[3] > 0 )
    {
        sprintf( buf, "%s%s", buf, ones_numerals[digits[3]] );
        sprintf( buf, "%s thousand ", buf );
    }

    if ( digits[2] > 0 )
    {
        sprintf( buf, "%s%s", buf, ones_numerals[digits[2]] );
        sprintf( buf, "%s hundred ", buf );
    }

    if ( digits[1] > 0 )
    {
        sprintf( buf, "%s%s", buf, tens_numerals[digits[1]] );
        if ( digits[0] > 0 ) sprintf( buf, "%s-", buf );
    }

    if ( digits[0] > 0 )
    {
        sprintf( buf, "%s%s", buf, ones_numerals[digits[0]] );
    }

    if ( buf[(t = strlen(buf)-1)] == ' ' )  buf[t] = '\0';
    if ( buf[(t = strlen(buf)-1)] == ' ' )  buf[t] = '\0';
    return buf;
}


char *smash_article( char *text )
{
    char *arg;
    char buf[MAX_STRING_LENGTH];
    static char buf2[MAX_STRING_LENGTH];

    one_argument( text, buf );

    if ( !str_cmp( buf, "the" ) ||
         !str_cmp( buf, "an"  ) ||
         !str_cmp( buf, "a"   ) )
    {
        arg = one_argument( text, buf );
        sprintf( buf2, "%s", arg );
    }
    else strcpy( buf2, text );

    return buf2;
}



/*
 * Sees if last char is 's' and returns 'is' or 'are' pending.
 */
char * is_are( char *text )
{
    while ( *text != '\0' )
    {
        text++;
    }

    text--;

    if ( LOWER(*text) == 's' && LOWER(*text-1) != 's' )  
        return "are";
   else return "is";
}

char *pluralize( char *argument )
{
    static char buf[MAX_STRING_LENGTH];
    char *v;

    sprintf( buf, "%s", smash_article(argument) );
    v = strstr( buf, " of " );

    if ( v == NULL )
    {
        if ( LOWER(buf[strlen(buf)-1]) == 'y' )
        {
            buf[strlen(buf)-1] = 'i';
            strcat( buf, "es" );
        }
        else
        if ( LOWER(buf[strlen(buf)-1]) == 'f' 
          && !(LOWER(buf[strlen(buf)-2]) == 'i'
            && LOWER(buf[strlen(buf)-3]) == 'o') )
        {
            buf[strlen(buf)-1] = 'v';
            strcat( buf, "es" );
        }
        else
        if ( LOWER(buf[strlen(buf)-1]) == 'h' )
        strcat( buf, "es" );
        else            
        if ( LOWER(buf[strlen(buf)-1]) == 's' )
        {
            if ( LOWER(buf[strlen(buf)-2]) == 'u'
              && !IS_VOWEL(LOWER(buf[strlen(buf)-3])) )
            {
                buf[strlen(buf)-2] = 'i';
                buf[strlen(buf)-1] = '\0';
            }
            else
            strcat( buf, "es" );
        }
        else
        strcat( buf, "s" );
    }
    else
    {
        char xbuf[MAX_STRING_LENGTH];

        sprintf( xbuf, "%s", v );
        buf[strlen(buf)-strlen(v)] = '\0';

        if ( LOWER(buf[strlen(buf)-1]) == 'y' )
        {
            buf[strlen(buf)-1] = 'i';
            strcat( buf, "es" );
        }
        else
        if ( LOWER(buf[strlen(buf)-1]) == 'f' 
          && !(LOWER(buf[strlen(buf)-2]) == 'i'
            && LOWER(buf[strlen(buf)-3]) == 'o') )
        {
            buf[strlen(buf)-1] = 'v';
            strcat( buf, "es" );
        }
        else
        if ( LOWER(buf[strlen(buf)-1]) == 'h' )
        strcat( buf, "es" );
        else            
        if ( LOWER(buf[strlen(buf)-1]) == 's' )
        {
            if ( LOWER(buf[strlen(buf)-2]) == 'u'
              && !IS_VOWEL(LOWER(buf[strlen(buf)-3])) )
            {
                buf[strlen(buf)-2] = 'i';
                buf[strlen(buf)-1] = '\0';
            }
            else
            strcat( buf, "es" );
        }
        else
        strcat( buf, "s" );

        strcat( buf, xbuf );
    }

    return buf;
}

char *trunc_fit( char *argument, int length )
{
    static char buf[MAX_STRING_LENGTH];
    int x;

    if ( argument == NULL )
    return argument;

    for ( x = 0;  (x < length) && (*argument != '\0');  x++ )
    {
        buf[x] = *argument;
        argument++;
    };
    buf[x] = '\0';

    return buf;
};


int is_colour( char code )
{
    switch( code )
    {
    case 'x':
	return 0;	break;
    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 'n':
	return 7;	break;		/* White */
    case 'D':
	return 8;	break;		/* Black */
    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 */
    }
    return 0;
}

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

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

/*
 * Makes the string the correct length for format with colour.
 * Note: isprint checks for any printable character, including
 *       a space.
 */
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++] = 'x';
    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++] = 'x';
    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;
}