pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* speech.c */

#include "copyrite.h"
#include "config.h"

/* Commands which involve speaking */
#include <ctype.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif

#include "conf.h"
#include "externs.h"
#include "mushdb.h"
#include "intrface.h"
#include "match.h"
#include "attrib.h"
#include "parse.h"
#include "confmagic.h"

static void oemit_notify_except _((dbref loc, dbref exc1, dbref exc2, const char *msg));
const char *reconstruct_message _((char *arg1, char *arg2));
int okay_pemit _((dbref player, dbref target));
static dbref speech_loc _((dbref thing));
void do_say _((dbref player, const char *tbuf1));
void do_oemit _((dbref player, const char *arg1, const char *arg2));
void do_whisper _((dbref player, const char *arg1, const char *arg2, int noisy));
void do_whisper_list _((dbref player, const char *arg1, const char *arg2, int noisy));
void do_pemit_list _((dbref player, char *list, const char *message));
void do_pemit _((dbref player, const char *arg1, const char *arg2, int silent));
void do_pose _((dbref player, const char *tbuf1, int space));
void do_wall _((dbref player, const char *message, dbref privs, int key));
void do_page _((dbref player, const char *arg1, const char *arg2));
void do_multipage _((dbref player, const char *arg1, const char *arg2));
int filter_found _((dbref thing, const char *msg, int flag));
void propagate_sound _((dbref thing, const char *msg));
void do_think _((dbref player, const char *message));
void do_emit _((dbref player, const char *tbuf1));
void do_remit _((dbref player, const char *arg1, const char *arg2));
void do_lemit _((dbref player, const char *tbuf1));
void do_zemit _((dbref player, const char *arg1, const char *arg2));
const char *spname _((dbref thing));
void ns_esnotify _((char *dest, dbref speaker, dbref (*func) (), void *fdata));
static void do_audible_stuff _((dbref loc, dbref exc1, dbref exc2, const char *msg));
dbref na_zemit _((dbref current, void *data));

const char *
spname(thing)
    dbref thing;
{
  if (!FULL_INVIS)
    return Name(thing);

  /* if FULL_INVIS is defined, dark objects and dark wizards will be
   * Something and Someone, respectively.
   */

  if (!Dark(thing))
    return (Name(thing));
  else {
    if (Typeof(thing) != TYPE_PLAYER)
      return ("Something");
    else
      return ("Someone");
  }
}


int
okay_pemit(player, target)
    dbref player;
    dbref target;
{
  if (Pemit_All(player))
    return 1;
  if ((player != target) &&
      ((Typeof(target) == TYPE_PLAYER) &&
       (Haven(target) ||
	!eval_lock(player, target, Page_Lock)
       )))
    return 0;
  else
    return 1;
}

static dbref
speech_loc(thing)
    dbref thing;
{
  /* This is the place where speech, poses, and @emits by thing should be
   * heard. For things and players, it's the loc; For rooms, it's the room
   * itself; for exits, it's the source. */
  if (!GoodObject(thing))
    return NOTHING;
  switch (Typeof(thing)) {
  case TYPE_ROOM:
    return thing;
  case TYPE_EXIT:
    return Home(thing);
  default:
    return Location(thing);
  }
}


void
do_say(player, tbuf1)
    dbref player;
    const char *tbuf1;
{
  dbref loc;

  loc = speech_loc(player);
  if (!GoodObject(loc))
    return;

#ifdef SPEECH_LOCK
  if (!Hasprivs(player) && !eval_lock(player, loc, Speech_Lock)) {
    notify(player, "You may not speak here!");
    return;
  }
#endif

  /* notify everybody */
  notify_noecho(player, tprintf("You say, \"%s\"", tbuf1));
  notify_except(db[loc].contents, player,
		tprintf("%s says, \"%s\"", spname(player), tbuf1));
}

void
do_oemit(player, arg1, arg2)
    dbref player;
    const char *arg1;
    const char *arg2;
{
  dbref who;
  dbref loc;
  char *temp;
  dbref pass[2];

  if ((temp = (char *) index(arg1, '/')) == NULL) {
    loc = speech_loc(player);
    if (!GoodObject(loc))
      return;
    who = match_result(player, arg1, NOTYPE, MAT_OBJECTS);
  } else {
    *temp++ = '\0';

    /* first find the room */
    loc = noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING);
    if (!GoodObject(loc))
      return;

    /* then find the player */
    who = match_result(loc, temp, NOTYPE, MAT_REMOTE);
  }

#ifdef SPEECH_LOCK
  if (!Hasprivs(player) && !eval_lock(player, loc, Speech_Lock)) {
    notify(player, "You may not speak here!");
    return;
  }
#endif

  if (!GoodObject(who))
    who = player;

  if (GoodObject(Location(who))) {
    pass[0] = Location(who);
    pass[1] = who;
    notify_anything(player, na_except, pass, ns_esnotify, 0, arg2);
  }
}

