/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	Realms of Aurealis 		James Rhone aka Vall of RoA

utils.c					Various functions of a utility
					nature.

		******** Heavily modified and expanded ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		******** Heavily modified and expanded ********
		        All rights reserved henceforth. 

    Please note that no guarantees are associated with any code from
Realms of Aurealis.  All code which has been released to the general
public has been done so with an 'as is' pretense.  RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license.   *** Read, Learn, Understand, Improve ***
*************************************************************************/
#include "conf.h"
#include "sysdep.h"

// Added 03/16/98 to facilitate tprintf() - callahan
#include <stdio.h>
#include <stdarg.h>
#include <sys/stat.h>

#include "structures.h"
#include "utils.h"
#include "comm.h"
#include "screen.h"
#include "magic.h"
#include "handler.h"
#include "db.h"
#include "affect.h"
#include "lists.h"
#include "global.h"
#include "darkenelf.h"

// given string, return associated class number 7/8/98 -jtrhone
int get_class_num(char *name)
{
  int i;

  for (i = 1; i <= NUM_CLASSES; i++)
    if (is_abbrev(name, clarray[i].class_name))
      return i;

  return -1; 
}

// generic player data filename getter  6/21/98 -jtrhone
int get_pdata_filename(char *orig_name, char *ext, char *filename)
{
   char *ptr, name[30];

   if (!*orig_name)
      return 0;

   str_cpy(name, orig_name, 30, "get_pdata_filename");
   for (ptr = name; *ptr; ptr++)
      *ptr = tolower(*ptr);

   switch (tolower(*name)) {
   case 'a': case 'b': case 'c': case 'd': case 'e':
      sprintf(filename, "plrobjs/A-E/%s.%s", name, ext); break;
   case 'f': case 'g': case 'h': case 'i': case 'j':
      sprintf(filename, "plrobjs/F-J/%s.%s", name, ext); break;
   case 'k': case 'l': case 'm': case 'n': case 'o':
      sprintf(filename, "plrobjs/K-O/%s.%s", name, ext); break;
   case 'p': case 'q': case 'r': case 's': case 't':
      sprintf(filename, "plrobjs/P-T/%s.%s", name, ext); break;
   case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
      sprintf(filename, "plrobjs/U-Z/%s.%s", name, ext); break;
   default:
      sprintf(filename, "plrobjs/ZZZ/%s.%s", name, ext); break;
   }
   return 1;
}

// returns ptr to space filled null terminated str of i length
// 5/28/98 -jtrhone
char *padding(int i)
{
  static char pad[MAX_STRING_LENGTH];
  int cnt;

  for (cnt = 0; cnt < i; cnt++)
    pad[cnt] = ' ';
  pad[cnt] = '\0';
 
  return pad;
}
// logging free, keep track of ptrs which we free and log function name...
// 5/13/98 -jtrhone
void free_log(void *ptr, char *txt)
{
  if (!memfp)
  {
    memfp = fopen("misc/memlog.txt", "wt");
    if (!memfp)
    {
      mudlog("SYSERR: Unable to open memlog...", BRF, LEV_IMM, TRUE);
      free(ptr);
      return;
    } 
  }

  fprintf(memfp, "%p : %s\n", ptr, txt);
  fflush(memfp);
  free(ptr);
}

// front end to all strcpys, takes two normal args plus max len and name of caller
// to help facilitate the discovery of memory overwrites... 4/23/98 -jtrhone
char *str_cpy(char *dest, char *src, int max, char *fname)
{
  static char buf[MAX_INPUT_LENGTH];

#ifdef DEBUG
  if (strlen(src)+1 > max)
  {
    sprintf(buf, "!SYSWAR!: str_cpy() bound violation in %s.", fname);
    mudlog(buf, BRF, LEV_IMM, TRUE);
  
    // only copy the amount we can...
    strncpy(dest, src, max-1);
    dest[max-1] = '\0';
    return dest;
  }
#endif

  return strcpy(dest, src);
}

// front end to all strcats, takes two normal args plus max len and name of caller
// to help facilitate the discovery of memory overwrites... 4/23/98 -jtrhone
char *str_cat(char *dest, char *src, int max, char *fname)
{
  static char buf[MAX_INPUT_LENGTH];

#ifdef DEBUG
  if (strlen(dest) + strlen(src)+1 > max)
  {
    sprintf(buf, "!SYSWAR!: str_cat() bound violation in %s.", fname);
    mudlog(buf, BRF, LEV_IMM, TRUE);
  
    // only copy the amount we can...
    strncat(dest, src, max-1);
    dest[max-1] = '\0';
    return dest;
  }
#endif

  return strcat(dest, src);
}


