/***************************************************************************
 *  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.                                               *
 *                                                                         *
 *  Copyright (C) 1992, 1993 Michael Chastain, Michael Quan, Mitchell Tse  *
 *  Performance optimization and bug fixes by MERC Industries.             *
 *  You can use our stuff in any way you like whatsoever so long as this   *
 *  copyright notice remains intact.  If you like it please drop a line    *
 *  to mec@garnet.berkeley.edu.                                            *
 *                                                                         *
 *  This is free software and you are benefitting.  We hope that you       *
 *  share your changes too.  What goes around, comes around.               *
 ***************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <time.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>

#include "structs.h"
#include "mob.h"
#include "obj.h"
#include "utils.h"
#include "interp.h"
#include "handler.h"
#include "db.h"

#define DFLT_PORT   4000
#define MAX_NAME_LENGTH 15
#define MAX_HOSTNAME    256
#define OPT_USEC    250000      /* Microseconds per pass    */

extern int errno;

/* externs */

extern char greetings[MAX_STRING_LENGTH];

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 struct ban_t *ban_list;        /* In db.c */

/* local globals */

struct descriptor_data *descriptor_list, *next_to_process;

int god    = 0;      /* all new chars are gods! */
int slow_death = 0;  /* Shut her down, Martha, she's sucking mud */
int m_shutdown = 0;    /* clean shutdown */
int maxdesc;



void shutdown_request(void);
void logsig(void);
void hupsig(void);

char * get_from_q(struct txt_q *queue);
void game_loop(int s);
int init_socket(int port);
int new_connection(int s);
int new_descriptor(int s);
int process_output(struct descriptor_data *t);
int process_input(struct descriptor_data *t);
void close_socket(struct descriptor_data *d);
void flush_queues(struct descriptor_data *d);
void nonblock(int s);
void parse_name(struct descriptor_data *desc, char *arg);
int number_playing(void);

/* extern functions */

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 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);



int main( int argc, char *argv[] )
{
    int pos;
    int port	= DFLT_PORT;
    char *dir	= DFLT_DIR;
    int control;

    for ( pos = 1; pos < argc && argv[pos][0] == '-' ; pos++ )
    {
	switch (*(argv[pos] + 1))
	{
	case 'g':
	    god = 1;
	    log( "God creation mode selected." );
	    break;

	case 'd':
	    if ( argv[pos][2] != '\0' )
		dir = &argv[pos][2];
	    else if (++pos < argc)
		dir = &argv[pos][0];
	    else
	    {
		fprintf( stderr, "Directory arg expected after -d.\n\r" );
		exit( 1 );
	    }
	    break;

	default:
	    fprintf( stderr, "Unknown option -%c.\n\r", argv[pos][1] );
	    exit( 1 );
	    break;
	}
    }
    
    if (pos < argc)
    {
	if (!isdigit(argv[pos][0]))
	{
	    fprintf( stderr, "Usage: %s [-g] [-d pathname] [port #]\n", 
		argv[0] );
	    exit( 1 );
	}
	else if ( ( port = atoi(argv[pos]) ) <= 1024 )
	{
	    printf( "Illegal port #\n" );
	    exit( 1 );
	}
    }

    sprintf( log_buf, "[Port %d] [Dir %s].", port, dir );
    log( log_buf );

    if ( chdir( dir ) < 0 )
    {
	perror( dir );
	exit( 1 );
    }
    
    srandom( time(0) );

    /*
     * Optional memory tuning.
     */
#if defined(sun)
    mallopt( M_MXFAST, 96 );
    mallopt( M_NLBLKS, 1024 );
#endif

    signal( SIGPIPE, SIG_IGN );
    control   = init_socket( port );
    boot_db( );
    game_loop( control );

    while ( descriptor_list )
	close_socket( descriptor_list );
    log( "Normal termination of game." );
    exit( 0 );
    return 0;
}