void
do_whisper(player, arg1, arg2, noisy)
    dbref player;
    const char *arg1;
    const char *arg2;
    int noisy;



				/* 0 for silent, 1 for noisy */
{
  dbref who;
  int key;
  const char *gap;
  char tbuf1[BUFFER_LEN];

  switch (who = match_result(player, arg1, TYPE_PLAYER, MAT_NEAR_THINGS | MAT_CONTAINER)) {
  case NOTHING:
    notify(player, "Whisper to whom?");
    break;
  case AMBIGUOUS:
    notify(player, "I don't know who you mean!");
    break;
  default:
    gap = " ";
    switch (*arg2) {
    case SEMI_POSE_TOKEN:
      gap = "";
    case POSE_TOKEN:
      key = 1;
      arg2 = arg2 + 1;
      break;
    default:
      key = 2;
      break;
    }
    switch (key) {
    case 1:
      notify(player, tprintf("%s senses, \"%s%s%s\"", db[who].name,
			     db[player].name, gap, arg2));
      notify(who, tprintf("You sense: %s%s%s", db[player].name, gap, arg2));
      break;
    case 2:
      notify(player,
	     tprintf("You whisper, \"%s\" to %s.", arg2, db[who].name));
      notify(who,
	     tprintf("%s whispers, \"%s\"", db[player].name, arg2));
      break;
    }
    if (noisy && GoodObject(Location(player)) &&
	(getrandom(100) < WHISPER_LOUDNESS) &&
	(Location(player) == Location(who))) {
      sprintf(tbuf1, "%s whispers to %s.", Name(player), Name(who));
      notify_except2(Contents(Location(player)), player, who, tbuf1);
    }
    break;
  }
}

void
do_whisper_list(player, arg1, arg2, noisy)
    dbref player;
    const char *arg1;
    const char *arg2;
    int noisy;
{
  dbref who;
  int key;
  const char *gap;
  char tbuf[BUFFER_LEN], *bp;
  char tbuf2[BUFFER_LEN], *bp2;
  char overbuf[BUFFER_LEN];
  dbref good[100];
  int gcount, bcount;
  char bad[BUFFER_LEN], *bptr;
  char *head;
  char *tail;
  char spot;
  int overheard;

  if (!arg1 || !*arg1) {
    notify(player, "Whisper to whom?");
    return;
  }
  if (!arg2 || !*arg2) {
    notify(player, "Whisper what?");
    return;
  }
  overheard = 0;
  head = (char *) arg1;
  /* Figure out what kind of message */
  gap = " ";
  switch (*arg2) {
  case SEMI_POSE_TOKEN:
    gap = "";
  case POSE_TOKEN:
    key = 1;
    break;
  default:
    key = 2;
    break;
  }

  /* Make up a list of good and bad names */
  gcount = 0;
  bcount = 0;
  bptr = bad;
  safe_str("Unable to whisper to:", bad, &bptr);
  while (head && *head) {
    while (*head == ' ')
      head++;
    tail = head;
    while (*tail && (*tail != ' ')) {
      if (*tail == '"') {
	head++;
	tail++;
	while (*tail && (*tail != '"'))
	  tail++;
      }
      if (*tail)
	tail++;
    }
    tail--;
    if (*tail != '"')
      tail++;
    spot = *tail;
    *tail = 0;
    who = match_result(player, head, TYPE_PLAYER, MAT_NEAR_THINGS | MAT_CONTAINER);
    if (!GoodObject(who)) {
      safe_chr(' ', bad, &bptr);
      safe_str(head, bad, &bptr);
      bcount++;
    } else {
      /* A good whisper */
      good[gcount++] = who;
      if (gcount >= 100) {
	notify(player, "Too many people to whisper to.");
	break;
      }
    }

    *tail = spot;
    head = tail;
    if (*head == '"')
      head++;
  }

  *bptr = '\0';
  /* Set up list of good names */
  bp = tbuf;
  for (who = 0; who < gcount; who++) {
    safe_str(Name(good[who]), tbuf, &bp);
    switch (gcount) {
    case 1:
      break;
    case 2:
      if (who == gcount - 2)
	safe_str(" and ", tbuf, &bp);
      break;
    default:
      if (who < gcount - 1)
	safe_str(", ", tbuf, &bp);
      if (who == gcount - 2)
	safe_str("and ", tbuf, &bp);
      break;
    }
  }
  *bp = '\0';

  bp2 = tbuf2;
  switch (key) {
  case 1:
    safe_str(tbuf, tbuf2, &bp2);
    safe_str(" senses, \"", tbuf2, &bp2);
    safe_str(Name(player), tbuf2, &bp2);
    safe_str(gap, tbuf2, &bp2);
    safe_str(arg2 + 1, tbuf2, &bp2);
    safe_chr('"', tbuf2, &bp2);
    break;
  case 2:
    safe_str("You whisper, \"", tbuf2, &bp2);
    safe_str(arg2, tbuf2, &bp2);
    safe_str("\" to ", tbuf2, &bp2);
    safe_str(tbuf, tbuf2, &bp2);
    break;
  }
  *bp2 = '\0';
  if (bcount)
    notify(player, bad);
  if (!gcount)
    return;
  notify(player, tbuf2);

  /* Tell each recipient */
  switch (key) {
  case 1:
    for (who = 0; who < gcount; who++) {
      bp2 = tbuf2;
      safe_str("You sense: ", tbuf2, &bp2);
      safe_str(Name(player), tbuf2, &bp2);
      safe_str(gap, tbuf2, &bp2);
      safe_str(arg2 + 1, tbuf2, &bp2);
      *bp2 = '\0';
      notify(good[who], tbuf2);
      if (noisy && !overheard && GoodObject(Location(player)) &&
	  (getrandom(100) < WHISPER_LOUDNESS) &&
	  (Location(player) == Location(who))) {
	bp2 = overbuf;
	safe_str(Name(player), overbuf, &bp2);
	safe_str(" whispers to ", overbuf, &bp2);
	safe_str(Name(good[who]), overbuf, &bp2);
	safe_chr('.', overbuf, &bp2);
	*bp2 = '\0';
	overheard = 1;
      }
    }
    break;
  case 2:
    for (who = 0; who < gcount; who++) {
      bp2 = tbuf2;
      safe_str(Name(player), tbuf2, &bp2);
      safe_str(" whispers", tbuf2, &bp2);
      if (gcount > 1) {
	safe_str(" to ", tbuf2, &bp2);
	safe_str(tbuf, tbuf2, &bp2);
      }
      safe_str(": ", tbuf2, &bp2);
      safe_str(arg2, tbuf2, &bp2);
      *bp2 = '\0';
      notify(good[who], tbuf2);
      if (noisy && !overheard && GoodObject(Location(player)) &&
	  (getrandom(100) < WHISPER_LOUDNESS) &&
	  (Location(player) == Location(who))) {
	overheard = 1;
	bp2 = overbuf;
	safe_str(Name(player), tbuf2, &bp2);
	safe_str(" whispers", tbuf2, &bp2);
	if (gcount > 1) {
	  safe_str(" to ", tbuf2, &bp2);
	  safe_str(tbuf, tbuf2, &bp2);
	}
	safe_chr('.', overbuf, &bp2);
	*bp2 = '\0';
      }
    }
    break;
  }

  if (overheard) {
    dbref first = Contents(Location(player));
    if (!GoodObject(first))
      return;
    DOLIST(first, first) {
      overheard = 1;
      for (who = 0; who < gcount; who++) {
	if (first == good[who]) {
	  overheard = 0;
	  break;
	}
      }
      if (overheard)
	notify_noecho(first, overbuf);
    }
  }
}

