/****************************************************************
 * players.c: Common robot code for TinyMUD automata
 *
 * HISTORY
 * 01-May-91  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Don't page idle players msgs.
 *
 * 05-May-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Seventh sequential release.  Add the quote player
 *	command, Avoid saving duplicate player dialog.
 *	Mods for TinyHELL.
 *
 * 02-Apr-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Sixth experimental release.  Add time information to
 *	who_is queries.
 *
 * 19-Jan-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Sixth experimental (expand player data in player file)
 *
 * 09-Jan-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Fourth general release (save room contents, trap ignore)
 *
 * 25-Jan-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Third interim release (allow numeric IP addresses)
 *
 * 05-Jan-90  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Second General Release.
 *
 * 31-Dec-89  Michael Mauldin (mlm) at Carnegie-Mellon University
 *	Created.
 ****************************************************************/

# include <stdio.h>
# include <ctype.h>
# include <time.h>
# include <sys/types.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
# include <setjmp.h>
# include <netinet/in.h>
# include <netdb.h>
# include <ctype.h>
# include <varargs.h>

# include "robot.h"
# include "vars.h"

/****************************************************************
 * find_player: return player name
 ****************************************************************/

long find_player (name)
char *name;
{ register long i;
  char pat[MSGSIZ];

  if (!name && !*name) return (-1);

  strcpy (pat, lcstr (name));
  
  if (streq (pat, "you") || streq (pat, "yourself"))
  { strcpy (pat, lcstr (myname)); }

  for (i=0; i<players; i++)
  { if (strcmp (pat, lcstr (player[i].name)) == 0)
    { return (i); }
  }
  
  return (-1);
}

/****************************************************************
 * close_player: return player name, or speaker name if "me"
 ****************************************************************/

long close_player (str, who)
char *str, *who;
{ register long i;
  char pat[MSGSIZ];

  if (!str && !*str) return (-1);

  strcpy (pat, lcstr (str));
  
  if (streq (pat, "you") || streq (pat, "yourself"))
  { strcpy (pat, lcstr (myname)); }

  else if (streq (pat, "me") || streq (pat, "myself") || streq (pat, "i"))
  { strcpy (pat, lcstr (who)); }

  for (i=0; i<players; i++)
  { if (strcmp (pat, lcstr (player[i].name)) == 0)
    { return (i); }
  }
  
  return (-1);
}

/****************************************************************
 * add_player: return player name
 ****************************************************************/

long add_player (name)
char *name;
{ register long i;

  if (reserved (name)) return (-1);

  if ((i = find_player (name)) >= 0)
  { return (i); }
  
  /* Expand array if need be */
  if (players >= maxplayer)
  { PLAYER *oldplayer = player;

    maxplayer = maxplayer * 6 / 5 + 50;
    player_sp = maxplayer * sizeof (PLAYER);
    player = (PLAYER *) ralloc (player_sp);

    fprintf (stderr, "Util: expanding player array to %ld entries\n",
	     maxplayer);

    if (player == NULL)
    { crash_robot ("Malloc returns NULL in add_player"); }
  
    for (i=0; i<players; i++) player[i] = oldplayer[i];

    free (oldplayer);
  }

  fprintf (stderr, "Play: adding player %s, number %ld\n", name, players);

  player[players].name = makestring (name);
  player[players].number = 0;
  player[players].present = 0;
  player[players].active = 0;
  player[players].firstsaw = now;
  player[players].lastsaw = 0;
  player[players].lastspoke = 0;
  player[players].lastheard = 0;
  player[players].lastplace = -1;
  player[players].lastactive = 0;
  player[players].lastgave = 0;
  player[players].lastdona = 0;
  player[players].dontotal = 0;
  player[players].lastkill = 0;
  player[players].lastoffend = 0;
  player[players].flags = 0;
  player[players].lastheralded = 0;
  player[players].lastlook = 0;
  player[players].user1 = 0;
  player[players].user2 = 0;

  player[players].desc = NULL;
  player[players].carry = NULL;
  player[players].dialog = NULL;
  player[players].email = NULL;
  player[players].msgs = NULL;
  
  return (players++);
}

/****************************************************************
 * clear_present
 ****************************************************************/

clear_present ()
{ register long i;

  for (i=0; i<players; i++)
  { player[i].present = 0; }

  player[me].active = 1;
  player[me].present = 1;
}

/****************************************************************
 * clear_active
 ****************************************************************/

clear_active ()
{ register long i;

  for (i=0; i<players; i++)
  { player[i].active = 0; }

  if (me >= 0)
  { player[me].active = 1;
    player[me].present = 1;
  }
}

/****************************************************************
 * saw_player: update player database
 ****************************************************************/

saw_player (name, place, desc)
char *name, *place, *desc;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  if (place && *place) rm = add_room (place, desc);
  clock = now;
  
  player[pl].lastsaw = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].present = 1;
}

/****************************************************************
 * arrive_player: update player database
 ****************************************************************/

arrive_player (name, place, desc)
char *name, *place, *desc;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  if (place && *place) rm = add_room (place, desc);
  clock = now;
  
  player[pl].lastsaw = clock;
  player[pl].lastactive = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].present = 1;
  player[pl].active = 1;
}

/****************************************************************
 * leave_player: update player database
 ****************************************************************/

leave_player (name, place, desc)
char *name, *place, *desc;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  if (place && *place) rm = add_room (place, desc);
  clock = now;
  
  player[pl].lastsaw = clock;
  player[pl].lastactive = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].present = 0;
  player[pl].active = 1;
}

/****************************************************************
 * idle_player: update player database
 ****************************************************************/

idle_player (name, idle)
char *name;
long idle;
{ long pl = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  clock = now;
  
  player[pl].lastactive = clock - idle;
  player[pl].active = 1;
}

/****************************************************************
 * active_player: update player database
 ****************************************************************/

active_player (name, place, desc)
char *name, *place, *desc;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);

  if (place && *place && desc && *desc)
  { if (find_room (place, desc) == hererm) player[pl].present = 1;
    rm = add_room (place, desc);
  }
  clock = now;
  
  player[pl].lastactive = clock;
  player[pl].lastsaw = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  

  player[pl].active = 1;
}

/****************************************************************
 * gave_player: update player database
 ****************************************************************/

gave_player (name, amount)
char *name;
long amount;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  rm = add_room (here, desc);
  clock = now;
  
  player[pl].lastactive = clock;
  player[pl].lastsaw = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].lastgave = clock;
}

/****************************************************************
 * donate_player: update player database
 ****************************************************************/

donate_player (name, amount)
char *name;
long amount;
{ long pl = -1, rm = -1, clock;

  if ((pl = add_player (name)) < 0) return (-1);
  rm = add_room (here, desc);
  clock = now;
  
  player[pl].lastactive = clock;
  player[pl].lastsaw = clock;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].lastdona = clock;
  player[pl].dontotal += amount;
}

