daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(			  DalekenMUD 1.12 (C) 2000			)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael	 ||
    || Chastain, Michael Quan, and Mitchell Tse.			 ||
    || Original Diku Mud copyright (C) 1990, 1991			 ||
    || by Sebastian Hammer, Michael Seifert, Hans Henrik St{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  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.					 ||
    || ----------------------------------------------------------------- ||
    ||                            act_comm.c                             ||
    || Player communication code, channels, languages and quests.        ||
 *_/<>\_________________________________________________________________/<>\_*/


#include <stdarg.h>
#include "mud.h"
#include "event.h"

struct spk_type
{
    const char	*old;
    const char	*new;
};


/*
 * local auction variables
 */
OBJ_DATA	*auction_item = NULL;
CHAR_DATA	*bidder = NULL;
CHAR_DATA	*auction_owner;
int		current_bid;
int		auction_state;

/*
 * Local functions.
 */
void quest_request	args( ( CHAR_DATA *ch, CHAR_DATA *mob ) );
void quest_complete	args( ( CHAR_DATA *ch, CHAR_DATA *mob ) );
void quest_info		args( ( CHAR_DATA *ch ) );
void quest_identify	args( ( CHAR_DATA *ch, CHAR_DATA *mob,
				const char *argument ) );
void quest_improve	args( ( CHAR_DATA *ch, CHAR_DATA *mob,
				const char *argument ) );
bool can_speak		args( ( CHAR_DATA *ch, int lang ) );
void init_auction	args( ( void ) );
bool can_quit		args( ( CHAR_DATA *ch ) );
DESCRIPTOR_DATA *quit_char args( ( CHAR_DATA *ch ) );
TRADE_DATA *new_trade	args( ( CHAR_DATA *ch ) );
void show_trade		args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
OBJ_DATA *find_trade_obj args( ( CHAR_DATA *ch, CHAR_DATA *victim,
				 const char *name ) );
bool check_trade	args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );
void affect_trade	args( ( CHAR_DATA *ch, CHAR_DATA *victim ) );

INTERPRETER_FUN nanny_get_name;


/*
 * How to make a string look drunk by Apex <robink@htsa.hva.nl>
 * Modified and enhanced by Maniac from Mythran
 */
void makedrunk( char *string, CHAR_DATA *ch )
{
    char buf[MAX_STRING_LENGTH];
    char temp;
    char *start;
    int randomnum;
    int drunkpos;
    int drunklevel;
    int pos = 0;

    /*
     * Check how drunk a person is...
     */
    if( IS_NPC( ch ) )
	return;
    if( ( drunklevel = ch->pcdata->condition[COND_DRUNK] / 3 ) > 0 )
    {
	start = string;
	do
	{
	    temp = UPPER( *string );
	    drunkpos = temp - 'A';

	    if( ( temp >= 'A' ) && ( temp <= 'Z' ) )
	    {
		if( drunklevel > drunk[drunkpos].min_drunk_level )
		{
		    randomnum =
			number_range( 0, drunk[drunkpos].number_of_rep );
		    strcpy( &buf[pos],
			    drunk[drunkpos].replacement[randomnum] );
		    pos +=
			strlen( drunk[drunkpos].replacement[randomnum] );
		}
		else
		    buf[pos++] = *string;
	    }
	    else
	    {
		if( ( temp >= '0' ) && ( temp <= '9' ) )
		{
		    temp = '0' + number_range( 0, 9 );
		    buf[pos++] = temp;
		}
		else if( temp == '&' )
		{
		    buf[pos++] = *string++;
		    buf[pos++] = *string;
		}
		else
		    buf[pos++] = *string;
	    }
	}
	while( *string++ );
	buf[pos] = '\0';
	strcpy( start, buf );
    }
    return;
}


bool can_speak( CHAR_DATA *ch, int lang )
{
    int sn;
    char buf[32];

    if( IS_SET( race_table[ch->race].languages, lang )
	|| ch->level >= LEVEL_HERO )
	return TRUE;
    if( IS_NPC( ch ) )
	return FALSE;
    sprintf( buf, "%s language", flag_string( language_flags, &lang ) );
    if( ( sn = skill_lookup( buf ) ) < 0 )
	return FALSE;
    if( get_success( ch, sn, 100 ) )
	return TRUE;
    return FALSE;
}


const char *languageshift( CHAR_DATA *ch, CHAR_DATA *victim, char *argument )
{
    char *p;
    char shift[ 27 ];
    int i;
    int length;
    char buf[MAX_STRING_LENGTH];
    char *bufptr;

    static const struct spk_type bad_spk_table[] =
    {
	{ " ",		" "		},
	{ "yes ",	"yep "		},
	{ "no ",	"naaho ",	},
	{ "my name is", "i calls meself"},
	{ "dont you",	"doncha"	},
	{ "are not",	"aint"		},
	{ "have",	"'av"		},
	{ "my",		"me"		},
	{ "hello",	"oy"		},
	{ "hi ",	"oy "		},
	{ "i am",	"im"		},
	{ "it is",	"tis"		},
	{ "the ",	"da "		},
	{ " the",	" da"		},
	{ "thank",	"fank"		},
	{ "that",	"dat"		},
	{ "with",	"wiv"		},
	{ "they",	"day"		},
	{ "this",	"dis"		},
	{ "then",	"den"		},
	{ "there",	"ver"		},
	{ "their",	"ver"		},
	{ "thing",	"fing"		},
	{ "think",	"fink"		},
	{ "was",	"woz"		},
	{ "would",	"wud"		},
	{ "what",	"wot"		},
	{ "where",	"weer"		},
	{ "when",	"wen"		},
	{ "are",	"is"		},
	{ "you",	"ya"		},
	{ "your",	"yer"		},
	{ "dead",	"ded"		},
	{ "kill",	"smack"		},
	{ "food",	"nosh"		},
	{ "blood",	"blud"		},
	{ "vampire",	"sucker"	},
	{ "kindred",	"suckers"	},
	{ "fire",	"hot"		},
	{ "dwarf",	"stunty"	},
	{ "dwarves",	"stunties"	},
	{ "goblin",	"gobbo"		},
	{ "death",	"def"		},
	{ "immune",	"mune"		},
	{ "immunit",	"munit"		},
	{ "children",	"nippers"	},
	{ "childe",	"nipper"	},
	{ "child",	"nipper"	},
	{ "tradition",	"wassname"	},
	{ "generation", "batch"		},
	{ "founded",	"made"		},
	{ "sired",	"nipped"	},
	{ "sire",	"dad"		},
	{ "lineage",	"istory"	},
	{ "recognize",	"dats"		},
	{ "recognise",	"dats"		},
	{ "decapitate", "headchop"	},
	{ "decap",	"chop"		},
	{ "recites",	"sez"		},
	{ "recite",	"sez"		},
	{ "", "" }
    };
    static const struct spk_type archaic_spk_table[] =
    {
	{ " ",		" ",		},
	{ "yes",	"verily"	},
	{ " no",	" nay"		},
	{ "no ",	"nay "		},
	{ " have",	" hath"		},
	{ "hello",	"hail"		},
	{ "hi ",	"hail "		},
	{ " hi",	" hail"		},
	{ "my ",	"mine "		},
	{ " my",	" mine"		},
	{ "are",	"art"		},
	{ "yours",	"thine"		},
	{ "your",	"thy"		},
	{ "you",	"thou"		},
	{ "evil",	"base"		},
	{ "clothing",	"garb"		},
	{ "clothes",	"garb"		},
	{ "wear",	"don"		},
	{ "tricked",	"beguiled"	},
	{ "trick",	"beguile"	},
	{ "i think",	"methinks"	},
	{ "do ",	"doth "		},
	{ " do",	" doth"		},
	{ "it was",	"'twas"		},
	{ "before",	"ere"		},
	{ "will",	"wilt"		},
	{ "perhaps",	"perchance"	},
	{ "cool",	"fantastic"	},
	{ "lucky",	"fortuitous"	},
	{ "", "" }
    };


    if( IS_NPC( ch ) || ch->pcdata->language == LANG_COMMON
	|| !strcmp( "none", flag_string( language_flags,
					 &ch->pcdata->language ) ) )
	return NULL;

    /*
     * Specialised languages, always translate.
     */
    buf[0] = '\0';
    bufptr = &buf[0];
    if( ch->pcdata->language == LANG_BAD )
    {
	for( p = argument; *p; p += length )
	{
	    for( i = 0; ( length = strlen( bad_spk_table[i].old ) ) > 0; ++i )
	    {
		if( !str_prefix( bad_spk_table[i].old, p ) )
		{
		    strcpy( bufptr, bad_spk_table[i].new );
		    bufptr += strlen( bad_spk_table[i].new );
		    break;
		}
	    }
	    if( length == 0 )
	    {
		length = 1;
		*bufptr++ = *p;
	    }
	}
	*bufptr = '\0';
	strcpy( argument, buf );
	return "Bad";
    }
    else if( ch->pcdata->language == LANG_ARCHAIC )
    {
	for( p = argument; *p; p += length )
	{
	    for( i = 0; ( length = strlen( archaic_spk_table[i].old ) ) > 0;
		 ++i )
	    {
		if( !str_prefix( archaic_spk_table[i].old, p ) )
		{
		    strcpy( bufptr, archaic_spk_table[i].new );
		    bufptr += strlen( archaic_spk_table[i].new );
		    break;
		}
	    }
	    if( length == 0 )
	    {
		length = 1;
		*bufptr++ = *p;
	    }
	}
	*bufptr = '\0';
	strcpy( argument, buf );
	return "Archaic";
    }

    /*
     * generic languages.
     */
    if( can_speak( victim, ch->pcdata->language ) )
	return flag_string( language_flags, &ch->pcdata->language );

    strcpy( shift, flag_string( language_flags, &ch->pcdata->language ) );
    for( p = &shift[0]; *p; p++ )
	if( !strchr( shift, LOWER( *p ) ) )
	    *p = LOWER( *p );
    for( i = 0; i < 26; i++ )
    {
	if( !strchr( shift, 'z' - i ) )
	    *p++ = 'z' - i;
    }

    for( p = argument; *p != '\0'; p++ )
    {
	if( *p == '&' )
	    p++;
	else if( *p >= 'a' && *p <= 'z' )
	    *p = shift[*p - 'a'];
	else if( *p >= 'A' && *p <= 'Z' )
	    *p = shift[*p - 'A'] - 'a' + 'A';
    }
    return flag_string( language_flags, &ch->pcdata->language );
}



/*
 * Generic channel function.
 */