void
do_pemit_list(player, list, message)
    dbref player;
    char *list;
    const char *message;
{
  /* Send a message to a list of dbrefs. To avoid repeated generation
   * of the NOSPOOF string, we set it up the first time we encounter
   * something Nospoof, and then check for it thereafter.
   * The list is destructively modified.
   */

  char tbuf[BUFFER_LEN], *bp, *p;
  dbref who;
  char *msg;

  /* If no list or no message, further processing is pointless. */
  if (!message || !*message || !list || !*list)
    return;

  *tbuf = '\0';

  for (p = (char *) strtok(list, " ");
       p != NULL;
       p = (char *) strtok(NULL, " ")) {
    who = noisy_match_result(player, p, NOTYPE, MAT_ABSOLUTE);
    if (GoodObject(who) && okay_pemit(player, who)) {
      msg = replace_string("##", tprintf("#%d", who), message);
      if (Nospoof(who)) {
	if ((*tbuf == '\0') || strstr(message, "##")) {
	  bp = tbuf;
	  if (PARANOID_NOSPOOF)
	    safe_str(tprintf("[%s(#%d)->] ",
			     Name(player), player), tbuf, &bp);
	  else
	    safe_str(tprintf("[%s->] ", Name(player)), tbuf, &bp);
	  safe_str(msg, tbuf, &bp);
	  *bp = '\0';
	}
	notify(who, tbuf);
      } else {
	notify(who, msg);
      }
      mush_free((Malloc_t) msg, "replace_string.buff");
    }
  }
}

void
do_pemit(player, arg1, arg2, silent)
    dbref player;
    const char *arg1;
    const char *arg2;
    int silent;
{
  dbref who;

  switch (who = match_result(player, arg1, NOTYPE, MAT_OBJECTS | MAT_HERE | MAT_CONTAINER)) {
  case NOTHING:
    notify(player, "I don't see that player here.");
    break;
  case AMBIGUOUS:
    notify(player, "I don't know who you mean!");
    break;
  default:
    if (Typeof(who) != TYPE_PLAYER && Typeof(who) != TYPE_THING) {
      notify(player, "Only players and things can hear @pemits.");
      break;
    }
    if (!okay_pemit(player, who)) {
      notify(player, tprintf("I'm sorry, but %s wishes to be left alone now.",
			     db[who].name));
      return;
    }
    if (!silent)
      notify(player,
	     tprintf("You pemit \"%s\" to %s.", arg2, db[who].name));
    if (Nospoof(who)) {
      if (PARANOID_NOSPOOF)
	notify(who, tprintf("[%s(#%d)->%s] %s", db[player].name, player,
			    db[who].name, arg2));
      else
	notify(who,
	    tprintf("[%s->%s] %s", db[player].name, db[who].name, arg2));
    } else {
      notify(who,
	     tprintf("%s", arg2));
    }
    break;
  }
}

