mud/lua/
mud/player/
/***************************************************************************
 *  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 ) );
void	note_remove	args( ( CHAR_DATA *ch, NOTE_DATA *pnote ) );
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( ch->name, pnote->to_list ) )
        return TRUE;

    return FALSE;
}



void note_attach( CHAR_DATA *ch )
{
    NOTE_DATA *pnote;

    if ( ch->pnote != NULL )
        return;

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

    pnote->next		= NULL;
    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 )
{
    char to_new[MAX_INPUT_LENGTH];
    char to_one[MAX_INPUT_LENGTH];
    FILE *fp;
    NOTE_DATA *prev;
    char *to_list;

    /*
     * 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 != NULL; prev = prev->next ) {
            if ( prev->next == pnote )
                break;
        }

        if ( prev == NULL ) {
            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->next	= note_free;
    note_free	= pnote;

    /*
     * Rewrite entire list.
     */
    fclose( fpReserve );
    if ( ( fp = fopen( NOTE_FILE, "w" ) ) == NULL ) {
        perror( NOTE_FILE );
    } else {
        for ( pnote = note_list; pnote != NULL; pnote = pnote->next ) {
            fprintf( fp, "Sender  %s~\nDate    %s~\nTo      %s~\nSubject %s~\nText\n%s~\n\n",
                     pnote->sender,
                     pnote->date,
                     pnote->to_list,
                     pnote->subject,
                     pnote->text
                   );
        }
        fclose( fp );
    }
    fpReserve = fopen( NULL_FILE, "r" );
    return;
}



void do_note( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];
    char arg[MAX_INPUT_LENGTH];
    NOTE_DATA *pnote;
    int vnum;
    int anum;

    if ( IS_NPC(ch) )
        return;

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

    if ( !str_cmp( arg, "list" ) ) {
        vnum = 0;
        for ( pnote = note_list; pnote != NULL; pnote = pnote->next ) {
            if ( is_note_to( ch, pnote ) ) {
                sprintf( buf, "[%3d] %s: %s\r\n",
                         vnum, pnote->sender, pnote->subject );
                send_to_char( buf, ch );
                vnum++;
            }
        }
        return;
    }

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

        if ( !str_cmp( argument, "all" ) ) {
            fAll = TRUE;
            anum = 0;
        } else if ( is_number( argument ) ) {
            fAll = FALSE;
            anum = atoi( argument );
        } else {
            send_to_char( "Note read which number?\r\n", ch );
            return;
        }

        vnum = 0;
        for ( pnote = note_list; pnote != NULL; pnote = pnote->next ) {
            if ( is_note_to( ch, pnote ) && ( vnum++ == anum || fAll ) ) {
                sprintf( buf, "[%3d] %s: %s\r\n%s\r\nTo: %s\r\n",
                         vnum - 1,
                         pnote->sender,
                         pnote->subject,
                         pnote->date,
                         pnote->to_list
                       );
                send_to_char( buf, ch );
                send_to_char( pnote->text, ch );
                return;
            }
        }

        send_to_char( "No such note.\r\n", ch );
        return;
    }

    if ( !str_cmp( arg, "+" ) ) {
        note_attach( ch );
        strcpy( buf, ch->pnote->text );
        if ( strlen(buf) + strlen(argument) >= MAX_STRING_LENGTH - 4 ) {
            send_to_char( "Note too long.\r\n", ch );
            return;
        }

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

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

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

    if ( !str_cmp( arg, "clear" ) ) {
        if ( ch->pnote != NULL ) {
            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->next	= note_free;
            note_free		= ch->pnote;
            ch->pnote		= NULL;
        }

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

    if ( !str_cmp( arg, "show" ) ) {
        if ( ch->pnote == NULL ) {
            send_to_char( "You have no note in progress.\r\n", ch );
            return;
        }

        sprintf( buf, "%s: %s\r\nTo: %s\r\n",
                 ch->pnote->sender,
                 ch->pnote->subject,
                 ch->pnote->to_list
               );
        send_to_char( buf, ch );
        send_to_char( ch->pnote->text, ch );
        return;
    }

    if ( !str_cmp( arg, "post" ) ) {
        FILE *fp;
        char *strtime;

        if ( ch->pnote == NULL ) {
            send_to_char( "You have no note in progress.\r\n", ch );
            return;
        }

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

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

        fclose( fpReserve );
        if ( ( fp = fopen( NOTE_FILE, "a" ) ) == NULL ) {
            perror( NOTE_FILE );
        } else {
            fprintf( fp, "Sender  %s~\nDate    %s~\nTo      %s~\nSubject %s~\nText\n%s~\n\n",
                     pnote->sender,
                     pnote->date,
                     pnote->to_list,
                     pnote->subject,
                     pnote->text
                   );
            fclose( fp );
        }
        fpReserve = fopen( NULL_FILE, "r" );

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

    if ( !str_cmp( arg, "remove" ) ) {
        if ( !is_number( argument ) ) {
            send_to_char( "Note remove which number?\r\n", ch );
            return;
        }

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

        send_to_char( "No such note.\r\n", ch );
        return;
    }

    send_to_char( "Huh?  Type 'help note' for usage.\r\n", ch );
    return;
}



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

    if ( argument[0] == '\0' ) {
        sprintf( buf, "%s what?\r\n", verb );
        buf[0] = UPPER(buf[0]);
        return;
    }

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

    REMOVE_BIT(ch->deaf, channel);

    switch ( channel ) {
    default:
        sprintf( buf, "You %s '%s'.\r\n", verb, argument );
        send_to_char( buf, ch );
        sprintf( buf, "$n %ss '$t'.",     verb );
        break;

    case CHANNEL_IMMTALK:
        sprintf( buf, "$n: $t." );
        position	= ch->position;
        ch->position	= POS_STANDING;
        act( buf, ch, argument, NULL, TO_CHAR );
        ch->position	= position;
        break;
    }

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

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

        if ( d->connected == CON_PLAYING
                &&   vch != ch
                &&  !IS_SET(och->deaf, channel) ) {
            if ( channel == CHANNEL_IMMTALK && !IS_HERO(och) )
                continue;
            if ( channel == CHANNEL_YELL
                    &&   vch->in_room->area != ch->in_room->area )
                continue;

            position		= vch->position;
            if ( channel != CHANNEL_SHOUT && channel != CHANNEL_YELL )
                vch->position	= POS_STANDING;
            act( buf, ch, argument, vch, TO_VICT );
            vch->position	= position;
        }
    }

    return;
}



