Tapestries/
Tapestries/area/
Tapestries/area/current/
Tapestries/area/helps/
Tapestries/area/tmp/
Tapestries/ideas/
Tapestries/log/
Tapestries/player/
Tapestries/player/b/
Tapestries/player/d/
Tapestries/player/e/
Tapestries/player/h/
Tapestries/player/j/
Tapestries/player/k/
Tapestries/player/l/
Tapestries/player/m/
Tapestries/player/n/
Tapestries/player/r/
Tapestries/player/s/
Tapestries/player/t/
Tapestries/player/u/
Tapestries/player/z/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.doc' as well the Merc       *
 *  license in 'license.txt'.  In particular, you may not remove either of *
 *  these copyright notices.                                               *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

#if defined(macintosh)
#include <types.h>
#else
#include <sys/types.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"



/*
 * Local functions.
 */
bool	is_note_to	args( ( CHAR_DATA *ch, NOTE_DATA *pnote ) );
void	note_attach	args( ( CHAR_DATA *ch, int spool ) );
void	note_remove	args( ( CHAR_DATA *ch, NOTE_DATA *pnote, int spool ) );

void	talk_channel	args( ( CHAR_DATA *ch, char *argument,
			    int channel, const char *verb ) );


bool is_note_to( CHAR_DATA *ch, NOTE_DATA *pnote )
{
    if( !str_cmp( ch->name, pnote->sender ) )
	return TRUE;

    if( is_name( "all", pnote->to_list ) )
	return TRUE;

    if( IS_HERO( ch )
     && is_name( "immortal", pnote->to_list ) )
	return TRUE;

    if( is_name_old( ch->name, pnote->to_list ) )
	return TRUE;

    return FALSE;
}



void note_attach( CHAR_DATA *ch, int spool )
{
    NOTE_DATA	*pnote;

    if( ch->pnote )
	return;

    if( !note_free )
    {
	pnote	  = alloc_perm( sizeof( *ch->pnote ) );
    }
    else
    {
	pnote	  = note_free;
	note_free = note_free->next;
    }

    pnote->next		= NULL;
    pnote->spool	= spool;
    pnote->anon		= FALSE;
    pnote->sender	= str_dup( ch->name );
    pnote->date		= str_dup( "" );
    pnote->to_list	= str_dup( "" );
    pnote->subject	= str_dup( "" );
    pnote->text		= str_dup( "" );
    ch->pnote		= pnote;
    return;
}



void note_remove( CHAR_DATA *ch, NOTE_DATA *pnote, int spool )
{
    NOTE_DATA	*prev;
    FILE	*fp;
    char	*to_list;
    char	to_new[ MAX_INPUT_LENGTH ];
    char	to_one[ MAX_INPUT_LENGTH ];
    char	buf[   MAX_STRING_LENGTH ];

    switch( spool )
    {
	case( SPOOL_NOTE ):
	    sprintf( buf, "note" );
	    break;
	case( SPOOL_IDEA ):
	    sprintf( buf, "idea" );
	    break;
	case( SPOOL_MAIL ):
	    sprintf( buf, "mail" );
	    break;
	case( SPOOL_BUG ):
	    sprintf( buf, "bug" );
	    break;
	case( SPOOL_QUEST ):
	    sprintf( buf, "qwest" );
	    break;
	default:
	    sprintf( buf, "note" );
	    break;
    }

    /*
     * Build a new to_list.
     * Strip out this recipient.
     */
    to_new[0]	= '\0';
    to_list	= pnote->to_list;
    while( *to_list != '\0' )
    {
	to_list	= one_argument( to_list, to_one );
	if( to_one[ 0 ] != '\0' && str_cmp( ch->name, to_one ) )
	{
	    strcat( to_new, " " );
	    strcat( to_new, to_one );
	}
    }

    /*
     * Just a simple recipient removal?
     */
    if( str_cmp( ch->name, pnote->sender ) && to_new[0] != '\0' )
    {
	free_string( pnote->to_list );
	pnote->to_list = str_dup( to_new + 1 );
	return;
    }

    /*
     * Remove note from linked list.
     */
    if( pnote == note_list )
    {
	note_list = pnote->next;
    }
    else
    {
	for( prev = note_list ; prev ; prev = prev->next )
	{
	    if( prev->next == pnote )
		break;
	}

	if( !prev )
	{
	    bug( "Note_remove: pnote not found.", 0 );
	    return;
	}

	prev->next = pnote->next;
    }

    free_string( pnote->text    );
    free_string( pnote->subject );
    free_string( pnote->to_list );
    free_string( pnote->date    );
    free_string( pnote->sender  );
    pnote->spool	= SPOOL_NOTE;
    pnote->anon 	= FALSE;
    pnote->next		= note_free;
    note_free		= pnote;

    /*
     * Rewrite entire list.
     */
    fclose( fpReserve );
    if( !( fp = fopen( NOTE_FILE, "w" ) ) )
    {
	perror( NOTE_FILE );
    }
    else
    {
	for( pnote = note_list ; pnote ; pnote = pnote->next )
	{
	    fprintf( fp, "Sender  %s~\n", pnote->sender );
	    fprintf( fp, "Spool   %d\n",  pnote->spool      );
	    fprintf( fp, "Anon    %d\n",  pnote->anon       );
	    fprintf( fp, "Date    %s~\n", pnote->date );
	    fprintf( fp, "Stamp   %ld\n", pnote->date_stamp );
	    fprintf( fp, "To      %s~\n", pnote->to_list );
	    fprintf( fp, "Subject %s~\n", pnote->subject );
	    fprintf( fp, "Text\n%s~\n\n", pnote->text );
	}
	fclose( fp );
    }
    fpReserve = fopen( NULL_FILE, "r" );
    return;
}


void do_note( CHAR_DATA *ch, char *argument )
{
    note_process( ch, argument, SPOOL_NOTE );
}
void do_idea( CHAR_DATA *ch, char *argument )
{
    note_process( ch, argument, SPOOL_IDEA );
}
void do_mail( CHAR_DATA *ch, char *argument )
{
    note_process( ch, argument, SPOOL_MAIL );
}
void do_bug( CHAR_DATA *ch, char *argument )
{
    note_process( ch, argument, SPOOL_BUG );
}
void do_qwest( CHAR_DATA *ch, char *argument )
{
    note_process( ch, argument, SPOOL_QUEST );
}