// Check to see if ch has reached limit for summoning. 04/21/98 -callahan
BOOL MaxSummons(CharData *ch)
{
  CharData *mob;   
  int levels;

  for (levels = 0, mob = character_list; mob; mob = mob->next)
    if (SUMMONER(mob) == GET_IDNUM(ch))
      levels += Level(mob);

  if (levels >= (2 * Level(ch))) {
    SendChar("You have reached your maximum summoning capacity.\r\n", ch);
    return TRUE;
  }
 
  return FALSE;  
}


// Checks to see if two chars share a group relationship. 08/11/98 -callahan
BOOL InSameGroup(CharData *ch, CharData *sch)
{
  if (!ch || !sch)   
    return FALSE;   

  if (ch == sch)
    return TRUE;

  if (!Master(ch) && !Master(sch))
    return FALSE;

  if ((ch == Master(sch)) || (sch == Master(ch)))
    return TRUE;
 
  if (Master(ch) == Master(sch))
    return TRUE; 

  return FALSE;    
}

// Checks to see if a number is within a range. - 04/21/98 -callahan
int InRange(int value, int low, int high)
{
  if ((value <= high) && (value >= low))
    return TRUE;
  else
    return FALSE;
}

// Attempts to match a player's idnum in the given list. 03/16/98 -callahan
int IdListMember(IdList * list, long idnum)
{
  IdList *member;
  
  for (member = list; member; member = member->next)
    if (member->idnum == idnum)
      return 1;
  return 0;
}

// next couple deal with groups... 12/17/97 -jtrhone
// get a ptr to the groups leader...
chdata *group_leader(chdata *ch)
{
  chdata *k;

  if (!IS_AFFECTED(ch, AFF_GROUP))
    return NULL;

  if (!(k = ch->master))
    k = ch;

  return k;
}

// return ptr to next in group... NULL if none or done
chdata *group_member(chdata *ch, BOOL inroom)
{
  chdata *l;
  struct follow_type *f;

  if (!(l = group_leader(ch)))
    return NULL;

  // see if the leader has been returned yet
  // MUST check room on leader too  1/22/98 -jtrhone
  if (!CHAR_FLAGGED(l, CH_GRP) && (!inroom || SAME_ROOM(ch, l)))
  {
    SET_BIT(CHAR_FLAGS(l), CH_GRP);
    return l;
  }

  for (f = l->followers; f; f=f->next)
    if (!CHAR_FLAGGED(f->follower, CH_GRP))
      if (IS_AFFECTED(f->follower, AFF_GROUP) && 
         (!inroom || SAME_ROOM(f->follower, ch)) && f->follower != l)
      {
        SET_BIT(CHAR_FLAGS(f->follower), CH_GRP);
        return f->follower;  
      }

  // we must have NO followers left OR they all have the CH_GRP flag on em
  // so.. go thru group and remove the flag then return NULL

  REMOVE_BIT(CHAR_FLAGS(l), CH_GRP);

  for (f = l->followers; f; f=f->next)
    REMOVE_BIT(CHAR_FLAGS(f->follower), CH_GRP);
  
  return NULL;
}

int num_in_group(chdata *ch)
{
  int count = 0;
  chdata *k;

  if (!IS_AFFECTED(ch, AFF_GROUP))
    return 0;

  while ((k = group_member(ch, TRUE)))
    count++;

  return count;
}

int	MIN(int a, int b)
{
   return a < b ? a : b;
}

int	MAX(int a, int b)
{
   return a > b ? a : b;
}

/* creates a random number in interval [from;to] */
int	number(int from, int to)
{
  int targ;

  if (from > to)
  {
    targ = from;
    from = to;
    to = targ;
  }

  targ = (to - from + 1);

  if (!targ)
    targ = 1;

  return((random() % targ) + from);
}

/* simulates dice roll */
int	dice(int number, int size)
{
   int	r, sum = 0;
  
   size = MAX(size, 1); /* no more crasho */
   for (r = 1; r <= number; r++)
      sum += ((random() % size) + 1);
   return(sum);
}

void skip_spaces(char **string)
{
  if (!*string || !**string) return;

  for (; **string && isspace(**string); (*string)++)
    ;
}

/* returns: 0 if equal, 1 if arg1 > arg2, -1 if arg1 < arg2  */
/* scan 'till found different or end of both                 */
int	str_cmp(char *arg1, char *arg2)
{
   int	chk, i;

   if (!arg1)
     return (-1);
   if (!arg2)
     return (1);

   for (i = 0; *(arg1 + i) || *(arg2 + i); i++)
      if ((chk = LOWER(*(arg1 + i)) - LOWER(*(arg2 + i))))
	 if (chk < 0)
	    return (-1);
	 else
	    return (1);
   return(0);
}

