/* ************************************************************************
*  file: comm.c , Communication module.                   Part of DIKUMUD *
*  Usage: Communication, central game loop.                               *
*  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
*  All Rights Reserved                                                    *
*  Using *any* part of DikuMud without having read license.doc is         *
*  violating our copyright.
************************************************************************* */

#include "os.h"

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "prototypes.h"

#define DFLT_PORT 4000          /* default port */
#define MAX_NAME_LENGTH 15
#define MAX_HOSTNAME   256
#define OPT_USEC 250000         /* time delay corresponding to 4 passes/sec */



/* externs */

/* extern struct char_data *character_list; */

extern struct room_data *world; /* In db.c */
extern int top_of_world;        /* In db.c */
extern struct time_info_data time_info; /* In db.c */
extern char help[];
extern bool wizlock;

/* local globals */

struct descriptor_data *descriptor_list, *next_to_process;

int lawful = 0;                 /* work like the game regulator */
int slow_death = 0;             /* Shut her down, Martha, she's sucking mud */
int shutdown_server = 0;        /* clean shutdown */
#if defined __FreeBSD__
int greboot = 0;                /* reboot the game after a shutdown */
#else
int reboot = 0;                 /* reboot the game after a shutdown */
#endif
int no_specials = 0;            /* Suppress ass. of special routines */

int maxdesc, avail_descs;
int tics = 0;                   /* for extern checkpointing */

int get_from_q (struct txt_q *queue, char *dest);
/* write_to_q is in comm.h for the macro */
void run_the_game (int port);
void game_loop (SOCKET s);
int init_socket (int port);
int new_connection (SOCKET s);
int new_descriptor (SOCKET s);
int process_output (struct descriptor_data *t);
int process_input (struct descriptor_data *t);
void close_sockets (int s);
void close_socket (struct descriptor_data *d);
struct timeval timediff (struct timeval *a, struct timeval *b);
void flush_queues (struct descriptor_data *d);
void nonblock (SOCKET s);
void parse_name (struct descriptor_data *desc, char *arg);
int load (void);
void coma (SOCKET s);


/* extern fcnts */

struct char_data *make_char (char *name, struct descriptor_data *desc);
void boot_db (void);
void zone_update (void);
void affect_update (void);      /* In spells.c */
void point_update (void);       /* In limits.c */
void free_char (struct char_data *ch);
void mobile_activity (void);
void string_add (struct descriptor_data *d, char *str);
void perform_violence (void);
void stop_fighting (struct char_data *ch);
void show_string (struct descriptor_data *d, char *input);
void gr (SOCKET s);

void check_reboot (void);


/* *********************************************************************
*  main game loop and related stuff              *
********************************************************************* */