void note_process( CHAR_DATA *ch, char *argument, int spool )
{
    NOTE_DATA	*pnote;
    char	*buf1;
    char	buf[  MAX_STRING_LENGTH     ];
    char	buf2[ MAX_STRING_LENGTH     ];
    char	spoolbuf[ MAX_STRING_LENGTH ];
    char	arg[  MAX_INPUT_LENGTH      ];
    int		vnum;
    int		anum;

    if( IS_NPC( ch ) )
	return;

    buf1	= NULL;

    argument	= one_argument( argument, arg );
    smash_tilde( argument );

    switch( spool )
    {
	case( SPOOL_NOTE ):
	    sprintf( buf2, "note" );
	    break;
	case( SPOOL_IDEA ):
	    sprintf( buf2, "idea" );
	    break;
	case( SPOOL_MAIL ):
	    sprintf( buf2, "mail" );
	    break;
	case( SPOOL_BUG ):
	    sprintf( buf2, "bug" );
	    break;
	case( SPOOL_QUEST ):
	    sprintf( buf2, "qwest" );
	    break;
	default:
	    sprintf( buf2, "note" );
	    break;
    }

    if( !*arg )
    {
	switch( spool )
	{
	    case SPOOL_NOTE:
		do_note( ch, "read" );
		break;
	    case SPOOL_IDEA:
		do_idea( ch, "read" );
		break;
	    case SPOOL_BUG:
		do_bug( ch, "read" );
		break;
	    case SPOOL_QUEST:
		do_qwest( ch, "read" );
		break;
	    case SPOOL_MAIL:
		do_mail( ch, "read" );
		break;
	    default:
		return;
	}
	return;
    }

    if( ( !str_prefix( arg, "list" ) ) )
    {
	vnum    = 0;
	buf1	= NULL;
	for( pnote = note_list ; pnote ; pnote = pnote->next )
	{
	    if( ( is_note_to( ch, pnote ) ) && ( spool == pnote->spool ) )
	    {
		if( pnote->anon )
		{
		    sprintf( buf, "[{g%3d{r%s{x] {cAnonymous{x: %s\n\r",
			vnum,
			( pnote->date_stamp > ch->last_note[spool]
			 && str_cmp( pnote->sender, ch->name ) ) ? "N" : " ",
			pnote->subject );
		}
		else
		{
		    sprintf( buf, "[{g%3d{r%s{x] {c%s{x: %s\n\r",
			vnum,
			( pnote->date_stamp > ch->last_note[ spool ]
			 && str_cmp( pnote->sender, ch->name ) ) ? "N" : " ",
			pnote->sender, pnote->subject );
		}
		str_cat( &buf1, buf );
		vnum++;
	    }
	}
	page_to_char( buf1, ch );
	free( buf1 );

	return;
    }

    if( !str_prefix( arg, "read" ) )
    {
	bool fAll;

	if( !str_cmp( argument, "all" ) )
	{
	    fAll = TRUE;
	    anum = 0;
	}
	else if( !*argument || !str_prefix( argument, "next" ) )
	  /* read next unread note */
	{
	    vnum	= 0;
	    buf1	= NULL;
	    for( pnote = note_list ; pnote ; pnote = pnote->next )
	    {
		if( is_note_to( ch, pnote )
		 && str_cmp( ch->name, pnote->sender )
		 && ch->last_note[spool] < pnote->date_stamp
		 && pnote->spool == spool )
		{
		    if( pnote->anon )
		    {
			sprintf( buf, "[{g%3d{x] {cAnonymous{x: %s\n\r%s\n\rTo: %s\n\r",
			    vnum,
			    pnote->subject,
			    pnote->date,
			    pnote->to_list );
		    }
		    else
		    {
			sprintf( buf, "[{g%3d{x] {c%s{x: %s\n\r%s\n\rTo: %s\n\r",
			    vnum,
			    pnote->sender,
			    pnote->subject,
			    pnote->date,
			    pnote->to_list );
		    }
		    str_cat( &buf1, buf );
		    str_cat( &buf1, pnote->text );
		    ch->last_note[spool] = UMAX( ch->last_note[spool],
						 pnote->date_stamp );
		    page_to_char( buf1, ch );
		    free( buf1 );
		    return;
		}
		else if( pnote->spool == spool )
		    vnum++;
	    }
	    sprintf( spoolbuf, "You have no unread %ss.\n\r", buf2 );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}
	else if( is_number( argument ) )
	{
	    fAll = FALSE;
	    anum = atoi( argument );
	}
	else
	{
	    sprintf( spoolbuf, "%s read which number\n\r", buf2 );
	    spoolbuf[0] = UPPER( spoolbuf[0] );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}

	vnum    = 0;
	buf1	= NULL;
	for( pnote = note_list ; pnote ; pnote = pnote->next )
	{
	    if( is_note_to( ch, pnote ) 
	     && ( pnote->spool == spool )
	     && ( vnum++ == anum || fAll ) )
	    {
		if( pnote->anon )
		{
		    sprintf( buf, "[{g%3d{x] {cAnonymous{x: %s\n\r%s\n\rTo: %s\n\r",
			vnum - 1,
			pnote->subject,
			pnote->date,
			pnote->to_list );
		}
		else
		{
		    sprintf( buf, "[{g%3d{x] {c%s{x: %s\n\r%s\n\rTo: %s\n\r",
			vnum - 1,
			pnote->sender,
			pnote->subject,
			pnote->date,
			pnote->to_list );
		}
		str_cat( &buf1, buf );
		str_cat( &buf1, pnote->text );
		if ( !fAll )
		    page_to_char( buf1, ch );
		else
		    str_cat( &buf1, "\n\r" );
		ch->last_note[spool] = UMAX( ch->last_note[spool], pnote->date_stamp );
		if ( !fAll )
		{
		    free( buf1 );
		    return;
		}
	    }
	}

	sprintf( spoolbuf, "No such %s.\n\r", buf2 );
	if( !fAll )
	    send_to_char_bw( spoolbuf, ch );
	else
	    page_to_char( buf1, ch );

	free( buf1 );
	return;
    }

    if( !str_cmp( arg, "+" ) )
    {
	note_attach( ch, spool );
	strcpy( buf, ch->pnote->text );
	if( strlen( buf ) + strlen( argument ) >= MAX_STRING_LENGTH - 200 )
	{
	    sprintf( spoolbuf, "%s too long.\n\r", buf2 );
	    spoolbuf[0] = UPPER( spoolbuf[0] );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}

	strcat( buf, argument );
	strcat( buf, "\n\r"   );
	free_string( ch->pnote->text );
	ch->pnote->text = str_dup( buf );
	send_to_char_bw( "Ok, line added.\n\r", ch );
	return;
    }

/*
 * 'note help' function add by Ant, Nov '94
 */
    if( !str_prefix( arg, "help" ) )
    {
	do_help( ch, buf2 );
	return;
    }
/*
 * 'note -' function add by Ant, Nov '94
 * Updated -Ant, Jan '96
 */
    if( !str_cmp( arg, "-" ) )
    {
	if( !ch->pnote )
	{
	    sprintf( spoolbuf, 
		"Hmmm....better make a %s before chopping lines from it.\n\r", 
		buf2 );
	    send_to_char_bw( spoolbuf, ch );
	}
	else
	{
	    strcpy( buf, ch->pnote->text );
	    free_string( ch->pnote->text );
	    ch->pnote->text = strip_last_line( buf );
	    send_to_char_bw( "Line Removed.\n\r", ch );
	}
	return;
    }

    if( !str_cmp( arg, "anonymous" ) )
    {
	note_attach( ch, spool );
	send_to_char_bw( "Anonymous toggled: ", ch );
	if( ch->pnote->anon )
	{
	    ch->pnote->anon = FALSE;
	    send_to_char_bw( "Off.\n\r", ch );
	}
	else
	{
	    ch->pnote->anon = TRUE;
	    send_to_char_bw( "On.\n\r", ch );
	}
	return;
    }

    if( !str_prefix( arg, "subject" ) )
    {
	note_attach( ch, spool );
	free_string( ch->pnote->subject );
	ch->pnote->subject = str_dup( argument );
	send_to_char_bw( "Ok.\n\r", ch );
	return;
    }

    if( !str_prefix( arg, "from" ) && IS_IMMORTAL( ch ) )
    {
	note_attach( ch, spool );
	free_string( ch->pnote->sender );
	ch->pnote->sender = str_dup( argument );
	send_to_char_bw( "Ok.\n\r", ch );
	return;
    }

    if( !str_prefix( arg, "to" ) )
    {
	note_attach( ch, spool );
	free_string( ch->pnote->to_list );
	ch->pnote->to_list = str_dup( argument );
	send_to_char_bw( "Ok.\n\r", ch );
	return;
    }

    if( !str_prefix( arg, "clear" ) )
    {
	if ( ch->pnote )
	{
	    free_string( ch->pnote->text    );
	    free_string( ch->pnote->subject );
	    free_string( ch->pnote->to_list );
	    free_string( ch->pnote->date    );
	    free_string( ch->pnote->sender  );
	    ch->pnote->anon 	= FALSE;
	    ch->pnote->spool	= SPOOL_NOTE;
	    ch->pnote->next	= note_free;
	    note_free		= ch->pnote;
	    ch->pnote		= NULL;
	}

	send_to_char_bw( "Ok.\n\r", ch );
	return;
    }

    if( !str_prefix( arg, "show" ) )
    {
	if ( !ch->pnote )
	{
	    sprintf( spoolbuf, "You have no %s in progress.\n\r", buf2 );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}

	switch( ch->pnote->spool )
	{
	case( SPOOL_NOTE ):
	    sprintf( buf2, "note" );
	    break;
	case( SPOOL_IDEA ):
	    sprintf( buf2, "idea" );
	    break;
	case( SPOOL_MAIL ):
	    sprintf( buf2, "mail" );
	    break;
	case( SPOOL_BUG ):
	    sprintf( buf2, "bug" );
	    break;
	case( SPOOL_QUEST ):
	    sprintf( buf2, "qwest" );
	    break;
	default:
	    sprintf( buf2, "note" );
	    break;
	}
	sprintf( buf, "{c%s{x: %s\n\rTo: %s\n\rSpool: %s\n\r",
		ch->pnote->anon ? "Anonymous" : ch->pnote->sender,
		ch->pnote->subject,
		ch->pnote->to_list,
		buf2 );
	str_cat( &buf1, buf );
	str_cat( &buf1, ch->pnote->text );

	page_to_char( buf1, ch );
	free( buf1 );

	return;
    }

    if( !str_prefix( arg, "post" ) || !str_prefix( arg, "send" ) )
    {
	FILE *fp;
	char *strtime;

	if( ( spool == SPOOL_QUEST ) && !IS_IMMORTAL( ch ) )
	{
	    send_to_char_bw( 
		"Only Immortals can post to this spool, note deleted.\n\r", 
		ch );
	    do_qwest( ch, "clear" );
	    return;
	}

	if( !ch->pnote )
	{
	    sprintf( spoolbuf, "You have no %s in progress.\n\r", buf2 );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}

	if( !str_cmp( ch->pnote->to_list, "" ) )
	{
	    send_to_char_bw(
	      "You need to provide a recipient (name, all, imm, gods, or imp).\n\r",
			 ch );
	    return;
	}

	if( !str_cmp( ch->pnote->subject, "" ) )
	{
	    send_to_char_bw( "You need to provide a subject.\n\r", ch );
	    return;
	}

	ch->pnote->next			= NULL;
	strtime				= ctime( &current_time );
	strtime[strlen(strtime)-1]	= '\0';
	free_string( ch->pnote->date );
	ch->pnote->date			= str_dup( strtime );
	ch->pnote->date_stamp           = current_time;

	if( !note_list )
	{
	    note_list	= ch->pnote;
	}
	else
	{
	    for( pnote = note_list; pnote->next; pnote = pnote->next )
		;
	    pnote->next	= ch->pnote;
	}
	pnote		= ch->pnote;
	ch->pnote       = NULL;

	fclose( fpReserve );
	if( !( fp = fopen( NOTE_FILE, "a" ) ) )
	{
	    perror( NOTE_FILE );
	}
	else
	{
	    fprintf( fp, "Sender  %s~\n", pnote->sender     );
	    fprintf( fp, "Spool   %d\n",  pnote->spool      );
	    fprintf( fp, "Anon    %d\n",  pnote->anon       );
	    fprintf( fp, "Date    %s~\n", pnote->date       );
	    fprintf( fp, "Stamp   %ld\n", pnote->date_stamp );
	    fprintf( fp, "To      %s~\n", pnote->to_list    );
	    fprintf( fp, "Subject %s~\n", pnote->subject    );
	    fprintf( fp, "Text\n%s~\n\n", pnote->text       );
	    fclose( fp );
	}
	fpReserve = fopen( NULL_FILE, "r" );

	sprintf( spoolbuf, "Your %s has been posted.\n\r", buf2 );
	send_to_char_bw( spoolbuf, ch );
	return;
    }

/*
 * Fixed and updated, Jan '96 -Ant
 */
    if( !str_prefix( arg, "remove" ) )
    {
	if( !is_number( argument ) )
	{
	    sprintf( spoolbuf, "%s remove which number?\n\r", buf2 );
	    spoolbuf[0] = UPPER( spoolbuf[0] );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}

	anum = atoi( argument );
	vnum = 0;
	for( pnote = note_list ; pnote ; pnote = pnote->next )
	{
	    if( is_note_to( ch, pnote ) 
	     && ( pnote->spool == spool )
	     && ( vnum++ == anum ) )
	    {
		note_remove( ch, pnote, spool );
		send_to_char_bw( "Ok.\n\r", ch );
		return;
	    }
	}

	sprintf( spoolbuf, "No such %s.\n\r", buf2 );
	send_to_char_bw( spoolbuf, ch );
	return;
    }

/*
 * Updated, Jan '96 -Ant
 */
    if( !str_prefix( arg, "delete" ) && get_trust( ch ) >= 59 )
    {
	if( !is_number( argument ) )
	{
	    sprintf( spoolbuf, "%s delete which number?\n\r", buf2 );
	    spoolbuf[0] = UPPER( spoolbuf[0] );
	    send_to_char_bw( spoolbuf, ch );
	    return;
	}
 
        anum = atoi( argument );
        vnum = 0;
        for( pnote = note_list ; pnote ; pnote = pnote->next )
        {
            if( spool == pnote->spool 
	      && is_note_to( ch, pnote )
	      && vnum++ == anum )
            {
                note_delete( pnote );
                send_to_char_bw( "Ok.\n\r", ch );
                return;
            }
        }
 
	sprintf( spoolbuf, "No such %s\n\r", buf2 );
	send_to_char_bw( spoolbuf, ch );
        return;
    }

    sprintf( spoolbuf, "Huh?  Type 'help %s' for usage.\n\r", buf2 );
    send_to_char_bw( spoolbuf, ch );

    if( buf1 )
	free( buf1 );

    return;
}