/****************************************************************
 * assault_player: update player database
 ****************************************************************/

assault_player (name)
char *name;
{ long pl = -1, rm = -1;

  if ((pl = add_player (name)) < 0) return (-1);
  rm = add_room (here, desc);
  
  player[pl].lastactive = now;
  player[pl].lastsaw = now;
  if (rm >= 0) player[pl].lastplace = rm;
  
  player[pl].lastkill = now;

  player[pl].present = 1;
  player[pl].active = 1;
  strcpy (killer, name);
}

/****************************************************************
 * heard_player: update player database
 ****************************************************************/

heard_player (name, str)
char *name, *str;
{ long pl = -1;
  char buf[(MSGSIZ+DIALOGSIZE+2)];
  register char *s, *t;
  long len, bytes;

  if ((pl = add_player (name)) < 0) return (-1);

  player[pl].lastheard = now;
  player[pl].lastactive = now;
  player[pl].active = 1;

  if (msgtype < M_PAGE)
  { player[pl].present = 1;
    if (hererm >= 0) player[pl].lastplace = hererm;
  }

  /* Track last few lines of dialog  - avoid duplicate sentences */
  if (str &&
      !is_hearts (lcstr (str)) &&
      !stlmatch (str, "something to ") &&
      !stlmatch (str, "I once heard  ") &&
      !sindex (str, "quote ") &&
      !sindex (lcstr (str), "give me pennies") &&
      !is_tell (lcstr (str)) &&
      (!isowner (name) ||
		!sindex (lcstr (str), "code") &&
		!sindex (lcstr (str), "shutdown") &&
		!sindex (lcstr (str), "terse") &&
		!sindex (lcstr (str), "debug")) &&
      (player[pl].dialog == NULL || !sindex (player[pl].dialog, str)))
  { if (player[pl].dialog == NULL)
    { player[pl].dialog = makefixstring ("", DIALOGSIZE); }

    /* Copy message into buf, removing vertical bars (|) */
    for (s=str, t=buf, bytes=0; *s && bytes < BIGBUF; s++)
    { if (*s != '|')
      { *t++ = *s; bytes++; }
    }

    /* Append older messages, separated by vertical bars (|) */
    *t++ = '|'; bytes++;
    for (s=player[pl].dialog; *s && bytes < BIGBUF; )
    { *t++ = *s++; bytes++; }
    *t++ = '\0';

    if (bytes > (DIALOGSIZE + MSGSIZ))
    { fprintf (stderr, "Warn: heard_player, bytes moved %ld!\n", bytes); }
    
    buf[DIALOGSIZE-1] = '\0';
    
    if ((bytes = strlen (buf)) >= DIALOGSIZE)
    { crash_robot ("buf too long in heard_player, %ld bytes", bytes); }
    
    strcpy (player[pl].dialog, buf);
  }
}

/****************************************************************
 * spoke_player: update player database
 ****************************************************************/

spoke_player (name)
char *name;
{ long pl = -1;

  if ((pl = add_player (name)) < 0) return (-1);

  player[pl].lastspoke = now;
}

/****************************************************************
 * read_players: Read in a TinyMud players file
 ****************************************************************/

#define OLDEST(X,Y) ((X)?((X)<(Y)?(X):(Y)):(Y))
#define NEWEST(X,Y) ((X)>(Y)?(X):(Y))