/* returns: 0 if equal, 1 if arg1 > arg2, -1 if arg1 < arg2  */
/* scan 'till found different, end of both, or n reached     */
int	strn_cmp(char *arg1, char *arg2, int n)
{
   int	chk, i;

   for (i = 0; (*(arg1 + i) || *(arg2 + i)) && (n > 0); i++, n--)
      if ((chk = LOWER(*(arg1 + i)) - LOWER(*(arg2 + i))))
	 if (chk < 0)
	    return (-1);
	 else
	    return (1);

   return(0);
}

/* stuff a chunk of chars on end of string, allocate, etc */
/* NOTE, caller must FREE what it gets back... */
char *str_add(char *str, char *add)
{
  if (!add || !*add)
    return str;

  if (!str || !*str) /* just strdup it */
  {
    return STR_DUP(add);
  }

  RECREATE(str, char, strlen(str) + strlen(add) + 1);
  return str; 
}

/* go one past eol in a string, used in comlist scanning */
/* RoA jtrhone */
char *scan_past_eol(char *p)
{
  // fastforward while not newline
  while (*p && !ISNEWL(*p))
    p++;
  // now fastforward while newline
  while (*p && ISNEWL(*p))
    p++;
  return p;
}

char *scan_past_eol_and_spaces(char *p)
{
  // fastforward while not newline
  while (*p && !ISNEWL(*p))
    p++;
  // now fastforward while newline
  while (*p && ISNEWL(*p))
    p++;
  // now fastforward while space
  skip_spaces(&p);
  return p;
}

// given a ptr to a opening bracket '{' count how many chars are INSIDE this
// opening and its matching closing bracket
int chars_in_block(char *p)
{
  int i = 0, block = 1;

  if (!p || !*p || *p != '{')
  {
    mudlog("SYSERR: chars_in_block p != {", BRF, LEV_IMM, TRUE);
    return -1;
  }

  p++;
  for (; *p; p++)
  {
    switch (*p) {
      case '{':
        block++;
        break;
      case '}':
        block--;
        break;
      default: break;
    }

    if (block)
      i++;
    else
      break;
  } 

  // block BETTER be 0, or else we errored...
  if (block)
    return -1;
  else
    return i;
}

/* skip an entire zone comlist block of reset commands */
/* used if target mob/obj is not loaded for whatever reason */
/* RoA jtrhone */
/* IMPORTANT!!: ASSUME we are pointing at { */
char *skip_block(char *pt)
{
  int depth;

  pt++;
  for (depth = 1; depth > 0 && *pt; pt++)
  {
   if (*pt == '{')  // go into another block
    depth++;
   else
   if (*pt == '}')  // exit a block
    depth--;  
  }
  pt = scan_past_eol(pt);  // fly past all the newlines
  return pt;
}

// given a string, TRANSFORM each piece into underline (-=-=-=)
// until \n or \r is reached
void underline(char *buf)
{
  int i;
  char *tmp;
  char rep = '-';

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

  for (i = 1, tmp = buf; *tmp && *tmp != '\n' && *tmp != '\r'; tmp++)
  {
    if ( (i = !i) ) rep = '=';
    else            rep = '-'; 
    *tmp = rep;
  }
}

/* log a death trap hit */
void	log_death_trap(chdata *ch)
{
   char	buf[MAX_STRING_LENGTH];

   sprintf(buf, "%s hit death trap #%d (%s)", GET_NAME(ch),
       world[ch->in_room].number, world[ch->in_room].name);
   mudlog(buf, BRF, LEV_IMM, TRUE);
}

/* writes a string to the log */
void	log(char *str)
{
   long	ct;
   char	*tmstr;

   ct = time(0);
   tmstr = asctime(localtime(&ct));
   *(tmstr + strlen(tmstr) - 1) = '\0';
   fprintf(stderr, "%-19.19s :: %s\n", tmstr, str);
}