void note_delete( NOTE_DATA *pnote )
{
    FILE 	*fp;
    NOTE_DATA 	*prev;
 
    /*
     * Remove note from linked list.
     */
    if ( pnote == note_list )
    {
        note_list = pnote->next;
    }
    else
    {
        for ( prev = note_list ; prev ; prev = prev->next )
        {
            if ( prev->next == pnote )
                break;
        }
 
        if ( prev == NULL )
        {
            bug( "Delete: pnote not found.", 0 );
            return;
        }
 
        prev->next = pnote->next;
    }
 
    free_string( pnote->text    );
    free_string( pnote->subject );
    free_string( pnote->to_list );
    free_string( pnote->date    );
    free_string( pnote->sender  );
    pnote->spool	= SPOOL_NOTE;
    pnote->anon 	= FALSE;
    pnote->next		= note_free;
    note_free		= pnote;

 
    /*
     * Rewrite entire list.
     */
    fclose( fpReserve );
    if ( !( fp = fopen( NOTE_FILE, "w" ) ) )
    {
        perror( NOTE_FILE );
    }
    else
    {
        for ( pnote = note_list ; pnote ; pnote = pnote->next )
        {
            fprintf( fp, "Sender  %s~\n", pnote->sender		);
	    fprintf( fp, "Spool   %d\n",  pnote->spool      	);
	    fprintf( fp, "Anon    %d\n",  pnote->anon       	);
            fprintf( fp, "Date    %s~\n", pnote->date		);
            fprintf( fp, "Stamp   %ld\n",  pnote->date_stamp	);
            fprintf( fp, "To      %s~\n", pnote->to_list	);
            fprintf( fp, "Subject %s~\n", pnote->subject	);
            fprintf( fp, "Text\n%s~\n",   pnote->text		);
        }
        fclose( fp );
    }
    fpReserve = fopen( NULL_FILE, "r" );
    return;
}