read_players (fn)
char *fn;
{ char buf[BIGBUF], name[MSGSIZ], msgtxt[BIGBUF], wname[MSGSIZ];
  register char *s;
  FILE *mfile;
  long npl = 0, timestmp, cur = -1, nread = 0;
  PLAYER *pp;
  long t_number, t_dontotal, t_firstsaw, t_flags, t_lastactive, t_lastdona;
  long t_lastgave, t_lastheard, t_lastkill, t_lastlook, t_lastoffend;
  long t_lastplace, t_lastsaw, t_lastspoke, t_user1, t_user2;

  /* Open the players file */
  if ((mfile = fopen (fn, "r")) == NULL)
  { if (access (fn, 0) < 0)
    { fprintf (stderr, "Util: starting with blank players\n");
      realloc_players (200);
      return (1);
    }
    else
    { fatal ("Util: can't read %s.\n", fn); }
  }

  /* Read player file header */
  if (!fgets (buf, BIGBUF, mfile))
  { fclose (mfile);
    fprintf (stderr,
	     "Util: null players file, %s, will be over-written\n", fn);
    realloc_players (200);
    return (1);
  }
  
  /* Check for old style players file */  
  if (stlmatch (buf, "Gloria Players File"))
  { fclose (mfile);
    fatal ("Old style players file, use mn6cvt to convert it\n");
  }

  /* Check for new style header */
  if (!stlmatch (buf, "Maas-Neotek Players file"))
  { fclose (mfile);
    fatal ("Fatal, '%s' %s\nBad line: %s\n",
	   fn, "does not have a valid Maas-Neotek players file header", buf);
  }
  
  /* Now read room list */  
  while (fgets (buf, BIGBUF, mfile))
  { buf[strlen (buf) - 1] = '\0';

    if (*buf != '\0' && buf[1] != ':')
    { fprintf (stderr, "Warn: bad player line: %s\n", buf);
      continue;
    }

    /* Handle each line differently */
    switch (*buf)
    { case 'K':	if (npl > 0)
    		{ fprintf (stderr, "Warning, new K: line %s\n", buf); }
		else
		{ npl = atol (buf+2);
		  if (npl <= 0)
		  {   fclose (mfile);
		    fprintf (stderr, "Warning in %s, %s\n: %s\n",
			   fn, "number of players should be positive", buf);
		  }
		
		  if (npl < 200) npl = 200;
		
		  realloc_players (npl);
		}
		break;

      case 'G': if (sscanf (buf, "G:%ld %[^\n]", &heraldtime, herald) != 2)
		{ *herald = '\0'; heraldtime = 0; }
		break;

      case 'T': 
      case 'H': 
      case 'P': 
      case 'I': /* Ignore extra information for now */
		break;

      case 'W':	if (sscanf (buf, "W:%[^\n]", wname) == 1)
		{ if (!streq (world, wname))
		  { fprintf (stderr, "Wrld: new is %s, old was %s\n",
			     world, wname);
		  }
		}
		break;

      case 'M': if (cur >= 0)
		{ if (sscanf (buf, "M:%d %[^\n]", &timestmp, msgtxt) == 2)
		  { add_msg (cur, timestmp, msgtxt); }
		  else
		  { fprintf (stderr, "Warn: bogus M line: %s", buf); }
		}
		break;
      
      case 'N': 
#ifdef DEBUG_PLAYERS
		if ((++nread % 100) == 0)
		{ fprintf (stderr, "Read %d entries...\n", nread); }
#endif
		if (!valid_user_name (buf+2))
		{ cur = -1;
		  fprintf (stderr, "Warn: bogus player name '%s'\n", buf);
		}
		else if ((cur = find_player (buf+2)) >= 0)
		{ if (cur >= maxplayer)
		  { fprintf (stderr, "Woah! cur %d > maxplayer 5d.\n",
			     cur, maxplayer); }
		}
		else
		{ cur = players++;
#ifdef DEBUG_PLAYERS
		  if ((players % 500) == 0)
		  { fprintf (stderr, "Read %d unique players...\n", players); }
#endif
		  player[cur].name = makestring (buf+2);
		}
		break;

      case 'F':	if (cur >= 0)
		{ pp = &player[cur];
	          if (sscanf (buf,
			      "F:%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld",
			      &t_number, &t_dontotal, &t_firstsaw, &t_flags,
			      &t_lastactive, &t_lastdona, &t_lastgave,
			      &t_lastheard, &t_lastkill, &t_lastlook,
			      &t_lastoffend, &t_lastplace, &t_lastsaw,
			      &t_lastspoke, &t_user1, &t_user2) == 16)
	          { 
		    pp->number = OLDEST (pp->number, t_number);
		    pp->dontotal += t_dontotal;
		    pp->firstsaw = OLDEST (pp->firstsaw, t_firstsaw);
		    pp->flags |= t_flags;
		    pp->lastactive = NEWEST (pp->lastactive, t_lastactive);
		    pp->lastdona = NEWEST (pp->lastdona, t_lastdona);
		    pp->lastgave = NEWEST (pp->lastgave, t_lastgave);
		    pp->lastheard = NEWEST (pp->lastheard, t_lastheard);
		    pp->lastkill = NEWEST (pp->lastkill, t_lastkill);
		    pp->lastlook = NEWEST (pp->lastlook, t_lastlook);
		    pp->lastoffend = NEWEST (pp->lastoffend, t_lastoffend);
		    pp->lastplace = t_lastplace;
		    pp->lastsaw = NEWEST (pp->lastsaw, t_lastsaw);
		    pp->lastspoke = NEWEST (pp->lastspoke, t_lastspoke);
		    pp->user1 = t_user1;
		    pp->user2 = t_user2;
		  }
		  else
		  { fprintf (stderr, "Warn: Bogus F line: %s\n", buf); }
		}
		
		break;

      case 'S':	if (cur >= 0)
		{ if (player[cur].dialog)
		  { strncpy (player[cur].dialog, buf + 2, DIALOGSIZE);
		    player[cur].dialog[DIALOGSIZE-1] = '\0';
		  }
		  else
		  { player[cur].dialog = makefixstring (buf + 2, DIALOGSIZE); }
		}
		break;

      case 'D':	if (cur >= 0)
		{ freestring (player[cur].desc);
		  player[cur].desc = makestring (buf + 2);
		}
		break;

      case 'C':	if (cur >= 0)
		{ if (player[cur].carry)
		  { strncpy (player[cur].carry, buf + 2, DIALOGSIZE);
		    player[cur].carry[DIALOGSIZE-1] = '\0';
		  }
		  else
		  { player[cur].carry = makefixstring (buf + 2, DIALOGSIZE); }
		}
		break;

      case 'E':	if (cur >= 0)
		{ freestring (player[cur].email);
		  player[cur].email = makestring (buf + 2);
		}
		break;

      case '#':
      case '\0': break;

      default:	fprintf (stderr, "Warn: bad player file line: %s\n", buf);
    }
  }
  
  fclose (mfile);

  fprintf (stderr, "Util: read %ld players from %s\n", players, fn);

  return (1);
}

/****************************************************************
 * realloc_players:
 ****************************************************************/

realloc_players (n)
long n;
{ PLAYER *oldplayer = player;
  long oldmax = maxplayer;
  register long i;

  maxplayer = n+100;

  player_sp = maxplayer * sizeof (PLAYER);
  player = (PLAYER *) ralloc (player_sp);

  fprintf (stderr, "Util: allocating player array of %ld entries\n",
	   maxplayer);

  if (player == NULL)
  { crash_robot ("malloc returns NULL in realloc_players"); }

  /* If expanding, copy over old information */
  i = 0;
  if (oldplayer)  
  { fprintf (stderr, "Copying old array[%d..%d] to new array...\n",
	     i, oldmax-1);

    for (; i<oldmax; i++)
    { player[i] = oldplayer[i]; }
  
    free (oldplayer);
  }
  
  /* Now zero out the rest of the array */
  fprintf (stderr, "Util: clearing out player array from %d to %d...\n",
	   i, maxplayer);
  for (; i<maxplayer; i++)
  { player[i].number = -1;
    player[i].present = 0;
    player[i].active = 0;
    player[i].firstsaw = 0;
    player[i].lastsaw = 0;
    player[i].lastspoke = 0;
    player[i].lastheard = 0;
    player[i].lastplace = 0;
    player[i].lastactive = 0;
    player[i].lastgave = 0;
    player[i].lastdona = 0;
    player[i].dontotal = 0;
    player[i].lastkill = 0;
    player[i].lastoffend = 0;
    player[i].flags = 0;
    player[i].lastheralded = 0;
    player[i].lastlook = 0;
    player[i].user1 = 0;
    player[i].user2 = 0;

    player[i].name = NULL;
    player[i].desc = NULL;
    player[i].carry = NULL;
    player[i].dialog = NULL;
    player[i].email = NULL;
    player[i].msgs = NULL;
  }
}

/****************************************************************
 * write_players: Write current state of players
 ****************************************************************/