int main (int argc, char **argv)
{
  int port;
  char buf[512];
  int pos = 1;
  char *dir;

  port = DFLT_PORT;
  dir = DFLT_DIR;

  while ((pos < argc) && (*(argv[pos]) == '-')) {
    switch (*(argv[pos] + 1)) {
    case 'l':
      lawful = 1;
      log ("Lawful mode selected.");
      break;
    case 'd':
      if (*(argv[pos] + 2))
        dir = argv[pos] + 2;
      else if (++pos < argc)
        dir = argv[pos];
      else {
        log ("Directory arg expected after option -d.");
        exit (0);
      }
      break;
    case 's':
      no_specials = 1;
      log ("Suppressing assignment of special routines.");
      break;
    default:
      sprintf (buf, "Unknown option -% in argument string.",
        *(argv[pos] + 1));
      log (buf);
      break;
    }
    pos++;
  }

  if (pos < argc)
    if (!isdigit ((int)*argv[pos])) {
      fprintf (stderr, "Usage: %s [-l] [-s] [-d pathname] [ port # ]\n",
        argv[0]);
      exit (0);
    } else if ((port = atoi (argv[pos])) <= 1024) {
      printf ("Illegal port #\n");
      exit (0);
    }

  sprintf (buf, "Running game on port %d.", port);
  log (buf);

#ifdef _MSC_VER
  if (_chdir (dir) < 0) {
#else
  if (chdir (dir) < 0) {
#endif
    perror ("chdir");
    exit (0);
  }

  sprintf (buf, "Using %s as data directory.", dir);
  log (buf);

  OS_SRAND (time (0));
  WIN32STARTUP
  run_the_game (port);
  WIN32CLEANUP
  return (0);
}





#define PROFILE(x)


/* Init sockets, run game, and cleanup sockets */
void run_the_game (int port)
{
  int s;
  PROFILE (extern etext ();
    )

  PROFILE (monstartup ((int) 2, etext);
    )

    descriptor_list = NULL;

  log ("Signal trapping.");
  signal_setup ();

  log ("Opening mother connection.");
  s = init_socket (port);

  if (lawful && load () >= 6) {
    log ("System load too high at startup.");
    coma (s);
  }

  boot_db ();

  log ("Entering game loop.");

  game_loop (s);

  close_sockets (s);

  PROFILE (monitor (0);
    )

#if defined __FreeBSD__
    if (greboot) {
#else
    if (reboot) {
#endif
    log ("Rebooting.");
    WIN32CLEANUP
    exit (52);                  /* what's so great about HHGTTG, anyhow? */
  }

  log ("Normal termination of game.");
}






/* Accept new connects, relay commands, and call 'heartbeat-functs' */
void game_loop (SOCKET s)
{
  int tmp_room, old_len;
  fd_set input_set, output_set, exc_set, dummy_set;
  struct timeval last_time, now, timespent, timeout, null_time;
  static struct timeval opt_time;
  char comm[MAX_INPUT_LENGTH];
  struct descriptor_data *t, *point, *next_point;
  int pulse = 0;

  null_time.tv_sec = 0;
  null_time.tv_usec = 0;

  opt_time.tv_usec = OPT_USEC;  /* Init time values */
  opt_time.tv_sec = 0;
  gettimeofday (&last_time, NULL);

#ifdef WIN32
  maxdesc = 1;
#else
  maxdesc = s;
#endif
  avail_descs = getdtablesize () - 2;   /* !! Change if more needed !! */

  /* Main loop */
  while (!shutdown_server) {
    /* Check what's happening out there */
    FD_ZERO (&input_set);
    FD_ZERO (&output_set);
    FD_ZERO (&exc_set);
    FD_SET (s, &input_set);
#ifdef WIN32
    FD_ZERO (&dummy_set);
    FD_SET (s, &dummy_set);
#endif
    for (point = descriptor_list; point; point = point->next) {
      FD_SET (point->descriptor, &input_set);
      FD_SET (point->descriptor, &exc_set);
      FD_SET (point->descriptor, &output_set);
    }

    /* check out the time */
    gettimeofday (&now, NULL);
    timespent = timediff (&now, &last_time);
    timeout = timediff (&opt_time, &timespent);
    last_time.tv_sec = now.tv_sec + timeout.tv_sec;
    last_time.tv_usec = now.tv_usec + timeout.tv_usec;
    if (last_time.tv_usec >= 1000000) {
      last_time.tv_usec -= 1000000;
      last_time.tv_sec++;
    }

    block_signals();

    if (select (maxdesc + 1, &input_set, &output_set, &exc_set, &null_time)
      < 0) {
      perror ("Select poll");
      WIN32CLEANUP
      exit (1);
    }

#ifdef WIN32   /* windows select demands a valid fd_set */
    if (select (0, (fd_set *) 0, (fd_set *) 0, &dummy_set, &timeout) == SOCKET_ERROR) {
#else
    if (select (0, (fd_set *) 0, (fd_set *) 0, (fd_set *) 0, &timeout) == SOCKET_ERROR) {
#endif
      perror ("Select sleep");
      WIN32CLEANUP
      exit (1);
    }

    restore_signals();

    /* Respond to whatever might be happening */

    /* New connection? */
    if (FD_ISSET (s, &input_set))
      if (new_descriptor (s) < 0)
        perror ("New connection");

    /* kick out the freaky folks */
    for (point = descriptor_list; point; point = next_point) {
      next_point = point->next;
      if (FD_ISSET (point->descriptor, &exc_set)) {
        FD_CLR (point->descriptor, &input_set);
        FD_CLR (point->descriptor, &output_set);
        close_socket (point);
      }
    }

    for (point = descriptor_list; point; point = next_point) {
      next_point = point->next;
      if (FD_ISSET (point->descriptor, &input_set))
        if (process_input (point) < 0)
          close_socket (point);
    }

    /* process_commands; */
    for (point = descriptor_list; point; point = next_to_process) {
      next_to_process = point->next;

      if ((--(point->wait) <= 0) && get_from_q (&point->input, comm)) {
        if (point->character && point->connected == CON_PLYNG &&
          point->character->specials.was_in_room != NOWHERE) {
          if (point->character->in_room != NOWHERE)
            char_from_room (point->character);
          char_to_room (point->character,
            point->character->specials.was_in_room);
          point->character->specials.was_in_room = NOWHERE;
          act ("$n has returned.", TRUE, point->character, 0, 0, TO_ROOM);
          affect_total (point->character);
        }

        point->wait = 1;
        if (point->character)
          point->character->specials.timer = 0;
        point->prompt_mode = 1;

        if (point->str)
          string_add (point, comm);
        else if (!point->connected)
          if (point->showstr_point)
            show_string (point, comm);
          else
            command_interpreter (point->character, comm);
        else
          nanny (point, comm);
      }
    }


    for (point = descriptor_list; point; point = next_point) {
      next_point = point->next;
      if (FD_ISSET (point->descriptor, &output_set) && point->output.head)
        if (process_output (point) < 0)
          close_socket (point);
        else
          point->prompt_mode = 1;
    }

    /* give the people some prompts */
    for (point = descriptor_list; point; point = point->next)
      if (point->prompt_mode) {
        if (point->str)
          write_to_descriptor (point->descriptor, "] ");
        else if (!point->connected)
          if (point->showstr_point)
            write_to_descriptor (point->descriptor, "*** Press return ***");
          else
            write_to_descriptor (point->descriptor, "> ");
        point->prompt_mode = 0;
      }



    /* handle heartbeat stuff */
    /* Note: pulse now changes every 1/4 sec  */

    pulse++;

    if (!(pulse % PULSE_ZONE)) {
      zone_update ();
      if (lawful)
        gr (s);
    }


    if (!(pulse % PULSE_MOBILE))
      mobile_activity ();

    if (!(pulse % PULSE_VIOLENCE))
      perform_violence ();

    if (!(pulse % (SECS_PER_MUD_HOUR * 4))) {
      weather_and_time (1);
      affect_update ();
      point_update ();
      if (time_info.hours == 1)
        update_time ();
    }

    if (pulse >= 2400) {
      pulse = 0;
      if (lawful)
        night_watchman ();
      check_reboot ();
    }

    tics++;                     /* tics since last checkpoint signal */
  }
}






/* ******************************************************************
*  general utility stuff (for local use)                   *
****************************************************************** */




int get_from_q (struct txt_q *queue, char *dest)
{
  struct txt_block *tmp;

  /* Q empty? */
  if (!queue->head)
    return (0);

  tmp = queue->head;
  strcpy (dest, queue->head->text);
  queue->head = queue->head->next;

  free (tmp->text);
  free (tmp);

  return (1);
}




void write_to_q (char *txt, struct txt_q *queue)
{
  struct txt_block *new;

  CREATE (new, struct txt_block, 1);
  CREATE (new->text, char, strlen (txt) + 1);

  strcpy (new->text, txt);

  /* Q empty? */
  if (!queue->head) {
    new->next = NULL;
    queue->head = queue->tail = new;
  } else {
    queue->tail->next = new;
    queue->tail = new;
    new->next = NULL;
  }
}







struct timeval timediff (struct timeval *a, struct timeval *b)
{
  struct timeval rslt, tmp;

  tmp = *a;

  if ((rslt.tv_usec = tmp.tv_usec - b->tv_usec) < 0) {
    rslt.tv_usec += 1000000;
    --(tmp.tv_sec);
  }
  if ((rslt.tv_sec = tmp.tv_sec - b->tv_sec) < 0) {
    rslt.tv_usec = 0;
    rslt.tv_sec = 0;
  }
  return (rslt);
}






/* Empty the queues before closing connection */
void flush_queues (struct descriptor_data *d)
{
  char dummy[MAX_STRING_LENGTH];

  while (get_from_q (&d->output, dummy));
  while (get_from_q (&d->input, dummy));
}






/* ******************************************************************
*  socket handling               *
****************************************************************** */




int init_socket (int port)
{
  SOCKET s;
  char *opt;
  char hostname[MAX_HOSTNAME + 1];
  struct sockaddr_in sa;
  struct hostent *hp;
  struct linger ld;

  bzero (&sa, sizeof (struct sockaddr_in));
  gethostname (hostname, MAX_HOSTNAME);
  hp = gethostbyname (hostname);
  if (hp == NULL) {
    perror ("gethostbyname");
    WIN32CLEANUP
    exit (1);
  }
  sa.sin_family = hp->h_addrtype;
  sa.sin_port = htons ((unsigned short) port);
  s = socket (AF_INET, SOCK_STREAM, 0);
  if (s == INVALID_SOCKET) {
    perror ("Init-socket");
    WIN32CLEANUP
    exit (1);
  }
  if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR,
      (char *) &opt, sizeof (opt)) < 0) {
    perror ("setsockopt REUSEADDR");
    WIN32CLEANUP
    exit (1);
  }

  ld.l_onoff = 1;
  ld.l_linger = 1000;
  if (setsockopt (s, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof (ld)) < 0) {
    perror ("setsockopt LINGER");
    WIN32CLEANUP
    exit (1);
  }
  if (bind (s, (struct sockaddr *) &sa, sizeof (sa)) < 0) {
    perror ("bind");
    close (s);
    WIN32CLEANUP
    exit (1);
  }
  listen (s, 3);
  return (s);
}





int new_connection (SOCKET s)
{
  struct sockaddr_in isa;
  /* struct sockaddr peer; */
#ifdef WIN32
  int i;
#else
  socklen_t i;
#endif
  SOCKET t;
  char buf[100];

  i = sizeof (isa);
  getsockname (s, (struct sockaddr *) &isa, & i);


  if ((t = accept (s, (struct sockaddr *) &isa, &i)) == INVALID_SOCKET) {
    perror ("Accept");
    return (-1);
  }
  nonblock (t);

  /*

     i = sizeof(peer);
     if (!getpeername(t, &peer, &i))
     {
     *(peer.sa_data + 49) = '\0';
     sprintf(buf, "New connection from addr %s.\n", peer.sa_data);
     log(buf);
     }

   */

  return (t);
}





int new_descriptor (SOCKET s)
{
  int desc;
  struct descriptor_data *newd;
#ifdef WIN32
  int size;
#else
  socklen_t size;
#endif
  struct sockaddr_in sock;
  struct hostent *from;
  char buf[10];

  if ((desc = new_connection (s)) < 0)
    return (-1);

  if (wizlock) {
    write_to_descriptor (desc, "The game is wizlocked...");
    close (desc);
    return (0);
  }

#ifdef WIN32
  if ((maxdesc + 1) >= avail_descs) {
#else
  if ((desc + 1) >= avail_descs) {
#endif
    write_to_descriptor (desc, "Sorry.. The game is full...\n\r");
    close (desc);
    return (0);
#ifdef WIN32
  } else
    maxdesc++;
#else
  } else if (desc > maxdesc)
    maxdesc = desc;
#endif

  CREATE (newd, struct descriptor_data, 1);

  /* find info */
  size = sizeof (sock);
  if (getpeername (desc, (struct sockaddr *) &sock, &size) < 0) {
    perror ("getpeername");
    *newd->host = '\0';
  } else if (!(from = gethostbyaddr ((char *) &sock.sin_addr,
        sizeof (sock.sin_addr), AF_INET))) {
    strcpy (newd->host, inet_ntoa (sock.sin_addr));
  } else {
    strncpy (newd->host, from->h_name, 49);
    *(newd->host + 49) = '\0';
  }


  /* init desc data */
  newd->descriptor = desc;
  newd->connected = 1;
  newd->wait = 1;
  newd->prompt_mode = 0;
  *newd->buf = '\0';
  newd->str = 0;
  newd->showstr_head = 0;
  newd->showstr_point = 0;
  *newd->last_input = '\0';
  newd->output.head = NULL;
  newd->input.head = NULL;
  newd->next = descriptor_list;
  newd->character = 0;
  newd->original = 0;
  newd->snoop.snooping = 0;
  newd->snoop.snoop_by = 0;

  /* prepend to list */

  descriptor_list = newd;

  SEND_TO_Q (GREETINGS, newd);
  SEND_TO_Q ("By what name do you wish to be known? ", newd);

  return (0);
}





int process_output (struct descriptor_data *t)
{
  char i[MAX_STRING_LENGTH + 1];

  if (!t->prompt_mode && !t->connected)
    if (write_to_descriptor (t->descriptor, "\n\r") < 0)
      return (-1);


  /* Cycle thru output queue */
  while (get_from_q (&t->output, i)) {
    if (t->snoop.snoop_by) {
      write_to_q ("% ", &t->snoop.snoop_by->desc->output);
      write_to_q (i, &t->snoop.snoop_by->desc->output);
    }
    if (write_to_descriptor (t->descriptor, i))
      return (-1);
  }

  if (!t->connected && !(t->character && !IS_NPC (t->character) &&
      IS_SET (t->character->specials.act, PLR_COMPACT)))
    if (write_to_descriptor (t->descriptor, "\n\r") < 0)
      return (-1);

  return (1);
}


int write_to_descriptor (int desc, char *txt)
{
  int sofar, thisround, total;

  total = strlen (txt);
  sofar = 0;

  do {
    thisround = send (desc, txt + sofar, total - sofar, 0);
    if (thisround < 0) {
      perror ("Write to socket");
      return (-1);
    }
    sofar += thisround;
  }
  while (sofar < total);

  return (0);
}





int process_input (struct descriptor_data *t)
{
  int sofar, thisround, begin, squelch, i, k, flag;
  char tmp[MAX_INPUT_LENGTH + 2], buffer[MAX_INPUT_LENGTH + 60];

  sofar = 0;
  flag = 0;
  begin = strlen (t->buf);

  /* Read in some stuff */
  do {
    if ((thisround = recv (t->descriptor, t->buf + begin + sofar,
          MAX_STRING_LENGTH - (begin + sofar) - 1, 0)) > 0)
      sofar += thisround;
    else if (thisround < 0)
      if (GETERROR != EWOULDBLOCK) {
        perror ("Read1 - ERROR");
        return (-1);
      } else
        break;
    else {
      log ("EOF encountered on socket read.");
      return (-1);
    }
  }
  while (!ISNEWL (*(t->buf + begin + sofar - 1)));

  *(t->buf + begin + sofar) = 0;

  /* if no newline is contained in input, return without proc'ing */
  for (i = begin; !ISNEWL (*(t->buf + i)); i++)
    if (!*(t->buf + i))
      return (0);

  /* input contains 1 or more newlines; process the stuff */
  for (i = 0, k = 0; *(t->buf + i);) {
    if (!ISNEWL (*(t->buf + i)) && !(flag = (k >= (MAX_INPUT_LENGTH - 2))))
      if (*(t->buf + i) == '\b')        /* backspace */
        if (k) {                /* more than one char ? */
          if (*(tmp + --k) == '$')
            k--;
          i++;
        } else
          i++;                  /* no or just one char.. Skip backsp */
      else if (isascii (*(t->buf + i)) && isprint ((int)*(t->buf + i))) {
        /* trans char, double for '$' (printf)  */
        if ((*(tmp + k) = *(t->buf + i)) == '$')
          *(tmp + ++k) = '$';
        k++;
        i++;
      } else
        i++;
    else {
      *(tmp + k) = 0;
      if (*tmp == '!')
        strcpy (tmp, t->last_input);
      else
        strcpy (t->last_input, tmp);

      write_to_q (tmp, &t->input);

      if (t->snoop.snoop_by) {
        write_to_q ("% ", &t->snoop.snoop_by->desc->output);
        write_to_q (tmp, &t->snoop.snoop_by->desc->output);
        write_to_q ("\n\r", &t->snoop.snoop_by->desc->output);
      }

      if (flag) {
        sprintf (buffer, "Line too long. Truncated to:\n\r%s\n\r", tmp);
        if (write_to_descriptor (t->descriptor, buffer) < 0)
          return (-1);

        /* skip the rest of the line */
        for (; !ISNEWL (*(t->buf + i)); i++);
      }

      /* find end of entry */
      for (; ISNEWL (*(t->buf + i)); i++);

      /* squelch the entry from the buffer */
      for (squelch = 0;; squelch++)
        if ((*(t->buf + squelch) = *(t->buf + i + squelch)) == '\0')
          break;
      k = 0;
      i = 0;
    }
  }
  return (1);
}




void close_sockets (int s)
{
  log ("Closing all sockets.");

  while (descriptor_list)
    close_socket (descriptor_list);

  close (s);
}





void close_socket (struct descriptor_data *d)
{
  struct affected_type *af;
  struct descriptor_data *tmp;
  char buf[100];

  close (d->descriptor);
  flush_queues (d);

#ifndef WIN32
  if (d->descriptor == maxdesc)
#endif
    --maxdesc;

  /* Forget snooping */
  if (d->snoop.snooping)
    d->snoop.snooping->desc->snoop.snoop_by = 0;

  if (d->snoop.snoop_by) {
    send_to_char ("Your victim is no longer among us.\n\r",
      d->snoop.snoop_by);
    d->snoop.snoop_by->desc->snoop.snooping = 0;
  }

  if (d->character)
    if (d->connected == CON_PLYNG) {
      save_char (d->character, NOWHERE);
      act ("$n has lost $s link.", TRUE, d->character, 0, 0, TO_ROOM);
      sprintf (buf, "Closing link to: %s.", GET_NAME (d->character));
      log (buf);
      d->character->desc = 0;
    } else {
      sprintf (buf, "Losing player: %s.", GET_NAME (d->character));
      log (buf);

      free_char (d->character);
  } else
    log ("Losing descriptor without char.");


  if (next_to_process == d)     /* to avoid crashing the process loop */
    next_to_process = next_to_process->next;

  if (d == descriptor_list)     /* this is the head of the list */
    descriptor_list = descriptor_list->next;
  else {                        /* This is somewhere inside the list */

    /* Locate the previous element */
    for (tmp = descriptor_list; (tmp->next != d) && tmp; tmp = tmp->next);

    tmp->next = d->next;
  }
  if (d->showstr_head)
    free (d->showstr_head);
  free (d);
}





void nonblock (SOCKET s)
{
#ifdef WIN32
  unsigned long flags = 1;

  if (ioctlsocket (s, FIONBIO, &flags)) {
#else
  if (fcntl (s, F_SETFL, FNDELAY) == -1) {
#endif
    perror ("Noblock");
    WIN32CLEANUP
    exit (1);
  }
}




#define COMA_SIGN \
"\n\r \
DikuMUD is currently inactive due to excessive load on the host machine.\n\r \
Please try again later.\n\r \
\n\r \
   Sadly,\n\r \
\n\r \
    the DikuMUD system operators\n\r\n\r"


/* sleep while the load is too high */
void coma (SOCKET s)
{
  fd_set input_set;
  static struct timeval timeout = {
    60,
    0
  };
  int conn;

  int workhours (void);
  int load (void);

  log ("Entering comatose state.");

  block_signals();

  while (descriptor_list)
    close_socket (descriptor_list);

  FD_ZERO (&input_set);
  do {
    FD_SET (s, &input_set);
    if (select (64, &input_set, 0, 0, &timeout) < 0) {
      perror ("coma select");
      WIN32CLEANUP
      exit (1);
    }
    if (FD_ISSET (s, &input_set)) {
      if (load () < 6) {
        log ("Leaving coma with visitor.");
        restore_signals();
        return;
      }
      if ((conn = new_connection (s)) >= 0) {
        write_to_descriptor (conn, COMA_SIGN);
#if defined WIN32
        Sleep (2000);
#else
        sleep (2);
#endif
        close (conn);
      }
    }

    tics = 1;
    if (workhours ()) {
      log ("Working hours collision during coma. Exit.");
      WIN32CLEANUP
      exit (0);
    }
  }
  while (load () >= 6);

  log ("Leaving coma.");
  restore_signals();
}



/* ****************************************************************
* Public routines for system-to-player-communication        *
**************************************************************** */



void send_to_char (char *messg, struct char_data *ch)
{

  if (ch->desc && messg)
    write_to_q (messg, &ch->desc->output);
}




void send_to_all (char *messg)
{
  struct descriptor_data *i;

  if (messg)
    for (i = descriptor_list; i; i = i->next)
      if (!i->connected)
        write_to_q (messg, &i->output);
}


void send_to_outdoor (char *messg)
{
  struct descriptor_data *i;

  if (messg)
    for (i = descriptor_list; i; i = i->next)
      if (!i->connected)
        if (OUTSIDE (i->character))
          write_to_q (messg, &i->output);
}


void send_to_except (char *messg, struct char_data *ch)
{
  struct descriptor_data *i;

  if (messg)
    for (i = descriptor_list; i; i = i->next)
      if (ch->desc != i && !i->connected)
        write_to_q (messg, &i->output);
}



void send_to_room (char *messg, int room)
{
  struct char_data *i;

  if (messg)
    for (i = world[room].people; i; i = i->next_in_room)
      if (i->desc)
        write_to_q (messg, &i->desc->output);
}




void send_to_room_except (char *messg, int room, struct char_data *ch)
{
  struct char_data *i;

  if (messg)
    for (i = world[room].people; i; i = i->next_in_room)
      if (i != ch && i->desc)
        write_to_q (messg, &i->desc->output);
}

void send_to_room_except_two
  (char *messg, int room, struct char_data *ch1, struct char_data *ch2) {
  struct char_data *i;

  if (messg)
    for (i = world[room].people; i; i = i->next_in_room)
      if (i != ch1 && i != ch2 && i->desc)
        write_to_q (messg, &i->desc->output);
}



/* higher-level communication */


void act (char *str, int hide_invisible, struct char_data *ch,
  struct obj_data *obj, void *vict_obj, int type)
{
  register char *strp, *point, *i = NULL;
  struct char_data *to;
  char buf[MAX_STRING_LENGTH];

  if (!str)
    return;
  if (!*str)
    return;

  if (type == TO_VICT)
    to = (struct char_data *) vict_obj;
  else if (type == TO_CHAR)
    to = ch;
  else
    to = world[ch->in_room].people;

  for (; to; to = to->next_in_room) {
    if (to->desc && ((to != ch) || (type == TO_CHAR)) &&
      (CAN_SEE (to, ch) || !hide_invisible) && AWAKE (to) &&
      !((type == TO_NOTVICT) && (to == (struct char_data *) vict_obj))) {
      for (strp = str, point = buf;;)
        if (*strp == '$') {
          switch (*(++strp)) {
          case 'n':
            i = PERS (ch, to);
            break;
          case 'N':
            i = PERS ((struct char_data *) vict_obj, to);
            break;
          case 'm':
            i = HMHR (ch);
            break;
          case 'M':
            i = HMHR ((struct char_data *) vict_obj);
            break;
          case 's':
            i = HSHR (ch);
            break;
          case 'S':
            i = HSHR ((struct char_data *) vict_obj);
            break;
          case 'e':
            i = HSSH (ch);
            break;
          case 'E':
            i = HSSH ((struct char_data *) vict_obj);
            break;
          case 'o':
            i = OBJN (obj, to);
            break;
          case 'O':
            i = OBJN ((struct obj_data *) vict_obj, to);
            break;
          case 'p':
            i = OBJS (obj, to);
            break;
          case 'P':
            i = OBJS ((struct obj_data *) vict_obj, to);
            break;
          case 'a':
            i = SANA (obj);
            break;
          case 'A':
            i = SANA ((struct obj_data *) vict_obj);
            break;
          case 'T':
            i = (char *) vict_obj;
            break;
          case 'F':
            i = fname ((char *) vict_obj);
            break;
          case '$':
            i = "$";
            break;
          default:
            log ("Illegal $-code to act():");
            log (str);
            break;
          }
          while (*point = *(i++))
            ++point;
          ++strp;
        } else if (!(*(point++) = *(strp++)))
          break;

      *(--point) = '\n';
      *(++point) = '\r';
      *(++point) = '\0';

      write_to_q (CAP (buf), &to->desc->output);
    }
    if ((type == TO_VICT) || (type == TO_CHAR))
      return;
  }
}