void do_auction( CHAR_DATA *ch, char *argument )
{
    talk_channel( ch, argument, CHANNEL_AUCTION, "auction" );
    return;
}



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



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



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



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



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



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



void do_immtalk( CHAR_DATA *ch, char *argument )
{
    talk_channel( ch, argument, CHANNEL_IMMTALK, "immtalk" );
    return;
}



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

    act( "$n says '$T'.", ch, NULL, argument, TO_ROOM );
    act( "You say '$T'.", ch, NULL, argument, TO_CHAR );
    return;
}



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

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

    argument = one_argument( argument, arg );

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

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

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

    act( "You tell $N '$t'.", ch, argument, victim, TO_CHAR );
    position		= victim->position;
    victim->position	= POS_STANDING;
    act( "$n tells you '$t'.", ch, argument, victim, TO_VICT );
    victim->position	= position;
    victim->reply	= ch;

    return;
}



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

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

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

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

    act( "You tell $N '$t'.", ch, argument, victim, TO_CHAR );
    position		= victim->position;
    victim->position	= POS_STANDING;
    act( "$n tells you '$t'.", ch, argument, victim, TO_VICT );
    victim->position	= position;
    victim->reply	= ch;

    return;
}



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

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

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

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

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

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



/*
 * All the posing stuff.
 */
struct	pose_table_type {
    char *	message[2*MAX_CLASS];
};