void talk_channel( CHAR_DATA *ch, const char *argument, int channel,
		   const char *verb )
{
    DESCRIPTOR_DATA *d;
    char buf[MAX_INPUT_LENGTH];
    char orig[MAX_INPUT_LENGTH];
    char mesg[MAX_STRING_LENGTH];
    const char *p;

    if( argument[0] == '\0' )
    {
	if( IS_SET( ch->deaf, channel ) )
	{
	    charprintf( ch, "%s channel turned on.\n\r", capitalize( verb ) );
	    REMOVE_BIT( ch->deaf, channel | CHANNEL_DEAF );
	}
	else
	{
	    charprintf( ch, "%s channel turned off.\n\r", capitalize( verb ) );
	    SET_BIT( ch->deaf, channel );
	}
	return;
    }

    if( ch && !IS_NPC( ch ) && xIS_SET( ch->act, PLR_SILENCE ) )
    {
	sprintf( buf, "You can't %s.\n\r", verb );
	send_to_char( buf, ch );
	return;
    }

    if( ch && ( IS_AFFECTED( ch, AFF_MUTE )
		|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE )
		|| IS_SET( ch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
		|| IS_SET( ch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }
    if( ch && channel != CHANNEL_INFO )
	REMOVE_BIT( ch->deaf, channel | CHANNEL_DEAF );

    /* strip linefeed colour codes */
    if( ch && get_trust( ch ) < L_JUN )
    {
	char *tmp;

	while( ( tmp = strstr( argument, "&/" ) ) )
	    tmp[1] = 'n';
    }

    strcpy( mesg, argument );
    switch( channel )
    {
    default:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&yYou %s '$t&n&y', in %s.", verb, p );
	else
	    sprintf( buf, "&yYou %s '$t&n&y'&n", verb );
	act( buf, ch, mesg, NULL, TO_CHAR );
	sprintf( buf, "&y$n %ss '$t&n&y'", verb );
	break;

    case CHANNEL_GRATZ:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&gYou congratulate $t&n&g, in %s.", p );
	else
	    sprintf( buf, "&gYou congratulate $t&n" );
	act( buf, ch, mesg, NULL, TO_CHAR );
	strcpy( buf, "&g$n congratulates $t" );
	break;

    case CHANNEL_SHOUT:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&YYou shout '$t&n&Y', in %s.", p );
	else
	    strcpy( buf, "&YYou shout '$t&n&Y'&n" );
	act( buf, ch, mesg, NULL, TO_CHAR );
	strcpy( buf, "&Y$n shouts '$t&n&Y'" );
	break;

    case CHANNEL_MUSIC:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&mYou music '$t&n&m', in %s.", p );
	else
	    strcpy( buf, "&mYou music '$t&n&m'&n" );
	act( buf, ch, mesg, NULL, TO_CHAR );
	strcpy( buf, "&m$n musics '$t&n&m'" );
	break;

    case CHANNEL_YELL:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&rYou yell '$t&n&r', in %s.", p );
	else
	    sprintf( buf, "&rYou yell '%s&n&r'&n", argument );
	act( buf, ch, mesg, NULL, TO_CHAR );
	strcpy( buf, "&r$n yells '$t&n&r'" );
	break;

    case CHANNEL_QUESTION:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&bYou %s '$t&n&b', in %s.", verb, p );
	else
	    sprintf( buf, "&bYou %s '$t&n&b'&n", verb );
	act( buf, ch, mesg, NULL, TO_CHAR );
	sprintf( buf, "&b$n %ss '$t&n&b'", verb );
	break;

    case CHANNEL_CLANTALK:
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    sprintf( buf, "&K%s(%s&K) '$t&n&K', in %s.", verb, PERS( ch, ch ), p );
	else
	    sprintf( buf, "&K%s(%s&K) '$t&n&K'&n", verb, PERS( ch, ch ) );
	act( buf, ch, mesg, NULL, TO_CHAR );
	sprintf( buf, "&K%s($n) '$t&n&K'", verb );
	break;

    case CHANNEL_AUCTION:
	strcpy( buf, "&rAUCTION: $t" );
	act( buf, ch, argument, NULL, TO_CHAR );
	break;

    case CHANNEL_IMMTALK:
	strcpy( buf, "&c$n: $t" );
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    act( "&c$n: $t, in $T", ch, mesg, p, TO_CHAR );
	else
	    act( buf, ch, argument, NULL, TO_CHAR );
	break;

    case CHANNEL_SENIORTALK:
	strcpy( buf, "&m[$n] $t" );
	if( ( p = languageshift( ch, ch, mesg ) ) )
	    act( "&m[$n] $t, in $T", ch, mesg, p, TO_CHAR );
	else
	    act( buf, ch, argument, NULL, TO_CHAR );
	break;

    case CHANNEL_INFO:
	if( ch )
	    strcpy( buf, argument );
	else
	    strcpy( buf, "&MINFO: $t" );
	break;
    }

    /*
     * Make the words look drunk if needed...
     */
    strcpy( orig, argument );
    if( ch && channel != CHANNEL_INFO )
	makedrunk( orig, ch );

    for( d = descriptor_list; d; d = d->next )
    {
	CHAR_DATA *och;
	CHAR_DATA *vch;

	och = d->original ? d->original : d->character;
	vch = d->character;

	if( !IS_SET( d->interpreter->flags, INTERPRETER_NOMESSAGE )
	    && vch != ch
	    && !IS_SET( och->deaf, channel )
	    && !IS_SET( och->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	    && !IS_SET( och->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
	{
	    if( xIS_SET( och->act, PLR_BUSY )
		|| IS_SET( och->deaf, CHANNEL_DEAF ) )
		continue;
	    if( ( channel == CHANNEL_SENIORTALK ) && get_trust( och ) < L_SEN )
		continue;
	    if( channel == CHANNEL_IMMTALK && get_trust( och ) <= L_HER )
		continue;
	    if( ch && channel == CHANNEL_YELL
		&& vch->in_room->area != ch->in_room->area )
		continue;
	    if( ch && channel == CHANNEL_CLANTALK
		&& och->pcdata->clan != ch->pcdata->clan )
		continue;
	    if( ( channel == CHANNEL_SHOUT || channel == CHANNEL_YELL )
		&& !IS_AWAKE( vch ) )
		continue;

	    strcpy( mesg, orig );
	    if( ch && ( p = languageshift( ch, vch, mesg ) ) )
	    {
		char *q;

		q = strchr( buf, '\0' );
		sprintf( q, ", in %s.", p );
		act( buf, ch, mesg, vch, TO_VICT );
		*q = '\0';
	    }
	    else if( ch )
		act( buf, ch, argument, vch, TO_VICT );
	    else
		act( buf, vch, argument, NULL, TO_CHAR );
	}
    }

    return;
}


void do_auction( CHAR_DATA *ch, const char *argument )
{
    char buf[MAX_INPUT_LENGTH];
    char arg1[MAX_INPUT_LENGTH];
    int this_bid;
    OBJ_DATA *obj;

    if( IS_NPC( ch ) )
    {
	send_to_char( "No way!\n\r", ch );
	return;
    }

    argument = one_argument( argument, arg1 );

    if( arg1[0] == '\0' && !auction_item )
    {
	send_to_char( "There is currently no item for auction.\n\r", ch );
	send_to_char( "You can auction your own item now.\n\r", ch );
	return;
    }
    if( arg1[0] == '\0' && auction_item )
    {
	send_to_char( "The item currently up for auction is:\n\r", ch );
	charprintf( ch, "Name: %s \n\rLevel: %3d\n\r",
		    auction_item->short_descr, auction_item->level );
	charprintf( ch, "Item put up by %s.\n\r", PERS( auction_owner, ch ) );
	if( bidder )
	    charprintf( ch, "The current bid is %d gold from %s.\n\r",
			current_bid, bidder->name );
	else
	    charprintf( ch, "The reserve is %d gold.\n\r", current_bid );
	return;
    }

    if( auction_item && ( !str_cmp( arg1, "bid" ) || !str_cmp( arg1, "bet" ) ) )
    {
	if( argument[0] == '\0' || !is_number_special( argument ) )
	{
	    send_to_char( "Try bidding a number next time, eh?\n\r", ch );
	    return;
	}
	this_bid = atoi_special( argument );
	if( this_bid > ch->gold )
	{
	    send_to_char( "You don't have enough gold for that bid.\n\r", ch );
	    return;
	}
	if( this_bid < current_bid + 100 )
	{
	    send_to_char( "You must bid at least 100 over the current bid.\n\r", ch );
	    return;
	}
	if( ch == auction_owner )
	{
	    send_to_char( "You can't bid on your own item.\n\r", ch );
	    return;
	}
	if( ch == bidder )
	{
	    send_to_char( "Don't worry, you allready have the bid.\n\r", ch );
	    return;
	}
	bidder = ch;
	current_bid = this_bid;
	auction_state = 0;
	sprintf( buf, "%s bids %d on %s.", ch->name,
		this_bid, auction_item->short_descr );
	talk_channel( ch, buf, CHANNEL_AUCTION, "auction" );
	return;
    }

    if( auction_item )
    {
	send_to_char( "Sorry, you can't auction yet.\n\r", ch );
	return;
    }
    obj = get_obj_carry( ch, arg1 );
    if( !obj )
    {
	send_to_char( "You can't auction something you don't have.\n\r", ch );
	return;
    }
    if( obj->wear_loc != WEAR_NONE )
    {
	send_to_char( "I would suggest removing it first.\n\r", ch );
	return;
    }
    if( !can_drop_obj( ch, obj ) )
    {
	send_to_char( "You can't let go of it!\n\r", ch );
	return;
    }
    switch( obj->item_type )
    {
    case ITEM_KEY:
    case ITEM_MONEY:
    case ITEM_TREASURE:
    case ITEM_TRASH:
    case ITEM_CORPSE_PC:
    case ITEM_CORPSE_NPC:
    case ITEM_FOOD:
	charprintf( ch, "You can't auction %ss.\n\r",
		    flag_string( type_flags, &obj->item_type ) );
	return;
    default:
	break;
    }
    if( is_number_special( argument ) )
	current_bid = UMAX( 0, atoi_special( argument ) );
    else
	current_bid = 0;
    obj_from_char( obj );
    auction_item = obj;
    auction_owner = ch;
    bidder = NULL;
    auction_state = 0;
    if( current_bid )
	sprintf( buf, "%s puts %s up for sale, with a reserve of %d.",
		ch->name, obj->short_descr, current_bid );
    else
	sprintf( buf, "%s puts %s up for sale.",
		ch->name, obj->short_descr );
    talk_channel( ch, buf, CHANNEL_AUCTION, "auction" );
    return;
}


void init_auction( )
{
    bidder = NULL;
    auction_owner = NULL;
    auction_item = NULL;
    auction_state = -1;
    current_bid = 0;
    return;
}


void update_auction( )
{
    char buf[100];

    buf[0] = '\0';

    switch( ++auction_state )
    {
    default:
    case 0:
	auction_state = -1;
	break;
    case 1:
	wiznet( auction_owner, WIZ_TICKS, 0, "Auction tick (going zero)." );
	return;
    case 2:
	sprintf( buf, "%s going once.", auction_item->short_descr );
	break;
    case 3:
	sprintf( buf, "%s going twice.", auction_item->short_descr );
	break;
    case 4:
	if( bidder && auction_item )
	{
	    if( bidder->gold >= current_bid )
	    {
		sprintf( buf, "%s sold to %s for %d gold.",
			auction_item->short_descr, bidder->name, current_bid );
		talk_channel( auction_owner, buf, CHANNEL_AUCTION, "auction" );
		act( "The auctioneer appears before you and hands you $p.",
		    bidder, auction_item, NULL, TO_CHAR );
		act( "The auctioner appears before $n and hands $m $p.",
		    bidder, auction_item, NULL, TO_ROOM );
		bidder->gold -= current_bid;
		auction_owner->gold += current_bid;
		obj_to_char( auction_item, bidder );
		init_auction( );
	    }
	    else
	    {
		talk_channel( auction_owner,
			     "Current bid can't be met, the auction will continue.",
			     CHANNEL_AUCTION, "auction" );
		current_bid /= 2;
		bidder = NULL;
		auction_state = 0;
		sprintf( buf, "The bid is now halved to %d.", current_bid );
	    }
	}
	else
	{
	    sprintf( buf, "No bids for %s, item will be returned to owner.",
		    auction_item->short_descr );
	    talk_channel( auction_owner, buf, CHANNEL_AUCTION, "auction" );
	    obj_to_char( auction_item, auction_owner );
	    init_auction( );
	}
    }
    if( auction_state >= 0 )
	talk_channel( auction_owner, buf, CHANNEL_AUCTION, "auction" );
    return;
}


void do_deaf( CHAR_DATA *ch, const char *argument )
{
    if( IS_SET( ch->deaf, CHANNEL_DEAF ) )
    {
	send_to_char( "You no longer ignore channels.\n\r", ch );
	REMOVE_BIT( ch->deaf, CHANNEL_DEAF );
    }
    else
    {
	SET_BIT( ch->deaf, CHANNEL_DEAF );
	send_to_char( "You now ignore all channels.\n\r", ch );
    }
}


void do_chat( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_CHAT, "chat" );
    return;
}


void do_clantalk( CHAR_DATA *ch, const char *argument )
{
    if( !is_clan( ch ) )
    {
	send_to_char( "You belong to neither clan, guild nor order.\n\r", ch );
	return;
    }
    talk_channel( ch, argument, CHANNEL_CLANTALK,
		  flag_string( clan_type_flags, &ch->pcdata->clan->clan_type ) );
    return;
}


/*
 * Alander's new channels.
 */
void do_music( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_MUSIC, "music" );
    return;
}



void do_question( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_QUESTION, "question" );
    return;
}


void do_answer( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_QUESTION, "answer" );
    return;
}


void do_shout( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_SHOUT, "shout" );
    WAIT_STATE( ch, 3 * PULSE_PER_SECOND );
    return;
}


void do_yell( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_YELL, "yell" );
    return;
}


void do_gratz( CHAR_DATA *ch, const char *argument )
{
    talk_channel( ch, argument, CHANNEL_GRATZ, "congratulate" );
    return;
}


void do_immtalk( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *rch;

    rch = get_char( ch );

    if( !authorized( rch, "immtalk" ) )
	return;

    talk_channel( ch, argument, CHANNEL_IMMTALK, "immtalk" );
    return;
}


void do_seniortalk( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *rch;

    rch = get_char( ch );

    if( !authorized( rch, "seniortalk" ) )
	return;

    talk_channel( ch, argument, CHANNEL_SENIORTALK, "seniortalk" );
    return;
}