/* New PROC: syslog by Fen Jul 3, 1992 */
void mudlog(char *str, char type, sbyte level, byte file)
{
   dsdata *i;
   char *tmp;
   long	ct;  
   char	tp;
   char buf[1024];

   ct  = time(0);
   tmp = asctime(localtime(&ct));

   // don't send color codes to log... 2/12/98 -jtrhone
   if (file)
   {
     strcpy(buf2, str);
     killp(buf2);
     fprintf(stderr, "%-19.19s :: %s\n", tmp, buf2);
   }

   if (level < 0)
      return;
  
   if (type < BUG)
    sprintf(buf, "%%2%%B[%%0 %s %%2%%B]%%0\n\r", str );
   else
    sprintf(buf, "%%5%%B[%%0 %s %%5%%B]%%0\n\r", str );

    for (i=descriptor_list; i; i = i->next) 
       if (D_CHECK(i) && SEND_OK(i->character))
       {
          tp = ((PRF_FLAGGED(i->character, PRF_LOG1) ? 1 : 0) +
                (PRF_FLAGGED(i->character, PRF_LOG2) ? 2 : 0));

          if ((GET_LEVEL(i->character) >= level) && 
		(tp >= type || PLR_FLAGGED(i->character, PLR_LOGALL))) 

             send_to_char(buf, i->character);	  
       }
   return;
}

void	sprintbit(long vektor, char *names[], char *result)
{
   long	nr;

   *result = '\0';
   if (vektor < 0) {
      strcpy(result, "SPRINTBIT ERROR!");
      return;
   }

   // Slightly modified for comma-separation. 03/21/98 -callahan
   for (nr = 0; vektor; vektor >>= 1) {
      if (IS_SET(1, vektor)) {
	 if (*names[nr] != '\n') {
	    strcat(result, names[nr]);
	    strcat(result, ", ");
	 } else
	    strcat(result, "UNDEFINED ");
      }
      if (*names[nr] != '\n')
	 nr++;
   }

   if (!*result)
      strcat(result, "None.");
   else {
     result[strlen(result) - 2] = '\0';
     strcat(result, ".");	// Add a period to the end. -callahan
   }
}

void	sprinttype(int type, char *names[], char *result)
{
   int	nr;

   for (nr = 0; (*names[nr] != '\n'); nr++)
      ;
   if (type < nr)
      strcpy(result, names[type]);
   else
      strcpy(result, "<UNDEF>");
}

// Facilitate easily-read time formats. 03/21/98 -callahan

// Formats string as: 3d 04:23, or if no days, 04:23
char *time_format_1(time_t dt)
{
  register struct tm *delta;
  static char buf[64];
  
  if (dt < 0)
    dt = 0;
    
  delta = gmtime(&dt);  
   
  if (delta->tm_yday > 0)
    sprintf(buf, "%dd %02d:%02d", delta->tm_yday, delta->tm_hour, delta->tm_min);
  else
    sprintf(buf, "%02d:%02d", delta->tm_hour, delta->tm_min);

  return buf;
}

// Formats string as: 3d (for days), 4h (for hours), or 23m (for minutes)
char *time_format_2(time_t dt)
{
  register struct tm *delta;
  static char buf[64];
  
  if (dt < 0)
    dt = 0;
    
  delta = gmtime(&dt);
  
  if (delta->tm_yday > 0)
    sprintf(buf, "%dd", delta->tm_yday);
  else if (delta->tm_hour > 0)
    sprintf(buf, "%dh", delta->tm_hour);
  else if (delta->tm_min > 0)
    sprintf(buf, "%dm", delta->tm_min);
  else
    sprintf(buf, "%ds", delta->tm_sec);
 
  return buf;
}

char *time_format_3(time_t dt)
{   
  register struct tm *delta;
  static char buf[64];
  
  if (dt < 0)
    dt = 0;
    
  delta = gmtime(&dt);
  
  if (delta->tm_yday > 0)
    sprintf(buf, "%d day%s", delta->tm_yday, (delta->tm_yday > 1) ? "s" : "");
  else if (delta->tm_hour > 0)
    sprintf(buf, "%d hour%s", delta->tm_hour, (delta->tm_hour > 1) ? "s" : "");
  else if (delta->tm_min > 0)
    sprintf(buf, "%d minute%s", delta->tm_min, (delta->tm_min > 1) ? "s" : "");
  else
    sprintf(buf, "%d second%s", delta->tm_sec, (delta->tm_sec > 1) ? "s" : "");
  
  return buf;
}

/* Calculate the REAL time passed over the last t2-t1 centuries (secs) */
struct time_info_data real_time_passed(time_t t2, time_t t1)
{
   long	secs;
   struct time_info_data now;

   secs = (long) (t2 - t1);

   now.hours = (secs / SECS_PER_REAL_HOUR) % 24;  /* 0..23 hours */
   secs -= SECS_PER_REAL_HOUR * now.hours;

   now.day = (secs / SECS_PER_REAL_DAY);          /* 0..34 days  */
   secs -= SECS_PER_REAL_DAY * now.day;

   now.month = -1;
   now.year  = -1;

   return now;
}