write_players (outname)
char *outname;
{ register long i;
  register O_EXIT *xp;
  long swap = 0, newplayers = 0;
  char tmpfile[MSGSIZ];
  MSGS *mp;
  FILE *outfil;

  if (players <= 0) return (0);

  sprintf (tmpfile, "%s.NEW", outname);
  if (access (outname, 0) == 0)
  { swap++;
    if ((outfil = fopen (tmpfile, "w")) == 0)
    { perror (tmpfile); return (0); }
  }
  else
  { if ((outfil = fopen (outname, "w")) == 0)
    { perror (outname); return (0); }
  }
  
# ifndef NO_WEED_PLAYERS
  /* Weed out old players that havent been seen for a while */
  for (i=0; i<players; i++)
  { long age;

    if (player[i].lastactive > 0)
    { age = now - player[i].lastactive; }
    else if (player[i].lastsaw > 0)
    { age = now - player[i].lastsaw; }
    else
    { age = now - player[i].firstsaw; }

    /* Everyone hangs around for 2 months, plus we remember trusties & jerks */
    if (age < 60 * DAYS || PLAYER_GET (i, PL_JERK | PL_REMEMBER))
    { newplayers++; continue; }

    /* If there are non-entities (no messages or quotes), get rid of them */
    if (player[i].dialog == NULL && player[i].msgs == NULL)
    { PLAYER_SET (i, PL_OLD); continue; }
    
#ifdef WEED_500
    /* Other players hang around for 500 days */
    if (age < 500 * DAYS)
    { newplayers++; continue; }
#endif
    
    /* If we havent seen them for 500 days, good riddance */
    PLAYER_SET (i, PL_OLD); continue;
  }
#else
  /* Keep everyone */
  for (i=0; i<players; i++)
  { PLAYER_CLR (i, PL_OLD); }

  newplayers = players;
# endif

  fprintf (outfil, "Maas-Neotek Players file: %s\n\n", VERSION);

  fprintf (outfil, "K:%ld players\n", newplayers);
  fprintf (outfil, "T:%s", ctime (&now));
  fprintf (outfil, "W:%s\n", world);
  fprintf (outfil, "H:%s\n", mudhost);
  fprintf (outfil, "P:%ld\n", mudport);
  fprintf (outfil, "I:%s\n", myname);
  if (*herald && heraldtime > 0)
  { fprintf (outfil, "G:%ld %s\n", heraldtime, herald); }

  for (i=0; i<players; i++)
  { if (player[i].name && player[i].name[0] && !PLAYER_GET (i, PL_OLD))
    { fprintf (outfil, "\nN:%s\n", player[i].name);
      fprintf (outfil,
	 "F:%ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld %ld\n",
	       player[i].number, player[i].dontotal, player[i].firstsaw,
	       player[i].flags, player[i].lastactive, player[i].lastdona,
	       player[i].lastgave, player[i].lastheard, player[i].lastkill,
	       player[i].lastlook, player[i].lastoffend, player[i].lastplace,
	       player[i].lastsaw, player[i].lastspoke, player[i].user1,
	       player[i].user2);

      if (player[i].desc && player[i].desc[0])
      { fprintf (outfil, "D:%s\n", player[i].desc); }

      if (player[i].carry && player[i].carry[0])
      { fprintf (outfil, "C:%s\n", player[i].carry); }

      if (player[i].email && player[i].email[0])
      { fprintf (outfil, "E:%s\n", player[i].email); }

      if (player[i].dialog && player[i].dialog[0])
      { fprintf (outfil, "S:%s\n", player[i].dialog); }
      
      for (mp = player[i].msgs; mp; mp = mp->next)
      { fprintf (outfil, "M:%ld %s\n", mp->timestmp, mp->text); }
    }
  }
  
  fclose (outfil);
  
  if (swap)
  { return (swap_files (tmpfile, outname)); }
  else
  { return (1); }
}

/****************************************************************
 * player_query
 ****************************************************************/

player_query (pl, name)
long pl;
char *name;
{ long dur = now - player[pl].lastsaw;
  char answer[BIGBUF];

  if (name && *name) strcpy (speaker, name);

  /* Four cases: here, seen known place, seen unknown place, not seen */
  if (player[pl].present)
  { if (name && streq (name, player[pl].name))
    { sprintf (answer, "\"You are here in %s", here); }
    else
    { if (player[pl].active)
      { sprintf (answer, "\"%s is right here in %s",
		 player[pl].name, here);
      }
      else
      { sprintf (answer, "\"%s has been asleep here in %s for about %s",
		player[pl].name, here,
		time_dur (now - player[pl].lastactive));
      }
    }
  }

  else if (player[pl].lastplace >= 0 &&
	   player[pl].lastplace < rooms &&
	   player[player[pl].lastplace].name &&
	   player[pl].lastsaw > 0)
  { sprintf (answer, "\"%s was %sin %s about %s ago",
	     player[pl].name,
	     (((now - player[pl].lastsaw) > 600 ||
	      player[pl].active) ? "" : "asleep "),
	     room_name (player[pl].lastplace), time_dur (dur));
  }
  else if (player[pl].lastsaw > 0)
  { sprintf (answer, "\"I saw %s about %s ago",
	     player[pl].name, time_dur (dur));
  }
  else
  { sprintf (answer, "\"I haven't seen %s", player[pl].name); }

  if (name)
  { sprintf (answer, "%s{, n}.", answer); }
  else
  { strcat (answer, "."); }
  
  reply ("%s", answer);
}

/****************************************************************
 * all_player_query
 ****************************************************************/

typedef struct astruct { long pl, ago; } P_ORDER;

cmp_saw (a, b)
register P_ORDER *a, *b;
{ return (a->ago - b->ago); }

all_player_query (dur, name)
long dur;
char *name;
{ long limit, recent;
  P_ORDER *order;
  register long pl, i, cnt;
  int numpl;
  char who[MSGSIZ];
  
  strcpy (who, name);
  
  if (alone > 2 && msgtype < M_WHISPER) msgtype = M_WHISPER;

  limit = now - dur;
  recent = now - 300;
  
  numpl = players;

  /* Make a permutation array, keyed by "timeago" */  
  if ((order = (P_ORDER *) ralloc (numpl * sizeof (P_ORDER))) == NULL)
  { crash_robot ("malloc returns NULL in all_player_query"); }

  /* Fill the array */
  for (i=0; i<numpl; i++)
  { order[i].pl = i; order[i].ago = now - player[i].lastsaw; }

  /* Sort by time ago */
  qsort (order, numpl, sizeof (P_ORDER), cmp_saw);

  /* Now go through the players in order of recency */
  for (i = 0, cnt = 0; i < numpl && cnt < 20; i++)
  { pl = order[i].pl;

    if (player[pl].name && player[pl].name[0] &&
        !streq (player[pl].name, name) &&
	!streq (player[pl].name, myname) &&
	(player[pl].active || (now - player[pl].lastactive) < 12 * HOURS) &&
	((cnt < 10) || (player[pl].lastsaw > recent)))
    { strcpy (speaker, who);
      player_query (pl, NULL); cnt++;
    }
  }
  
  reply ("|done");

  /* Free the temporary array before returning */  
  free (order);
}

/****************************************************************
 * reserved: Words that cannot (or should not) be real users
 ****************************************************************/

char *rw[] = {
  "(", "a", "about", "an", "and", "any", "anything", "but", "damn", "few",
  "for", "fuck", "haha", "hello", "help", "hey", "hi", "how", "i", "in",
  "maybe", "more", "most", "no", "nope", "not", "of", "oh", "out", "piss",
  "right", "shit", "some", "test", "testing", "thanks", "that", "the",
  "these", "this", "those", "what", "when", "where", "who", "why", "with",
  "wrong", "yeah", "yep", "yes", "you", "your", "yup", NULL };