void
do_pose(player, tbuf1, space)
    dbref player;
    const char *tbuf1;
    int space;
{
  dbref loc;

  loc = speech_loc(player);
  if (!GoodObject(loc))
    return;

#ifdef SPEECH_LOCK
  if (!Hasprivs(player) && !eval_lock(player, loc, Speech_Lock)) {
    notify(player, "You may not speak here!");
    return;
  }
#endif

  /* notify everybody */
  if (!space)
    notify_except(db[loc].contents, NOTHING,
		  tprintf("%s %s", spname(player), tbuf1));
  else
    notify_except(db[loc].contents, NOTHING,
		  tprintf("%s%s", spname(player), tbuf1));
}

void
do_wall(player, message, privs, key)
    dbref player;
    const char *message;
    int privs;
    int key;
{
  /* privs is 0 for wizard wizwall, 1 for royalty-wizard wizwall,
   * 2 is for general wall
   */

  const char *gap, *prefix;
  int mask;

  /* Only @wall is available to those with the announce power.
   * Only @rwall is available to royalty.
   */
  if (!(Wizard(player) ||
	((privs == 2) && Can_Announce(player)) ||
	((privs == 1) && Royalty(player)))) {
    notify(player,
	   "Posing as a wizard could be hazardous to your health.");
    return;
  }
  /* put together the message and figure out what type it is */
  gap = " ";
  switch (*message) {
  case SAY_TOKEN:
    key = 1;
    message = message + 1;
    break;
  case SEMI_POSE_TOKEN:
    gap = "";
  case POSE_TOKEN:
    key = 2;
    message = message + 1;
    break;
  }

  if (!*message) {
    notify(player, "What did you want to say?");
    return;
  }
  if (privs == 0) {
    /* to wizards only */
    mask = WIZARD;
    prefix = WIZWALL_PREFIX;
  }
#ifdef ROYALTY_FLAG
  else if (privs == 1) {
    /* to wizards and royalty */
    mask = WIZARD | ROYALTY;
    prefix = RWALL_PREFIX;
  }
#endif				/* ROYALTY_FLAG */
  else {
    /* to everyone */
    mask = 0;
    prefix = WALL_PREFIX;
  }

  /* broadcast the message */
  switch (key) {
  case 2:
    flag_broadcast(mask, 0, "%s %s%s%s", prefix, db[player].name, gap,
		   message);
    break;
  case 3:
    flag_broadcast(mask, 0, "%s [%s]: %s", prefix, db[player].name, message);
    break;
  default:
    flag_broadcast(mask, 0, "%s %s %s, \"%s\"", prefix, db[player].name,
		   ((privs != 2) ? "says" : "shouts"), message);
  }

  /* log it if necessary */
  if ((privs == 2) || (options.log_walls && (privs == 0))) {
    switch (key) {
    case 2:
      do_log(LT_WIZ, player, NOTHING, "(MSG/%s) %s%s%s",
	     (privs == 0) ? "wiz" : "all", Name(player), gap, message);
      break;
    default:
      do_log(LT_WIZ, player, NOTHING, "(MSG/%s) %s",
	     (privs == 0) ? "wiz" : "all", message);
      break;
    }
  }
}