void do_chat( CHAR_DATA *ch, char *argument )
{
    DESCRIPTOR_DATA	*d;
    char		buf[ MAX_STRING_LENGTH ];
    bool		switched = FALSE;
 
    if( argument[0] == '\0' )
    {
	if( IS_SET( ch->chan, CHAN_CHAT ) )
	{
	    send_to_char_bw( "Chat channel is now OFF.\n\r", ch );
	    REMOVE_BIT( ch->chan, CHAN_CHAT );
	}
	else
	{
	    send_to_char_bw( "Chat channel is now ON.\n\r", ch );
	    SET_BIT( ch->chan, CHAN_CHAT );
	}
    }
    else
    {
	if( IS_SET( ch->pen, PEN_NOCHAT ) || IS_SET( ch->pen, PEN_SILENCE ) )
	{
	    send_to_char_bw( "You have been gagged by the administration!\n\r", ch );
	    return;
	}

	SET_BIT( ch->chan, CHAN_CHAT );
 
	if( ch->desc
	 && ch->desc->original
	 && IS_SET( ch->desc->original->act, PLR_SWITCH ) )
	{
	    switched = TRUE;
	}

	sprintf( buf, "{dYou chat '%s'{x\n\r", argument );
	send_to_char( buf, ch );
	for( d = descriptor_list ; d ; d = d->next )
	{
	    CHAR_DATA *victim;
 
	    victim = d->original ? d->original : d->character;
 
	    if( d->connected == CON_PLAYING
	     && d->character != ch
	     && IS_SET( victim->chan, CHAN_CHAT )
	     && !IS_SET( victim->chan, CHAN_QUIET ) )
	    {
		if( switched )
		{
		    act_new( "{y({r$n, switched{y){x",
			ch->desc->original, NULL,
			victim, TO_VICT, POS_SLEEPING );
		}
		act_new( "{d$n chats '$t'{x", 
		   ch, argument, victim, TO_VICT, POS_SLEEPING );
	    }
	}
    }
}