reserved (word)
char *word;
{ register char **s;

  for (s=rw; *s; s++)
  { if (strfoldeq (*s, word)) return (1); }
  
  return (0);
}

/****************************************************************
 * check_players: If we start a new map, clear lastpace for each
 *		  player
 ****************************************************************/

check_players ()
{ register long pl;

  if (rooms == 0 && players > 0)
  { for (pl=0; pl<players; pl++)
    { player[pl].lastplace  = -1; }
    
    fprintf (stderr, "Play: cleared %ld player's lastplace flags\n", players);
  }
}

/****************************************************************
 * look_up_player: Find a player's tinymud ID number
 ****************************************************************/

long look_up_player (name)
char *name;
{ long pl;
  static long inlook = 0;

  if ((pl = find_player (name)) < 0)	return (0);
  if (player[pl].number > 0)		return (player[pl].number);

# ifdef OLD_EXAMINE
  if (inlook)
  { fprintf (stderr, "Warn: got recursive look_up_players, dropping one\n");
    return (0);
  }

  lastlock = 0;

  inlook++;
  sendmud ("%s %s\n@lock me = *%s\nexamin me\n%s %s\n@lock me = me",
	   opre, numpre, name, opre, outpre);
  waitfor (outsuf);	/* Eat lock output */
  waitfor (outsuf);	/* Eat examine output */
  waitfor (outsuf);	/* Eat relock output */
  inlook--;
  
  if (lastlock > 0)
  { player[pl].number = lastlock; }
  else
  { lastlock = 0; }
# else
  lastlock = 0;
# endif
  
  return (lastlock);
}

/****************************************************************
 * look_at_thing: Find a thing's description
 ****************************************************************/

look_at_thing (name)
char *name;
{ long pl;

  if ((pl = find_player (name)) >= 0)
  { player[pl].lastlook = now; }
  
  if (debug)
  { fprintf (stderr, "Look: issuing look at '%s'(%ld)\n", name, pl); }

  sendmud ("%s %s %s\nlook %s\n%s %s",
	   opre, plypre, name, name, opre, outpre);
  waitfor (outsuf);	/* Eat look output output */
  
  return (1);
}

/****************************************************************
 * killed_me_today: Return true is 'name' attempted to kill us
 ****************************************************************/

killed_me_today (name)
char *name;
{ long pl, dur;

  if ((pl = find_player (name)) >= 0)
  { dur = now - player[pl].lastkill;
  
    if (dur < 18 * HOURS)
    { return (dur); }
  }
  
  return (0);
}

/****************************************************************
 * object_query: Look at player names, descriptions, and inventories.
 ****************************************************************/

object_query (obj, name)
char *obj;
{ register long pl;
  int numpl;
  long printed = 0;

  if (alone > 2 && msgtype < M_WHISPER) msgtype = M_WHISPER;

  numpl = players;

  for (pl=0; pl<numpl; pl++)
  { if (sindex (lcstr (player[pl].name), obj) ||
	player[pl].desc  && sindex (lcstr (player[pl].desc), obj) ||
	player[pl].carry && sindex (lcstr (player[pl].carry), obj))
    { if (!printed++)
      { reply ("\"Here are the matches for %s{, n}:", obj); }
	
      if (printed > 10 && (alone > 1 || !isowner (name)))
      { reply ("\"There are more matches, but %s, %s.",
	       " it's too crowded here to go on", name);
	return;
      }

      strcpy (speaker, name);
      reply ("|");

      strcpy (speaker, name);
      if (player[pl].desc)
      { reply ("| %s's description is: %s", player[pl].name,player[pl].desc);}

      strcpy (speaker, name);
      if (player[pl].carry)
      { reply ("| %s carries: %s", player[pl].name, player[pl].carry); }
      
      if (!player[pl].desc && !player[pl].carry)
      { strcpy (speaker, name);
	reply ("| %s", player[pl].name);
      }
    }
  }
  
  strcpy (speaker, name);

  if (!printed)
  { reply ("\"I don't know anyone matching %s{, n}.", obj); }
  else
  { reply ("|done."); }
}

/****************************************************************
 * asleep_query: Who is asleep in this room
 ****************************************************************/

asleep_query (name)
char *name;
{ char buf[BUFSIZ];
  register long pl;
  long printed = 0;

  strcpy (buf, "");
  for (pl=0; pl<players; pl++)
  { if (player[pl].present && !player[pl].active)
    { if (printed) strcat (buf, " ");
      strcat (buf, player[pl].name);
      printed++;
    }
  }

  if (!printed)
  { reply ("\"I don't see anyone asleep here{, n}."); }
  else
  { reply ("\"Well %s, I see %ld player%s asleep here: %s", name,
	   printed, printed == 1 ? "" : "s", buf);
  }
}

/****************************************************************
 * awake_query: Who is awake in this room
 ****************************************************************/

awake_query (name)
char *name;
{ char buf[BUFSIZ];
  register long pl;
  long printed = 0;

  strcpy (buf, "");
  for (pl=0; pl<players; pl++)
  { if (player[pl].present && player[pl].active)
    { if (printed) strcat (buf, " ");
      strcat (buf, player[pl].name);
      printed++;
    }
  }

  if (!printed)
  { reply ("\"I don't see anyone awake here{, n}."); }
  else
  { reply ("\"Well %s, I see %ld player%s awake here: %s", name,
	   printed, printed == 1 ? "" : "s", buf);
  }
}

/****************************************************************
 * connected_query: Who is connected
 ****************************************************************/

connected_query (name)
char *name;
{ char buf[BIGBUF];
  register long pl;
  long printed = 0, lurk = 0;
  
  wsynch ();

  strcpy (buf, "");
  for (pl=0; pl<players; pl++)
  { if (player[pl].active && (player[pl].lastactive + 30 * MINUTES > now))
    { if (printed) strcat (buf, " ");
      strcat (buf, player[pl].name);
      printed++;
    }
  }

  if (!printed)
  { reply ("\"I don't see anyone idle less than 30 minutes{, n}."); }
  else
  { reply ("\"Well %s, I see %ld player%s idle less than 30 minutes: %s", name,
	   printed, printed == 1 ? "" : "s", buf);
  }

  strcpy (buf, "");
  for (pl=0; pl<players; pl++)
  { if (player[pl].active && (player[pl].lastactive + 30 * MINUTES <= now))
    { if (lurk) strcat (buf, " ");
      strcat (buf, player[pl].name);
      lurk++;
    }
  }

  if (lurk)
  { reply ("\"I %ssee %ld player%s idle more than 30 minutes: %s",
	   printed ? "also " : "", lurk, lurk == 1 ? "" : "s", buf);
  }
}

/****************************************************************
 * who_is: Who is a particular player
 ****************************************************************/