void
do_page(player, arg1, arg2)
    dbref player;
    const char *arg1;
    const char *arg2;
{
  dbref target;
  char *message;
  const char *gap;
  int key;
  char tbuf[BUFFER_LEN];
  char tbuf2[BUFFER_LEN];
  char tbuf3[BUFFER_LEN];
  char *bp3;
  char *head;
  char *tail;
  char spot;
  ATTR *a;

  if (arg2 && *arg2) {
    head = (char *) arg1;
    message = (char *) arg2;
  } else {
    head = NULL;
    message = (char *) arg1;
  }

  if (!head || !*head) {
    a = atr_get_noparent(player, "LASTPAGED");
    if (!a) {
      notify(player, "You haven't paged anyone since connecting.");
      return;
    }
    strcpy(tbuf2, uncompress(a->value));
    if (!message || !*message) {
      notify(player, tprintf("You last paged %s.", tbuf2));
      return;
    }
    head = (char *) tbuf2;
  }
  if (Haven(player))
    notify(player, "You are set HAVEN and cannot receive pages.");

  bp3 = tbuf3;
  while (head && *head) {
    while (*head == ' ')
      head++;
    tail = head;
    while (*tail && (*tail != ' ')) {
      if (*tail == '"') {
	head++;
	tail++;
	while (*tail && (*tail != '"'))
	  tail++;
      }
      if (*tail)
	tail++;
    }
    tail--;
    if (*tail != '"')
      tail++;
    spot = *tail;
    *tail = 0;
    target = lookup_player(head);
    if (!GoodObject(target))
      target = short_page(head);
    *tail = spot;
    head = tail;
    if (*head == '"')
      head++;

    if (target == NOTHING) {
      notify(player, "I can't find who you're trying to page.");
      continue;
    } else if (target == AMBIGUOUS) {
      notify(player, "I'm not sure who you want to page!");
      continue;
    } else if (!GoodObject(target)) {
      notify(player, "I can't figure out who you want to page.");
      continue;
    } else if (!(Toggles(target) & PLAYER_CONNECT) ||
	       (Dark(target) && Haven(target))) {
      page_return(player, target, "Away", "AWAY",
		  "That player is not connected.");
      continue;
    } else if (Haven(target)) {
      page_return(player, target, "Haven", "HAVEN",
		  "That player is not accepting any pages.");
      continue;
    } else if (!eval_lock(player, target, Page_Lock)) {
      if (Dark(target))
	page_return(player, target, "Away", "AWAY",
		    "That player is not connected.");
      else
	page_return(player, target, "Haven", "HAVEN",
		    "That player is not accepting your pages.");
      continue;
    }
    if (!payfor(player, PAGE_COST)) {
      notify(player, tprintf("You don't have enough %s.", MONIES));
      continue;
    }
    gap = " ";
    switch (*message) {
    case SEMI_POSE_TOKEN:
      gap = "";
    case POSE_TOKEN:
      key = 1;
      break;
    default:
      key = 3;
      break;
    }

    /* Add this person to the list of people last paged */
    if (bp3 != tbuf3)
      safe_chr(' ', tbuf3, &bp3);
    if (strchr(Name(target), ' ')) {
      safe_chr('"', tbuf3, &bp3);
      safe_str(Name(target), tbuf3, &bp3);
      safe_chr('"', tbuf3, &bp3);
    } else {
      safe_str(Name(target), tbuf3, &bp3);
    }

    /* this is a hack: truncate the message if it's going to overflow
     * the tprintf buffer.
     */
    if (message && *message && (strlen(message) > BUFFER_LEN - 32))
      message[BUFFER_LEN - 32] = '\0';

    switch (key) {
    case 1:
      sprintf(tbuf, "From afar, %s%s%s", db[player].name, gap, message + 1);
      notify(player, tprintf("Long distance to %s: %s%s%s", db[target].name,
			     db[player].name, gap, message + 1));
      break;
    case 3:
      sprintf(tbuf, "%s pages: %s", db[player].name, message);
      notify(player, tprintf("You paged %s with '%s'.", db[target].name,
			     message));
      break;
    }
    if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
      notify(target, tprintf("[#%d] %s", player, tbuf));
    else
      notify(target, tbuf);
    page_return(player, target, "Idle", "IDLE", NULL);
  }
  *bp3 = '\0';
  if (tbuf3 && *tbuf3)
    atr_add(player, "LASTPAGED", tbuf3, GOD, NOTHING);
}

/* Like page, but combine the names of the pagees. Less private, more
 * esthetic. Limited to 100 recipients.
 */