/* Calculate the MUD time passed over the last t2-t1 centuries (secs) */
// changed # of months to 16 ( 0 - 15 ) -roa
struct time_info_data mud_time_passed(time_t t2, time_t t1)
{
   long	secs;
   struct time_info_data now;

   secs = (long) (t2 - t1);

   now.hours = (secs / SECS_PER_MUD_HOUR) % 24;  /* 0..23 (24 hours) */
   secs -= SECS_PER_MUD_HOUR * now.hours;

   now.day = (secs / SECS_PER_MUD_DAY) % 35;     /* 0..34 (35 days)  */
   secs -= SECS_PER_MUD_DAY * now.day;

   now.month = (secs / SECS_PER_MUD_MONTH) % 16; /* 0..15 (16 months) */
   secs -= SECS_PER_MUD_MONTH * now.month;

   now.year = (secs / SECS_PER_MUD_YEAR);        /* 0..XX? years */

   return now;
}

struct time_info_data age(chdata *ch)
{
   struct time_info_data player_age;

   player_age = mud_time_passed(time(0), ch->player.time.birth);
   player_age.year += 17;   /* All players start at 17 */
   return player_age;
}

// follower stuff, formerly from sparse.c -roa
/* Check if making CH follow VICTIM will create an illegal */
/* Follow "Loop/circle"                                    */
BOOL circle_follow(chdata *ch, chdata *victim)
{
   chdata *k;
   for (k = victim; k; k = k->master) 
      if (k == ch)
	 return(TRUE);
   return(FALSE);
}

void CharToDust(CharData *ch)
{
  ObjData *dust, *obj, *next_obj, *money, *create_money(int amount);
  int i;
  static char buf[MAX_STRING_LENGTH];
  void float_sink_object(obdata *obj, int room);

  CREATE(dust, ObjData, 1);
  clear_object(dust);

  dust->touched = TRUE;
  dust->item_number = NOWHERE;
  InRoom(dust) = NOWHERE;
  dust->plr_num = 0;

  dust->name = str_dup("dust");

  sprintf(buf, "A pile of %s's dust is lying here.", Name(ch));
  dust->description = str_dup(buf);

  sprintf(buf, "%s's dust", Name(ch));
  dust->shdesc = str_dup(buf);

  dust->type_flag = ITEM_CONTAINER;
  OBJ_WEARS(dust) = ITEM_TAKE;
  OBJ_EXTRAS(dust) = ITEM_NODONATE;
  dust->value[0] = 0;
  dust->weight = 1;
  dust->timer = max_npc_corpse_time + 10;

  for (obj = ch->carrying; obj; obj = next_obj) {
    next_obj = obj->next_content;
    act("$p falls to the floor from $n's crumbling remains.",
        FALSE, ch, obj, 0, TO_ROOM);
    obj_from_char(obj);
    obj_to_room(obj, InRoom(ch));
  }

  for (i = 0; i < MAX_WEAR; i++)
    if (EQ(ch, i)) {
      act("$p falls to the floor from $n's crumbling remains.",
          FALSE, ch, EQ(ch, i), 0, TO_ROOM);
      obj_to_room(unequip_char(ch, i, TRUE), InRoom(ch));
    }

  if (Gold(ch) > 0) {
    money = create_money(Gold(ch));
    act("Some gold falls to the floor from $n's crumbling remains.",
        FALSE, ch, 0, 0, TO_ROOM);
    obj_to_room(money, InRoom(ch));
    Gold(ch) = 0;
  }  
  object_list_new_owner(dust, NULL);
  float_sink_object(dust, InRoom(ch));
}


/* Called when stop following persons, or stopping charm */
/* This will NOT do if a character quits/dies!!          */
void stop_follower(chdata *ch)
{
   struct follow_type *j, *k;

   assert(ch->master);

   if (IS_AFFECTED(ch, AFF_CHARM)) {
      act("You realize that $N is a jerk!", FALSE, ch, 0, ch->master, TO_CHAR);
      act("$n realizes that $N is a jerk!", FALSE, ch, 0, ch->master, TO_NOTVICT);
      act("$n hates your guts!", FALSE, ch, 0, ch->master, TO_VICT);
      if (affected_by_spell(ch, SPELL_CHARM_PERSON))
	 affect_from_char(ch, SPELL_CHARM_PERSON);
   } else {
      act("You stop following $N.", FALSE, ch, 0, ch->master, TO_CHAR);
      act("$n stops following $N.", FALSE, ch, 0, ch->master, TO_NOTVICT);
      act("$n stops following you.", FALSE, ch, 0, ch->master, TO_VICT);
   }

   if (ch->master->followers->follower == ch) 
   { /* Head of follower-list? */
      k = ch->master->followers;
      ch->master->followers = k->next;
      FREENULL(k);
   }
   else 
   { /* locate follower who is not head of list */
      for (k = ch->master->followers; k->next->follower != ch; k = k->next)
	 ;

      j = k->next;
      k->next = j->next;
      free(j);
      j = NULL;
   }
   ch->master = NULL;
   REMOVE_BIT(AFF_FLAGS(ch), AFF_CHARM | AFF_GROUP);
}

