legend/
legend/area/
legend/player/
/*
 * There are a few things you need to do to get this working.
 * Firstly I suggest putting the prototype for the cprintf()'s in
 * your main headerfile. Add something like this.
 *
 *  int cprintf   ( char *buf, char *ptr, ... ) __attribute__ ((format(printf, 2, 3)));
 *  int cnprintf  ( char *buf, int maxlen, char *ptr, ... ) __attribute__ ((format(printf, 3, 4)));
 *
 * Secondly, you may want to add cprintf.c to your Makefile.
 *
 * And finally you may want to edit the ansi tag strings found
 * below, so they fit the ansi tags you allow on your mud.
 *
 * cprintf.c by Brian Graversen
 * ----------------------------
 * web:  http://www.daimi.au.dk/~jobo/dystopia/
 * mail: jobo@daimi.au.dk
 */

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>

/*
 * should also include your main headerfile,
 * I assume it's merc.h. We need this for stuff
 * like TRUE, FALSE, MAX_STRING_LENGTH, etc.
 */
#include "merc.h"

#define  _NO_STRING_LIMIT_      -1

/*
 * Just edit these three defines to make sure cprintf()
 * works with your ansi color code. The COLOR_TAG is
 * the pretag that comes before the ansitag, on most muds
 * this is '{' or '#'. Secondly, add all the valid color
 * tags to the ANSI_STRING, if #R is an ansi tag on your
 * mud, then you must add R to ANSI_STRING, just do this
 * for all the valid uses. Now, do you have certain non-ansi
 * tags that are converted into something else? For instance
 * some muds like to use '#-' as a replacement for '~', others,
 * may use '##' as a replacement for '#'. These special conversion
 * tags should be added to REPLACE_STRING.
 *
 * The following settings works with dystopia 1.4/1.4CE.
 */
#define  _NO_STRING_LIMIT_      -1
#define  COLOR_TAG              '#'
#define  BG_COLOR_TAG           '^'
#define  ANSI_STRING            "0123456789bBcCdDefFgGiIlLnNopPrRsuvVwWyYxz()@/."
#define  REPLACE_STRING         "-#+^"


/*
 * local procedures
 */
char *string_restrict  ( char *str, int size );
int   collen           ( const char *str );
int  _cprintf          ( char *buf, int maxlen, char *ptr, va_list ap );

/*
 * Acts like sprintf(), but doesn't break alignment
 * due to colors. It only supports %d and %s. Returns
 * the amount of chars copied.
 */
int cprintf(char *buf, char *ptr, ...)
{
  va_list ap;

  va_start(ap, ptr);

  return _cprintf(buf, _NO_STRING_LIMIT_, ptr, ap);
}

/*
 * Just as cprintf(), but safer, since you can restrict
 * the maximum amount of copied chars. It will return
 * the amount of copied chars, unless the output was
 * truncated due to reaching maxlen before it was done
 * copying the entire string, in which case it will return -1.
 */
int cnprintf(char *buf, int maxlen, char *ptr, ...)
{
  va_list ap;

  va_start(ap, ptr);

  return _cprintf(buf, maxlen, ptr, ap);
}