void do_say( CHAR_DATA *ch, char *argument )
{
    if( argument[0] == '\0' )
    {
	send_to_char_bw( "Say what?\n\r", ch );
	return;
    }

    act_new( "{u$n says '$T'.{x",
	ch, NULL, argument, TO_ROOM, POS_RESTING );
    act_new( "{uYou say '$T'.{x",
	ch, NULL, argument, TO_CHAR, POS_RESTING );

    mprog_speech_trigger( argument, ch );
    return;
}

void do_shout( CHAR_DATA *ch, char *argument )
{
    /* TODO */
    return;
}

void do_immtalk( CHAR_DATA *ch, char *argument )
{
    DESCRIPTOR_DATA	*d;
    char		buf[ MAX_STRING_LENGTH ];
    bool		switched = FALSE;
 
    if( argument[0] == '\0' )
    {
	if( IS_SET( ch->chan, CHAN_IMM ) )
	{
	    send_to_char_bw( "Immortal channel is now OFF.\n\r", ch );
	    REMOVE_BIT( ch->chan, CHAN_IMM );
	}
	else
	{
	    send_to_char_bw( "Immortal channel is now ON.\n\r", ch );
	    SET_BIT( ch->chan, CHAN_IMM );
	}
    }
    else
    {
	if( IS_SET( ch->pen, PEN_SILENCE ) )
	{
	    send_to_char_bw( "You have been gagged by the administration!\n\r", ch );
	    return;
	}

	SET_BIT( ch->chan, CHAN_IMM );
 
	if( ch->desc
	 && ch->desc->original
	 && IS_SET( ch->desc->original->act, PLR_SWITCH ) )
	{
	    switched = TRUE;
	}

	sprintf( buf, "$n: %s", argument );
	act_new( "{i$n [{IImmortal{i]: $t{x",
	    ch, argument, NULL, TO_CHAR, POS_DEAD );
	for( d = descriptor_list ; d ; d = d->next )
	{
	    CHAR_DATA *victim;
 
	    victim = d->original ? d->original : d->character;
 
	    if( d->connected <= CON_PLAYING
	     && d->character != ch
	     && IS_SET( victim->chan, CHAN_IMM )
	     && victim->wizbit )
	    {
		if( switched )
		{
		    act_new( "{y({r$n, switched{y){x",
			ch->desc->original, NULL,
			victim, TO_VICT, POS_DEAD );
		}
		act_new("{i$n [{IImmortal{i]: $t{x",
		    ch, argument, victim, TO_VICT, POS_DEAD );
	    }
	}
    }
}



void do_tell( CHAR_DATA *ch, char *argument )
{
    CHAR_DATA	*victim;
    char	arg[ MAX_INPUT_LENGTH ];

    if( !IS_NPC( ch ) && IS_SET( ch->pen, PEN_SILENCE ) )
    {
	send_to_char_bw( "Your message didn't get through.\n\r", ch );
	return;
    }

    argument = one_argument( argument, arg );

    if( arg[0] == '\0' || argument[0] == '\0' )
    {
	send_to_char_bw( "Tell whom what?\n\r", ch );
	return;
    }

    /*
     * Can tell to PC's anywhere, but NPC's only in same room.
     * -- Furey
     */
    if( !( victim = get_char_world( ch, arg ) )
     || ( IS_NPC( victim ) && victim->in_room != ch->in_room ) )
    {
	send_to_char_bw( "They aren't here.\n\r", ch );
	return;
    }

    act_new( "{kYou tell $N '$t'.{x",
	ch, argument, victim, TO_CHAR, POS_SLEEPING );
    act_new( "{k$n tells you '$t'.{x",
	ch, argument, victim, TO_VICT, POS_SLEEPING );
    victim->reply	= ch;

    return;
}



void do_reply( CHAR_DATA *ch, char *argument )
{
    CHAR_DATA	*victim;

    if( !IS_NPC( ch ) && IS_SET( ch->pen, PEN_SILENCE ) )
    {
	send_to_char_bw( "Your message didn't get through.\n\r", ch );
	return;
    }

    if( !( victim = ch->reply ) )
    {
	send_to_char_bw( "They aren't here.\n\r", ch );
	return;
    }

    if( !IS_IMMORTAL( ch ) && !IS_AWAKE( victim ) )
    {
	act_new( "$E can't hear you.",
	    ch, 0, victim, TO_CHAR, POS_SLEEPING );
	return;
    }

    act_new( "{lYou tell $N '$t'.{x",
	ch, argument, victim, TO_CHAR, POS_SLEEPING );
    act_new( "{l$n tells you '$t'.{x",
	ch, argument, victim, TO_VICT, POS_SLEEPING );
    victim->reply	= ch;

    return;
}