who_is (pers, name)
char *pers, *name;
{ long pl, msgcnt;
  long printed=0;
  char who[MSGSIZ];

  pl = find_player (pers);
  strcpy (who, name);

  if (msgtype < M_WHISPER) msgtype = M_WHISPER;

  /* Unknown player */
  if (pl < 0 && streq (name, "(>"))
  { switch (nrrint (189, 3))
    { case 0: zinger ("\"That's the heartsbot{, n}.");  break;
      case 1: zinger ("\"The hearts playing robot{, n}."); break;
      case 2: zinger ("\"(> moderates games of Hearts{, n}."); break;
    }

    return (1);
  }

  /* Unknown player */
  if (pl < 0)
  { switch (nrrint (190, 5))
    { case 0: reply ("\"I have never seen player %s{, n}.", pers);  break;
      case 1: reply ("\"I've never seen %s{, n}.", pers); break;
      case 2: reply ("\"%s who{, n}?", pers); break;
      case 3: reply ("\"I don't know any %s{, n}.", pers); break;
      case 4: reply ("\"Never met %s{, n}.",
      		     malep (pers) ? "him" : "her"); break;
    }

    return (1);
  }

  /* Check for person asking about robot */
  if (streq (pers, lcstr (myname)))
  { reply ("\"My description is %s.", mydesc);
    return (1);
  }

  if (debug)
  { fprintf (stderr, "Who:  gave dossier of %s(%ld)\n", pers, pl); }
  
  printed = who_is_hook (pers, name);
  
  if (printed && is_newbie (name)) return (1);

  /*
   * Need short description for newbies...something that does not sound
   * like it came from a computer (no X seconds ago, and no long repetition
   * of description
   */

  /* Give more info about non-owners */
  if (!streq (pers, "fuzzy") || paging)
  {
    /* Report players last description */
    if (!is_newbie (name) && player[pl].number > 0)
    { strcpy (speaker, who);
      if (streq (pers, lcstr (name)))
      { reply ("\"Your id number is %ld{, n}", player[pl].number); }
      else
      { reply ("\"%s's id number is %ld{, n}",
	          pers, player[pl].number);
      }
      printed++;
    }

    /* Report players last description */
    if (player[pl].desc && player[pl].desc[0])
    { strcpy (speaker, who);

      if (strfoldeq (pers, name))
      { reply ("\"As of %s ago, your description was: %s",
	          time_dur (now-player[pl].lastlook), player[pl].desc);
      }
      else
      { reply ("\"As of %s ago, %s's description was: %s",
	          time_dur (now-player[pl].lastlook), pers, player[pl].desc);
      }
      printed++;
    }

    /* Report players last Email address */
    if (player[pl].email && player[pl].email[0])
    { strcpy (speaker, who);

      if (strfoldeq (pers, name))
      { reply ("\"Your Email address: %s", player[pl].email); }
      else
      { reply ("\"%s's Email address: %s", pers, player[pl].email); }
      printed++;
    }
    
    if ((msgcnt = msg_count (pl)) > 0)
    { strcpy (speaker, who);
      reply ("\"I have %d message%s for %s.",
		msgcnt, msgcnt == 1 ? "" : "s", pers);
    }

    /* Report players last inventory */
    if (player[pl].carry && player[pl].carry[0])
    { strcpy (speaker, who);

      if (streq (pers, lcstr (name)))
      { reply ("\"You were carrying: %s", player[pl].carry); }
      else
      { reply ("\"%s was carrying: %s", pers, player[pl].carry); }
      printed++;
    }

    /* Report first time seen */
    if (player[pl].firstsaw > 0)
    { strcpy (speaker, who);

      reply ("\"I first saw %s logged in %s ago", pers,
		time_dur (now - player[pl].firstsaw));
    }

    /*----  Report various memory things about people ----*/
    
    /* Juicy quotes */
    if (player[pl].dialog && player[pl].dialog[0])
    { strcpy (speaker, who);
      quote_player (pers, name, 0); }

    /* Report last time assualted */
    if (player[pl].lastkill)
    { strcpy (speaker, who);
      reply ("\"%s last attacked me %s ago", pers,
		time_dur (now - player[pl].lastkill));
    }
    
    /* Report last time donated, and total amount */
    if (player[pl].lastdona)
    { strcpy (speaker, who);
      reply ("\"%s last gave me money %s ago, and %s %ld %s.", pers,
		time_dur (now - player[pl].lastdona),
		(player[pl].dontotal == 1) ?
			"had the generosity to give me" :
			"has given me a total of",
		player[pl].dontotal,
		(player[pl].dontotal == 1) ? "whole penny" : "pennies");
    }
    
    /* Report jerk status */
    if (PLAYER_GET (pl, PL_JERK))
    { strcpy (speaker, who);
      reply ("\"I will not obey commands from %s", pers); }
  }

  if (!printed)
  { strcpy (speaker, who);
    if (streq (pers, lcstr (name)))
    { reply ("\"I don't really know who you are{, n}."); }
    else
    { reply ("\"I don't really know who %s is{, n}.", pers); }
  }
}

/****************************************************************
 * quote_player: Repeat the words of another player
 ****************************************************************/

quote_player (pers, name, gossip)
char *pers, *name;
int gossip;
{ long pl;
  long printed=0;

  /* Check for person asking about robot */
  if (streq (pers, lcstr (myname)) || streq (pers, "yourself"))
  { switch (nrrint (191, 3))
    { case 0: reply ("\"I'm not very quotable{, n}."); break;
      case 1: reply ("\"I'll leave that to others{, n}."); break;
      case 2: reply ("\"Sorry, I don't do recursion{, n}."); break;
    }
    return (1);
  }

  /* Check for 'me' */
  if (streq (pers, "me"))
  { pers = name; }
  
  if (streq (pers, lcstr (name)) && randint (100) < 20)
  { reply ("\"A little vain today, aren't we{, n}?");
    return (1);
  }

  /* Now do lookup */
  pl = find_player (pers);
  if (debug) fprintf (stderr, "Quote query: %s(%ld)\n", pers, pl);

  /* Unknown player */
  if (pl < 0)
  { if (gossip) reply ("\"I have never heard player %s{, n}.", pers);
    return (1);
  }

  /* Report various memory things about people */
  if (player[pl].dialog && player[pl].dialog[0])
  { register char *s, *t, *head, *tail;
    char *sent = NULL;
    long cnt=0;
    char buf[DIALOGSIZE];

    head = player[pl].dialog;
    tail = head + strlen (head);
    
    /* Now each '|' (or s == head) is start of sentence */
    for (s=head; *s; s++)
    { if (s == head || s[-1] == '|')
      { for (t=s; *t && *t != '|'; t++) ;
	if (*t == '|')
	{ strcpy (buf, lcstr (s));
	  buf[t-s] = '\0';
	}
	else
	{ break; }

	/* Dont report players queries */
	if (sindex (buf, "where is") ||
	    sindex (buf, "tell") ||
	    sindex (buf, "how do i g") ||
	    sindex (buf, "how do you g") ||
	    stlmatch (buf, "something to") ||
	    sindex (buf, "who is"))
	{ if (debug) fprintf (stderr, "Play: skipping '%s'\n", buf);
	  continue;
	}

	if (debug) fprintf (stderr, "Play: using    '%s'\n", buf);

	if (randint (++cnt) == 0) sent = s;
      }
    }
    
    /* If we chose a sentence, repeat it */
    if (sent)
    { strcpy (buf, sent);
      for (s=buf; *s && *s != '|'; s++) ;
      *s = '\0';
 
      if (streq (pers, lcstr (name)) || streq (pers, name))
      { reply ("\"I once heard you say, '%s'", buf); }
      else
      { reply ("\"I once heard %s say, '%s'", pers, buf); }
      printed++;
    }
  }
  
  if (!printed && gossip)
  { reply ("\"I haven't heard %s say anything quotable{, n}.", pers); }
  
  return (printed);
}