void do_say( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *vch;
    OBJ_DATA *obj;
    const char *keyword;
    const char *p;
    char arg[MAX_INPUT_LENGTH];
    char trig[MAX_INPUT_LENGTH];
    char buf[MAX_INPUT_LENGTH];
    char mesg[MAX_STRING_LENGTH];

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

    if( IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE )
	|| IS_SET( ch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( ch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

    if( !IS_AWAKE( ch ) )
    {
	send_to_char( "You mumble something in your sleep.\n\r", ch );
	send_to_char( "&cYou say 'MMumble MUMBLE mumph mrr'&n\n\r", ch );
	act( "&c$n says 'MMumble MUMBLE mumph mrr'", ch, NULL, NULL, TO_ROOM );
	return;
    }

    p = strchr( argument, '\0' ) - 1;
    switch( *p-- )
    {
    case '?':
	keyword = "ask";
	break;
    case ')':
	if( *p == ':' )
	    keyword = "smile";
	else if( *p-- == '-' && *p == ':' )
	    keyword = "smile";
	else
	    keyword = "say";
	break;
    case '(':
	if( *p == ':' )
	    keyword = "frown";
	else if( *p-- == '-' && *p == ':' )
	    keyword = "frown";
	else
	    keyword = "says";
	break;
    case '!':
	keyword = "exclaim";
	break;
    default:
	keyword = "say";
	break;
    }

    strcpy( arg, argument );
    strcpy( mesg, argument );
    makedrunk( mesg, ch );

    REMOVE_BIT( SysInfo->flags, SYSINFO_ACT_TRIGGER );
    if( ( p = languageshift( ch, ch, mesg ) ) )
    {
	sprintf( buf, "&cYou %s '$t&n&c', in %s.", keyword, p );
	act( buf, ch, mesg, NULL, TO_CHAR );
    }
    else
	act( "&cYou $t '$T&n&c'&n", ch, keyword, mesg, TO_CHAR );

    kill_colour( trig, arg );
    for( vch = ch->in_room->people; vch; vch = vch->next_in_room )
    {
	const char *p;

	if( vch->deleted || !IS_AWAKE( vch ) || vch == ch )
	    continue;
	strcpy( mesg, arg );
	REMOVE_BIT( SysInfo->flags, SYSINFO_ACT_TRIGGER );
	if( ( p = languageshift( ch, vch, mesg ) ) )
	    sprintf( buf, "&c$n %ss '$t&n&c', in %s.", keyword, p );
	else
	    sprintf( buf, "&c$n %ss '$t&n&c'", keyword );
	act( buf, ch, mesg, vch, TO_VICT );

	if( !IS_NPC( vch ) )
	    continue;
	if( IS_SET( spec_table[vch->spec_fun].usage, SPEC_TELL )
	    && ( *spec_table[vch->spec_fun].spec_fun )
	    ( vch, ch, SPEC_TELL, (void *)argument ) )
	    continue;
	if( xIS_SET( vch->pIndexData->progtypes, SPEECH_PROG ) )
	    mprog_wordlist_check( trig, vch, ch, NULL, NULL, SPEECH_PROG );
    }
    if( xIS_SET( ch->in_room->progtypes, SPEECH_PROG ) )
	rprog_wordlist_check( trig, ch->in_room, ch, NULL, NULL, SPEECH_PROG );
    for( obj = ch->in_room->contents; obj; obj = obj->next_content )
	if( !obj->deleted && xIS_SET( obj->pIndexData->progtypes,
				      SPEECH_PROG ) )
	    oprog_wordlist_check( trig, ch, obj, NULL, SPEECH_PROG );
    return;
}



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

    if( IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE )
	|| IS_SET( ch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( ch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

    argument = one_argument( argument, arg );

    /*
     * 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( "They aren't here.\n\r", ch );
	return;
    }

    if( ( !IS_NPC( ch ) && ( xIS_SET( ch->act, PLR_SILENCE )
			 || xIS_SET( ch->act, PLR_NO_TELL ) ) )
	|| IS_SET( victim->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( victim->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your message didn't get through.\n\r", ch );
	return;
    }

    if( !victim->desc )
    {
	if( IS_SET( spec_table[victim->spec_fun].usage, SPEC_TELL )
	    && ( *spec_table[victim->spec_fun].spec_fun )
		 ( victim, ch, SPEC_TELL, (void *)argument ) )
	    act( "&BYou tell $N '$t&n&B'&b", ch, argument, victim, TO_CHAR );
	else
	    act( "$N is link dead.", ch, 0, victim, TO_CHAR );
	return;
    }

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

    strcpy( arg, argument );
    makedrunk( arg, ch );

    if( !IS_IMMORTAL( ch ) && !IS_AWAKE( victim ) )
    {
	act( "FYI, $E is currently asleep.", ch, NULL, victim, TO_CHAR );
	act( "&BYou try to tell $N '$t&B'&n", ch, arg, victim, TO_CHAR );
	position = victim->position;
	victim->position = POS_STANDING;
	act( "&BYou dream of $n coming and telling you '$t&B'&n", ch, arg, victim, TO_VICT );
	victim->position = position;
    }
    else
    {
	act( "&BYou tell $N '$t&n&B'&b", ch, arg, victim, TO_CHAR );
	position = victim->position;
	victim->position = POS_STANDING;
	act( "&B$n tells you '$t&n&B'&n", ch, arg, victim, TO_VICT );
	victim->position = position;
    }
    victim->reply = ch;

    if( xIS_SET( victim->act, PLR_AFK ) )
	act( "&gJust so you know, $E is AFK.&n", ch, NULL, victim, TO_CHAR );
    if( xIS_SET( victim->act, PLR_BUSY ) )
	act( "&gJust so you know, $E is &nBUSY&g.&n", ch, NULL, victim, TO_CHAR );

    return;
}



void do_reply( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *victim;
    int position;
    char buf[MAX_INPUT_LENGTH];

    if( IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE )
	|| IS_SET( ch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( ch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

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

    if( ( !IS_NPC( ch ) && ( xIS_SET( ch->act, PLR_SILENCE )
			 || xIS_SET( ch->act, PLR_NO_TELL ) ) )
	|| IS_SET( victim->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( victim->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your message didn't get through.\n\r", ch );
	return;
    }

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

    if( !victim->desc )
    {
	act( "$N is link dead.", ch, 0, victim, TO_CHAR );
	return;
    }

    strcpy( buf, argument );
    makedrunk( buf, ch );

    if( !IS_IMMORTAL( ch ) && !IS_AWAKE( victim ) )
    {
	act( "FYI, $E is currently asleep.", ch, 0, victim, TO_CHAR );
	act( "&BYou try to tell $N '$t&B'&n", ch, buf, victim, TO_CHAR );
	position = victim->position;
	victim->position = POS_STANDING;
	act( "&BYou dream of $n coming and telling you '$t&B'&n", ch, buf, victim, TO_VICT );
	victim->position = position;
    }
    else
    {
	act( "&BYou tell $N '$t&n&B'&b", ch, buf, victim, TO_CHAR );
	position = victim->position;
	victim->position = POS_STANDING;
	act( "&B$n tells you '$t&n&B'&n", ch, buf, victim, TO_VICT );
	victim->position = position;
    }
    victim->reply = ch;

    if( xIS_SET( victim->act, PLR_AFK ) )
	act( "Just so you know, $E is AFK.", ch, NULL, victim, TO_CHAR );

    return;
}



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

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

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

    strcpy( buf, argument );
    plast = strchr( argument, '\0' ) - 1;
    if( isalpha( *plast ) )
	strcat( buf, "." );
    REMOVE_BIT( SysInfo->flags, SYSINFO_ACT_TRIGGER );
    act( "&y$n $T", ch, NULL, buf, TO_ROOM );
    REMOVE_BIT( SysInfo->flags, SYSINFO_ACT_TRIGGER );
    act( "&y$n $T", ch, NULL, buf, TO_CHAR );
    return;
}



void do_bug( CHAR_DATA *ch, const char *argument )
{
    if( argument[0] == '\0' )
    {
	send_to_char( "The Implementors look at you quizzically.\n\r", ch );
	return;
    }

    append_file( ch, BUG_FILE, argument );
    send_to_char( "Ok.	Thanks.\n\r", ch );
    return;
}



void do_idea( CHAR_DATA *ch, const char *argument )
{
    if( argument[0] == '\0' )
    {
	send_to_char( "The Implementors look at you quizzically.\n\r", ch );
	return;
    }

    append_file( ch, IDEA_FILE, argument );
    send_to_char( "Ok.	Thanks.\n\r", ch );
    return;
}



void do_typo( CHAR_DATA *ch, const char *argument )
{
    if( argument[0] == '\0' )
    {
	send_to_char( "The Implementors look at you quizzically.\n\r", ch );
	return;
    }

    append_file( ch, TYPO_FILE, argument );
    send_to_char( "Ok.	Thanks.\n\r", ch );
    return;
}


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


bool can_quit( CHAR_DATA *ch )
{
    if( IS_NPC( ch ) )
	return FALSE;

    if( IS_SWITCHED( ch ) )
    {
	send_to_char( "You can't quit until you are back in your own body.\n\r", ch );
	return FALSE;
    }
    if( xIS_SET( ch->act, PLR_BATTLE ) )
    {
	send_to_char( "Not right in the middle of a battle!\n\r", ch );
	return FALSE;
    }
    if( ch == auction_owner || ch == bidder )
    {
	send_to_char(
	    "You can't quit while participating in an auction.\n\r", ch );
	return FALSE;
    }
    if( ch->position == POS_FIGHTING )
    {
	send_to_char( "No way! You are fighting.\n\r", ch );
	return FALSE;
    }
    if( ch->position < POS_STUNNED )
    {
	send_to_char( "You're not DEAD yet.\n\r", ch );
	return FALSE;
    }
    return TRUE;
}


DESCRIPTOR_DATA *quit_char( CHAR_DATA *ch )
{
    DESCRIPTOR_DATA *d;
    OBJ_DATA *obj, *obj_next;

    if( ch->pcdata->quest->type != QUEST_NONE )
    {
	ch->pcdata->quest->type = QUEST_NONE;
	ch->pcdata->quest->time = 30;
    }

    if( ch->class == CLASS_BUILDER )
	do_asave( ch, "changed" );
    if( ch->desc && ch->desc->pString )
	string_add( ch, "@" );
    if( ch->pcdata->trade )
	do_trade( ch, "withdraw" );

    do_call( ch, "all" );
    for( obj = ch->carrying; obj; obj = obj_next )
    {
	obj_next = obj->next_content;
	if( get_trust( ch ) < obj->level - 5 )
	{
	    act( "$p drops to the ground.", ch, obj, NULL, TO_ROOM );
	    obj_from_char( obj );
	    obj_to_room( obj, ch->in_room );
	}
    }
    do_quote( ch, "" );
    send_to_char( "\n\r", ch );

    act( "$n has left the game.", ch, NULL, NULL, TO_ROOM );
    sprintf( log_buf, "%s has quit.", ch->name );
    log_string( log_buf );
    wiznet( ch, WIZ_LOGINS, get_trust( ch ), log_buf );
    if( !IS_IMMORTAL( ch ) )
	talk_channel( NULL, log_buf, CHANNEL_INFO, "INFO" );

    if( xIS_SET( ch->act, PLR_VT100 ) )
	send_to_char( VT_RESET_TERMINAL VT_SETWIN_CLEAR, ch );

    write_to_descriptor_nice( ch->desc );

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


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

    if( !can_quit( ch ) )
	return;

    d = quit_char( ch );

    if( d )
	close_socket( d );

    return;
}


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

    if( ch->level < SysInfo->saveat )
    {
	charprintf( ch, "You must get to level %d to be able to save.\n\r",
		    SysInfo->saveat );
	return;
    }

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



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

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

    if( !( victim = get_char_room( ch, argument ) ) )
    {
	send_to_char( "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( "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 )
    {
	bug( "Add_follower: non-null master." );
	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 )
    {
	bug( "Stop_follower: null master." );
	return;
    }

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

    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 *name )
{
    CHAR_DATA *fch;

    if( ch->master )
	stop_follower( ch );

    ch->leader = NULL;

    for( fch = char_list; fch; fch = fch->next )
    {
	if( fch->deleted )
	    continue;
	if( fch->master == ch )
	    stop_follower( fch );
	if( fch->leader == ch )
	    fch->leader = NULL;
	if( fch->tracking == ch )
	{
	    send_to_char(
		"The trail goes cold and you lose track of your quarry.\n\r",
		fch );
	    fch->tracking = NULL;
	}
    }

    return;
}



void do_order( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *victim;
    CHAR_DATA *och;
    CHAR_DATA *och_next;
    char arg[MAX_INPUT_LENGTH];
    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 ) ) )
	{
	    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; och = och_next )
    {
	och_next = och->next_in_room;

	if( och->deleted )
	    continue;

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

    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, const char *argument )
{
    CHAR_DATA *victim;
    char tmp[MAX_INPUT_LENGTH];
    const char *pos_table[] =
    { "&rDEAD!&g", "&rMortally Wounded!&g", "Incapacitated.",
      "Stunned.", "Sleeping.", "Meditating.", "Resting.",
      "Sitting.", "Prone.", "Prone.", "Fighting!", "Standing.", "" };

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

	leader = ( ch->leader ) ? ch->leader : ch;
	charprintf( ch, "&B%s&n&B's group:&n\n\r", PERS( leader, ch ) );

	for( gch = char_list; gch; gch = gch->next )
	{
	    int i;
	    int mm = 0;

	    if( gch->deleted || !is_same_group( gch, ch ) )
		continue;

	    for( i = 0; i < MAGIC_MAX; ++i )
		mm += get_max_mana( gch, i );
	    charprintf( ch, "&b[&c%2d %s&b]&g %s&g %4d/%4d hp "
			"%4d/%4d mana %4d/%4d mv\n\r",
			gch->level,
			IS_NPC( gch ) ? "Mob"
			: (char *)class_table[gch->class].who_name,
			colour_strpad( tmp, PERS( gch, ch ), 18 ),
			gch->hit, get_max_hit( gch ),
			total_mana( gch->mana ), mm,
			gch->move, get_max_move( gch ) );
	    charprintf( ch, "\t\t\t[%d xptnl]  Position: %s&n\n\r",
			gch->exp / 100,
			pos_table[gch->position] );
	}
	return;
    }
    /* a neat little shortcut */
    else if( !str_cmp( argument, "all" ) )
    {
	for( victim = ch->in_room->people; victim; victim = victim->next_in_room )
	{
	    if( victim->master != ch || victim->leader == ch
		|| !can_see( ch, victim ) )
		continue;

	    victim->leader = ch;
	    act( "$N joins your group.", ch, NULL, victim, TO_CHAR );
	    act( "You join $o group.", ch, NULL, victim, TO_VICT );
	    act( "$N joins $o group.", ch, NULL, victim, TO_NOTVICT );
	}
	send_to_char( "&gEveryone that follows you is now in your group.&n\n\r",
		      ch );
	return;
    }

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

    /*
     * Disband the group.
     * ?? Should we check that ch can see victim ??
     */
    if( victim == ch )
    {
	for( victim = char_list; victim; victim = victim->next )
	{
	    if( victim->leader == ch || victim->master == ch )
		stop_follower( victim );
	}
	act( "You have disbanded your group.", ch, NULL, NULL, TO_CHAR );
	act( "$n has disbanded $s group.", ch, NULL, NULL, TO_ROOM );
	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( "You remove $N from your group.", ch, NULL, victim, TO_CHAR );
	act( "$n removes you from $s group.", ch, NULL, victim, TO_VICT );
	act( "$n removes $N from $s group.", ch, NULL, victim, TO_NOTVICT );
	return;
    }

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


/*
 * 'Split' originally by Gnort, God of Chaos.
 */
void do_split( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *gch;
    char buf[MAX_STRING_LENGTH];
    char arg[MAX_INPUT_LENGTH];
    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_special( 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; gch = gch->next_in_room )
    {
	if( gch->deleted )
	    continue;
	if( is_same_group( gch, ch ) )
	    members++;
    }

    if( members < 2 )
    {
	if( !strcmp( argument, AUTOLOOK ) )
	    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;

    charprintf( ch,
	    "&yYou split %d gold coins.	 Your share is %d gold coins.&n\n\r",
	    amount, share + extra );

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

    for( gch = ch->in_room->people; gch; gch = gch->next_in_room )
    {
	if( gch->deleted )
	    continue;
	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, const char *argument )
{
    CHAR_DATA *gch;
    char orig[MAX_INPUT_LENGTH];
    char mesg[MAX_STRING_LENGTH];

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

    if( IS_AFFECTED( ch, AFF_MUTE )
	|| IS_SET( race_table[ch->race].race_abilities, RACE_MUTE )
	|| IS_SET( ch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( ch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	send_to_char( "Your lips move but no sound comes out.\n\r", ch );
	return;
    }

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

    strcpy( orig, argument );
    makedrunk( orig, ch );

    for( gch = char_list; gch; gch = gch->next )
    {
	strcpy( mesg, argument );
	if( is_same_group( gch, ch )
	    && !IS_SET( gch->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	    && !IS_SET( gch->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE )
	    && !IS_SET( race_table[gch->race].race_abilities, RACE_MUTE )
	    && !IS_AFFECTED( gch, AFF_MUTE ) )
	{
	    char buf[MAX_INPUT_LENGTH];
	    const char *p;

	    if( ( p = languageshift( ch, gch, mesg ) ) )
		sprintf( buf, "&m$N tell%s the group '$t', in %s.", p,
			 ch == gch ? "" : "s" );
	    else
		sprintf( buf, "&m$N tell%s the group '$t'.",
			 ch == gch ? "" : "s" );
	    act( buf, gch, mesg, ch, TO_CHAR );
	}
    }

    return;
}

/*
 * Sent in by Judson Knott <jek@conga.oit.unc.edu>
 */
void do_beep( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *victim;

    if( IS_NPC( ch ) )
	return;

   if( argument[0] == '\0' )
    {
	send_to_char( "Beep who?\n\r", ch );
	return;
    }

    if( !( victim = get_char_world( ch, argument ) ) )
    {
	send_to_char( "They are not here.\n\r", ch );
	return;
    }

    if( IS_NPC( victim ) || xIS_SET( victim->act, PLR_NO_BEEP ) )
    {
	send_to_char( "They are not beepable.\n\r", ch );
	act( "$N has just tried to beep you.", victim, NULL, ch, TO_CHAR );
	return;
    }

    charprintf( ch, "&yYou beep %s.&n\n\r", victim->name );
    charprintf( victim, "&r\a\a%s has &RBEEP&red you.&n\n\r", ch->name );

    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->deleted || bch->deleted )
	return FALSE;

    if( ach->leader )
	ach = ach->leader;
    if( bch->leader )
	bch = bch->leader;
    return ach == bch;
}


bool is_same_clan( CHAR_DATA *ach, CHAR_DATA *bch )
{
    if( ach->deleted || bch->deleted )
	return FALSE;

    if( is_clan( ach ) && is_clan( bch ) )
	return ach->pcdata->clan == bch->pcdata->clan;
    else
	return FALSE;
}


void do_multi_clas( CHAR_DATA *ch, const char *argument )
{
    send_to_char( "Why don't you type it in properly?\n\r", ch );
    send_to_char( "Just so we're sure you are serious.\n\r", ch );
    return;
}

void do_multi_class( CHAR_DATA *ch, const char *argument )
{
    int Class;
    int i;
    bool allowed = TRUE;
    int num_classes = 0;

    if( IS_NPC( ch ) )
	return;
    if( get_first_class( ch ) == CLASS_NONE && ch->class > AVAIL_CLASS )
    {
	send_to_char( "You aren't allowed to multi-class.\n\r", ch );
	return;
    }
    if( argument[0] == '\0' )
    {
	do_help( ch, "multi-class" );
	return;
    }

    if( !str_cmp( argument, "mage" ) )
	Class = CLASS_MAGE;
    else if( !str_cmp( argument, "cleric" ) )
	Class = CLASS_CLERIC;
    else if( !str_cmp( argument, "thief" ) )
	Class = CLASS_THIEF;
    else if( !str_cmp( argument, "warrior" ) )
	Class = CLASS_WARRIOR;
    else if( !str_prefix( "martial", argument ) )
	Class = CLASS_MARTIAL_ARTIST;
    else
    {
	send_to_char( "There are 5 classes available:\n\r", ch );
	send_to_char( "\tMage, Cleric, Thief, Warrior and Martial Artist.\n\r",
		     ch );
	return;
    }

    if( ch->pcdata->multi_class[Class] >= CLASS_ADEPT )
    {
	send_to_char( "You allready have that one.\n\r", ch );
	return;
    }
    for( i = 0; i < AVAIL_CLASS; ++i )
    {
	if( ch->pcdata->multi_class[i] == CLASS_ASPIRING )
	{
	    send_to_char( "One at a time please.\n\r", ch );
	    return;
	}
	if( ch->pcdata->multi_class[i] >= CLASS_ADEPT )
	    num_classes++;
    }
    if( get_first_class( ch ) != CLASS_NONE
	&& get_second_class( ch ) != CLASS_NONE )
	num_classes--;
    switch( num_classes )
    {
    case 0:
	allowed = TRUE;
	break;
    case 1:
	if( ch->level < 100 || ch->class == Class )
	{
	    allowed = FALSE;
	}
	break;
    case 2:
	if( ch->level < 200 || ch->class == Class )
	{
	    allowed = FALSE;
	}
	break;
    case 3:
	send_to_char( "You have allready done all you can about this.\n\r", ch );
	allowed = FALSE;
	break;
    default:
	send_to_char( "Whoa! that's enough don't you think?\n\r", ch );
	allowed = FALSE;
	break;
    }
    if( ch->trust > L_APP )
	allowed = TRUE;

    if( !allowed )
    {
	send_to_char( "Sorry you can't multi-class in that class.\n\r", ch );
	return;
    }

    ch->sublevel = 1;
    ch->pcdata->multi_class[Class] = CLASS_ASPIRING;
    send_to_char( "&gYou have chosen to add the &c", ch );
    send_to_char( flag_string( class_flags, &Class ), ch );
    send_to_char( "&g class to your character's skills.\n\r", ch );
    send_to_char( "Remember the skills from this class "
		  "will soon become yours.\n\r", ch );
    if( num_classes == 0 )
    {
	send_to_char( "\n\rVery soon you will also have a new class,\n\r",
		     ch );
	send_to_char( "with skills specific to it.\n\r", ch );
    }
    send_to_char( "&GBest of luck with your initiation.&n\n\r", ch );
    return;
}


void do_hire( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *mob;
    char arg1[MAX_STRING_LENGTH];
    int index;
    int cost;
    int money;
    EVENT *e;

    if( IS_NPC( ch ) )
	return;

    argument = one_argument( argument, arg1 );
    if( arg1[0] == '\0' || argument[0] == '\0'
	|| !is_number_special( argument ) )
    {
	send_to_char( "Syntax: hire <who> <bid amount>\n\r", ch );
	return;
    }
    mob = get_char_room( ch, arg1 );
    if( !mob )
    {
	send_to_char( "They aren't here.\n\r", ch );
	return;
    }
    if( !IS_NPC( mob ) || !xIS_SET( mob->act, ACT_MERCENARY ) )
    {
	send_to_char( "They would probably be insulted by your suggestion.\n\r", ch );
	return;
    }
    if( IS_AFFECTED( mob, AFF_MUTE )
	|| IS_SET( race_table[mob->race].race_abilities, RACE_MUTE )
	|| IS_SET( mob->in_room->room_flags, ROOM_CONE_OF_SILENCE )
	|| IS_SET( mob->in_room->room_flags, ROOM_TEMP_CONE_OF_SILENCE ) )
    {
	act( "$N makes wide gestures with $S hands, it appears that $E's mute.",
	     ch, NULL, mob, TO_CHAR );
	return;
    }

    if( mob->master || get_time_left( mob->events, evn_mercenary ) >= 0 )
    {
	send_to_char( "That person is otherwise employed at the moment.\n\r", ch );
	return;
    }

    index = number_fuzzy( ch->level - mob->level );

    if( index < -10 )
	do_say( mob, "You would humiliate me? I spit on your offer!" );
    if( index >= -10 && index < -5 )
	do_say( mob, "Hmm, not a very attractive offer, I will wait for better." );
    else if( index >= -5 && index < 0 )
	do_say( mob, "Ah yes, work. I will take you up on your offer." );
    else if( index == 0 )
	do_say( mob, "Very well, shall we call it a deal?" );
    else if( index > 0 && index <= 5 )
	do_say( mob, "Master, your will is mine!" );
    else if( index > 5 && index <= 10 )
	do_say( mob, "Mighty one, I fear for my safety, I can't accept." );
    else
	do_say( mob, "Are you stupid!?! What could I offer?" );
    if( index < -5 || index > 5 )
    {
	send_to_char( "Sorry no deal here.\n\r", ch );
	return;
    }
    cost = mob->level * ( 20 - index ) * mob->level / 20;
    cost = number_range( cost * 4 / 5, cost * 6 / 5 );
    /* extra cost for a hurt mob, mercenaries don't like to die */
    if( mob->hit < mob->max_hit * 4 / 5 )
    {
	act( "$n looks reluctant to take your money.",
	     mob, NULL, ch, TO_VICT );
	cost += mob->max_hit - mob->hit;
    }
    money = atoi_special( argument );
    if( money > ch->gold )
    {
	do_say( mob, "Hey Tightwad! if you don't have the cash, no deal!" );
	return;
    }
    if( money < 3 * cost )
    {
	do_say( mob, "Sorry that amount is too small for my liking.\n\r" );
	return;
    }
    if( money > 50 * cost )
	do_say( mob, "Master, I am humbled by your riches." );
    if( money > ch->gold )
    {
	do_say( mob, "Hey you dont actually HAVE that money do you?" );
	return;
    }
    ch->gold -= money;
    ch->in_room->area->economy += money;
    mob->master = ch;
    e = create_char_event( mob, evn_mercenary,
		       UMIN( ( ( money / cost ) - 1 ), 50 ) * PULSE_TICK );
    e->data[0] = mob->in_room->vnum;

    xSET_BIT( mob->affected_by, AFF_CHARM );

    return;
}


void do_setname( CHAR_DATA *ch, const char *argument )
{
    char tmp[MAX_INPUT_LENGTH];
    char buf[MAX_INPUT_LENGTH];

    if( IS_NPC( ch ) )
	return;

    if( !str_cmp( argument, "clear" )
	|| !str_cmp( argument, "reset" )
	|| !str_cmp( argument, "none" ) )
    {
	free_string( ch->short_descr );
	ch->short_descr = &str_empty[0];
	send_to_char( "Setname cleared.\n\r", ch );
	return;
    }
    else if( argument[0] == '\0' )
    {
	send_to_char( "Your name is now set to:\n\r", ch );
	charprintf( ch, "   %s&n.\n\r",
		    ( ch->short_descr && ch->short_descr[0] != '\0' )
		    ? ch->short_descr : ch->name );
	return;
    }

    strcpy( tmp, argument );
    str_limit( tmp, 50 );

    strcpy( buf, tmp );
    kill_colour( tmp, buf );

    if( !IS_IMMORTAL( ch ) && !str_str( tmp, ch->name ) )
    {
	send_to_char( "You need to include your own name in that.\n\r", ch );
	return;
    }

    free_string( ch->short_descr );
    ch->short_descr = str_dup( buf );

    do_setname( ch, "" );
    return;
}


void do_quest( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *mob;
    char arg[ MAX_INPUT_LENGTH ];

    if( IS_NPC( ch ) )
	return;
    argument = one_argument( argument, arg );

    for( mob = ch->in_room->people; mob; mob = mob->next_in_room )
    {
	if( xIS_SET( mob->act, ACT_QUESTMASTER ) )
	    break;
    }

    if( !str_cmp( arg, "request" ) )
	quest_request( ch, mob );
    else if( !str_cmp( arg, "complete" ) )
	quest_complete( ch, mob );
    else if( !str_cmp( arg, "info" ) )
	quest_info( ch );
    else if( !str_cmp( arg, "identify" ) )
	quest_identify( ch, mob, argument );
    else if( !str_cmp( arg, "improve" ) )
	quest_improve( ch, mob, argument );
    else if( !str_cmp( arg, "score" ) || !str_cmp( arg, "points" ) )
    {
	charprintf( ch, "&gYou have %d quest points.&n\n\r",
		    ch->pcdata->quest->score );
    }
    else if( !str_cmp( arg, "time" ) )
    {
	if( ch->pcdata->quest->time <= 0 )
	    send_to_char( "You aren't on a quest!\n\r", ch );
	else if( ch->pcdata->quest->type == QUEST_NONE )
	{
	    if( ch->pcdata->quest->time == 1 )
		send_to_char( "You have less than a minute before you can quest again.\n\r", ch );
	    else
		charprintf( ch, "You have %d minutes left before you can quest again.\n\r",
			    ch->pcdata->quest->time );
	}
	else
	{
	    if( ch->pcdata->quest->time == 1 )
		send_to_char( "You have less than a minute to complete the quest.\n\r", ch );
	    else
		charprintf( ch, "You have %d minutes left to complete the quest.\n\r",
			    ch->pcdata->quest->time );
	}
    }
    else if( !str_cmp( arg, "cancel" ) || !str_cmp( arg, "giveup" ) )
    {
	switch( ch->pcdata->quest->type )
	{
	case QUEST_NONE:
	    send_to_char( "But you aren't questing!\n\r", ch );
	    return;
	case QUEST_RACE_CORPSE:
	case QUEST_BODY_PART:
	    free_mem( ch->pcdata->quest->target,
		      sizeof( struct quest_race_corpse ) );
	    break;
	case QUEST_OBJ_MATERIAL:
	    free_mem( ch->pcdata->quest->target,
		      sizeof( struct quest_obj_material ) );
	    break;
	}
	ch->pcdata->quest->target = NULL;
	ch->pcdata->quest->type = QUEST_NONE;
	ch->pcdata->quest->time = 30;

	send_to_char( "Ok, giving up on this quest :(\n\r", ch );
    }
    else
	send_to_char(
	    "Syntax: quest <option> <args>\n\r"
	    "Options: REQUEST, COMPLETE, INFO, SCORE, TIME, CANCEL, IMPROVE\n\r",
	    ch );

    return;
}


void quest_request( CHAR_DATA *ch, CHAR_DATA *mob )
{
    QUEST_DATA		*qq = ch->pcdata->quest;
    OBJ_INDEX_DATA	*pObj;
    OBJ_DATA		*obj, *in_obj;
    ROOM_INDEX_DATA	*pRoom = NULL;
    MOB_INDEX_DATA	*pMob = NULL;
    CHAR_DATA		*victim = NULL;
    int			type, period, i;
    char		buf[ MAX_INPUT_LENGTH ];

    if( !mob )
    {
	send_to_char( "You can't do that here.\n\r", ch );
	return;
    }
    if( qq->time > 0 )
    {
	send_to_char( "You can't ask for a quest right now.\n\r", ch );
	return;
    }

    type = number_range( 0, QUEST_OBJ_MATERIAL );
    period = number_range( 10, 30 );
    qq->reward = power( 10000, 5, 20 - period );
    qq->reward = number_range( qq->reward * 4 / 5, qq->reward * 6 / 5 );
    switch( type )
    {
    case QUEST_KILL_COMPLETE:
    case QUEST_NONE:
    default:
	qq->time = 5;
	qq->type = QUEST_NONE;
	sprintf(
	    buf, "Sorry %s, I don't have any quests for you at the moment.",
	    ch->name );
	do_say( mob, buf );
	return;
    case QUEST_ITEM:
	for( ;; )
	{
	    pObj = get_obj_index( number_range( 100, 65536 ) );
	    if( pObj && pObj->level <= LEVEL_HERO
		&& IS_SET( pObj->extra_flags, ITEM_TAKE ) )
		break;
	}
	for( ;; )
	{
	    pRoom = get_room_index( number_range( 100, 65536 ) );
	    if( pRoom && ch->level + 2 >= pRoom->area->min
		&& ch->level - 2 <= pRoom->area->max
		&& !IS_SET( pRoom->area->area_flags, AREA_HIDE ) )
		break;
	}
	sprintf( buf, "Well %s, I would like you to "
		 "retrieve an item that I lost recently.", ch->name );
	do_say( mob, buf );
	sprintf( buf, "I lost %s&x somewhere near %s&x.",
		 pObj->short_descr, pRoom->name );
	do_say( mob, buf );
	sprintf( buf, "That is a place somewhere in %s&x.", pRoom->area->name );
	do_say( mob, buf );
	do_say( mob, "Can you get that for me please?" );

	obj = create_object( pObj, ch->level );
	obj_to_room( obj, pRoom );
	set_timer_tick( obj, period * 2 + 5 );
	create_obj_event( obj, evn_imp_grab,
			  ( period * 2 + 6 ) * PULSE_TICK );
	SET_BIT( obj->extra_flags, ITEM_QUEST|ITEM_TAKE );
	qq->target = (void *)obj;
	break;
    case QUEST_KILL:
	for( i = 0; i < 10000; i++ )
	{
	    pRoom = NULL;
	    pMob = get_mob_index( number_range( 100, 65536 ) );
	    if( pMob && pMob->level >= ch->level - 1
		&& pMob->level < ch->level + UMIN( 10, ch->level / 3 )
		&& pMob->count > 0
		&& get_trust( ch ) + 2 >= pMob->area->min
		&& !IS_SET( pMob->area->area_flags, AREA_HIDE ) )
		for( victim = char_list; victim; victim = victim->next )
		{
		    if( IS_NPC( victim ) && victim->pIndexData == pMob
			&& !IS_SET( victim->in_room->room_flags, ROOM_SAFE ) )
		    {
			pRoom = victim->in_room;
			break;
		    }
		}
	    if( pRoom )
		break;
	}
	if( !victim )
	{
	    send_to_char( "Bummer, no mobs your level.\n\r", ch );
	    qq->time = 5;
	    qq->type = QUEST_NONE;
	    return;
	}
	sprintf( buf, "%s my friend, I have a grim task for you.", ch->name );
	do_say( mob, buf );
	sprintf( buf, "My dire enemy, %s&x, has thwarted me for too long.",
		 pMob->short_descr );
	do_say( mob, buf );
	sprintf( buf, "Seem them out near %s&x and kill them for me if you please.",
		 pRoom->name );
	do_say( mob, buf );
	sprintf( buf, "That is a place somewhere in %s.", pRoom->area->name );
	do_say( mob, buf );

	qq->target = (void *)pMob;
	qq->reward = power( qq->reward, 5, victim->level - ch->level );
	break;
    case QUEST_BODY_PART:
	for( i = 0; part_loss_table[i].chance > 0; ++i )
	    ;
	i = number_range( 0, i - 1 );
	qq->reward *= 8;
	qq->reward /= part_loss_table[i].chance
	    - ( (i == 0) ? 0 : part_loss_table[i-1].chance );
	qq->target = alloc_mem( sizeof( struct quest_race_corpse ) );
	((struct quest_race_corpse *)qq->target)->race
	    = part_loss_table[i].body_parts;
	((struct quest_race_corpse *)qq->target)->min_lvl
	    = ch->level - 3 + dice( 2, 5 );

	sprintf( buf, "%s, my studies require me to study particular limbs.",
		 ch->name );
	do_say( mob, buf );
	i = part_loss_table[i].body_parts;
	sprintf( buf, "Please would you find me a limb that has: %s.",
		 flag_string( body_part_flags, &i ) );
	do_say( mob, buf );
	sprintf( buf, "Could you make the limb at least level %d?",
		 ((struct quest_race_corpse *)qq->target)->min_lvl );
	do_say( mob, buf );
	break;
    case QUEST_RACE_CORPSE:
	for( i = 0; i < 10000; i++ )
	{
	    pMob = get_mob_index( number_range( 100, 65536 ) );
	    if( pMob && pMob->level >= ch->level - 1
		&& pMob->level < ch->level + UMIN( 10, ch->level / 3 )
		&& pMob->count > 0
		&& get_trust( ch ) + 2 >= pMob->area->min
		&& !IS_SET( pMob->area->area_flags, AREA_HIDE ) )
		break;
	}
	if( !pMob )
	{
	    send_to_char( "Bummer, no mobs your level.\n\r", ch );
	    qq->time = 5;
	    qq->type = QUEST_NONE;
	    return;
	}
	i = pMob->race;
	sprintf( buf, "%s, can you please find me a %s corpse?",
		 ch->name, race_table[i].name );
	do_say( mob, buf );
	do_say( mob, "I find that my books do not give me sufficient information on these." );
	do_say( mob, "I would like to do a post mortem examination of one." );

	qq->target = alloc_mem( sizeof( struct quest_race_corpse ) );
	((struct quest_race_corpse *)qq->target)->race = i;
	((struct quest_race_corpse *)qq->target)->min_lvl
	    = pMob->level;
	sprintf( buf, "Please make the corpse of at least level %d.",
		 ((struct quest_race_corpse *)qq->target)->min_lvl );
	do_say( mob, buf );
	qq->reward = qq->reward / 3 + qq->reward * race_tnl( i ) / 2000;
	break;
    case QUEST_OBJ_MATERIAL:
	do
	{
	    for( i = 0; material_flags[i].name[0]; i++ )
		;
	    i = number_range( 0, i - 1 );
	    for( obj = object_list; obj; obj = obj->next )
		if( !obj->deleted
		    && obj->pIndexData->material == material_flags[i].bit
		    && obj->level < ch->level - 10 )
		    break;
	}
	while( !obj );
	qq->reward = qq->reward * 200
	    - 3 * qq->reward * UMIN( 100, material_flags[i].bit ) / 2;
	qq->reward /= 100;
	qq->target = alloc_mem( sizeof( struct quest_obj_material ) );
	((struct quest_obj_material *)qq->target)->material
	    = material_flags[i].bit;
	sprintf( buf, "%s, I have need of a quantity of %s.",
		 ch->name, material_flags[i].name );
	do_say( mob, buf );

	i = dice( 3, 6 );
	qq->reward *= i + 8;
	qq->reward /= 15;
	((struct quest_obj_material *)qq->target)->min_weight = i;
	sprintf( buf, "I need you to fetch more than %dKg of this material.",
		 i );
	do_say( mob, buf );

	for( in_obj = obj; in_obj->in_obj; in_obj = in_obj->in_obj )
	    ;

	if( in_obj->carried_by )
	{
	    sprintf( buf, "To start, you can find some carried by %s&x.\n\r",
		     PERS( in_obj->carried_by, ch ) );
	}
	else
	{
	    sprintf( buf, "For starters, you can find some in %s&x.\n\r",
		     !in_obj->in_room ? "somewhere" : in_obj->in_room->name );
	}
	do_say( mob, buf );

	do_say( mob,
		"Then I would be able to construct a device I'm working on." );
	break;
    }
    do_say( mob, "I would be most grateful if you would do this for me." );
    sprintf( buf, "I'll give you %d minutes to complete this quest.",
	     period );
    do_say( mob, buf );
    qq->time = period;
    qq->type = type;
    return;
}


void quest_complete( CHAR_DATA *ch, CHAR_DATA *mob )
{
    QUEST_DATA	*qq = ch->pcdata->quest;
    OBJ_DATA	*obj, *objnext;
    int		pts;
    char	buf[ MAX_INPUT_LENGTH ];

    if( !mob )
    {
	send_to_char( "You can't do that here.\n\r", ch );
	return;
    }
    switch( qq->type )
    {
    case QUEST_NONE:
    default:
	send_to_char( "You aren't questing at the moment.\n\r", ch );
	return;
    case QUEST_ITEM:
	for( obj = ch->carrying; obj; obj = obj->next_content )
	    if( !obj->deleted && obj == (OBJ_DATA *)qq->target )
		break;
	if( !obj )
	{
	    do_say( mob, "But you haven't completed the quest!" );
	    return;
	}
	obj_from_char( obj );
	extract_obj( obj );
	break;
    case QUEST_KILL:
	do_say( mob, "But you haven't completed the quest!" );
	return;
    case QUEST_KILL_COMPLETE:
	break;
    case QUEST_BODY_PART:
	for( obj = ch->carrying; obj; obj = obj->next_content )
	    if( !obj->deleted && obj->item_type == ITEM_LIMB
		&& obj->level >= ((struct quest_race_corpse *)qq->target)->min_lvl
		&& ( ( obj->value[0]
		       & ((struct quest_race_corpse *)qq->target)->race )
		     == ((struct quest_race_corpse *)qq->target)->race ) )
		break;
	if( !obj )
	{
	    do_say( mob, "But you haven't completed the quest!" );
	    return;
	}
	obj_from_char( obj );
	extract_obj( obj );
	break;
    case QUEST_RACE_CORPSE:
	for( obj = ch->carrying; obj; obj = obj->next_content )
	    if( !obj->deleted && obj->item_type == ITEM_CORPSE_NPC
		&& obj->level >= ((struct quest_race_corpse *)qq->target)->min_lvl
		&& obj->value[0] == ((struct quest_race_corpse *)qq->target)->race )
		break;
	if( !obj )
	{
	    do_say( mob, "But you haven't completed the quest!" );
	    return;
	}
	free_mem( qq->target, sizeof( struct quest_race_corpse ) );
	obj_from_char( obj );
	extract_obj( obj );

	break;
    case QUEST_OBJ_MATERIAL:
	pts = 0;
	for( obj = ch->carrying; obj; obj = obj->next_content )
	    if( !obj->deleted && obj->wear_loc == WEAR_NONE
		&& obj->pIndexData->material == ((struct quest_obj_material *)qq->target)->material )
	    {
		SET_BIT( obj->extra_flags, ITEM_QUEST );
		if( ( pts += obj->weight ) >= ((struct quest_obj_material *)qq->target)->min_weight )
		    break;
	    }
	if( pts < ((struct quest_obj_material *)qq->target)->min_weight )
	    pts = 0;
	for( obj = ch->carrying; obj; obj = objnext )
	{
	    objnext = obj->next_content;
	    if( IS_SET( obj->extra_flags, ITEM_QUEST ) )
	    {
		if( pts > 0 )
		{
		    obj_from_char( obj );
		    extract_obj( obj );
		}
		else
		    REMOVE_BIT( obj->extra_flags, ITEM_QUEST );
	    }
	}
	if( pts == 0 )
	{
	    do_say( mob, "But you haven't completed the quest!" );
	    return;
	}
	free_mem( qq->target, sizeof( struct quest_obj_material ) );
	break;
    }
    sprintf( buf, "Thank you very much %s.", ch->name );
    do_say( mob, buf );
    pts = (qq->reward / 1000) - 5 + dice( 2, 5 );
    sprintf( buf, "For that I'll give you %d quest points and %d gold.",
	     pts, qq->reward );
    do_say( mob, buf );
    ch->gold += qq->reward;
    qq->score += pts;
    qq->time = 30;
    qq->reward = 0;
    qq->type = QUEST_NONE;
    qq->target = NULL;
    return;
}


void quest_info( CHAR_DATA *ch )
{
    QUEST_DATA *qq = ch->pcdata->quest;

    switch( qq->type )
    {
    case QUEST_NONE:
    default:
	send_to_char( "You aren't questing at the moment.\n\r", ch );
	break;
    case QUEST_ITEM:
	act( "&gYou are sent to get $p.", ch, qq->target, NULL, TO_CHAR );
	break;
    case QUEST_KILL_COMPLETE:
    case QUEST_KILL:
	act( "&gYou are sent to kill $t.", ch,
	     ((MOB_INDEX_DATA *)qq->target)->short_descr, NULL, TO_CHAR );
	break;
    case QUEST_BODY_PART:
	charprintf( ch, "&gYou are sent to find a limb of at least level %d.&n\n\r",
		    ((struct quest_race_corpse *)qq->target)->min_lvl );
	charprintf( ch, "&gIt must contain the limbs: %s.&n\n\r",
		    flag_string( body_part_flags, &((struct quest_race_corpse *)qq->target)->race ) );
	break;
    case QUEST_RACE_CORPSE:
	charprintf( ch, "&gYou are sent to find a %s corpse of at least level %d.&n\n\r",
		    race_table[((struct quest_race_corpse *)qq->target)->race].name,
		    ((struct quest_race_corpse *)qq->target)->min_lvl );
	break;
    case QUEST_OBJ_MATERIAL:
	charprintf( ch, "&gYou are sent to get %dKg of %s.&n\n\r",
		    ((struct quest_obj_material *)qq->target)->min_weight,
		    flag_string( material_flags, &((struct quest_obj_material *)qq->target)->material ) );
	break;
    }
    return;
}


void quest_identify( CHAR_DATA *ch, CHAR_DATA *mob, const char *argument )
{
    OBJ_DATA *obj;
    char buf[MAX_INPUT_LENGTH];

    if( !( obj = get_obj_carry( mob, argument ) ) )
    {
	do_say( mob, "I don't carry that item." );
	return;
    }
    sprintf( buf, "The %s item has these properties:", obj->short_descr );
    do_say( mob, buf );
    spell_identify( skill_lookup( "identify" ), mob->level, ch, obj );
    return;
}


void quest_improve( CHAR_DATA *ch, CHAR_DATA *mob, const char *argument )
{
    OBJ_DATA *obj;
    AFFECT_DATA *paf;
    char target[MAX_INPUT_LENGTH];
    char loc[MAX_INPUT_LENGTH];
    int type, sn;
    int cost;
    int max;
    bool found = FALSE;

    if( IS_NPC( ch ) )
	return;

    argument = one_argument( argument, target );
    argument = one_argument( argument, loc );

    if( !( obj = get_obj_carry( ch, target ) ) )
    {
	send_to_char( "Usage: quest improve <object> <type>\n\r", ch );
	return;
    }
    if( obj->wear_loc != WEAR_NONE )
    {
	send_to_char( "Take it off first.\n\r", ch );
	return;
    }

    if( !str_cmp( loc, "ac" ) )
    {
	type = APPLY_AC;	cost = 10;	max = obj->level / -2;
    }
    else if( !str_cmp( loc, "attackroll" ) || !str_cmp( loc, "hitroll" ) )
    {
	type = APPLY_HITROLL;	cost = 25;	max = obj->level / 10;
    }
    else if( !str_cmp( loc, "damroll" ) )
    {
	type = APPLY_DAMROLL;	cost = 20;	max = obj->level / 10;
    }
    else if( !str_cmp( loc, "str" ) || !str_cmp( loc, "strength" ) )
    {
	type = APPLY_STR;	cost = 50;	max = 2;
    }
    else if( !str_cmp( loc, "int" ) || !str_cmp( loc, "intelligence" ) )
    {
	type = APPLY_INT;	cost = 50;	max = 2;
    }
    else if( !str_cmp( loc, "wis" ) || !str_cmp( loc, "wisdom" ) )
    {
	type = APPLY_WIS;	cost = 50;	max = 2;
    }
    else if( !str_cmp( loc, "dex" ) || !str_cmp( loc, "dexterity" ) )
    {
	type = APPLY_DEX;	cost = 50;	max = 2;
    }
    else if( !str_cmp( loc, "con" ) || !str_cmp( loc, "constitution" ) )
    {
	type = APPLY_CON;	cost = 50;	max = 2;
    }
    else if( !str_cmp( loc, "resilience" ) )
    {
	type = APPLY_RESILIENCE;	cost = 100;	max = -10;
    }
    else
    {
	send_to_char( "Locations are: ac, attackroll, damroll, str, int, wis, "
		      "dex, con, resilience.\n\r", ch );
	send_to_char( "Use: 'quest improve <obj> <loc> appraise' for costs.\n\r", ch );
	return;
    }

    sn = skill_lookup( "quest improvement" );
    for( paf = obj->affected; paf; paf = paf->next )
    {
	int mod;

	if( paf->location == APPLY_AC || type == APPLY_RESILIENCE )
	    mod = -1 * paf->modifier;
	else
	    mod = paf->modifier;

	if( paf->type == sn )
	{
	    if( paf->location == type )
		cost = power( cost, 100, mod );
	    else
		cost = power( cost, 10, mod );
	}
    }

    if( argument[0] != '\0' )
    {
	charprintf( ch, "Cost to %s %s by 1 point: %d qp.\n\r",
		    ( type == APPLY_AC || type == APPLY_RESILIENCE )
		    ? "decrease" : "increase",
		    flag_string( apply_flags, &type ), cost );
	return;
    }

    if( ch->pcdata->quest->score < cost )
    {
	send_to_char( "You can't afford that improvement.\n\r", ch );
	return;
    }

    for( paf = obj->affected; paf; paf = paf->next )
    {
	if( paf->deleted || paf->type != sn
	    || paf->location != type )
	    continue;
	if( type == APPLY_AC || type == APPLY_RESILIENCE )
	{
	    if( paf->modifier <= max )
	    {
		send_to_char( "That item cannot be improved further.\n\r", ch );
		return;
	    }
	    paf->modifier--;
	}
	else
	{
	    if( paf->modifier >= max )
	    {
		send_to_char( "That item cannot be improved further.\n\r", ch );
		return;
	    }
	    paf->modifier++;
	}
	found = TRUE;
	break;
    }

    if( !found )
    {
	paf = new_affect( );

	paf->type = sn;
	paf->duration = -1;
	paf->location = type;
	if( type == APPLY_AC || type == APPLY_RESILIENCE )
	    paf->modifier = -1;
	else
	    paf->modifier = 1;
	vzero( paf->bitvector );
	paf->next = obj->affected;
	obj->affected = paf;
    }
    ch->pcdata->quest->score -= cost;
    charprintf( ch, "&gYou paid &y%d&g qp to improve &y%s&g.&n\n\r", cost,
		flag_string( apply_flags, &type ) );
    return;
}


void do_speak( CHAR_DATA *ch, const char *argument )
{
    int lang, sn;

    if( IS_NPC( ch ) )
	return;
    if( !*argument )
    {
	int pos = 10;
	char buf[32];

	send_to_char( "&gYour race can speak the following languages:\n\r"
		      "    &yCommon", ch );
	for( lang = 0; *language_flags[lang].name; ++lang )
	{
	    sprintf( buf, "%s language", language_flags[lang].name );
	    sn = skill_lookup( buf );
	    if( IS_SET( race_table[ch->race].languages,
			language_flags[lang].bit )
		|| ( sn >= 0 && ch->pcdata->learned[sn] ) )
	    {
		pos += strlen( language_flags[lang].name ) + 1;
		if( pos > 75 )
		{
		    send_to_char( "&n\n\r   &y", ch );
		    pos = 3;
		}
		charprintf( ch, " %s", language_flags[lang].name );
	    }
	}
	send_to_char( "&n\n\r", ch );
	return;
    }
    if( flag_value( &lang, language_flags, argument ) == NO_FLAG )
    {
	send_to_char( "That isn't a language.\n\r", ch );
	return;
    }
    if( ch->level < LEVEL_HERO && lang != LANG_COMMON
	&& !can_speak( ch, lang ) )
    {
	send_to_char( "You can't speak that yet.\n\r", ch );
	return;
    }
    ch->pcdata->language = lang;
    send_to_char( "You will now speak ", ch );
    send_to_char( flag_string( language_flags, &lang ), ch );
    send_to_char( ".\n\r", ch );
    return;
}


void wiznetf( CHAR_DATA *ch, int chan, int level, const char *fmt, ... )
{
    char buf[MAX_STRING_LENGTH];
    va_list args;

    va_start( args, fmt );
    vsprintf( buf, fmt, args );
    va_end( args );

    wiznet( ch, chan, level, buf );
}


void wiznet( CHAR_DATA *ch, int chan, int level, const char *string )
{
    DESCRIPTOR_DATA *d;
    char	     buf[ MAX_STRING_LENGTH ];

    strcpy( buf, "&c&4[WIZNET]&n" );

    switch( chan )
    {
    default:
	strcat( buf, "&c " );
	break;
    case WIZ_LOGINS:
	strcat( buf, "&b[LOGIN]&c " );
	break;
    case WIZ_TICKS:
	strcat( buf, "&y[TICK]&c " );
	break;
    case WIZ_DEATHS:
	strcat( buf, "&y[DEATH]&c " );
	break;
    case WIZ_LEVELS:
	strcat( buf, "&b[LEVEL]&c " );
	break;
    case WIZ_COMMANDS:
	strcat( buf, "&m[COMMAND] " );
	break;
    case WIZ_MISC:
	strcat( buf, "&w[MISC] " );
	break;
    case WIZ_CREATE:
	strcat( buf, "&g[CREATE] " );
	break;
    case WIZ_CLAN:
	strcat( buf, "&r{CLAN} " );
	break;
    case WIZ_DEBUG:
	strcat( buf, "&r<<&R!&r>> " );
	break;
    }
    strcat( buf, string );
    strcat( buf, "&n\n\r" );

    for ( d = descriptor_list; d; d = d->next )
    {
	if( !IS_SET( d->interpreter->flags, INTERPRETER_NOMESSAGE )
	    && IS_IMMORTAL( d->character )
	    && get_trust( d->character ) >= level
	    && IS_SET( CH( d )->deaf, chan )
	    && IS_SET( CH( d )->deaf, WIZ_ON )
	    && d->character != ch
	    && ( ch ? can_see( d->character, ch ) : TRUE ) )
	    send_to_char( buf, d->character );
    }
    return;
}


/* Healer code written for Merc 2.0 muds by Alander
   direct questions or comments to rtaylor@cie-2.uoregon.edu
   any use of this code must include this header */
/* I changed it, can I have my own header? please? --Sym :P */
void do_heal( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *mob;
    int cost,sn;
    SPELL_FUN *spell;
    const char *words;

    /* check for healer */
    for( mob = ch->in_room->people; mob; mob = mob->next_in_room )
    {
	if( IS_NPC( mob ) && xIS_SET( mob->act, ACT_HEALER ) )
	    break;
    }

    if( mob == NULL )
    {
	send_to_char( "You can't do that here.\n\r", ch );
	return;
    }

    if( mob->fighting != NULL )
    {
	act( "&c$N exclaims 'Can't you see i'm busy !!!'",
	     ch, NULL, mob, TO_CHAR);
	return;
    }

    if( mob->level < ch->level - 10 )
    {
	act( "&c$N says 'I can't help you, sorry'", ch, NULL, mob, TO_CHAR );
	return;
    }

    if( argument[0] == '\0' )
    {
	/* display price list */
	act( "&c$N says 'I offer the following spells:'",
	     ch, NULL, mob, TO_CHAR );
	charprintf(
	    ch, "  &ccure&g:      cure wounds &y%14d&g gold&n\n\r",
	    125	* mob->level );
	charprintf( ch, "  &cheal&g:      healing spell &y%12d&g gold&n\n\r",
		    250 * mob->level );
	send_to_char(
	    "  &cblind&g:     cure blindness        &y2000&g gold&n\n\r", ch );
	charprintf( ch, "  &cdisease&g:   cure disease &y%13d&g gold&n\n\r",
		    100 * mob->level );
	send_to_char(
	    "  &cpoison&g:    cure poison           &y2500&g gold&n\n\r", ch );
	charprintf( ch, "  &cuncurse&g:   remove hex &y%13d&g gold&n\n\r",
		    250 * mob->level );
	charprintf( ch, "  &cendurance&g: restore movement &y%9d&g gold&n\n\r",
		    12 * mob->level );
	charprintf( ch, "  &cmana&g:      mana balm &y%16d&g gold&n\n\r",
		    500 * mob->level );
	send_to_char(
	    "  &cfeast&g:     complete nourishment &y10000&g gold&n\n\r", ch );
	send_to_char( "&gType heal <type> to be healed.&n\n\r", ch );
	return;
    }

    switch( argument[0] )
    {
    case 'c' :
	spell	= spell_cure;
	sn	= skill_lookup( "cure" );
	words	= "judicandus";
	cost	= 150 * mob->level;
	break;

    case 'h' :
	spell	= spell_heal;
	sn	= skill_lookup( "heal" );
	words	= "pzar";
	cost	= 500 * mob->level;
	break;

    case 'b' :
	spell	= spell_cure_blindness;
	sn	= skill_lookup( "cure blindness" );
	words	= "judicandus noselacri";
	cost	= 2000;
	break;

    case 'd' :
	spell	= spell_lay_hands;
	sn	= skill_lookup( "lay hands" );
	words	= "judicandus eugzagz";
	cost	= 100 * mob->level;
	break;

    case 'p' :
	spell	= spell_cure_poison;
	sn	= skill_lookup("cure poison");
	words	= "judicandus sausabru";
	cost	= 2500;
	break;

    case 'u' :
	spell	= spell_remove_hex;
	sn	= skill_lookup( "remove hex" );
	words	= "candussido judifgz";
	cost	= 250 * mob->level;
	break;

    case 'e' :
	spell	= spell_endurance;
	sn	= skill_lookup( "endurance" );
	words	= "candusima";
	cost	= 12 * mob->level;
	break;

    case 'm' :
	spell	= spell_mana_balm;
	sn	= skill_lookup( "mana balm" );
	words	= "waia barw";
	cost	= 1000 * mob->level;
	break;

    case 'f':
	spell	= spell_feast;
	sn	= skill_lookup( "feast" );
	words	= "yjumagh";
	cost	= 10000;
	break;

    default :
	act( "&c$N says 'Type 'heal' for a list of spells.'",
	     ch, NULL, mob, TO_CHAR );
	return;
    }

    if( cost > ch->gold )
    {
	act( "&c$N says 'You do not have enough gold for my services.'",
	     ch, NULL, mob, TO_CHAR );
	return;
    }

    WAIT_STATE( ch, PULSE_VIOLENCE );

    ch->gold -= cost;
    act( "$n utters the words '$T'.", mob, NULL, words, TO_ROOM );

    if( sn == -1 )
	return;

    spell( sn, mob->level, mob, ch );
}


void do_bounty( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *victim;
    char arg[MAX_INPUT_LENGTH];
    int gold;

    if( IS_NPC( ch ) )
	return;
    argument = one_argument( argument, arg );

    if( arg[0] == '\0' )
    {
	send_to_char( "Usage: bounty <char> [addgold]\n\r", ch );
	return;
    }
    victim = get_char_world( ch, arg );
    if( !victim )
    {
	send_to_char( "You can't find that person.\n\r", ch );
	return;
    }
    if( argument[0] == '\0' || !is_number_special( argument ) )
    {
	if( !IS_NPC( victim ) && victim->pcdata->bounty > 0 )
	    charprintf( ch, "%s has a bounty of %s gold on their head.\n\r",
			victim->name,
			int_to_str_special( victim->pcdata->bounty ) );
	else
	    send_to_char( "Usage: bounty <char> [addgold]\n\r", ch );
	return;
    }

    if( IS_NPC( victim ) || ( !is_clan_enemy( victim, ch )
			      && !IS_IMMORTAL( ch ) ) )
    {
	send_to_char( "That person is no enemy of yours.\n\r", ch );
	return;
    }
    gold = atoi_special( argument );
    /* Minimum bounty, to stop piddly amounts being put up */
    if( gold < UMIN( 500000, ch->level * ch->level * 5 ) )
    {
	charprintf( ch, "Please put up a price of at least %s gold!\n\r",
		    int_to_str_special( UMIN( 500000,
					      ch->level * ch->level * 5 ) ) );
	return;
    }
    if( ch->gold + ch->pcdata->banked < gold )
    {
	send_to_char( "You have insufficient funds at your disposal.\n\r", ch );
	return;
    }
    ch->gold -= gold;
    if( ch->gold < 0 )
    {
	ch->pcdata->banked += ch->gold;
	ch->gold = 0;
    }
    victim->pcdata->bounty += gold;
    sprintf( arg, "%s has put a price of %s gold on the head of %s!!!",
	     ch->name, int_to_str_special( gold ), victim->name );
    talk_channel( NULL, arg, CHANNEL_INFO, "INFO" );
}


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

    if( IS_NPC( ch ) || !ch->desc )
    {
	send_to_char( "You can't do that!\n\r", ch );
	return;
    }

    if( ch->level < 2 )
    {
	send_to_char( "You aren't high enough level.\n\r", ch );
	return;
    }

    if( !strcmp( ch->name, argument ) )
    {
	charprintf( ch, "Tada, you suddenly turn into %s!\n\r", ch->name );
	return;
    }
    if( !can_quit( ch ) )
	return;

    charprintf( ch, "You slowly transform into %s!\n\r", argument );
    d = quit_char( ch );

    if( d )
    {
	d->interpreter = get_interpreter( "getName" );
	nanny_get_name( d, argument );
    }
}


#define IS_TRADE_LOCKED( _ch )	\
	( IS_SET( (_ch)->pcdata->trade->flags, TRADE_LOCKED ) \
	  || IS_SET( (_ch)->pcdata->trade->other->flags, TRADE_LOCKED ) )

/*
 * Allocate and initialise a trade object for a character.
 */
TRADE_DATA *new_trade( CHAR_DATA *ch )
{
    TRADE_DATA *tr;
    int i;

    tr = (TRADE_DATA *)alloc_mem( sizeof( TRADE_DATA ) );
    tr->ch = ch;
    tr->flags = 0;
    tr->gold = 0;
    for( i = 0; i < MAX_TRADE_OBJ; ++i )
	tr->objs[i] = NULL;
    return tr;
}


/*
 * Display an offer of trade from victim to ch.
 */
void show_trade( CHAR_DATA *ch, CHAR_DATA *victim )
{
    OBJ_DATA *obj;
    char buf[MAX_INPUT_LENGTH];
    int i;
    const char *kw;


    sprintf( buf, "&yGold: %d coins.&n\n\r", victim->pcdata->trade->gold );
    send_to_char( buf, ch );
    for( i = 0; i < MAX_TRADE_OBJ; ++i )
    {
	obj = victim->pcdata->trade->objs[i];
	if( !obj )
	    continue;
	sprintf( buf, "&gObject: %s&n\n\r", obj->short_descr );
	send_to_char( buf, ch );
    }
    if( ch == victim )
	kw = "have";
    else
	kw = "has";

    if( IS_SET( victim->pcdata->trade->flags, TRADE_AGREED ) )
	act( "&G$N $t agreed to the trade.", ch, kw, victim, TO_CHAR );
    else if( IS_SET( victim->pcdata->trade->flags, TRADE_LOCKED ) )
	act( "&G$N $t set a lock on the trade.", ch, kw, victim, TO_CHAR );
    return;
}


/*
 * Finds a trade object.
 */
OBJ_DATA *find_trade_obj( CHAR_DATA *ch, CHAR_DATA *victim, const char *name )
{
    OBJ_DATA *obj;
    int i;

    for( i = 0; i < MAX_TRADE_OBJ; ++i )
    {
	obj = victim->pcdata->trade->objs[i];
	if( obj && is_name( name, obj->name ) )
	    return obj;
    }
    return NULL;
}


/*
 * Checks that the items offered in trade still exist.
 */
bool check_trade( CHAR_DATA *ch, CHAR_DATA *victim )
{
    OBJ_DATA *obj;
    int i, nitem = 0, witem = 0;

    for( i = 0; i < MAX_TRADE_OBJ; ++i )
    {
	obj = ch->pcdata->trade->objs[i];
	if( !obj )
	    continue;
	if( obj->carried_by != ch )
	{
	    send_to_char( "An item is missing, the trade must be cancelled.\n\r", ch );
	    send_to_char( "An item is missing, the trade must be cancelled.\n\r", victim );
	    return FALSE;
	}
	if( obj->contains )
	{
	    send_to_char( "Containers must be empty for a trade, forcing withdrawal.\n\r", ch );
	    send_to_char( "Containers must be empty for a trade, forcing withdrawal.\n\r", victim );
	    return FALSE;
	}
	nitem++;
	witem += get_obj_weight( obj );
    }

    if( victim->carry_number + nitem > can_carry_n( victim )
	|| victim->carry_weight + witem > can_carry_w( victim ) )
    {
	send_to_char( "You can't carry the items!\n\r", ch );
	return FALSE;
    }

    return TRUE;
}


/*
 * Move items in trade from victim to ch.
 */
void affect_trade( CHAR_DATA *ch, CHAR_DATA *victim )
{
    OBJ_DATA *obj;
    int i;

    for( i = 0; i < MAX_TRADE_OBJ; ++i )
    {
	obj = victim->pcdata->trade->objs[i];
	if( !obj )
	    continue;

	act( "&g$n give$% $p to $N.", victim, obj, ch, TO_ALL );

	obj_from_char( obj );
	obj_to_char( obj, ch );
    }
    if( victim->pcdata->trade->gold > 0 )
	act( "&g$n give$% some gold to $N.", victim, NULL, ch, TO_ALL );
    ch->gold += victim->pcdata->trade->gold;
    return;
}


/*
 * Symposium's secure trade interface.
 */
void do_trade( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *victim;
    OBJ_DATA *obj;
    char arg[MAX_INPUT_LENGTH];
    int i;

    if( IS_NPC( ch ) )
	return;

    argument = one_argument( argument, arg );

    if( !ch->pcdata->trade )
    {
	if( total_mana( ch->mana ) < 100 + ch->level * 2 )
	{
	    send_to_char( "You need some more mana in order to initiate a trade.\n\r", ch );
	    return;
	}

	if( !str_cmp( arg, "offer" ) )
	{
	    take_generic_mana( ch, 100 + ch->level * 2 );

	    ch->pcdata->trade = new_trade( ch );
	    if( argument[0] != '\0'
		&& ( victim = get_char_room( ch, argument ) )
		&& victim != ch && !IS_NPC( victim ) )
	    {
		ch->pcdata->trade->other = new_trade( victim );
		ch->pcdata->trade->other->other = ch->pcdata->trade;

		act( "&c$n open$% an offer for a trade session with $N.",
		     ch, NULL, victim, TO_ALL );
	    }
	    else
	    {
		ch->pcdata->trade->other = NULL;

		act( "&c$n open$% an offer for a trade session.",
		     ch, NULL, NULL, TO_ALL );
	    }
	    return;
	}

	if( !str_cmp( arg, "join" ) )
	{
	    if( argument[0] == '\0'
		|| !( victim = get_char_room( ch, argument ) )
		|| IS_NPC( victim ) )
	    {
		send_to_char( "That person is not here.\n\r", ch );
		return;
	    }
	    if( victim->pcdata->trade == NULL )
	    {
		send_to_char( "They have not offered to trade.\n\r", ch );
		return;
	    }
	    if( victim->pcdata->trade->other != NULL
		&& victim->pcdata->trade->other->ch != ch )
	    {
		act( "&r$N has restricted the trade so you can't join.",
		     ch, NULL, victim, TO_CHAR );
		return;
	    }

	    take_generic_mana( ch, 100 + ch->level * 2 );

	    if( !victim->pcdata->trade->other )
	    {
		victim->pcdata->trade->other = new_trade( ch );
		victim->pcdata->trade->other->other = victim->pcdata->trade;
	    }

	    ch->pcdata->trade = victim->pcdata->trade->other;
	    ch->pcdata->trade->ch = ch;		/* sanity check */
	    act( "&c$n join$% a trade session with $N.",
		 ch, NULL, victim, TO_ALL );
	    return;
	}
	send_to_char( "Type HELP TRADE to get more information on trading securely.\n\r", ch );
	return;
    }

    if( !str_cmp( arg, "withdraw" ) || !str_cmp( arg, "leave" ) )
    {
	send_to_char( "&cYou withdraw from the trade.&n\n\r", ch );
	if( ch->pcdata->trade->other )
	{
	    victim = ch->pcdata->trade->other->ch;

	    if( victim->pcdata->trade )
	    {
		act( "&c$n withdraws from the trade.",
		     ch, NULL, victim, TO_VICT );
		victim->gold += victim->pcdata->trade->gold;
		victim->pcdata->trade = NULL;
	    }
	    free_mem( ch->pcdata->trade->other, sizeof( TRADE_DATA ) );
	}

	ch->gold += ch->pcdata->trade->gold;
	/* Note here that "victim" may not have a pointer to their trade
	 * object if they haven't accepted the deal. */
	free_mem( ch->pcdata->trade, sizeof( TRADE_DATA ) );
	ch->pcdata->trade = NULL;
	return;
    }

    if( !ch->pcdata->trade->other )
    {
	send_to_char( "No-one has accepted the offer of trade yet.\n\r", ch );
	return;
    }

    /* From here down a trade exists */
    victim = ch->pcdata->trade->other->ch;

    if( !str_prefix( arg, "examine" ) )
    {
	CHAR_DATA *tmpch;

	if( argument[0] == '\0' )
	{
	    send_to_char( "&mYour offer is as follows:\n\r", ch );
	    show_trade( ch, ch );
	    act( "&m$N's offer is as follows:", ch, NULL, victim, TO_CHAR );
	    show_trade( ch, victim );
	    return;
	}

	if( !( obj = find_trade_obj( ch, victim, argument ) )
	    && !( obj = find_trade_obj( ch, ch, argument ) ) )
	{
	    send_to_char( "You can't find that object.\n\r", ch );
	    return;
	}

	tmpch = obj->carried_by;
	if( tmpch != ch && tmpch != victim )
	{
	    send_to_char( "Object moved, ending trade.\n\r", ch );
	    send_to_char( "An object moved, forcing an end to the trade.\n\r",
			  victim );
	    do_trade( ch, "withdraw" );
	    return;
	}

	if( tmpch != ch )
	{
	    obj_from_char( obj );
	    obj_to_char( obj, ch );
	}

	send_to_char( "&gYou examine the object to determine its worth...&n\n\r", ch );
	do_examine( ch, obj->name );

	if( tmpch != ch )
	{
	    obj_from_char( obj );
	    obj_to_char( obj, tmpch );
	}

	return;
    }

    if( !str_prefix( arg, "identify" ) )
    {
	if( argument[0] == '\0' )
	{
	    send_to_char( "Which item did you want to identify?\n\r", ch );
	    return;
	}

	if( !( obj = find_trade_obj( ch, victim, argument ) )
	    && !( obj = find_trade_obj( ch, ch, argument ) ) )
	{
	    send_to_char( "You can't find that object.\n\r", ch );
	    return;
	}

	if( ch->gold < 50 + ch->level * 2 )
	{
	    send_to_char( "You have insufficient funds for identify.\n\r",
			  ch );
	    return;
	}

	ch->gold -= 50 + ch->level * 2;

	send_to_char( "&gYou examine the object to determine its worth...&n\n\r", ch );
	spell_identify( skill_lookup( "identify" ), ch->level, ch, obj );

	return;
    }

    if( !str_prefix( arg, "add" ) )
    {
	if( IS_TRADE_LOCKED( ch ) )
	{
	    send_to_char( "&rYou cannot make a change to a locked trade.&n\n\r", ch );
	    return;
	}

	if( is_number( argument ) )
	{
	    i = atoi( argument );
	    if( i <= 0 || i > ch->gold )
	    {
		send_to_char( "You have insufficient funds for that.\n\r", ch );
		return;
	    }
	    ch->pcdata->trade->gold += i;
	    ch->gold -= i;
	    sprintf( arg, "&yYou add %d gold to your trade.&n\n\r", i );
	    send_to_char( arg, ch );
	    sprintf( arg, "&y$n adds %d to $s trade.", i );
	    act( arg, ch, NULL, victim, TO_VICT );
	    return;
	}

	if( !( obj = get_obj_carry( ch, argument ) ) )
	{
	    send_to_char( "You can't find that item.\n\r", ch );
	    return;
	}
	if( !can_drop_obj( ch, obj ) || obj->contains )
	{
	    send_to_char( "You cannot offer that item.\n\r", ch );
	    return;
	}

	for( i = 0; i < MAX_TRADE_OBJ; ++i )
	{
	    if( ch->pcdata->trade->objs[i] == NULL )
	    {
		ch->pcdata->trade->objs[i] = obj;
		act( "&gYou add $p to the trade.", ch, obj, victim, TO_CHAR );
		act( "&g$n adds $p to the trade.", ch, obj, victim, TO_VICT );
		return;
	    }
	}

	send_to_char( "You have used all of your slots for items, sorry.\n\r", ch );
	return;
    }

    if( !str_prefix( arg, "remove" ) )
    {
	if( IS_TRADE_LOCKED( ch ) )
	{
	    send_to_char( "&rYou cannot make a change to a locked trade.&n\n\r", ch );
	    return;
	}

	if( is_number( argument ) )
	{
	    i = atoi( argument );
	    if( i <= 0 || i > ch->pcdata->trade->gold )
	    {
		send_to_char( "You cannot remove that amount!\n\r", ch );
		return;
	    }
	    ch->pcdata->trade->gold -= i;
	    ch->gold += i;
	    sprintf( arg, "&yYou remove %d gold from your trade.&n\n\r", i );
	    send_to_char( arg, ch );
	    sprintf( arg, "&y$n removes %d from $s trade.", i );
	    act( arg, ch, NULL, victim, TO_VICT );
	    return;
	}

	if( !( obj = find_trade_obj( ch, ch, argument ) ) )
	{
	    send_to_char( "You can't find that item.\n\r", ch );
	    return;
	}

	for( i = 0; i < MAX_TRADE_OBJ; ++i )
	{
	    if( obj == ch->pcdata->trade->objs[i] )
		ch->pcdata->trade->objs[i] = NULL;
	}

	act( "&gYou remove $p from the trade.", ch, obj, victim, TO_CHAR );
	act( "&g$n removes $p from the trade.", ch, obj, victim, TO_VICT );
	return;
    }

    if( !str_cmp( arg, "lock" ) )
    {
	if( IS_SET( ch->pcdata->trade->flags, TRADE_LOCKED ) )
	{
	    REMOVE_BIT( ch->pcdata->trade->flags, TRADE_LOCKED );
	    REMOVE_BIT( ch->pcdata->trade->flags, TRADE_AGREED );
	    REMOVE_BIT( victim->pcdata->trade->flags, TRADE_AGREED );
	    send_to_char( "&GYou unlock the trade.&n\n\r", ch );
	    act( "&G$n unlocks the trade.", ch, NULL, victim, TO_VICT );
	    return;
	}

	send_to_char( "&GYou lock the trade.&n\n\r", ch );
	act( "&G$n locks the trade.", ch, NULL, victim, TO_VICT );
	SET_BIT( ch->pcdata->trade->flags, TRADE_LOCKED );
	return;
    }

    if( !str_cmp( arg, "agree" ) || !str_cmp( arg, "accept" ) )
    {
	if( !IS_SET( ch->pcdata->trade->flags, TRADE_LOCKED )
	    || !IS_SET( victim->pcdata->trade->flags, TRADE_LOCKED ) )
	{
	    send_to_char( "&rBoth parties must lock the trade first.&n\n\r", ch );
	    return;
	}

	if( IS_SET( ch->pcdata->trade->flags, TRADE_AGREED ) )
	{
	    REMOVE_BIT( ch->pcdata->trade->flags, TRADE_AGREED );
	    send_to_char( "&GYou withdraw your agreement to the trade.&n\n\r",
			  ch );
	    act( "&G$n removes $s agreement to the trade.",
		 ch, NULL, victim, TO_VICT );
	    return;
	}

	if( !IS_SET( victim->pcdata->trade->flags, TRADE_AGREED ) )
	{
	    SET_BIT( ch->pcdata->trade->flags, TRADE_AGREED );
	    send_to_char( "&GYou signify your agreement for this trade.&n\n\r", ch );
	    act( "&G$n has agreed to the trade.", ch, NULL, victim, TO_VICT );
	    return;
	}

	if( check_trade( ch, victim ) && check_trade( victim, ch ) )
	{
	    affect_trade( ch, victim );
	    affect_trade( victim, ch );
	    act( "&GYou shake $N's hand and seal the deal.",
		 ch, NULL, victim, TO_CHAR );
	    act( "&G$n closes the deal, you shake $s hand and seal the deal.",
		 ch, NULL, victim, TO_VICT );
	    act( "&G$n has just closed the deal with $N.",
		 ch, NULL, victim, TO_NOTVICT );
	}
	else
	{
	    ch->gold += ch->pcdata->trade->gold;
	    victim->gold += victim->pcdata->trade->gold;
	}

	free_mem( ch->pcdata->trade->other, sizeof( TRADE_DATA ) );
	free_mem( ch->pcdata->trade, sizeof( TRADE_DATA ) );
	ch->pcdata->trade = NULL;
	victim->pcdata->trade = NULL;
	return;
    }

    send_to_char( "Type HELP TRADE to get more information on trading securely.\n\r", ch );
    return;
}