/* Accept new connects, relay commands, and call 'heartbeat-functs' */
void game_loop( int control )
{
    fd_set input_set, output_set, exc_set;
    static struct timeval null_time = {0, 0};
    struct timeval last_time, now_time, stall_time;
    char buf[100];
    struct descriptor_data *point, *next_point;
    int pulse = 0, mask;
    char *pcomm;
    bool fStall;

    gettimeofday(&last_time, (struct timezone *) 0);

    maxdesc = control;

    mask = sigmask(SIGUSR1) | sigmask(SIGUSR2) | sigmask(SIGINT) |
	sigmask(SIGPIPE) | sigmask(SIGALRM) | sigmask(SIGTERM) |
	sigmask(SIGURG) | sigmask(SIGXCPU) | sigmask(SIGHUP) |
	sigmask(SIGVTALRM);

#if 0
    mallocmap();
#endif

    /* Main loop */
    while (!m_shutdown)
    {
	/* Check what's happening out there */
	FD_ZERO(&input_set);
	FD_ZERO(&output_set);
	FD_ZERO(&exc_set);
	FD_SET(control, &input_set);
	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);
	}

	sigsetmask(mask);

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

	sigsetmask(0);

	/* Respond to whatever might be happening */
	
	/* New connection? */
	if (FD_ISSET(control, &input_set))
	    if (new_descriptor(control) < 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);
		if ( point->character )
		    save_char_obj(point->character);
		close_socket(point);
	    }
	}

	/* Read input */
	for (point = descriptor_list; point; point = next_point) 
	{
	    next_point = point->next;
	    if (FD_ISSET(point->descriptor, &input_set))
	    {
		if (process_input(point) < 0) 
		{
		    if ( point->character )
			save_char_obj( point->character );
		    close_socket(point);
		    continue;
		}
		point->newline = FALSE;
	    }
	    else
		point->newline = TRUE;
	}

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

	    if (--(point->wait) <= 0
	    && (pcomm = get_from_q( &point->input )) != NULL )
	    {
		if (point->character && point->connected == CON_PLAYING &&
		    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;

		if (point->str)
		    string_add(point, pcomm);
		else if (point->connected != CON_PLAYING)
		    nanny(point, pcomm);
		else if (point->showstr_point)
		    show_string(point, pcomm);
		else
		    command_interpreter(point->character, pcomm);
		free( pcomm );

		/* Cheesy way to force prompts */
		write_to_q( "", &point->output );
	    }
	}


	for (point = descriptor_list; point; point = next_point) 
	{
	    next_point = point->next;

	    if (FD_ISSET(point->descriptor, &output_set) && point->output.head)
	    {
		/* give the people some prompts */
		if (point->str)
		    write_to_q( "] ", &point->output );
		else if (point->connected != CON_PLAYING)
		    ;
		else if (point->showstr_point)
		    write_to_q( "*** Press return ***", &point->output );
		else
		{
		    if ( !IS_SET(point->character->specials.act, PLR_COMPACT) )
			write_to_q( "\n\r", &point->output );
		    sprintf( buf, "<%dhp %dm %dmv> ",
			GET_HIT(point->character),
			GET_MANA(point->character),
			GET_MOVE(point->character) );
		    write_to_q( buf, &point->output );
		}

		if (process_output(point) < 0)
		{
		    if ( point->character )
			save_char_obj( point->character );
		    close_socket(point);
		}
	    }
	}

	/* See if everyone wants clock FAST. */
	fStall = TRUE;
	for (point = descriptor_list; point; point = next_to_process)
	{
	    if ( point->connected != CON_PLAYING )
		continue;
	    if ( point->tick_wait > 0 )
		continue;
	    fStall = TRUE;
	}

	/*
	 * Heartbeat.
	 * All autonomous actions (including fighting and healing)
	 * are subdivisions of the basic pulse at OPT_USEC interval.
	 */

	pulse++;

	if (pulse % PULSE_ZONE == 0)
	    zone_update();

	if (pulse % PULSE_MOBILE == 0)
	    mobile_activity();

	if (pulse % PULSE_VIOLENCE == 0)
	    perform_violence();

	if (pulse % ((SECS_PER_MUD_HOUR*1000000)/OPT_USEC) == 0)
	{
	    weather_and_time(1);
	    affect_update();
	    point_update();
	}



	/*
	 * Synchronize to an OPT_USEC clock.
	 * Sleep( last_time + OPT_USEC - now ).
	 */
	if ( fStall )
	{
	    gettimeofday( &now_time, NULL );
	    stall_time.tv_usec  = last_time.tv_usec - now_time.tv_usec
			    + OPT_USEC;
	    stall_time.tv_sec   = last_time.tv_sec  - now_time.tv_sec;
	    if ( stall_time.tv_usec < 0 )
	    {
		stall_time.tv_usec += 1000000;
		stall_time.tv_sec--;
	    }
	    if ( stall_time.tv_usec >= 1000000 )
	    {
		stall_time.tv_usec -= 1000000;
		stall_time.tv_sec++;
	    }

	    if ( stall_time.tv_sec > 0
	    || ( stall_time.tv_sec == 0 && stall_time.tv_usec > 0 ) )
	    {
		if ( select( 0, NULL, NULL, NULL, &stall_time ) < 0 )
		{
		    perror( "Select stall" );
		    exit( 0 );
		}
	    }
	}

	gettimeofday( &last_time, NULL );
    }
}



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

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

    tmp         = queue->head;
    dest        = tmp->text;
    queue->head = tmp->next;

    free( tmp );
    return dest;
}



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;
    }
}