const	struct	pose_table_type	pose_table	[]	= {
    {
        {
            "You sizzle with energy.",
            "$n sizzles with energy.",
            "You feel very holy.",
            "$n looks very holy.",
            "You perform a small card trick.",
            "$n performs a small card trick.",
            "You show your bulging muscles.",
            "$n shows $s bulging muscles."
        }
    },

    {
        {
            "You turn into a butterfly, then return to your normal shape.",
            "$n turns into a butterfly, then returns to $s normal shape.",
            "You nonchalantly turn wine into water.",
            "$n nonchalantly turns wine into water.",
            "You wiggle your ears alternately.",
            "$n wiggles $s ears alternately.",
            "You crack nuts between your fingers.",
            "$n cracks nuts between $s fingers."
        }
    },

    {
        {
            "Blue sparks fly from your fingers.",
            "Blue sparks fly from $n's fingers.",
            "A halo appears over your head.",
            "A halo appears over $n's head.",
            "You nimbly tie yourself into a knot.",
            "$n nimbly ties $mself into a knot.",
            "You grizzle your teeth and look mean.",
            "$n grizzles $s teeth and looks mean."
        }
    },

    {
        {
            "Little red lights dance in your eyes.",
            "Little red lights dance in $n's eyes.",
            "You recite words of wisdom.",
            "$n recites words of wisdom.",
            "You juggle with daggers, apples, and eyeballs.",
            "$n juggles with daggers, apples, and eyeballs.",
            "You hit your head, and your eyes roll.",
            "$n hits $s head, and $s eyes roll."
        }
    },

    {
        {
            "A slimy green monster appears before you and bows.",
            "A slimy green monster appears before $n and bows.",
            "Deep in prayer, you levitate.",
            "Deep in prayer, $n levitates.",
            "You steal the underwear off every person in the room.",
            "Your underwear is gone!  $n stole it!",
            "Crunch, crunch -- you munch a bottle.",
            "Crunch, crunch -- $n munches a bottle."
        }
    },

    {
        {
            "You turn everybody into a little pink elephant.",
            "You are turned into a little pink elephant by $n.",
            "An angel consults you.",
            "An angel consults $n.",
            "The dice roll ... and you win again.",
            "The dice roll ... and $n wins again.",
            "... 98, 99, 100 ... you do pushups.",
            "... 98, 99, 100 ... $n does pushups."
        }
    },

    {
        {
            "A small ball of light dances on your fingertips.",
            "A small ball of light dances on $n's fingertips.",
            "Your body glows with an unearthly light.",
            "$n's body glows with an unearthly light.",
            "You count the money in everyone's pockets.",
            "Check your money, $n is counting it.",
            "Arnold Schwarzenegger admires your physique.",
            "Arnold Schwarzenegger admires $n's physique."
        }
    },

    {
        {
            "Smoke and fumes leak from your nostrils.",
            "Smoke and fumes leak from $n's nostrils.",
            "A spot light hits you.",
            "A spot light hits $n.",
            "You balance a pocket knife on your tongue.",
            "$n balances a pocket knife on your tongue.",
            "Watch your feet, you are juggling granite boulders.",
            "Watch your feet, $n is juggling granite boulders."
        }
    },

    {
        {
            "The light flickers as you rap in magical languages.",
            "The light flickers as $n raps in magical languages.",
            "Everyone levitates as you pray.",
            "You levitate as $n prays.",
            "You produce a coin from everyone's ear.",
            "$n produces a coin from your ear.",
            "Oomph!  You squeeze water out of a granite boulder.",
            "Oomph!  $n squeezes water out of a granite boulder."
        }
    },

    {
        {
            "Your head disappears.",
            "$n's head disappears.",
            "A cool breeze refreshes you.",
            "A cool breeze refreshes $n.",
            "You step behind your shadow.",
            "$n steps behind $s shadow.",
            "You pick your teeth with a spear.",
            "$n picks $s teeth with a spear."
        }
    },

    {
        {
            "A fire elemental singes your hair.",
            "A fire elemental singes $n's hair.",
            "The sun pierces through the clouds to illuminate you.",
            "The sun pierces through the clouds to illuminate $n.",
            "Your eyes dance with greed.",
            "$n's eyes dance with greed.",
            "Everyone is swept off their foot by your hug.",
            "You are swept off your feet by $n's hug."
        }
    },

    {
        {
            "The sky changes color to match your eyes.",
            "The sky changes color to match $n's eyes.",
            "The ocean parts before you.",
            "The ocean parts before $n.",
            "You deftly steal everyone's weapon.",
            "$n deftly steals your weapon.",
            "Your karate chop splits a tree.",
            "$n's karate chop splits a tree."
        }
    },

    {
        {
            "The stones dance to your command.",
            "The stones dance to $n's command.",
            "A thunder cloud kneels to you.",
            "A thunder cloud kneels to $n.",
            "The Grey Mouser buys you a beer.",
            "The Grey Mouser buys $n a beer.",
            "A strap of your armor breaks over your mighty thews.",
            "A strap of $n's armor breaks over $s mighty thews."
        }
    },

    {
        {
            "The heavens and grass change colour as you smile.",
            "The heavens and grass change colour as $n smiles.",
            "The Burning Man speaks to you.",
            "The Burning Man speaks to $n.",
            "Everyone's pocket explodes with your fireworks.",
            "Your pocket explodes with $n's fireworks.",
            "A boulder cracks at your frown.",
            "A boulder cracks at $n's frown."
        }
    },

    {
        {
            "Everyone's clothes are transparent, and you are laughing.",
            "Your clothes are transparent, and $n is laughing.",
            "An eye in a pyramid winks at you.",
            "An eye in a pyramid winks at $n.",
            "Everyone discovers your dagger a centimeter from their eye.",
            "You discover $n's dagger a centimeter from your eye.",
            "Mercenaries arrive to do your bidding.",
            "Mercenaries arrive to do $n's bidding."
        }
    },

    {
        {
            "A black hole swallows you.",
            "A black hole swallows $n.",
            "Valentine Michael Smith offers you a glass of water.",
            "Valentine Michael Smith offers $n a glass of water.",
            "Where did you go?",
            "Where did $n go?",
            "Four matched Percherons bring in your chariot.",
            "Four matched Percherons bring in $n's chariot."
        }
    },

    {
        {
            "The world shimmers in time with your whistling.",
            "The world shimmers in time with $n's whistling.",
            "The great god Mota gives you a staff.",
            "The great god Mota gives $n a staff.",
            "Click.",
            "Click.",
            "Atlas asks you to relieve him.",
            "Atlas asks $n to relieve him."
        }
    }
};