/****************************************************************
 * quote_random_player: Repeat the words of another player
 ****************************************************************/

quote_random_player (name)
char *name;
{ long pl;
  long printed=0;
  long cnt = 0;

  /* Loop through players, finding a quote */
  while (!printed && (pl = randint (players)) >= 0 && ++cnt < players)
  {
    /* Report various memory things about people */
    if (player[pl].dialog && player[pl].dialog[0])
    { register char *s, *t, *head, *tail;
      char *sent = NULL;
      long cnt=0;
      char buf[DIALOGSIZE];

      head = player[pl].dialog;
      tail = head + strlen (head);
    
      /* Now each '|' (or s == head) is start of sentence */
      for (s=head; *s; s++)
      { if (s == head || s[-1] == '|')
        { for (t=s; *t && *t != '|'; t++) ;
	  if (*t == '|')
	  { strcpy (buf, lcstr (s));
	    buf[t-s] = '\0';
	  }
	  else
	  { break; }

	  /* Dont report players queries */
	  if (sindex (buf, "where is") ||
	      sindex (buf, "tell") ||
	      sindex (buf, "how do i g") ||
	      sindex (buf, "how do you g") ||
	      stlmatch (buf, "something to") ||
	      sindex (buf, "who is"))
	  { if (debug) fprintf (stderr, "Play: skipping '%s'\n", buf);
	    continue;
	  }	

	  if (debug) fprintf (stderr, "Play: using    '%s'\n", buf);

	  if (randint (++cnt) == 0) sent = s;
        }
      }
    
      /* If we chose a sentence, repeat it */
      if (sent)
      { strcpy (buf, sent);
	for (s=buf; *s && *s != '|'; s++) ;
	*s = '\0';
   
	reply ("\"I once heard %s say, '%s'", player[pl].name, buf);
	printed++;
      }
    }
  }
  
  if (!printed)
  { reply ("\"I haven't heard any good gossip lately{, n}."); }
  
  return (printed);
}

/****************************************************************
 * is_jerk: True is player is a jerk
 ****************************************************************/

is_jerk (name)
char *name;
{ register long pl;

  if ((pl = find_player (name)) < 0) return (0);
  
  if (PLAYER_GET (pl, PL_JERK)) return (1);
  
  return (0);
}

/****************************************************************
 * valid_user_name:
 ****************************************************************/

valid_user_name (str)
register char *str;
{
  if (*str == '\0') return (0);
  
  if (reserved (str))
  { return (0); }
  
  while (*str)
  { if (*str < ' ' || *str > '~' || index (" \t=", *str)) return (0);
    str++;
  }
  
  return (1);
}

/****************************************************************
 * add_msg: Add a message to a players input queue
 ****************************************************************/

add_msg (pl, timestmp, msg)
long pl, timestmp;
char *msg;
{ MSGS *mp, *new;
  long mcnt = 0;

  if (!(new = (MSGS *) malloc (sizeof (MSGS))))
  { crash_robot ("malloc returns NULL in add_msg"); }

  new->text = makestring (msg);
  new->timestmp = timestmp;
  new->next = NULL;
  
  if ((mp = player[pl].msgs) == NULL)
  { player[pl].msgs = new; }
  else
  { for (mcnt=0; mp->next; mp = mp->next) mcnt++;
    mp->next = new;
  }
}

/***************************************************************
 * do_msgs: If someone is logged on or in the room, 
 *	check for their messages and repeat them
 ****************************************************************/

do_msgs ()
{ register long pl;
  MSGS *mp;
  char *who, *errmsg;
  static last_haven = 0;

  for (pl=0; pl<players; pl++)
  { if (player[pl].active &&
	(player[pl].msgs || *herald && !PLAYER_GET (pl, PL_HERALDED) ) &&
	(now - player[pl].lastactive <= 5 * MINUTES) &&
	(player[pl].present ||
	 pagedmsgs && (!PLAYER_GET(pl, PL_HAVEN) ||
	 	       (now - last_haven) > 10 * MINUTES)))
    { give_msgs (pl); }
  }
}

/****************************************************************
 * give_msgs:  Assume a player is active, send him/her any messages
 ****************************************************************/