void do_emote( CHAR_DATA *ch, char *argument )
{
    char	*plast;
    char	buf[ MAX_STRING_LENGTH ];

    if( !IS_NPC( ch ) && IS_SET( ch->act, PEN_NOEMOTE ) )
    {
	send_to_char_bw( "You can't show your emotions.\n\r", ch );
	return;
    }

    if( argument[0] == '\0' )
    {
	send_to_char_bw( "Emote what?\n\r", ch );
	return;
    }

    for( plast = argument ; *plast != '\0' ; plast++ )
	;

    strcpy( buf, argument );
    if( isalpha( plast[-1] ) )
	strcat( buf, "." );

    act_new( "$n $T", ch, NULL, buf, TO_ROOM, POS_RESTING );
    act_new( "$n $T", ch, NULL, buf, TO_CHAR, POS_RESTING );
    return;
}


void do_pose( CHAR_DATA *ch, char *argument )
{
    send_to_char_bw( "Pose removed for re-coding.\n\r", ch );
    return;
}

void do_typo( CHAR_DATA *ch, char *argument )
{
    append_file( ch, TYPO_FILE, argument );
    send_to_char( "Ok.  Thanks.\n\r", ch );
    return;
}

void do_rent( CHAR_DATA *ch, char *argument )
{
    send_to_char_bw( "There is no rent here.  Just save and quit.\n\r", ch );
    return;
}



void do_qui( CHAR_DATA *ch, char *argument )
{
    send_to_char( "If you want to QUIT, you have to spell it out.\n\r", ch );
    return;
}



void do_quit( CHAR_DATA *ch, char *argument )
{
    DESCRIPTOR_DATA	*d;

    if( IS_NPC( ch ) )
	return;

    if( ch->position == POS_FIGHTING )
    {
	send_to_char_bw( "No way! You are fighting.\n\r", ch );
	return;
    }

    if( ch->position  < POS_STUNNED  )
    {
	send_to_char_bw( "You're not DEAD yet.\n\r", ch );
	return;
    }

    send_to_char( 
	"Where I traveled I came forth, from the earth, into the weave, and out to space again.\n\r",
	ch );
    act_new( "$n has left the game.",
	ch, NULL, NULL, TO_ROOM, POS_RESTING );
    sprintf( log_buf, "%s has quit.", ch->name );
    log_string( log_buf );

    /*
     * After extract_char the ch is no longer valid!
     */
    save_char_obj( ch );
    d = ch->desc;
    extract_char( ch, TRUE );
    if( d )
	close_socket( d );

    return;
}



void do_save( CHAR_DATA *ch, char *argument )
{
    if( IS_NPC( ch ) )
	return;

    save_char_obj( ch );
    send_to_char_bw( "Ok.\n\r", ch );

    return;
}



void do_follow( CHAR_DATA *ch, char *argument )
{
    CHAR_DATA	*victim;
    char	arg[ MAX_INPUT_LENGTH ];

    one_argument( argument, arg );

    if( arg[ 0 ] == '\0' )
    {
	send_to_char_bw( "Follow whom?\n\r", ch );
	return;
    }

    if( !( victim = get_char_room( ch, arg ) ) )
    {
	send_to_char_bw( "They aren't here.\n\r", ch );
	return;
    }

    if( IS_AFFECTED( ch, AFF_CHARM )
     && ch->master )
    {
	act( "But you'd rather follow $N!",
	    ch, NULL, ch->master, TO_CHAR );
	return;
    }

    if( victim == ch )
    {
	if( !ch->master )
	{
	    send_to_char_bw( "You already follow yourself.\n\r", ch );
	    return;
	}
	stop_follower( ch );
	return;
    }
    
    if( ch->master )
	stop_follower( ch );

    add_follower( ch, victim );
    return;
}



void add_follower( CHAR_DATA *ch, CHAR_DATA *master )
{
  
    if ( ch->master != NULL )
    {
	bug( "Add_follower: non-null master.", 0 );
	return;
    }

    ch->master        = master;
    ch->leader        = NULL;

    if ( can_see( master, ch ) )
	act( "$n now follows you.", ch, NULL, master, TO_VICT );

    act( "You now follow $N.",  ch, NULL, master, TO_CHAR );

    return;
}



void stop_follower( CHAR_DATA *ch )
{

    if ( ch->master == NULL )
    {
	bug( "Stop_follower: null master.", 0 );
	return;
    }

    if ( IS_AFFECTED(ch, AFF_CHARM) )
    {
	REMOVE_BIT( ch->affected_by, AFF_CHARM );
	affect_strip( ch, gsn_charm_person );
    }

    if ( can_see( ch->master, ch ) )
	act( "$n stops following you.",
	    ch, NULL, ch->master, TO_VICT );
    act( "You stop following $N.",
	ch, NULL, ch->master, TO_CHAR );

    ch->master = NULL;
    ch->leader = NULL;
    return;
}



void die_follower( CHAR_DATA *ch )
{
    CHAR_DATA *fch;

    if ( ch->master != NULL )
	stop_follower( ch );

    ch->leader = NULL;

    for ( fch = char_list; fch != NULL; fch = fch->next )
    {
	if ( fch->master == ch )
	    stop_follower( fch );
	if ( fch->leader == ch )
	    fch->leader = fch;
    }

    return;
}



void do_order( CHAR_DATA *ch, char *argument )
{
    char arg[MAX_INPUT_LENGTH];
    CHAR_DATA *victim;
    CHAR_DATA *och;
    CHAR_DATA *och_next;
    bool found;
    bool fAll;

    argument = one_argument( argument, arg );

    if ( arg[0] == '\0' || argument[0] == '\0' )
    {
	send_to_char( "Order whom to do what?\n\r", ch );
	return;
    }

    if ( IS_AFFECTED( ch, AFF_CHARM ) )
    {
	send_to_char( "You feel like taking, not giving, orders.\n\r", ch );
	return;
    }

    if ( !str_cmp( arg, "all" ) )
    {
	fAll   = TRUE;
	victim = NULL;
    }
    else
    {
	fAll   = FALSE;
	if ( ( victim = get_char_room( ch, arg ) ) == NULL )
	{
	    send_to_char( "They aren't here.\n\r", ch );
	    return;
	}

	if ( victim == ch )
	{
	    send_to_char( "Aye aye, right away!\n\r", ch );
	    return;
	}

	if ( !IS_AFFECTED(victim, AFF_CHARM) || victim->master != ch )
	{
	    send_to_char( "Do it yourself!\n\r", ch );
	    return;
	}
    }

    found = FALSE;
    for ( och = ch->in_room->people; och != NULL; och = och_next )
    {
	och_next = och->next_in_room;

	if ( IS_AFFECTED(och, AFF_CHARM)
	&&   och->master == ch
	&& ( fAll || och == victim ) )
	{
	    found = TRUE;
	    act( "$n orders you to '$t'.", ch, argument, och, TO_VICT );
	    interpret( och, argument, FALSE );
	}
    }

    if ( found )
	send_to_char( "Ok.\n\r", ch );
    else
	send_to_char( "You have no followers here.\n\r", ch );
    return;
}