/* Called when a character that follows/is followed dies */
void	die_follower(chdata *ch)
{
   struct follow_type *j, *k;

   if (ch->master)
      stop_follower(ch);

   for (k = ch->followers; k; k = j) {
      j = k->next;
      stop_follower(k->follower);
   }
}

/* Do NOT call this before having checked if a circle of followers */
/* will arise. CH will follow leader                               */
void	add_follower(chdata *ch, chdata *leader)
{
   struct follow_type *k;

   assert(!ch->master);

   ch->master = leader;

   CREATE(k, struct follow_type, 1);

   k->follower = ch;
   k->next = leader->followers;
   leader->followers = k;

   act("You now follow $N.", FALSE, ch, 0, leader, TO_CHAR);
   if (can_see(leader, ch))
     act("$n starts following you.", TRUE, ch, 0, leader, TO_VICT);
   act("$n now follows $N.", TRUE, ch, 0, leader, TO_NOTVICT);
}


// Translates a number to an english word. 03/16/98 callahan
const char *num_to_word[401] =
{
  "zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
  "nine", "ten",

  "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
  "seventeen", "eighteen", "nineteen", "twenty",

  "twenty-one", "twenty-two", "twenty-three", "twenty-four", "twenty-five",
  "twenty-six", "twenty-seven", "twenty-eight", "twenty-nine", "thirty",

  "thirty-one", "thirty-two", "thirty-three", "thirty-four", "thirty-five",
  "thirty-six", "thirty-seven", "thirty-eight", "thirty-nine", "forty",

  "forty-one", "forty-two", "forty-three", "forty-four", "forty-five",
  "forty-six", "forty-seven", "forty-eight", "forty-nine", "fifty",
  
  "fifty-one", "fifty-two", "fifty-three", "fifty-four", "fifty-five",
  "fifty-six", "fifty-seven", "fifty-eight", "fifty-nine", "sixty",
  
  "sixty-one", "sixty-two", "sixty-three", "sixty-four", "sixty-five",
  "sixty-six", "sixty-seven", "sixty-eight", "sixty-nine", "seventy",

  "seventy-one", "seventy-two", "seventy-three", "seventy-four",
  "seventy-five", "seventy-six", "seventy-seven", "seventy-eight",
  "seventy-nine", "eighty",
  
  "eighty-one", "eighty-two", "eighty-three", "eighty-four", "eighty-five",
  "eighty-six", "eighty-seven", "eighty-eight", "eighty-nine", "ninety",
    
  "ninety-one", "ninety-two", "ninety-three", "ninety-four", "ninety-five",
  "ninety-six", "ninety-seven", "ninety-eight", "ninety-nine", "one-hundred",
        
  "one-hundred and one", "one-hundred and two", "one-hundred and three",
  "one-hundred and four", "one-hundred and five", "one-hundred and six",
  "one-hundred and seven", "one-hundred and eight", "one-hundred and nine",
  "one-hundred and ten",
   
  "one-hundred and eleven", "one-hundred and twelve",
  "one-hundred and thirteen", "one-hundred and fourteen",
  "one-hundred and fifteen", "one-hundred and sixteen",
  "one-hundred and seventeen", "one-hundred and eighteen",
  "one-hundred and nineteen", "one-hundred and twenty",

  "one-hundred and twenty-one", "one-hundred and twenty-two",
  "one-hundred and twenty-three", "one-hundred and twenty-four",
  "one-hundred and twenty-five", "one-hundred and twenty-six",
  "one-hundred and twenty-seven", "one-hundred and twenty-eight",
  "one-hundred and twenty-nine", "one-hundred and thirty",
  
  "one-hundred and thirty-one", "one-hundred and thirty-two",
  "one-hundred and thirty-three", "one-hundred and thirty-four",
  "one-hundred and thirty-five", "one-hundred and thirty-six",
  "one-hundred and thirty-seven", "one-hundred and thirty-eight",
  "one-hundred and thirty-nine", "one-hundred and forty",
  
  "one-hundred and forty-one", "one-hundred and forty-two",
  "one-hundred and forty-three", "one-hundred and forty-four",
  "one-hundred and forty-five", "one-hundred and forty-six",
  "one-hundred and forty-seven", "one-hundred and forty-eight",
  "one-hundred and forty-nine", "one-hundred and fifty",
  
  "one-hundred and fifty-one", "one-hundred and fifty-two",
  "one-hundred and fifty-three", "one-hundred and fifty-four",
  "one-hundred and fifty-five", "one-hundred and fifty-six",
  "one-hundred and fifty-seven", "one-hundred and fifty-eight",
  "one-hundred and fifty-nine", "one-hundred and sixty",

  "one-hundred and sixty-one", "one-hundred and sixty-two",
  "one-hundred and sixty-three", "one-hundred and sixty-four",
  "one-hundred and sixty-five", "one-hundred and sixty-six",
  "one-hundred and sixty-seven", "one-hundred and sixty-eight",
  "one-hundred and sixty-nine", "one-hundred and sevenety",
    
  "one-hundred and seventy-one", "one-hundred and seventy-two",
  "one-hundred and seventy-three", "one-hundred and seventy-four",
  "one-hundred and seventy-five", "one-hundred and seventy-six",
  "one-hundred and seventy-seven", "one-hundred and seventy-eight",
  "one-hundred and seventy-nine", "one-hundred and eighty",
  
  "one-hundred and eighty-one", "one-hundred and eighty-two",
  "one-hundred and eighty-three", "one-hundred and eighty-four",
  "one-hundred and eighty-five", "one-hundred and eighty-six",
  "one-hundred and eighty-seven", "one-hundred and eighty-eight",
  "one-hundred and eighty-nine", "one-hundred and ninety",
  
  "one-hundred and ninety-one", "one-hundred and ninety-two",
  "one-hundred and ninety-three", "one-hundred and ninety-four",
  "one-hundred and ninety-five", "one-hundred and ninety-six",
  "one-hundred and ninety-seven", "one-hundred and ninety-eight",
  "one-hundred and ninety-nine", "two-hundred",
  
  "two-hundred and one", "two-hundred and two", "two-hundred and three",
  "two-hundred and four", "two-hundred and five", "two-hundred and six",
  "two-hundred and seven", "two-hundred and eight", "two-hundred and nine",
  "two-hundred and ten",
  
  "two-hundred and eleven", "two-hundred and twelve",
  "two-hundred and thirteen", "two-hundred and fourteen",
  "two-hundred and fifteen", "two-hundred and sixteen",
  "two-hundred and seventeen", "two-hundred and eighteen", 
  "two-hundred and nineteen", "two-hundred and twenty",
  
  "two-hundred and twenty-one", "two-hundred and twenty-two",  
  "two-hundred and twenty-three", "two-hundred and twenty-four",
  "two-hundred and twenty-five", "two-hundred and twenty-six",
  "two-hundred and twenty-seven", "two-hundred and twenty-eight",
  "two-hundred and twenty-nine", "two-hundred and thirty",
  
  "two-hundred and thirty-one", "two-hundred and thirty-two",  
  "two-hundred and thirty-three", "two-hundred and thirty-four",
  "two-hundred and thirty-five", "two-hundred and thirty-six",
  "two-hundred and thirty-seven", "two-hundred and thirty-eight",
  "two-hundred and thirty-nine", "two-hundred and forty",
  
  "two-hundred and forty-one", "two-hundred and forty-two",
  "two-hundred and forty-three", "two-hundred and forty-four",
  "two-hundred and forty-five", "two-hundred and forty-six",
  "two-hundred and forty-seven", "two-hundred and forty-eight",
  "two-hundred and forty-nine", "two-hundred and fifty",
  
  "two-hundred and fifty-one", "two-hundred and fifty-two",
  "two-hundred and fifty-three", "two-hundred and fifty-four",
  "two-hundred and fifty-five", "two-hundred and fifty-six",
  "two-hundred and fifty-seven", "two-hundred and fifty-eight",
  "two-hundred and fifty-nine", "two-hundred and sixty",
  
  "two-hundred and sixty-one", "two-hundred and sixty-two",
  "two-hundred and sixty-three", "two-hundred and sixty-four",
  "two-hundred and sixty-five", "two-hundred and sixty-six",
  "two-hundred and sixty-seven", "two-hundred and sixty-eight",
  "two-hundred and sixty-nine", "two-hundred and sevenety",
  
  "two-hundred and seventy-one", "two-hundred and seventy-two",  
  "two-hundred and seventy-three", "two-hundred and seventy-four",
  "two-hundred and seventy-five", "two-hundred and seventy-six",
  "two-hundred and seventy-seven", "two-hundred and seventy-eight",
  "two-hundred and seventy-nine", "two-hundred and eighty",
  
  "two-hundred and eighty-one", "two-hundred and eighty-two",
  "two-hundred and eighty-three", "two-hundred and eighty-four",
  "two-hundred and eighty-five", "two-hundred and eighty-six",
  "two-hundred and eighty-seven", "two-hundred and eighty-eight",
  "two-hundred and eighty-nine", "two-hundred and ninety",
  "two-hundred and ninety-one", "two-hundred and ninety-two",
  "two-hundred and ninety-three", "two-hundred and ninety-four",
  "two-hundred and ninety-five", "two-hundred and ninety-six",
  "two-hundred and ninety-seven", "two-hundred and ninety-eight",
  "two-hundred and ninety-nine", "three-hundred",
  
"three-hundred and one", "three-hundred and two", "three-hundred and three",
"three-hundred and four", "three-hundred and five", "three-hundred and six",
  "three-hundred and seven", "three-hundred and eight",
  "three-hundred and nine", "three-hundred and ten",

  "three-hundred and eleven", "three-hundred and twelve",
  "three-hundred and thirteen", "three-hundred and fourteen",
  "three-hundred and fifteen", "three-hundred and sixteen",
  "three-hundred and seventeen", "three-hundred and eighteen",
  "three-hundred and nineteen", "three-hundred and twenty",
  
  "three-hundred and twenty-one", "three-hundred and twenty-two",
  "three-hundred and twenty-three", "three-hundred and twenty-four",
  "three-hundred and twenty-five", "three-hundred and twenty-six",
  "three-hundred and twenty-seven", "three-hundred and twenty-eight",
  "three-hundred and twenty-nine", "three-hundred and thirty",
  
  "three-hundred and thirty-one", "three-hundred and thirty-two",
  "three-hundred and thirty-three", "three-hundred and thirty-four",
  "three-hundred and thirty-five", "three-hundred and thirty-six",
  "three-hundred and thirty-seven", "three-hundred and thirty-eight",
  "three-hundred and thirty-nine", "three-hundred and forty",
  
  "three-hundred and forty-one", "three-hundred and forty-two",
  "three-hundred and forty-three", "three-hundred and forty-four",
  "three-hundred and forty-five", "three-hundred and forty-six",
  "three-hundred and forty-seven", "three-hundred and forty-eight",
  "three-hundred and forty-nine", "three-hundred and fifty",     
  
  "three-hundred and fifty-one", "three-hundred and fifty-two", 
  "three-hundred and fifty-three", "three-hundred and fifty-four", 
  "three-hundred and fifty-five", "three-hundred and fifty-six",
  "three-hundred and fifty-seven", "three-hundred and fifty-eight",
  "three-hundred and fifty-nine", "three-hundred and sixty", 
  
  "three-hundred and sixty-one", "three-hundred and sixty-two",
  "three-hundred and sixty-three", "three-hundred and sixty-four",
  "three-hundred and sixty-five", "three-hundred and sixty-six",
  "three-hundred and sixty-seven", "three-hundred and sixty-eight",
  "three-hundred and sixty-nine", "three-hundred and sevenety", 
  
  "three-hundred and seventy-one", "three-hundred and seventy-two",
  "three-hundred and seventy-three", "three-hundred and seventy-four",
  "three-hundred and seventy-five", "three-hundred and seventy-six",
  "three-hundred and seventy-seven", "three-hundred and seventy-eight",
  "three-hundred and seventy-nine", "three-hundred and eighty",
  
  "three-hundred and eighty-one", "three-hundred and eighty-two",
  "three-hundred and eighty-three", "three-hundred and eighty-four",
  "three-hundred and eighty-five", "three-hundred and eighty-six",
  "three-hundred and eighty-seven", "three-hundred and eighty-eight",
  "three-hundred and eighty-nine", "three-hundred and ninety",
  "three-hundred and ninety-one", "three-hundred and ninety-two",
  "three-hundred and ninety-three", "three-hundred and ninety-four",
  "three-hundred and ninety-five", "three-hundred and ninety-six",
  "three-hundred and ninety-seven", "three-hundred and ninety-eight",
  "three-hundred and ninety-nine", "four-hundred"
};


// Works like sprintf(), without the need for a buffer. 03/16/98 callahan
char *tprintf(char *format,...)
{
  static char buff[MAX_STRING_LENGTH];
  va_list ap;

  va_start(ap, format);
  vsprintf(buff, format, ap);
  va_end(ap);

  buff[MAX_STRING_LENGTH - 1] = '\0';
  return buff;
}