void
do_multipage(player, arg1, arg2)
    dbref player;
    const char *arg1;
    const char *arg2;
{
  dbref target;
  char *message;
  const char *gap;
  int key;
  char tbuf[BUFFER_LEN], *bp;
  char tbuf2[BUFFER_LEN], *bp2;
  char tbuf3[BUFFER_LEN], *bp3;
  dbref good[100];
  int gcount, bcount;
  char bad[BUFFER_LEN], *bptr;
  char *head;
  char *tail;
  char spot;
  ATTR *a;

  if (arg2 && *arg2) {
    head = (char *) arg1;
    message = (char *) arg2;
  } else {
    head = NULL;
    message = (char *) arg1;
  }

  if (!head || !*head) {
    a = atr_get_noparent(player, "LASTPAGED");
    if (!a) {
      notify(player, "You haven't paged anyone since connecting.");
      return;
    }
    strcpy(tbuf2, uncompress(a->value));
    if (!message || !*message) {
      notify(player, tprintf("You last paged %s.", tbuf2));
      return;
    }
    head = (char *) tbuf2;
  }
  if (Haven(player))
    notify(player, "You are set HAVEN and cannot receive pages.");

  /* Figure out what kind of message */
  gap = " ";
  switch (*message) {
  case SEMI_POSE_TOKEN:
    gap = "";
  case POSE_TOKEN:
    key = 1;
    break;
  default:
    key = 3;
    break;
  }

  /* Make up a list of good and bad names */
  gcount = 0;
  bcount = 0;
  bptr = bad;
  safe_str("Unable to page:", bad, &bptr);
  while (head && *head) {
    if (!payfor(player, PAGE_COST)) {
      notify(player, tprintf("You don't have enough %s.", MONIES));
      safe_str(head, bad, &bptr);
      break;
    }
    while (*head == ' ')
      head++;
    tail = head;
    while (*tail && (*tail != ' ')) {
      if (*tail == '"') {
	head++;
	tail++;
	while (*tail && (*tail != '"'))
	  tail++;
      }
      if (*tail)
	tail++;
    }
    tail--;
    if (*tail != '"')
      tail++;
    spot = *tail;
    *tail = 0;
    target = lookup_player(head);
    if (!GoodObject(target))
      target = short_page(head);
    if (!GoodObject(target)) {
      safe_chr(' ', bad, &bptr);
      safe_str(head, bad, &bptr);
      bcount++;
    } else if (!(Toggles(target) & PLAYER_CONNECT) ||
	       (Dark(target) && Haven(target))) {
      page_return(player, target, "Away", "AWAY",
		  "That player is not connected.");
      safe_chr(' ', bad, &bptr);
      safe_str(head, bad, &bptr);
      bcount++;
    } else if (Haven(target)) {
      page_return(player, target, "Haven", "HAVEN",
		  "That player is not accepting any pages.");
      safe_chr(' ', bad, &bptr);
      safe_str(head, bad, &bptr);
      bcount++;
    } else if (!eval_lock(player, target, Page_Lock)) {
      page_return(player, target, "Haven", "HAVEN",
		  "That player is not accepting your pages.");
      safe_chr(' ', bad, &bptr);
      safe_str(head, bad, &bptr);
      bcount++;
    } else {
      /* This is a good page */
      page_return(player, target, "Idle", "IDLE", NULL);
      good[gcount++] = target;
      if (gcount >= 100) {
	notify(player, "Too many page-recipients.");
	break;
      }
    }

    *tail = spot;
    head = tail;
    if (*head == '"')
      head++;
  }

  *bptr = '\0';
  /* Set up list of good names */
  bp = tbuf;
  bp3 = tbuf3;
  for (target = 0; target < gcount; target++) {
    safe_str(Name(good[target]), tbuf, &bp);
    safe_chr(' ', tbuf3, &bp3);
    if (strchr(Name(good[target]), ' ')) {
      safe_chr('"', tbuf3, &bp3);
      safe_str(Name(good[target]), tbuf3, &bp3);
      safe_chr('"', tbuf3, &bp3);
    } else {
      safe_str(Name(good[target]), tbuf3, &bp3);
    }
    switch (gcount) {
    case 1:
      break;
    case 2:
      if (target == gcount - 2)
	safe_str(" and ", tbuf, &bp);
      break;
    default:
      if (target < gcount - 1)
	safe_str(", ", tbuf, &bp);
      if (target == gcount - 2)
	safe_str("and ", tbuf, &bp);
      break;
    }
  }
  *bp = '\0';
  *bp3 = '\0';
  if (tbuf3 && *tbuf3)
    atr_add(player, "LASTPAGED", tbuf3, GOD, NOTHING);

  /* Tell player who s/he paged what */
  bp2 = tbuf2;
  switch (key) {
  case 1:
    safe_str("Long distance to ", tbuf2, &bp2);
    safe_str(tbuf, tbuf2, &bp2);
    safe_str(": ", tbuf2, &bp2);
    safe_str(Name(player), tbuf2, &bp2);
    safe_str(gap, tbuf2, &bp2);
    safe_str(message + 1, tbuf2, &bp2);
    break;
  case 3:
    safe_str("You paged ", tbuf2, &bp2);
    safe_str(tbuf, tbuf2, &bp2);
    safe_str(" with '", tbuf2, &bp2);
    safe_str(message, tbuf2, &bp2);
    safe_str("'.", tbuf2, &bp2);
    break;
  }
  *bp2 = '\0';

  if (gcount)
    notify(player, tbuf2);
  if (bcount)
    notify(player, bad);

  /* Tell each page recipient */
  switch (key) {
  case 1:
    for (target = 0; target < gcount; target++) {
      bp2 = tbuf2;
      if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
	safe_str(tprintf("[#%d] ", player), tbuf2, &bp);
      safe_str("From afar", tbuf2, &bp2);
      if (gcount > 1) {
	safe_str(" (to ", tbuf2, &bp2);
	safe_str(tbuf, tbuf2, &bp2);
	safe_chr(')', tbuf2, &bp2);
      }
      safe_str(", ", tbuf2, &bp2);
      safe_str(Name(player), tbuf2, &bp2);
      safe_str(gap, tbuf2, &bp2);
      safe_str(message + 1, tbuf2, &bp2);
      *bp2 = '\0';
      notify(good[target], tbuf2);
    }
    break;
  case 3:
    for (target = 0; target < gcount; target++) {
      bp2 = tbuf2;
      if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
	safe_str(tprintf("[#%d] ", player), tbuf2, &bp);
      safe_str(Name(player), tbuf2, &bp2);
      safe_str(" pages", tbuf2, &bp2);
      if (gcount > 1) {
	safe_chr(' ', tbuf2, &bp2);
	safe_str(tbuf, tbuf2, &bp2);
      }
      safe_str(": ", tbuf2, &bp2);
      safe_str(message, tbuf2, &bp2);
      *bp2 = '\0';
      notify(good[target], tbuf2);
    }
    break;
  }
}

void
ns_esnotify(dest, speaker, func, fdata)
    char *dest;
    dbref speaker;
    dbref (*func) ();
    void *fdata;
{
  if (!GoodObject(speaker))
    *dest = '\0';
  else if (PARANOID_NOSPOOF)
    sprintf(dest, "[%s(#%d)] ", spname(speaker), speaker);
  else
    sprintf(dest, "[%s:] ", spname(speaker));
}


int
filter_found(thing, msg, flag)
    dbref thing;
    const char *msg;
    int flag;


				/* 0 for @filter, 1 for @infilter */
{
  /* check to see if the message matches the filter pattern on thing */

  char *filter;
  ATTR *a;
  char *p, *bp;
  char *temp;			/* need this so we don't leak memory     
				 * by failing to free the storage
				 * allocated by safe_uncompress
				 */

  int i;
  int matched = 0;

  if (!flag)
    a = atr_get(thing, "FILTER");
  else
    a = atr_get(thing, "INFILTER");
  if (!a)
    return matched;

  filter = safe_uncompress(a->value);
  temp = filter;

  for (i = 0; (i < MAX_ARG) && !matched; i++) {
    p = bp = filter;
    process_expression(p, &bp, (char const **) &filter, 0, 0, 0,
		       PE_NOTHING, PT_COMMA, NULL);
    if (*filter == ',')
      *filter++ = '\0';
    matched = local_wild_match(p, msg);
  }

  free((Malloc_t) temp);
  return matched;
}