give_msgs (pl)
long pl;
{ MSGS *mp;
  char *who, *errmsg, msgbuf[MSGSIZ], *unheralded();
  char recip[MSGSIZ];
  register char *s, *t;
  int is_herald, result = 0;
  static last_haven = 0;

  if (pl == me)
  { PLAYER_SET (pl, PL_HERALDED); return 0; }

  if (pl < 0) return 0;

  if (now - player[pl].lastactive > 5 * MINUTES) return 0;

  who = player[pl].name;
    
  /* last_haven tracks the last time we paged a non-Havened player */
  if (!player[pl].present && !PLAYER_GET(pl, PL_HAVEN))
  { last_haven = now; }
  

  /* Clear out recip */
  strcpy (recip, "");

  /* Deliver any private messages */
  while ((is_herald =
	   (*herald &&
	    !PLAYER_GET (pl, PL_HERALDED) &&
	    !PLAYER_GET (pl, PL_HAVEN))) ||
	 (mp = player[pl].msgs))
  { strcpy (speaker, who);

    /* Attempt to deliver herald message */
    if (is_herald)
    { strcpy (recip, "");
      msgtype = player[pl].present ? M_SPOKEN : M_PAGE;
      msgstat = 0;

      /* Use %n as a replacement for the name of the recipient */
      for (s=herald, t=msgbuf; *s; s++)
      { if (*s == '%')
        { switch (*(++s))
	  { 
	    /* Name of recipient(s) */
	    case 'n':	strcpy (recip, unheralded (msgtype, pl));
	    		strcpy (t, recip);
			while (*t) t++;
			break;
	    case 't':	strcpy (t, timeofday (0));
			while (*t) t++;
			break;
	    default:	*t++ = *s;
	    		break;
	  }
	}
	else
	{ *t++ = *s; }
      }
      *t = '\0';
      
      /* Find recipients */
      if (*recip == '\0')
      { strcpy (recip, player[pl].name); }

      /* Whisper long messages to a single recipient in a crowded
       * room, instead of saying them out loud */
      if (strlen (msgbuf) > 64 && 
	  alone > 2 &&
	  !sindex (recip, ", ") &&
	  !streq (recip, "everybody") &&
	  msgtype == M_SPOKEN)
      { msgtype = M_WHISPER; }

      fprintf (stderr, "Hrld: [%s to %s] \"%s\"\n",
	       (msgtype == M_WHISPER ? "whisper" :
	        msgtype == M_SPOKEN ? "say" : "page"),
	        recip, msgbuf);

      /* Deliver the message */
      unlogged ("\"%s", msgbuf);
    }

    /* Attempt to deliver private message */
    else
    { msgtype = player[pl].present ? M_WHISPER : M_PAGE;
      msgstat = 0;
      fprintf (stderr, "Dlvr: to %s, %s old\n",
	       player[pl].name, exact_dur (now - mp->timestmp));
      unlogged ("\"%s ago, %s", time_dur (now - mp->timestmp), mp->text);
    }

    /* Mark herald bits on recipients */
    if (msgstat >= 0 && is_herald)
    { if (msgtype == M_SPOKEN)
      { long i;
      
        for (i=0; i<players; i++)
	{ if (player[i].present && player[i].active)
	  { PLAYER_SET (i, PL_HERALDED);
	    player[i].lastheralded = now;
	  }
	}
      }
      else
      { PLAYER_SET (pl, PL_HERALDED);
	player[pl].lastheralded = now;
	result++;
      }
    }

    /* Remove message if delivery successful */
    else if (msgstat >= 0)
    { player[pl].msgs = mp->next;
      freestring (mp->text);
      free (mp);
      PLAYER_CLR(pl, PL_HAVEN);
      
      result++;
    }

    /* Error message to log if failed */
    else
    { switch (msgstat)
      { case  1:	errmsg = "success";			break;
	case  0:	errmsg = "not yet sent";		break;
	case -1:	player[pl].present = 0;
		    errmsg = "whisper to player not present";  break;
	case -2:	player[pl].active = 0;
		    errmsg = "recipient disconnected";	break;
	case -3:	player[pl].active = 0;
		    errmsg = "no such player";		break;
	case -4:	PLAYER_SET(pl, PL_HAVEN);
		    errmsg = "recipient set haven";		break;
	default:	errmsg = "unknown error";
      }

      fprintf (stderr,
	       "Warn: msg type %d to %s gave status %d (%s)\n",
	       msgtype, who, msgstat, errmsg);
      
      break;
    }
  }
  
  return (result);
}

/****************************************************************
 * unheralded: Names of people present who havent gotten the herald
 *	       if more than 3 names, use "everybody".  If msg is not
 *	       spoken, just return name.
 ****************************************************************/

char *unheralded (msgtype, pl)
int msgtype, pl;
{ static char buf[MSGSIZ];
  register char *s = buf;
  int total = 0, count = 0;

  if (msgtype != M_SPOKEN)
  { return (player[pl].name); }

  /* First count recipients */
  for (pl=0; pl<players; pl++)
  { if (player[pl].active && player[pl].present &&
	!PLAYER_GET (pl, PL_HERALDED))
    { total++; }
  }

  if (total > 3) return ("everybody");

  /* Now collect their names */
  strcpy (buf, "");
  for (pl=0; pl<players; pl++)
  { if (player[pl].active && player[pl].present &&
	!PLAYER_GET (pl, PL_HERALDED))
    { if (++count == 1)
      { strcpy (buf, player[pl].name); }
      else
      { strcat (buf, count == total ? " and " : ", ");
        strcat (buf, player[pl].name);
      }
    }
  }

  return (s);
}

/****************************************************************
 * msg_count: Returns number of messages for player
 ****************************************************************/

long msg_count (pl)
long pl;
{ MSGS *mp;
  long cnt=0;

  for (mp = player[pl].msgs; mp; mp = mp->next) cnt++;
  
  return (cnt);
}

/****************************************************************
 * msg_total: Returns number of messages for player
 ****************************************************************/

long msg_total (name)
char *name;
{ MSGS *mp;
  long cnt=0, pls=0, pl;
  

  for (pl=0; pl<players; pl++)
  { if (mp = player[pl].msgs)
    { pls++;
      for (; mp; mp = mp->next) cnt++;
    }
  }
  
  if (pl > 0)
  { reply ("\"I am holding %d message%s for %d player%s, %s.",
	   cnt, cnt==1 ? "" : "s", pls, pls == 1 ? "" : "s", name);
  }
  else
  { reply ("\"I don't have any messages for anyone right now{, n}."); }
}

/****************************************************************
 * most_query: Which room(s) have the most of something
 ****************************************************************/

# define QCNT 10

most_pl_query (name, type)
char *name;
int type;
{ long pl, b, i, j, best[QCNT], bcnt = 0;
  double val, bestval[QCNT];
  
  /* Search through every room */
  for (pl=0; pl<players; pl++)
   { if ((now - player[pl].lastspoke) > 7 * DAYS) continue;

    switch (type)
    { case FRIENDLY: val = (double) player[pl].user1; break;
    }
       
    /* Now find place to insert in sorted list */
    for (i=0; i<bcnt; i++)
    { if (bestval[i] < val)
      {
        /* Move the rest of the list down by 1 */
	for (j=QCNT-1; j>i; j--)
	{ best[j] = best[j-1]; bestval[j] = bestval[j-1]; }

	/* Now insert this room */
	best[i] = pl; bestval[i] = val;
	if (bcnt < QCNT) bcnt++;
	
	break;
      }
    }

    /* If we ran off the list, but it is short, add to the end */
    if (i == bcnt && bcnt < QCNT)
    { best[bcnt] = pl;
      bestval[bcnt] = val;
      bcnt++;
    }
  }
  
  /* best[0..bcnt-1] and bestval[0..bcnt-1]  now hold sorted list */
  switch (type)
  { case FRIENDLY:	reply ("\"The %d friendliest players are:",
			       QCNT);
  			for (b=0; b<bcnt; b++)
			{ strcpy (speaker, name);
			  reply ("| %s spoke to me %1.0lf times.",
				 player[best[b]].name, bestval[b]);
			}
			break;
  }
}