void do_pose( CHAR_DATA *ch, char *argument )
{
    int level;
    int pose;

    if ( IS_NPC(ch) )
        return;

    level = UMIN( ch->level, sizeof(pose_table) / sizeof(pose_table[0]) - 1 );
    pose  = number_range(0, level);

    act( pose_table[pose].message[2*ch->class+0], ch, NULL, NULL, TO_CHAR );
    act( pose_table[pose].message[2*ch->class+1], ch, NULL, NULL, TO_ROOM );

    return;
}



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



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



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



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



void do_qui( CHAR_DATA *ch, char *argument )
{
    send_to_char( "If you want to QUIT, you have to spell it out.\r\n", 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( "No way! You are fighting.\r\n", ch );
        return;
    }

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

    send_to_char(
        "Had I but time--as this fell sergeant, Death,\r\nIs strict in his arrest--O, I could tell you--\r\nBut let it be.\r\n",
        ch );
    act( "$n has left the game.", ch, NULL, NULL, TO_ROOM );
    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 != NULL )
        close_socket( d );

    return;
}



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

    if ( ch->level < 2 ) {
        send_to_char( "You must be at least second level to save.\r\n", ch );
        return;
    }

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



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

    one_argument( argument, arg );

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

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

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

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

    if ( ( ch->level - victim->level < -5 || ch->level - victim->level >  5 )
            &&   !IS_HERO(ch) ) {
        send_to_char( "You are not of the right caliber to follow.\r\n", ch );
        return;
    }

    if ( ch->master != NULL )
        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?\r\n", ch );
        return;
    }

    if ( IS_AFFECTED( ch, AFF_CHARM ) ) {
        send_to_char( "You feel like taking, not giving, orders.\r\n", 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.\r\n", ch );
            return;
        }

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

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

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



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

    one_argument( argument, arg );

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

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

        for ( gch = char_list; gch != NULL; gch = gch->next ) {
            if ( is_same_group( gch, ch ) ) {
                sprintf( buf,
                         "[%2d %s] %-16s %4d/%4d hp %4d/%4d mana %4d/%4d mv %5d xp\r\n",
                         gch->level,
                         IS_NPC(gch) ? "Mob" : class_table[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 ) ) == NULL ) {
        send_to_char( "They aren't here.\r\n", ch );
        return;
    }

    if ( ch->master != NULL || ( ch->leader != NULL && ch->leader != ch ) ) {
        send_to_char( "But you are following someone else!\r\n", 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;
    }

    if ( ch->level - victim->level < -5
            ||   ch->level - victim->level >  5 ) {
        act( "$N cannot join $n's group.",     ch, NULL, victim, TO_NOTVICT );
        act( "You cannot join $n's group.",    ch, NULL, victim, TO_VICT    );
        act( "$N cannot join 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?\r\n", ch );
        return;
    }

    amount = atoi( arg );

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

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

    if ( ch->gold < amount ) {
        send_to_char( "You don't have that much gold.\r\n", 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.\r\n", ch );
        return;
    }

    share = amount / members;
    extra = amount % members;

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

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

    sprintf( buf,
             "You split %d gold coins.  Your share is %d gold coins.\r\n",
             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?\r\n", ch );
        return;
    }

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

    /*
     * Note use of send_to_char, so gtell works on sleepers.
     */
    sprintf( buf, "%s tells the group '%s'.\r\n", 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;
}