void
propagate_sound(thing, msg)
    dbref thing;
    const char *msg;
{
  /* pass a message on, for AUDIBLE, prepending a prefix, unless the
   * message matches a filter pattern.
   */

  char *bp;
  char const *asave, *ap;
  char tbuf1[BUFFER_LEN];
  ATTR *a;
  dbref loc = db[thing].location;
  char *wsave[10], *preserve[10];
  int j;
  dbref pass[2];

  if (!GoodObject(loc))
    return;

  /* check to see that filter doesn't suppress message */
  if (filter_found(thing, msg, 0))
    return;

  /* figure out the prefix */

  a = atr_get(thing, "PREFIX");

  bp = tbuf1;

  if (!a) {
    safe_str("From ", tbuf1, &bp);
    if (Typeof(thing) == TYPE_EXIT)
      safe_str(Name(Exits(thing)), tbuf1, &bp);
    else
      safe_str(Name(thing), tbuf1, &bp);
    safe_chr(',', tbuf1, &bp);
  } else {
    for (j = 0; j < 10; j++) {
      wsave[j] = wenv[j];
      wenv[j] = NULL;
    }
    wenv[0] = (char *) msg;
    save_global_regs("prefix_save", preserve);
    asave = safe_uncompress(a->value);
    ap = asave;
    process_expression(tbuf1, &bp, &ap, thing, orator, orator,
		       PE_DEFAULT, PT_DEFAULT, NULL);
    free((Malloc_t) asave);
    restore_global_regs("prefix_save", preserve);
    for (j = 0; j < 10; j++)
      wenv[j] = wsave[j];
  }
  if (bp != tbuf1)
    safe_chr(' ', tbuf1, &bp);
  safe_str(msg, tbuf1, &bp);
  *bp = '\0';

  /* Exits pass the message on to things in the next room.
   * Objects pass the message on to the things outside.
   * Don't tell yourself your own message.
   */

  if (Typeof(thing) == TYPE_EXIT) {
    notify_anything(orator, na_next, (void *) Contents(loc), NULL, 0, tbuf1);
  } else {
    pass[0] = Contents(loc);
    pass[1] = thing;
    notify_anything(orator, na_nextbut, pass, NULL, 0, tbuf1);
  }
}

static void
do_audible_stuff(loc, exc1, exc2, msg)
    dbref loc;
    dbref exc1;
    dbref exc2;
    const char *msg;
{
  char tbuf1[BUFFER_LEN];
  dbref e;
  int did = 0;
  if (Audible(loc)) {
    if (Typeof(loc) == TYPE_ROOM) {
      /* the strcpy is necessary to prevent choking in propagate_sound
       * when msg is an array of char and not a char * in the calling
       * function. Yes, this is ugly and stupid. It works. Kinda.
       */
      if (!did) {
	strcpy(tbuf1, msg);
	did = 1;
      }
      DOLIST(e, db[loc].exits) {
	if (Audible(e))
	  propagate_sound(e, tbuf1);
      }
    } else if ((loc != exc1) && (loc != exc2)) {
      if (!did) {
	strcpy(tbuf1, msg);
	did = 1;
      }
      propagate_sound(loc, tbuf1);
    }
  }
}

void
notify_except(first, exception, msg)
    dbref first;
    dbref exception;
    const char *msg;
{
  dbref loc;
  dbref pass[2];

  if (!GoodObject(first))
    return;
  loc = Location(first);
  if (!GoodObject(loc))
    return;

  if (exception == NOTHING)
    exception = AMBIGUOUS;

  pass[0] = loc;
  pass[1] = exception;
  notify_anything(orator, na_except, pass, &ns_esnotify, 0, msg);

  do_audible_stuff(loc, exception, NOTHING, msg);
}

void
notify_except2(first, exc1, exc2, msg)
    dbref first;
    dbref exc1;
    dbref exc2;
    const char *msg;
{
  dbref loc;
  dbref pass[3];

  if (!GoodObject(first))
    return;
  loc = Location(first);
  if (!GoodObject(loc))
    return;

  if (exc1 == NOTHING)
    exc1 = AMBIGUOUS;
  if (exc2 == NOTHING)
    exc2 = AMBIGUOUS;

  pass[0] = loc;
  pass[1] = exc1;
  pass[2] = exc2;

  notify_anything(orator, na_except2, pass, NULL, 0, msg);

  do_audible_stuff(loc, exc1, exc2, msg);
}