int _cprintf(char *buf, int maxlen, char *ptr, va_list ap)
{
  char dirty[100];
  char *s;
  int i, copied = 0;
  bool bEnd = FALSE;

  while(*ptr != '\0')
  {
    bool reverse = FALSE;
    int size = 0, max_size = 0, j = 0;
    bEnd = FALSE;

    switch(*ptr)
    {
      default:
	*buf++ = *ptr++;

	if (++copied == maxlen)
	  goto done_copied;

	break;
      case '%':

	/* should we align this in reverse ? */
	if (*(ptr + 1) == '-')
	{
	  ptr++;
	  reverse = TRUE;
	}

	/* get the size, if any */
	while (isdigit(*(ptr + 1)))
	{
	  size *= 10;
	  size += *(++ptr) - '0';
	}

	/* any size restrictions ? */
	if (*(ptr + 1) == '.')
	{
	  ptr++;
	  while (isdigit(*(ptr + 1)))
          {
	    max_size *= 10;
	    max_size += *(++ptr) - '0';
          }
	}

	switch(*(++ptr))
	{
          default:
	    *buf++ = '%';

            if (++copied == maxlen)
	      goto done_copied;

	    break;
          case 's':
	    s = va_arg(ap, char *);
	    s = string_restrict(s, max_size);

	    size -= collen(s);

	    if (!reverse)
	    {
	      while(size-- > 0)
	      {
		*buf++ = ' ';

                if (++copied == maxlen)
	          goto done_copied;
	      }
	    }
	    while(*s != '\0')
	    {
	      *buf++ = *s++;

	      if (!reverse && *s == '\0')
		bEnd = TRUE;

              if (++copied == maxlen)
                goto done_copied;
	    }
	    if (reverse)
	    {
	      while(size-- > 0)
	      {
		*buf++ = ' ';

                if (size == 0)
	          bEnd = TRUE;

                if (++copied == maxlen)
	          goto done_copied;
	      }
	    }
	    ptr++;
	    break;
          case 'd':
	    i = va_arg(ap, int);

	    /* a little trick to see how long the number is */
	    sprintf(dirty, "%d", i);
	    size -= strlen(dirty);

	    if (!reverse)
	    {
	      while(size-- > 0)
	      {
		*buf++ = ' ';

                if (++copied == maxlen)
	          goto done_copied;
	      }
	    }

	    while (dirty[j] != '\0')
	    {
	      *buf++ = dirty[j++];

	      if (!reverse && dirty[j] == '\0')
		bEnd = TRUE;

              if (++copied == maxlen)
                goto done_copied;
	    }

	    if (reverse)
	    {
	      while(size-- > 0)
	      {
		*buf++ = ' ';

                if (size == 0)
	          bEnd = TRUE;

                if (++copied == maxlen)
	          goto done_copied;
	      }
	    }
	    ptr++;
	    break;
	}
	break;
    }
  }

  /*
   * this is our jumppoint, we use a goto for cleaner code,
   * some people may argue that one should never use goto's
   * while others will argue that refusing to use goto's no
   * matter what, can result in code that's horrible to read.
   */
 done_copied:
  *buf = '\0';

  /* if the output was truncated, we return -1 */
  if (*ptr != '\0' && (*(++ptr) != '\0' || !bEnd))
    copied = -1;

  /* clean up */
  va_end(ap);

  /* return how much we copied */
  return copied;
}

/*
 * This nifty little function calculates the length of a
 * string without the color tags. If you use other tags
 * than those mentioned here, then you should add them.
 */
int collen(const char *str)
{
  int len = 0;

  while (*str != '\0')
  {
    int i = 0, j = 0;
    bool found = FALSE;

    switch(*str)
    {
      default:
	len++, str++;
	break;
      case BG_COLOR_TAG:
	str++;
	while (ANSI_STRING[i] != '\0' && !found)
	{
	  if (ANSI_STRING[i] == *str)
	  {
	    str++;
            found = TRUE;
	  }
	  i++;
	}
	while (REPLACE_STRING[j] != '\0' && !found)
	{
	  if (REPLACE_STRING[j] == *str)
	  {
	    len++, str++;
            found = TRUE;
	  }
	  j++;
	}
	if (!found)
          len++;
	break;
      case COLOR_TAG:
	str++;
	while (ANSI_STRING[i] != '\0' && !found)
	{
	  if (ANSI_STRING[i] == *str)
	  {
	    str++;
            found = TRUE;
	  }
	  i++;
	}
	while (REPLACE_STRING[j] != '\0' && !found)
	{
	  if (REPLACE_STRING[j] == *str)
	  {
	    len++, str++;
            found = TRUE;
	  }
	  j++;
	}
	if (!found)
          len++;
	break;
    }
  }

  return len;
}

/*
 * This nifty little function will return the
 * longest possible prefix of 'str' that can
 * be displayed in 'size' characters on a mud
 * client. (ie. it ignores ansi chars).
 */
char *string_restrict(char *str, int size)
{
  static char buf[MAX_STRING_LENGTH] = { '\0' };
  char *ptr = buf;
  int len = 0;
  bool done = FALSE;

  /* no size restrictions, we just return the string */
  if (size == 0)
    return str;

  while (*str != '\0' && !done)
  {
    int i = 0, j = 0;
    bool found = FALSE;

    switch(*str)
    {
      default:
	if (++len > size)
	{
	  done = TRUE;
	  break;
	}
	*ptr++ = *str++;
	break;
      case COLOR_TAG:
	str++;
	while (ANSI_STRING[i] != '\0' && !found)
	{
	  if (ANSI_STRING[i] == *str)
	  {
	    *ptr++ = COLOR_TAG;
	    *ptr++ = *str++;
            found = TRUE;
	  }
	  i++;
	}
	while (REPLACE_STRING[j] != '\0' && !found)
	{
	  if (REPLACE_STRING[j] == *str)
	  {
            if (++len > size)
            {
              done = TRUE;
	      break;
            }
	    *ptr++ = COLOR_TAG;
	    *ptr++ = *str++;
            found = TRUE;
	  }
	  j++;
	}
	if (!found)
	{
          if (++len > size)
          {
            done = TRUE;
	    break;
          }
          *ptr++ = COLOR_TAG;
	}
	break;
    }
  }
  *ptr = '\0';

  return buf;
}