void do_group( CHAR_DATA *ch, char *argument )
{
    CHAR_DATA	*victim;
    char	buf[ MAX_STRING_LENGTH ];
    char	arg[ MAX_INPUT_LENGTH  ];

    one_argument( argument, arg );

    if( arg[ 0 ] == '\0' )
    {
	CHAR_DATA	*gch;
	CHAR_DATA	*leader;

	leader = ( ch->leader ) ? ch->leader : ch;
	sprintf( buf, "%s's group:\n\r", PERS( leader, ch ) );
	send_to_char( buf, ch );

	for( gch = char_list ; gch ; gch = gch->next )
	{
	    if( is_same_group( gch, ch ) )
	    {
		sprintf( buf,
		"[%s] %-16s %4d/%4d hp %4d/%4d mana %4d/%4d mv %5d xp\n\r",
		    IS_NPC( gch ) ? "Mob " : gch->class->who_name,
		    capitalize( PERS(gch, ch) ),
		    gch->hit,   gch->max_hit,
		    gch->mana,  gch->max_mana,
		    gch->move,  gch->max_move,
		    gch->exp    );
		send_to_char( buf, ch );
	    }
	}
	return;
    }

    if( !( victim = get_char_room( ch, arg ) ) )
    {
	send_to_char( "They aren't here.\n\r", ch );
	return;
    }

    if( ch->master
     || ( ch->leader && ch->leader != ch ) )
    {
	send_to_char( "But you are following someone else!\n\r", ch );
	return;
    }

    if( victim->master != ch
     && ch != victim )
    {
	act( "$N isn't following you.", ch, NULL, victim, TO_CHAR );
	return;
    }

    if( is_same_group( victim, ch )
     && ch != victim )
    {
	victim->leader = NULL;
	act( "$n removes $N from $s group.", ch, NULL, victim, TO_NOTVICT );
	act( "$n removes you from $s group.", ch, NULL, victim, TO_VICT   );
	act( "You remove $N from your group.", ch, NULL, victim, TO_CHAR  );
	return;
    }

    victim->leader = ch;
    act( "$N joins $n's group.", ch, NULL, victim, TO_NOTVICT );
    act( "You join $n's group.", ch, NULL, victim, TO_VICT    );
    act( "$N joins your group.", ch, NULL, victim, TO_CHAR    );
    return;
}



/*
 * 'Split' originally by Gnort, God of Chaos.
 */
void do_split( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];
    char arg[MAX_INPUT_LENGTH];
    CHAR_DATA *gch;
    int members;
    int amount;
    int share;
    int extra;

    one_argument( argument, arg );

    if ( arg[0] == '\0' )
    {
	send_to_char( "Split how much?\n\r", ch );
	return;
    }
    
    amount = atoi( arg );

    if ( amount < 0 )
    {
	send_to_char( "Your group wouldn't like that.\n\r", ch );
	return;
    }

    if ( amount == 0 )
    {
	send_to_char( "You hand out zero coins, but no one notices.\n\r", ch );
	return;
    }

    if ( ch->gold < amount )
    {
	send_to_char( "You don't have that much gold.\n\r", ch );
	return;
    }
  
    members = 0;
    for ( gch = ch->in_room->people; gch != NULL; gch = gch->next_in_room )
    {
	if ( is_same_group( gch, ch ) )
	    members++;
    }

    if ( members < 2 )
    {
	send_to_char( "Just keep it all.\n\r", ch );
	return;
    }
	    
    share = amount / members;
    extra = amount % members;

    if ( share == 0 )
    {
	send_to_char( "Don't even bother, cheapskate.\n\r", ch );
	return;
    }

    ch->gold -= amount;
    ch->gold += share + extra;

    sprintf( buf,
	"You split %d gold coins.  Your share is %d gold coins.\n\r",
	amount, share + extra );
    send_to_char( buf, ch );

    sprintf( buf, "$n splits %d gold coins.  Your share is %d gold coins.",
	amount, share );

    for ( gch = ch->in_room->people; gch != NULL; gch = gch->next_in_room )
    {
	if ( gch != ch && is_same_group( gch, ch ) )
	{
	    act( buf, ch, NULL, gch, TO_VICT );
	    gch->gold += share;
	}
    }

    return;
}



void do_gtell( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];
    CHAR_DATA *gch;

    if ( argument[0] == '\0' )
    {
	send_to_char( "Tell your group what?\n\r", ch );
	return;
    }

    if ( IS_SET( ch->pen, PEN_NOTELL ) )
    {
	send_to_char( "Your message didn't get through!\n\r", ch );
	return;
    }

    /*
     * Note use of send_to_char, so gtell works on sleepers.
     */
    sprintf( buf, "%s tells the group '%s'.\n\r", ch->name, argument );
    for ( gch = char_list; gch != NULL; gch = gch->next )
    {
	if ( is_same_group( gch, ch ) )
	    send_to_char( buf, gch );
    }

    return;
}



/*
 * It is very important that this be an equivalence relation:
 * (1) A ~ A
 * (2) if A ~ B then B ~ A
 * (3) if A ~ B  and B ~ C, then A ~ C
 */
bool is_same_group( CHAR_DATA *ach, CHAR_DATA *bch )
{
    if ( ach->leader != NULL ) ach = ach->leader;
    if ( bch->leader != NULL ) bch = bch->leader;
    return ach == bch;
}