static void
oemit_notify_except(loc, exc1, exc2, msg)
    dbref loc;
    dbref exc1;
    dbref exc2;
    const char *msg;
{
  dbref pass[3];

  if (!GoodObject(loc))
    return;

  if (exc1 == NOTHING)
    exc1 = AMBIGUOUS;
  if (exc2 == NOTHING)
    exc2 = AMBIGUOUS;

  pass[0] = loc;
  pass[1] = exc1;
  pass[2] = exc2;

  notify_anything(orator, na_except2, pass, ns_esnotify, 0, msg);

  do_audible_stuff(loc, exc1, exc2, msg);
}

void
do_think(player, message)
    dbref player;
    const char *message;
{
  /* privately tell yourself a message */

  /* notify the player only, with no special fanfare */
  /* Why was this tprintf'd? */
  notify(player, message);
}


void
do_emit(player, tbuf1)
    dbref player;
    const char *tbuf1;
{
  dbref loc;

  loc = speech_loc(player);
  if (!GoodObject(loc))
    return;

#ifdef SPEECH_LOCK
  if (!Hasprivs(player) && !eval_lock(player, loc, Speech_Lock)) {
    notify(player, "You may not speak here!");
    return;
  }
#endif

  /* notify everybody */
  notify_anything(player, na_loc, (void *) loc, ns_esnotify, 0, tbuf1);
}

void
do_remit(player, arg1, arg2)
    dbref player;
    const char *arg1;
    const char *arg2;
{
  dbref room;
  const char *rmno;

  room = match_result(player, arg1, NOTYPE, MAT_EVERYTHING);
  if (!GoodObject(room)) {
    notify(player, "I can't find that.");
  } else {
    if (Typeof(room) == TYPE_EXIT) {
      notify(player, "There can't be anything in that!");
    } else if (!okay_pemit(player, room)) {
      notify(player, tprintf("I'm sorry, but %s wishes to be left alone now.",
			     db[room].name));
#ifdef SPEECH_LOCK
    } else if (!Hasprivs(player) && !eval_lock(player, room, Speech_Lock)) {
      notify(player, "You may not speak there!");
#endif
    } else {

      rmno = unparse_object(player, room);
      if (Location(player) == room) {
	notify(player, arg2);
      } else if (!SILENT_PEMIT) {
	notify(player,
	       tprintf("You remit, \"%s\" in %s", arg2, rmno));
      }
      oemit_notify_except(room, player, room, arg2);
    }
  }
}

void
do_lemit(player, tbuf1)
    dbref player;
    const char *tbuf1;
{
  /* give a message to the "absolute" location of an object */

  dbref room;
  int rec = 0;

  /* only players and things may use this command */
  if (!Mobile(player))
    return;

  /* prevent infinite loop if player is inside himself */
  if (((room = db[player].location) == player) || !GoodObject(room)) {
    notify(player, "Invalid container object.");
    fprintf(stderr, "** BAD CONTAINER **  #%d is inside #%d.\n",
	    player, room);
    return;
  }
  while ((Typeof(room) != TYPE_ROOM) && (rec < 15)) {
    room = db[room].location;
    rec++;
  }
  if (rec > 15) {
    notify(player, "Too many containers.");
    return;
#ifdef SPEECH_LOCK
  } else if (!Hasprivs(player) && !eval_lock(player, room, Speech_Lock)) {
    notify(player, "You may not speak there!");
#endif
  } else {
    notify(player, tprintf("You lemit: \"%s\"", tbuf1));
    oemit_notify_except(room, player, room, tbuf1);
  }
}

dbref
na_zemit(current, data)
    dbref current;
    void *data;
{
  dbref this;
  dbref room;
  dbref *dbrefs = data;
  this = dbrefs[0];
  do {
    if (this == NOTHING) {
      for (room = dbrefs[1]; room < db_top; room++) {
	if ((Typeof(room) == TYPE_ROOM) && (Zone(room) == dbrefs[2])
#ifdef SPEECH_LOCK
	    && (Hasprivs(dbrefs[3]) || eval_lock(dbrefs[3], room, Speech_Lock))
#endif
	  )
	  break;
      }
      if (!(room < db_top))
	return NOTHING;
      this = room;
      dbrefs[1] = room + 1;
    } else if (Typeof(this) == TYPE_ROOM) {
      this = Contents(this);
    } else {
      this = Next(this);
    }
  } while ((this == NOTHING) || (this == dbrefs[4]));
  dbrefs[0] = this;
  return this;
}

void
do_zemit(player, arg1, arg2)
    dbref player;
    const char *arg1;
    const char *arg2;
{
  const char *where;
  dbref zone;
  dbref pass[5];

  zone = match_result(player, arg1, NOTYPE, MAT_ABSOLUTE);
  if (!GoodObject(zone)) {
    notify(player, "Invalid zone.");
    return;
  }
  if (!controls(player, zone)) {
    notify(player, "Permission denied.");
    return;
  }
  /* this command is computationally expensive */
  if (!payfor(player, FIND_COST)) {
    notify(player, "Sorry, you don't have enough money to do that.");
    return;
  }
  where = unparse_object(player, zone);
  notify(player,
	 tprintf("You zemit, \"%s\" in zone %s", arg2, where));

  pass[0] = NOTHING;
  pass[1] = 0;
  pass[2] = zone;
  pass[3] = player;
  pass[4] = player;
  notify_anything(player, na_zemit, pass, ns_esnotify, 0, arg2);
}