/* Empty the queues before closing connection */
void flush_queues(struct descriptor_data *d)
{
    char *pbuf;

    while ( ( pbuf = get_from_q( &d->input ) ) != NULL )
	free( pbuf );

    while ( ( pbuf = get_from_q( &d->output ) ) != NULL )
	free( pbuf );
}



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

    gethostname(hostname, MAX_HOSTNAME);
    hp = gethostbyname(hostname);
    if (hp == NULL)
    {
	perror("gethostbyname");
	exit(1);
    }

    sa.sin_family      = hp->h_addrtype;
    sa.sin_port        = htons(port);
    sa.sin_addr.s_addr = 0;
    sa.sin_zero[0]     = 0;
    sa.sin_zero[1]     = 0;
    sa.sin_zero[2]     = 0;
    sa.sin_zero[3]     = 0;
    sa.sin_zero[4]     = 0;
    sa.sin_zero[5]     = 0;
    sa.sin_zero[6]     = 0;
    sa.sin_zero[7]     = 0;

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) 
    {
	perror("Init-socket");
	exit(1);
    }
    if (setsockopt (s, SOL_SOCKET, SO_REUSEADDR,
	(char *) &opt, sizeof (opt)) < 0) 
    {
	perror ("setsockopt SO_REUSEADDR");
	exit (1);
    }

#if defined(SO_DONTLINGER)
    {
	struct linger ld;

	ld.l_onoff  = 1;
	ld.l_linger = 1000;

	if (setsockopt(s, SOL_SOCKET, SO_DONTLINGER, &ld, sizeof(ld)) < 0) 
	{
	    perror("setsockopt SO_DONTLINGER");
	    exit( 1 );
	}
    }
#endif

    if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0)
    {
	perror("bind");
	close(s);
	exit(1);
    }
    listen(s, 3);
    return(s);
}



int new_connection(int s)
{
    struct sockaddr_in isa;
    size_t i;
    int t;

    i = sizeof(isa);

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

    return(t);
}



int number_playing(void)
{
    struct descriptor_data *d;

    int i;

    for ( i = 0, d = descriptor_list ; d ; d = d->next )
	i++;

    return(i);
}



int new_descriptor(int s)
{
    int desc;
    struct descriptor_data *newd;
    size_t size;
    struct sockaddr_in sock;
    struct hostent *from;
    struct ban_t *tmp;

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

    if (desc > maxdesc)
	maxdesc = desc;

    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
    {
	strcpy(newd->host, inet_ntoa(sock.sin_addr));
	from = gethostbyaddr( (char *) &sock.sin_addr,
	    sizeof(sock.sin_addr), AF_INET );
	if ( from )
	{
	    strncpy(newd->host, from->h_name, 49);
	    *(newd->host + 49) = '\0';
	}
    }
	
#if defined(TWIT_FILTER)
    /*
     * Furey: a site-specific rebuke.
     */
    if ( !strncmp( newd->host, "etch2mac", 8 ) )
    {
	write_to_descriptor( desc,
    "Your site is full of twits.  You are banned for a week.\n\r" );
	close( desc );
	free( newd );
	return 0;
    }
#endif

    /*
     * Swiftest: I added the following to ban sites. I don't
     * endorse banning of sites, but Copper has few descriptors now
     * and some people from certain sites keep abusing access by
     * using automated 'autodialers' and leaving connections hanging.
     */
    for ( tmp = ban_list; tmp; tmp = tmp->next )
    {
	if ( !str_cmp( tmp->name, newd->host ) )
	{
	    write_to_descriptor( desc,
	"Your site has been banned from MERCDikuMud.\n\r" );
	    close( desc );
	    free( newd );
	    return 0;
	}
    }

    
    /* init desc data */
    newd->descriptor = desc;
    newd->connected  = 1;
    newd->wait = 1;
    *newd->buf = '\0';
    newd->str = 0;
    newd->showstr_head = 0;
    newd->showstr_point = 0;
    newd->newline = FALSE;
    *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;

    write_to_q( greetings, &newd->output );
    write_to_q( "By what name do you wish to be known? ", &newd->output );

    return(0);
}