/*
 * Colour setting and unsetting, way cool, Ant Oct 94
 *        revised to include config colour, Ant Feb 95
 */
void do_colour( CHAR_DATA *ch, char *argument )
{
    char 	arg[ MAX_STRING_LENGTH ];

    if( IS_NPC( ch ) )
	return;

    argument = one_argument( argument, arg );

    if( !*arg )
    {
	if( !IS_SET( ch->act, PLR_COLOUR ) )
	{
	    send_to_char_bw( "Colour is now ON, Way Cool!\n\r", ch );
	    SET_BIT( ch->act, PLR_COLOUR );
	}
	else
	{
	    send_to_char_bw( "Colour is now OFF, <sigh>\n\r", ch );
	    REMOVE_BIT( ch->act, PLR_COLOUR );
	}
	return;
    }

    if( !str_cmp( arg, "default" ) )
    {
	default_colour( ch );
	send_to_char_bw( "Colour setting set to default values.\n\r", ch );
	return;
    }

    if( !str_cmp( arg, "all" ) )
    {
	all_colour( ch, argument );
	return;
    }

    if( !str_cmp( arg, "text" ) )
    {
	ALTER_COLOUR( text )
    }
    else if( !str_cmp( arg, "auction" ) )
    {
	ALTER_COLOUR( auction )
    }
    else if( !str_cmp( arg, "chat" ) )
    {
	ALTER_COLOUR( chat )
    }
    else if( !str_cmp( arg, "music" ) )
    {
	ALTER_COLOUR( music )
    }
    else if( !str_cmp( arg, "question" ) )
    {
	ALTER_COLOUR( question )
    }
    else if( !str_cmp( arg, "answer" ) )
    {
	ALTER_COLOUR( answer )
    }
    else if( !str_cmp( arg, "class_text" ) )
    {
	ALTER_COLOUR( class_text )
    }
    else if( !str_cmp( arg, "class_name" ) )
    {
	ALTER_COLOUR( class_name )
    }
    else if( !str_cmp( arg, "immtalk_text" ) )
    {
	ALTER_COLOUR( immtalk_text )
    }
    else if( !str_cmp( arg, "immtalk_type" ) )
    {
	ALTER_COLOUR( immtalk_type )
    }
    else if( !str_cmp( arg, "info" ) )
    {
	ALTER_COLOUR( info )
    }
    else if( !str_cmp( arg, "tell" ) )
    {
	ALTER_COLOUR( tell )
    }
    else if( !str_cmp( arg, "reply" ) )
    {
	ALTER_COLOUR( reply )
    }
    else if( !str_cmp( arg, "say" ) )
    {
	ALTER_COLOUR( say )
    }
    else if( !str_cmp( arg, "gtell_text" ) )
    {
	ALTER_COLOUR( gtell_text )
    }
    else if( !str_cmp( arg, "gtell_type" ) )
    {
	ALTER_COLOUR( gtell_type )
    }
    else if( !str_cmp( arg, "bbro" ) )
    {
	ALTER_COLOUR( bbro )
    }
    else if( !str_cmp( arg, "room_title" ) )
    {
	ALTER_COLOUR( room_title )
    }
    else if( !str_cmp( arg, "room_text" ) )
    {
	ALTER_COLOUR( room_text )
    }
    else if( !str_cmp( arg, "room_exits" ) )
    {
	ALTER_COLOUR( room_exits )
    }
    else if( !str_cmp( arg, "room_things" ) )
    {
	ALTER_COLOUR( room_things )
    }
    else if( !str_cmp( arg, "prompt" ) )
    {
	ALTER_COLOUR( prompt )
    }
    else if( !str_cmp( arg, "fight_death" ) )
    {
	ALTER_COLOUR( fight_death )
    }
    else if( !str_cmp( arg, "fight_yhit" ) )
    {
	ALTER_COLOUR( fight_yhit )
    }
    else if( !str_cmp( arg, "fight_ohit" ) )
    {
	ALTER_COLOUR( fight_ohit )
    }
    else if( !str_cmp( arg, "fight_thit" ) )
    {
	ALTER_COLOUR( fight_thit )
    }
    else if( !str_cmp( arg, "fight_skill" ) )
    {
	ALTER_COLOUR( fight_skill )
    }
    else
    {
	send_to_char_bw( "Unrecognised Colour Parameter Not Set.\n\r", ch );
	return;
    }

    send_to_char_bw( "New Colour Parameter Set.\n\r", ch );
    return;
}

void do_unread( CHAR_DATA *ch, char *argument )
{
    NOTE_DATA	*pnote;
    char	sbuf[ MAX_STRING_LENGTH ];
    char	buf[ MAX_STRING_LENGTH  ];
    int		notes = 0;
    int		spool = 0;

    for( spool = 0 ; spool < MAX_SPOOL ; spool++ )
    {
	notes = 0;
	switch( spool )
	{
	    case( SPOOL_NOTE ):
		sprintf( sbuf, "note" );
		break;
	    case( SPOOL_IDEA ):
		sprintf( sbuf, "idea" );
		break;
	    case( SPOOL_MAIL ):
		sprintf( sbuf, "mail" );
		break;
	    case( SPOOL_BUG ):
		sprintf( sbuf, "bug" );
		break;
	    case( SPOOL_QUEST ):
		sprintf( sbuf, "qwest" );
		break;
	    default:
		sprintf( sbuf, "note" );
		break;
	}
	for( pnote = note_list ; pnote ; pnote = pnote->next )
	{
	    if( is_note_to( ch, pnote ) 
	     && str_cmp( ch->name, pnote->sender )
	     && pnote->spool == spool
	     && pnote->date_stamp > ch->last_note[ spool ] )
		notes++;
	}
	if( notes == 1 )
	{
	    sprintf( buf, "\n\rYou have one new %s waiting.\n\r", sbuf );
	    send_to_char_bw( buf, ch );
	}
	else if( notes > 1 )
	{
	    sprintf( buf, "\n\rYou have %d new %ss waiting.\n\r",
		notes,
		sbuf );
	    send_to_char_bw( buf, ch );
	}
    }
    return;
}