int process_output(struct descriptor_data *t)
{
    char buf[2 * MAX_STRING_LENGTH];
    int ibuf = 0;
    char *pstr;
    int ilen;

    if ( t->newline )
    {
	buf[ibuf++] = '\n';
	buf[ibuf++] = '\r';
    }

    /* Cycle thru output queue */
    while ( ( pstr = get_from_q(&t->output) ) != NULL )
    {  
	if(t->snoop.snoop_by)
	{
	    write_to_q("% ",&t->snoop.snoop_by->desc->output);
	    write_to_q(pstr, &t->snoop.snoop_by->desc->output);
	}
	ilen = strlen( pstr );
	if ( ibuf + ilen > sizeof(buf)-10 )
	    ilen = sizeof(buf)-10 - ibuf;
	memcpy( &buf[ibuf], pstr, ilen );
	ibuf += ilen;
	free( pstr );
	if ( ibuf > sizeof(buf)/2 )
	    break;
    }
    
    buf[ibuf] = '\0';
    return write_to_descriptor( t->descriptor, buf );
}


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

    total = strlen(txt);
    sofar = 0;

    do
    {
	thisround = write(desc, txt + sofar, total - sofar);
	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 = read(t->descriptor, t->buf + begin + sofar, 
	    MAX_STRING_LENGTH - (begin + sofar) - 1)) > 0){
	    sofar += thisround;
	      }
	else
	    if (thisround < 0)
		if(errno != 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(*(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_socket(struct descriptor_data *d)
{
    struct descriptor_data *tmp;

    process_output( d );
    close( d->descriptor );
    if ( d->descriptor == maxdesc )
	--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_PLAYING )
	{
	    act("$n has lost $s link.", TRUE, d->character, 0, 0, TO_ROOM);
	    sprintf(log_buf, "Closing link to: %s.", GET_NAME(d->character));
	    log( log_buf );
	    d->character->desc = 0;
	}
	else
	{
	    sprintf(log_buf, "Losing player: %s.", GET_NAME(d->character) );
	    log( log_buf );
	    free_char( d->character );
	}
    }
    else
    {
	log( "Losing descriptor without char." );
    }

    if (next_to_process == d)
	next_to_process = next_to_process->next;   

    if ( d == descriptor_list )
	descriptor_list = descriptor_list->next;
    else
    {
	/* 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(int s)
{
    if (fcntl(s, F_SETFL, FNDELAY) == -1)
    {
	perror("Noblock");
	exit(1);
    }
}



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_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 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 ||
	    (type == TO_VICT)) && 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++) ) != '\0' )
			++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;
    }
}



void night_watchman(void)
{
    long tc;
    struct tm *t_info;

    extern int m_shutdown;

    void send_to_all(char *messg);

    tc = time(0);
    t_info = localtime(&tc);

    if ((t_info->tm_hour == 8) && (t_info->tm_wday > 0) &&
	(t_info->tm_wday < 6))
    {
	if (t_info->tm_min > 50)
	{
	    log("Leaving the scene for the serious folks.");
	    send_to_all("Closing down. Thank you for flying DikuMUD.\n\r");
	    m_shutdown = 1;
	}
	else if (t_info->tm_min > 40)
	    send_to_all(
		"ATTENTION: Merc Mud will shut down in 10 minutes.\n\r");
	else if (t_info->tm_min > 30)
	    send_to_all("Warning: The game will close in 20 minutes.\n\r");
    }
}