LOP/
LOP/area/
LOP/boards/
LOP/channels/
LOP/clans/
LOP/classes/
LOP/color/
LOP/councils/
LOP/deity/
LOP/races/
LOP/src/specials/
/*****************************************************************************
 * DikuMUD (C) 1990, 1991 by:                                                *
 *   Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen,   *
 *   and Katja Nyboe.                                                        *
 *---------------------------------------------------------------------------*
 * MERC 2.1 (C) 1992, 1993 by:                                               *
 *   Michael Chastain, Michael Quan, and Mitchell Tse.                       *
 *---------------------------------------------------------------------------*
 * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider.                    *
 *   Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
 *         gorog, Grishnakh, Nivek, Tricops, and Fireblade.                  *
 *---------------------------------------------------------------------------*
 * SMAUG 1.7 FUSS by: Samson and others of the SMAUG community.              *
 *                    Their contributions are greatly appreciated.           *
 *---------------------------------------------------------------------------*
 * LoP (C) 2006, 2007, 2008 by: the LoP team.                                *
 *---------------------------------------------------------------------------*
 *                     Low-level communication module                        *
 *****************************************************************************/

#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include "h/mud.h"
#include "h/mccp.h"
#include "h/sha256.h"

/* Socket and TCP/IP stuff. */
#ifdef WIN32
   #include <io.h>
   #include <winsock2.h>
   #undef EINTR
   #undef EMFILE
   #define EINTR WSAEINTR
   #define EMFILE WSAEMFILE
   #define EWOULDBLOCK WSAEWOULDBLOCK
   #define MAXHOSTNAMELEN 32

   #define  TELOPT_ECHO '\x01'
   #define  NOP         '\xF1'
   #define  GA          '\xF9'
   #define  SB          '\xFA'
   #define  WILL        '\xFB'
   #define  WONT        '\xFC'
   #define  DO          '\xFD'
   #define  DONT        '\xFE'
   #define  IAC         '\xFF'

   void bailout( void );
   void shutdown_checkpoint( void );
#else
   #include <sys/socket.h>
   #include <netinet/in.h>
   #include <netinet/in_systm.h>
   #include <netinet/ip.h>
   #include <arpa/inet.h>
   #include <arpa/telnet.h>
   #include <netdb.h>
#endif

#ifdef IMC
   void imc_delete_info( void );
   void free_imcdata( bool complete );
#endif

char *show_big_nums( int millions, int amount );

extern bool sneaking_char;

bool check_race_bans( int iRace );
bool check_class_bans( int iClass );
bool valid_help( char *argument );
void show_unread_notes( CHAR_DATA *ch );
void check_auction( CHAR_DATA *ch );
void free_all_fnames( void );
void free_all_fish( void );
void update_transfer( int type, int size );
void free_transfer( void );
void nanny( DESCRIPTOR_DATA *d, char *argument );
bool multi_check( DESCRIPTOR_DATA *host, bool full );
bool is_reserved_name( char *name );
bool is_fighting( CHAR_DATA *ch );
void dispose_ban( BAN_DATA *ban, int type );
void close_all_areas( void );
void free_hints( void );
void free_commands( void );
void free_prog_actlists( void );
void free_boards( void );
void free_skills( void );
void free_socials( void );
void free_teleports( void );
void free_tongues( void );
void free_helps( void );
void free_all_news( void );
void free_hightables( void );
void free_all_calendarinfo( void );
void free_all_mwresets( void );
void free_all_channels( void );
void free_all_reserved( void );
void free_all_bti( void );
void free_all_banks( void );
void free_rewards( void );
void free_bans( void );
void free_clans( void );
void free_morphs( void );
void free_deities( void );
void free_councils( void );
void free_specfuns( void );

const char echo_off_str[] = { IAC, WILL, TELOPT_ECHO, '\0' };
const char echo_on_str[] = { IAC, WONT, TELOPT_ECHO, '\0' };
const char go_ahead_str[] = { IAC, GA, '\0' };
char nop_str[] = { IAC, NOP, '\0' };

char *keep_alive_msg[] =
{
   "This mud still has along way to go, before its completed.\r\n",
   "Feel free to make suggestions about ways to make the game better.\r\n",
   "Keeping link alive.\r\n"
};

void auth_maxdesc( int *md, fd_set *ins, fd_set *outs, fd_set *excs );
void auth_check( fd_set *ins, fd_set *outs, fd_set *excs );
void set_auth( DESCRIPTOR_DATA *d );
void kill_auth( DESCRIPTOR_DATA *d );
void save_sysdata( void );

/* Global variables. */
DESCRIPTOR_DATA *first_descriptor, *last_descriptor;
DESCRIPTOR_DATA *d_next;   /* Next descriptor in loop */
int num_descriptors;
bool mud_down; /* Shutdown       */
bool service_shut_down; /* Shutdown by operator closing down service */
time_t boot_time;
HOUR_MIN_SEC set_boot_time_struct;
HOUR_MIN_SEC *set_boot_time;
char str_boot_time[MIL];
char lastplayercmd[MIL * 2];
time_t current_time; /* Time of this pulse      */
int control;   /* Controlling descriptor  */
int newdesc;   /* New descriptor    */
fd_set in_set; /* Set of desc's for reading  */
fd_set out_set;   /* Set of desc's for writing  */
fd_set exc_set;   /* Set of desc's with errors  */
int maxdesc;
char *alarm_section = "(unknown)";

/* OS-dependent local functions. */
void game_loop( void );
void new_descriptor( void );
bool read_from_descriptor( DESCRIPTOR_DATA *d );
bool write_to_descriptor( DESCRIPTOR_DATA *d, char *txt, int length );

/* Other local functions (OS-independent). */
bool check_parse_name( char *name, bool newchar );
bool check_reconnect( DESCRIPTOR_DATA *d, char *name, bool fConn );
bool check_playing( DESCRIPTOR_DATA *d, char *name, bool kick );
int main( int argc, char **argv );
bool flush_buffer( DESCRIPTOR_DATA *d, bool fPrompt );
void read_from_buffer( DESCRIPTOR_DATA *d );
void stop_idling( CHAR_DATA *ch );
void free_desc( DESCRIPTOR_DATA *d );
void display_prompt( DESCRIPTOR_DATA *d );
void set_pager_input( DESCRIPTOR_DATA *d, char *argument );
bool pager_output( DESCRIPTOR_DATA *d );

int port;

/*
 * Clean all memory on exit to help find leaks
 * Yeah I know, one big ugly function -Druid
 * Added to AFKMud by Samson on 5-8-03.
 */
void cleanup_memory( void )
{
   int hash, loopa;
   CHAR_DATA *character;
   OBJ_DATA *object;
   DESCRIPTOR_DATA *desc, *desc_next;

#ifdef IMC
   fprintf( stderr, "%s", "IMC2 Data.\n" );
   free_imcdata( true );
   imc_delete_info( );
#endif

   fprintf( stderr, "%s", "Ban Data.\n" );
   free_bans( );

   fprintf( stderr, "%s", "Morph Data.\n" );
   free_morphs( );

   fprintf( stderr, "%s", "Commands.\n" );
   free_commands( );

   fprintf( stderr, "%s", "Deities.\n" );
   free_deities( );

   fprintf( stderr, "%s", "Clans.\n" );
   free_clans( );

   fprintf( stderr, "%s", "Councils.\n" );
   free_councils( );

   fprintf( stderr, "%s", "Socials.\n" );
   free_socials( );

   fprintf( stderr, "%s", "Helps.\n" );
   free_helps( );

   fprintf( stderr, "%s", "News.\n" );
   free_all_news( );

   fprintf( stderr, "%s", "HighScores.\n" );
   free_hightables( );

   fprintf( stderr, "%s", "Calendar.\n" );
   free_all_calendarinfo( );

   fprintf( stderr, "%s", "Mud Wide Resets.\n" );
   free_all_mwresets( );

   fprintf( stderr, "%s", "Reserved.\n" );
   free_all_reserved( );

   fprintf( stderr, "%s", "Bugs, Typos, and Ideas.\n" );
   free_all_bti( );

   fprintf( stderr, "%s", "Banks.\n" );
   free_all_banks( );

   fprintf( stderr, "%s", "Rewards.\n" );
   free_rewards( );

   fprintf( stderr, "%s", "Languages.\n" );
   free_tongues( );

   fprintf( stderr, "%s", "Hints.\n" );
   free_hints( );

   fprintf( stderr, "%s", "Boards.\n" );
   free_boards( );

   fprintf( stderr, "%s", "Whacking supermob.\n" );
   if( supermob )
   {
      char_from_room( supermob );
      UNLINK( supermob, first_char, last_char, next, prev );
      free_char( supermob );
   }

   fprintf( stderr, "%s", "Fish Names.\n" );
   free_all_fnames( );

   fprintf( stderr, "%s", "Fish.\n" );
   free_all_fish( );

   fprintf( stderr, "%s", "Objects.\n" );
   while( ( object = last_object ) )
      extract_obj( object );

   fprintf( stderr, "%s", "Characters.\n" );
   clean_char_queue( );
   while( ( character = last_char ) )
      extract_char( character, true );
   clean_char_queue( );

   fprintf( stderr, "%s", "Descriptors.\n" );
   for( desc = first_descriptor; desc; desc = desc_next )
   {
      desc_next = desc->next;
      UNLINK( desc, first_descriptor, last_descriptor, next, prev );
      free_desc( desc );
   }

   fprintf( stderr, "%s", "Races.\n" );
   for( hash = 0; hash < MAX_RACE; hash++ )
   {
      if( !race_table[hash] )
         continue;
      for( loopa = 0; loopa < MAX_WHERE_NAME; loopa++ )
         STRFREE( race_table[hash]->where_name[loopa] );
      STRFREE( race_table[hash]->name );
      DISPOSE( race_table[hash] );
   }

   fprintf( stderr, "%s", "Classes.\n" );
   for( hash = 0; hash < MAX_CLASS; hash++ )
   {
      if( !class_table[hash] )
         continue;
      STRFREE( class_table[hash]->name );
      DISPOSE( class_table[hash] );
   }

   fprintf( stderr, "%s", "Teleport Data.\n" );
   free_teleports( );

   fprintf( stderr, "%s", "Area Data Tables.\n" );
   close_all_areas( );

   fprintf( stderr, "%s", "System data.\n" );
   STRFREE( sysdata.mud_name );

   fprintf( stderr, "%s", "Skills and Herbs.\n" );
   free_skills( );

   fprintf( stderr, "%s", "Mudprog act lists.\n" );
   free_prog_actlists( );

   fprintf( stderr, "%s", "Specfun lists.\n" );
   free_specfuns( );

   fprintf( stderr, "%s", "Globals.\n" );
   STRFREE( ranged_target_name );
   free_transfer( );

   fprintf( stderr, "%s", "Channels.\n" );
   free_all_channels( );

   fprintf( stderr, "%s", "Checking string hash for leftovers.\n" );
   {
      for( hash = 0; hash < 1024; hash++ )
         hash_dump( hash );
   }

   fprintf( stderr, "%s", "Cleanup complete, exiting.\n" );
}  /* cleanup memory */

void init_socket( void )
{
   struct sockaddr_in sa;
   int x = 1;

   if( ( control = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 )
   {
      perror( "Init_socket: socket" );
      exit( 1 );
   }

   if( setsockopt( control, SOL_SOCKET, SO_REUSEADDR, ( void * )&x, sizeof( x ) ) < 0 )
   {
      perror( "Init_socket: SO_REUSEADDR" );
      close( control );
      exit( 1 );
   }

   memset( &sa, '\0', sizeof( sa ) );
   sa.sin_family = AF_INET;
   sa.sin_port = htons( port );

   if( bind( control, ( struct sockaddr * )&sa, sizeof( sa ) ) == -1 )
   {
      perror( "Init_socket: bind" );
      close( control );
      exit( 1 );
   }

   if( listen( control, SOMAXCONN ) < 0 )
   {
      perror( "Init_socket: listen" );
      close( control );
      exit( 1 );
   }
}

#ifdef WIN32
   int mainthread( int argc, char **argv )
#else
   int main( int argc, char **argv )
#endif
{
   struct timeval now_time;
   bool fCopyOver = false;
#ifdef IMC
   int imcsocket = -1;
#endif

   DONT_UPPER = false;
   num_descriptors = 0;
   first_descriptor = NULL;
   last_descriptor = NULL;

   /* Init time. */
   gettimeofday( &now_time, NULL );
   current_time = ( time_t ) now_time.tv_sec;
   current_time += ( time_t ) TIME_MODIFY; /* Increase it to EDT */
   boot_time = time( 0 );  /*  <-- I think this is what you wanted */
   mudstrlcpy( str_boot_time, distime( current_time ), sizeof( str_boot_time ) );

   /* Init boot time. */
   set_boot_time = &set_boot_time_struct;
   set_boot_time->manual = 0;

   /* Get the port number. */
   port = 4000;
   if( argc > 1 )
   {
      if( !is_number( argv[1] ) )
      {
         fprintf( stderr, "Usage: %s [port #]\n", argv[0] );
         exit( 1 );
      }
      else if( ( port = atoi( argv[1] ) ) <= 1024 )
      {
         fprintf( stderr, "Port number must be above 1024.\n" );
         exit( 1 );
      }
      if( argv[2] && argv[2][0] )
      {
         fCopyOver = true;
         control = atoi( argv[3] );
#ifdef IMC
         imcsocket = atoi( argv[4] );
#endif
      }
      else
         fCopyOver = false;
   }

   /* Run the game. */
#ifdef WIN32
   {
      /* Initialise Windows sockets library */
      unsigned short wVersionRequested = MAKEWORD( 1, 1 );
      WSADATA wsadata;
      int err;

      /* Need to include library: wsock32.lib for Windows Sockets */
      err = WSAStartup( wVersionRequested, &wsadata );
      if( err )
      {
         fprintf( stderr, "Error %i on WSAStartup\n", err );
         exit( 1 );
      }

      /* standard termination signals */
      signal( SIGINT, ( void * )bailout );
      signal( SIGTERM, ( void * )bailout );
   }
#endif /* WIN32 */

   log_string( "Booting Database" );
   boot_db( fCopyOver );
   log_string( "Initializing socket" );
   if( !fCopyOver )  /* We have already the port if copyover'ed */
      init_socket( );

#ifdef IMC
   /* Initialize and connect to IMC2 */
   imc_startup( false, imcsocket, fCopyOver );
#endif

   log_printf( "%s ready on port %d.", sysdata.mud_name, port );

   if( fCopyOver )
   {
      log_string( "Initiating hotboot recovery." );
      hotboot_recover( );
   }

   game_loop( );

   close( control );

#ifdef IMC
   imc_shutdown( false );
#endif

#ifdef WIN32
   /* Shut down Windows sockets */
   WSACleanup( ); /* clean up */
   kill_timer( ); /* stop timer thread */
#endif

   /* That's all, folks. */
   log_string( "Normal termination of game." );

   log_string( "Cleaning up Memory." );
   cleanup_memory( );

   exit( 0 );
}

/* LAG alarm! - Thoric */
void caught_alarm( int signum )
{
   char buf[MSL];

   bug( "%s: ALARM CLOCK!  In section %s", __FUNCTION__, alarm_section );
   mudstrlcpy( buf, "Alas, the hideous malevalent entity known only as 'Lag' rises once more!\r\n", sizeof( buf ) );
   echo_to_all( AT_IMMORT, buf, ECHOTAR_ALL );
   if( newdesc )
   {
      FD_CLR( newdesc, &in_set );
      FD_CLR( newdesc, &out_set );
      FD_CLR( newdesc, &exc_set );
      log_string( "clearing newdesc" );
   }
}

bool check_bad_desc( int desc )
{
   if( FD_ISSET( desc, &exc_set ) )
   {
      FD_CLR( desc, &in_set );
      FD_CLR( desc, &out_set );
      log_string( "Bad FD caught and disposed." );
      return true;
   }
   return false;
}

void accept_new( void )
{
   static struct timeval null_time;
   DESCRIPTOR_DATA *d;

   FD_ZERO( &in_set );
   FD_ZERO( &out_set );
   FD_ZERO( &exc_set );

   FD_SET( control, &in_set );

   maxdesc = control;

   for( d = first_descriptor; d; d = d->next )
   {
      maxdesc = UMAX( maxdesc, d->descriptor );
      FD_SET( d->descriptor, &in_set );
      FD_SET( d->descriptor, &out_set );
      FD_SET( d->descriptor, &exc_set );
   }

   if( select( maxdesc + 1, &in_set, &out_set, &exc_set, &null_time ) < 0 )
   {
      perror( "accept_new: select: poll" );
      exit( 1 );
   }

   if( FD_ISSET( control, &exc_set ) )
   {
      bug( "Exception raise on controlling descriptor %d", control );
      FD_CLR( control, &in_set );
      FD_CLR( control, &out_set );
   }
   else if( FD_ISSET( control, &in_set ) )
      new_descriptor( );
}

void game_loop( void )
{
   struct timeval last_time;
   char cmdline[MIL];
   DESCRIPTOR_DATA *d;

#ifndef WIN32
   signal( SIGPIPE, SIG_IGN );
   signal( SIGALRM, caught_alarm );
#endif

   fprintf( stderr, ".~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~[  Game Loop  ]~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.~`~.\n" );

   gettimeofday( &last_time, NULL );
   current_time = ( time_t )last_time.tv_sec + TIME_MODIFY;

   /* Main loop */
   while( !mud_down )
   {
      accept_new( );

      /*
       * Kick out descriptors with raised exceptions
       * or have been idle, then check for input.
       */
      for( d = first_descriptor; d; d = d_next )
      {
         d_next = d->next;

         if( ++d->tempidle >= PULSE_PER_SECOND )
         {
            d->idle++;  /* make it so a descriptor can idle out */
            d->tempidle = 0;
         }
         if( FD_ISSET( d->descriptor, &exc_set ) )
         {
            FD_CLR( d->descriptor, &in_set );
            FD_CLR( d->descriptor, &out_set );
            if( d->character && ( d->connected == CON_PLAYING || d->connected == CON_EDITING ) )
               save_char_obj( d->character );
            d->outtop = 0;
            close_socket( d, true );
            continue;
         }
         else if( ( !d->character && d->idle > 60 )  /* 1 minute */
         || ( d->connected != CON_PLAYING && d->idle > 180 )  /* 3 minutes */
         || d->idle > 3600 ) /* 1 hour */
         {
            write_to_descriptor( d, "Idle timeout... disconnecting.\r\n", 0 );
            d->outtop = 0;
            close_socket( d, true );
            continue;
         }
         else
         {
            d->fcommand = false;

            if( FD_ISSET( d->descriptor, &in_set ) )
            {
               d->idle = 0;
               d->tempidle = 0;
               if( d->character )
                  d->character->timer = 0;
               if( !read_from_descriptor( d ) )
               {
                  FD_CLR( d->descriptor, &out_set );
                  if( d->character && ( d->connected == CON_PLAYING || d->connected == CON_EDITING ) )
                     save_char_obj( d->character );
                  d->outtop = 0;
                  close_socket( d, false );
                  continue;
               }
            }

            if( d->character && d->character->wait > 0 )
            {
               --d->character->wait;
               continue;
            }

            read_from_buffer( d );
            if( d->incomm[0] && d->incomm[0] != '\0' )
            {
               d->fcommand = true;
               stop_idling( d->character );

               mudstrlcpy( cmdline, d->incomm, sizeof( cmdline ) );
               d->incomm[0] = '\0';

               if( d->character )
                  set_cur_char( d->character );

               if( d->pagepoint )
                  set_pager_input( d, cmdline );
               else
               {
                  switch( d->connected )
                  {
                     default:
                        nanny( d, cmdline );
                        break;

                     case CON_PLAYING:
                        interpret( d->character, cmdline );
                        break;

                     case CON_EDITING:
                        edit_buffer( d->character, cmdline );
                        break;
                  }
               }
            }
            /* later should add in a config for getting the message and time in mins to display it */
            else if( d->character && d->character->pcdata && xIS_SET( d->character->act, PLR_K_LIVE )
            && d->outtime < ( current_time - ( 60 * URANGE( 1, d->character->pcdata->kltime, 10 ) ) ) )
               write_to_descriptor( d, keep_alive_msg[number_range( 0, 2 )], 0 );
         }
         if( d == last_descriptor )
            break;
      }

#ifdef IMC
      imc_loop( );
#endif

      /* Autonomous game motion. */
      update_handler( );

      /* Output. */
      for( d = first_descriptor; d; d = d_next )
      {
         d_next = d->next;

         if( ( d->fcommand || d->outtop > 0 ) && FD_ISSET( d->descriptor, &out_set ) )
         {
            if( d->pagepoint )
            {
               if( !pager_output( d ) )
               {
                  if( d->character && ( d->connected == CON_PLAYING || d->connected == CON_EDITING ) )
                     save_char_obj( d->character );
                  d->outtop = 0;
                  close_socket( d, false );
               }
            }
            else if( !flush_buffer( d, true ) )
            {
               if( d->character && ( d->connected == CON_PLAYING || d->connected == CON_EDITING ) )
                  save_char_obj( d->character );
               d->outtop = 0;
               close_socket( d, false );
            }
         }
         if( d == last_descriptor )
            break;
      }

      /*
       * Synchronize to a clock.
       * Sleep( last_time + 1/PULSE_PER_SECOND - now ).
       * Careful here of signed versus unsigned arithmetic.
       */
      {
         struct timeval now_time;
         long secDelta;
         long usecDelta;

         gettimeofday( &now_time, NULL );
         usecDelta = ( ( int )last_time.tv_usec ) - ( ( int )now_time.tv_usec ) + 1000000 / PULSE_PER_SECOND;
         secDelta = ( ( int )last_time.tv_sec ) - ( ( int )now_time.tv_sec );
         while( usecDelta < 0 )
         {
            usecDelta += 1000000;
            secDelta -= 1;
         }

         while( usecDelta >= 1000000 )
         {
            usecDelta -= 1000000;
            secDelta += 1;
         }

         if( secDelta > 0 || ( secDelta == 0 && usecDelta > 0 ) )
         {
            struct timeval stall_time;

            stall_time.tv_usec = usecDelta;
            stall_time.tv_sec = secDelta;
#ifdef WIN32
            Sleep( ( stall_time.tv_sec * 1000L ) + ( stall_time.tv_usec / 1000L ) );
#else
            if( select( 0, NULL, NULL, NULL, &stall_time ) < 0 && errno != EINTR )
            {
               perror( "game_loop: select: stall" );
               exit( 1 );
            }
#endif
         }
      }

      gettimeofday( &last_time, NULL );
      current_time = ( time_t ) last_time.tv_sec;
      current_time += ( time_t ) TIME_MODIFY;
   }

   /* Save morphs so can sort later. --Shaddai */
   if( sysdata.morph_opt )
      save_morphs( );

   fflush( stderr ); /* make sure strerr is flushed */
}

void new_descriptor( void )
{
   DESCRIPTOR_DATA *dnew;
   char buf[MSL];
   char log_buf[MSL];
   extern char *help_greeting;
   struct sockaddr_in sock;
   struct hostent *from;
#ifndef WIN32
   socklen_t size;
#else
   unsigned int size;
   unsigned long arg = 1;
#endif
   int desc;

   size = sizeof( sock );
   if( check_bad_desc( control ) )
   {
      set_alarm( 0 );
      return;
   }

   set_alarm( 20 );
   alarm_section = "new_descriptor::accept";
   if( ( desc = accept( control, ( struct sockaddr * )&sock, &size ) ) < 0 )
   {
      perror( "New_descriptor: accept" );
      bug( "%s: accept", __FUNCTION__ );
      set_alarm( 0 );
      return;
   }

   if( check_bad_desc( control ) )
   {
      set_alarm( 0 );
      return;
   }

#if !defined(FNDELAY)
   #define FNDELAY O_NDELAY
#endif

   set_alarm( 20 );
   alarm_section = "new_descriptor: after accept";

#ifdef WIN32
   if( ioctlsocket( desc, FIONBIO, &arg ) == -1 )
#else
   if( fcntl( desc, F_SETFL, FNDELAY ) == -1 )
#endif
   {
      perror( "New_descriptor: fcntl: FNDELAY" );
      set_alarm( 0 );
      return;
   }
   if( check_bad_desc( control ) )
      return;

   CREATE( dnew, DESCRIPTOR_DATA, 1 );
   dnew->next = NULL;
   dnew->descriptor = desc;
   dnew->connected = CON_GET_NAME;
   dnew->outsize = 2000;
   dnew->idle = 0;
   dnew->tempidle = 0;
   dnew->lines = 0;
   dnew->scrlen = 24;
   dnew->port = ntohs( sock.sin_port );
   dnew->newstate = 0;
   dnew->prevcolor = 0x07;
   dnew->can_compress = false;
   dnew->speed = 32;
   CREATE( dnew->mccp, MCCP, 1 );
   CREATE( dnew->outbuf, char, dnew->outsize );

   mudstrlcpy( buf, inet_ntoa( sock.sin_addr ), sizeof( buf ) );
   if( sysdata.NAME_RESOLVING )
   {
      from = gethostbyaddr( ( char * )&sock.sin_addr, sizeof( sock.sin_addr ), AF_INET );
      dnew->host = STRALLOC( ( char * )( from ? from->h_name : buf ) );
   }
   else
      dnew->host = STRALLOC( buf );

   log_printf_plus( LOG_COMM, PERM_HEAD, "Host: %s, Port: %d.", dnew->host, dnew->port );

   if( multi_check( dnew, false ) )
   {
      free_desc( dnew );
      set_alarm( 0 );
      return;
   }

   if( check_total_bans( dnew ) )
   {
      write_to_descriptor( dnew, "Your site has been banned from this Mud.\r\n", 0 );
      free_desc( dnew );
      set_alarm( 0 );
      return;
   }

   LINK( dnew, first_descriptor, last_descriptor, next, prev );

   /* MCCP Compression */
   write_to_buffer( dnew, will_compress2_str, 0 );

   /* Send the greeting. */
   if( !help_greeting || help_greeting[0] == '\0' )
      send_to_desc( "There is currently no greeting file to display.\r\n", dnew );
   else if( help_greeting[0] == '.' )
      send_to_desc( help_greeting + 1, dnew );
   else
      send_to_desc( help_greeting, dnew );

   if( ++num_descriptors > sysdata.maxplayers )
      sysdata.maxplayers = num_descriptors;
   if( sysdata.maxplayers > sysdata.alltimemax )
   {
      sysdata.time_of_max = current_time;
      sysdata.alltimemax = sysdata.maxplayers;
      snprintf( log_buf, sizeof( log_buf ), "Broke all-time maximum player record: %d", sysdata.alltimemax );
      log_string_plus( log_buf, LOG_COMM, sysdata.perm_log );
      to_channel( log_buf, "monitor", PERM_IMM );
      save_sysdata( );
   }
   set_alarm( 0 );
}

void free_desc( DESCRIPTOR_DATA *d )
{
   close( d->descriptor );
   STRFREE( d->host );
   DISPOSE( d->outbuf );
   DISPOSE( d->pagebuf );
   compressEnd( d );
   DISPOSE( d->mccp );
   DISPOSE( d );
}

void close_socket( DESCRIPTOR_DATA *dclose, bool force )
{
   CHAR_DATA *ch;
   DESCRIPTOR_DATA *d;
   bool DoNotUnlink = false;

   /* flush outbuf */
   if( !force && dclose->outtop > 0 )
      flush_buffer( dclose, false );

   /* say bye to whoever's snooping this descriptor */
   if( dclose->snoop_by )
      write_to_buffer( dclose->snoop_by, "Your victim has left the game.\r\n", 0 );

   /* stop snooping everyone else */
   for( d = first_descriptor; d; d = d->next )
      if( d->snoop_by == dclose )
         d->snoop_by = NULL;

   if( ( ch = dclose->character ) )
   {
      log_printf_plus( LOG_COMM, get_trust( ch ), "Closing link to %s.", ch->pcdata->filename );
      if( dclose->connected == CON_PLAYING || dclose->connected == CON_EDITING )
      {
         act( AT_ACTION, "$n has lost $s link.", ch, NULL, NULL, TO_CANSEE );
         ch->desc = NULL;
      }
      else
      {
         ch->desc = NULL;
         free_char( ch );
      }
   }

   if( !DoNotUnlink )
   {
      if( d_next == dclose )
         d_next = d_next->next;
      UNLINK( dclose, first_descriptor, last_descriptor, next, prev );
   }

   compressEnd( dclose );

   if( dclose->descriptor == maxdesc )
      --maxdesc;

   free_desc( dclose );
   --num_descriptors;
}

bool read_from_descriptor( DESCRIPTOR_DATA *d )
{
   unsigned int iStart;
   int iErr;

   /* Hold horses if pending command already. */
   if( d->incomm[0] != '\0' )
      return true;

   /* Check for overflow. */
   iStart = strlen( d->inbuf );
   if( iStart >= sizeof( d->inbuf ) - 10 )
   {
      log_printf( "%s input overflow!", d->host );
      write_to_descriptor( d, "\r\n*** PUT A LID ON IT!!! ***\r\nYou can't enter the same command more than 20 consecutive times!\r\n", 0 );
      return false;
   }

   for( ;; )
   {
      int nRead;

      nRead = recv( d->descriptor, d->inbuf + iStart, sizeof( d->inbuf ) - 10 - iStart, 0 );
#ifdef WIN32
      iErr = WSAGetLastError( );
#else
      iErr = errno;
#endif
      if( nRead > 0 )
      {
         iStart += nRead;

         /* Update the incomm here before adding more to the line etc */
         update_transfer( 1, nRead );

         if( d->inbuf[iStart - 1] == '\r' || d->inbuf[iStart - 1] == '\n' )
            break;
      }
      else if( nRead == 0 )
      {
         if( d->character && d->character->name )
            log_printf_plus( LOG_COMM, get_trust( d->character ), "EOF encountered on read for %s.", d->character->name );
         else if( d->host )
            log_printf_plus( LOG_COMM, PERM_HEAD, "EOF encountered on read from %s.", d->host );
         else
            log_string_plus( "EOF encountered on read.", LOG_COMM, PERM_HEAD );
         return false;
      }
      else if( iErr == EWOULDBLOCK )
         break;
      else
      {
         perror( "Read_from_descriptor" );
         return false;
      }
   }

   d->inbuf[iStart] = '\0';
   return true;
}

/* Transfer one line from input buffer to input line. */
void read_from_buffer( DESCRIPTOR_DATA *d )
{
   int i, j, k, iac = 0;

   /* Hold horses if pending command already. */
   if( d->incomm[0] != '\0' )
      return;

   /* Look for at least one new line. */
   for( i = 0; d->inbuf[i] != '\r' && d->inbuf[i] != '\n' && i < MAX_INBUF_SIZE; i++ )
   {
      if( d->inbuf[i] == '\0' )
         return;
   }

   /* Canonical input processing. */
   for( i = 0, k = 0; d->inbuf[i] != '\r' && d->inbuf[i] != '\n'; i++ )
   {
      if( k >= MIL )
      {
         write_to_descriptor( d, "Line too long.\r\n", 0 );
         d->inbuf[i] = '\n';
         d->inbuf[i + 1] = '\0';
         break;
      }

      if( d->inbuf[i] == ( signed char )IAC )
         iac = 1;
      else if( iac == 1
      && ( d->inbuf[i] == ( signed char )DO || d->inbuf[i] == ( signed char )DONT || d->inbuf[i] == ( signed char )WILL ) )
         iac = 2;
      else if( iac == 2 )
      {
         iac = 0;
         if( d->inbuf[i] == ( signed char )TELOPT_COMPRESS2 )
         {
            if( d->inbuf[i - 1] == ( signed char )DO )
               compressStart( d );
            else if( d->inbuf[i - 1] == ( signed char )DONT )
               compressEnd( d );
         }
      }
      else if( d->inbuf[i] == '\b' && k > 0 )
         --k;
      else if( isascii( d->inbuf[i] ) && isprint( d->inbuf[i] ) )
         d->incomm[k++] = d->inbuf[i];
   }

   /* Finish off the line. */
   if( k == 0 )
      d->incomm[k++] = ' ';
   d->incomm[k] = '\0';

   /* Deal with bozos with #repeat 1000 ... */
   if( k > 1 || d->incomm[0] == '!' )
   {
      if( d->incomm[0] != '!' && strcmp( d->incomm, d->inlast ) )
         d->repeat = 0;
      else
      {
         if( ++d->repeat >= 20 )
         {
            write_to_descriptor( d, "\r\n*** PUT A LID ON IT!!! ***\r\nYou can't enter the same command more than 20 consecutive times!\r\n", 0 );
            mudstrlcpy( d->incomm, "quit", sizeof( d->incomm ) );
         }
      }
   }

   /* Do '!' substitution. */
   if( d->incomm[0] == '!' )
      mudstrlcpy( d->incomm, d->inlast, sizeof( d->incomm ) );
   else
      mudstrlcpy( d->inlast, d->incomm, sizeof( d->inlast ) );

   /* Shift the input buffer. */
   while( d->inbuf[i] == '\r' || d->inbuf[i] == '\n' )
      i++;
   for( j = 0; ( d->inbuf[j] = d->inbuf[i + j] ) != '\0'; j++ )
      ;
}

/* Low level output function. */
bool flush_buffer( DESCRIPTOR_DATA *d, bool fPrompt )
{
   char buf[MSL];
   int temp, size;

   if( !d )
   {
      bug( "%s: NULL descriptor.", __FUNCTION__ );
      return false;
   }

   temp = URANGE( 1, d->speed, 32 );
   size = URANGE( 128, ( 128 * temp ), ( MSL - 1 ) );

   /* If buffer has more than <size> inside, spit out .256K at a time */
   if( !mud_down && d->outtop > size )
   {
      memcpy( buf, d->outbuf, size );
      d->outtop -= size;
      memmove( d->outbuf, d->outbuf + size, d->outtop );
      if( d->snoop_by )
      {
         char snoopbuf[MSL];

         if( d->character && d->character->name )
         {
            snprintf( snoopbuf, sizeof( snoopbuf ), "%s", d->character->name );
            write_to_buffer( d->snoop_by, snoopbuf, 0 );
         }
         write_to_buffer( d->snoop_by, "% ", 2 );
         write_to_buffer( d->snoop_by, buf, 0 );
      }
      if( !write_to_descriptor( d, buf, size ) )
      {
         d->outtop = 0;
         return false;
      }
      return true;
   }

   /* Bust a prompt. */
   if( fPrompt && !mud_down && d->connected == CON_PLAYING )
   {
      CHAR_DATA *ch = NULL;

      ch = d->character;
      if( ch && !is_npc( ch ) && xIS_SET( ch->act, PLR_ANSI ) )
      {
         write_to_buffer( d, ANSI_RESET, 0 );
         d->prevcolor = 0x08;
      }

      if( xIS_SET( ch->act, PLR_BLANK ) )
         write_to_buffer( d, "\r\n", 2 );

      if( xIS_SET( ch->act, PLR_PROMPT ) )
         display_prompt( d );

      if( ch && !is_npc( ch ) && xIS_SET( ch->act, PLR_ANSI ) )
      {
         write_to_buffer( d, ANSI_RESET, 0 );
         d->prevcolor = 0x08;
      }

      if( xIS_SET( ch->act, PLR_TELNET_GA ) )
         write_to_buffer( d, go_ahead_str, 0 );

      if( xIS_SET( ch->act, PLR_BLANK ) && d->outtop > 0 )
         write_to_buffer( d, "\r\n", 2 );
   }

   /* Short-circuit if nothing to write. */
   if( d->outtop == 0 )
      return true;

   /* Snoop-o-rama. */
   if( d->snoop_by )
   {
      /* without check, 'force mortal quit' while snooped caused crash, -h */
      if( d->character && d->character->name )
      {
         snprintf( buf, sizeof( buf ), "%s", d->character->name );
         write_to_buffer( d->snoop_by, buf, 0 );
      }
      write_to_buffer( d->snoop_by, "% ", 2 );
      write_to_buffer( d->snoop_by, d->outbuf, d->outtop );
   }

   /* OS-dependent output. */
   if( !write_to_descriptor( d, d->outbuf, d->outtop ) )
   {
      d->outtop = 0;
      return false;
   }
   else
   {
      d->outtop = 0;
      return true;
   }
}

/* Append onto an output buffer. */
void write_to_buffer( DESCRIPTOR_DATA *d, const char *txt, unsigned int length )
{
   if( !d )
   {
      bug( "%s: NULL descriptor", __FUNCTION__ );
      return;
   }

   /* Normally a bug... but can happen if loadup is used. */
   if( !d->outbuf )
      return;

   /* Find length in case caller didn't. */
   if( length <= 0 )
      length = strlen( txt );

   /* Initial \r\n if needed. */
   if( d->outtop == 0 && !d->fcommand )
   {
      d->outbuf[0] = '\r';
      d->outbuf[1] = '\n';
      d->outtop = 2;
   }

   /* Expand the buffer as needed. */
   while( d->outtop + length >= d->outsize )
   {
      if( d->outsize > 32000 )
      {
         /* empty buffer */
         d->outtop = 0;

         /* Bugfix by Samson - moved bug() call up */
         bug( "Buffer overflow. Closing (%s)@%s.", d->character ? d->character->name : "???",
            d->host ? d->host : "???" );
         close_socket( d, true );
         return;
      }
      d->outsize *= 2;
      RECREATE( d->outbuf, char, d->outsize );
   }

   /* Copy. */
   strncpy( d->outbuf + d->outtop, txt, length );
   d->outtop += length;
   d->outbuf[d->outtop] = '\0';
}

/*
 * Added block checking to prevent random booting of the descriptor. Thanks go
 * out to Rustry for his suggestions. -Orion
 */
bool write_to_descriptor_old( int desc, char *txt, int length )
{
   int iStart = 0, nWrite = 0, nBlock = 0, iErr = 0;

   if( length <= 0 )
      length = strlen( txt );

   for( iStart = 0; iStart < length; iStart += nWrite )
   {
      nBlock = UMIN( length - iStart, 4096 );
      nWrite = send( desc, txt + iStart, nBlock, 0 );

      if( nWrite > 0 )
         update_transfer( 2, nWrite );

      if( nWrite == -1 )
      {
         iErr = errno;
         if( iErr == EWOULDBLOCK )
         {
            nWrite = 0;
            continue;
         }
         else
         {
            perror( "Write_to_descriptor" );
            return false;
         }
      }
   }
   return true;
}

/*
 * This is the MCCP version. Use write_to_descriptor_old to send non-compressed
 * text.
 * Updated to run with the block checks by Orion... if it doesn't work, blame
 * him.;P -Orion
 */
bool write_to_descriptor( DESCRIPTOR_DATA *d, char *txt, int length )
{
   int iStart = 0, nWrite = 0, nBlock, iErr, len;

   if( length <= 0 )
      length = strlen( txt );

   if( d && d->mccp->out_compress )
   {
      size_t mccpsaved = length;

      /* Won't send more then it has to so make sure we check if its under length */
      if( mccpsaved > strlen( txt ) )
         mccpsaved = strlen( txt );

      d->mccp->out_compress->next_in = ( unsigned char * )txt;
      d->mccp->out_compress->avail_in = length;

      while( d->mccp->out_compress->avail_in )
      {
         d->mccp->out_compress->avail_out = COMPRESS_BUF_SIZE - ( d->mccp->out_compress->next_out - d->mccp->out_compress_buf );

         if( d->mccp->out_compress->avail_out )
         {
            int status = deflate( d->mccp->out_compress, Z_SYNC_FLUSH );

            if( status != Z_OK )
               return false;
         }

         len = d->mccp->out_compress->next_out - d->mccp->out_compress_buf;
         if( len > 0 )
         {
            for( iStart = 0; iStart < len; iStart += nWrite )
            {
               nBlock = UMIN( len - iStart, MSL );
               nWrite = send( d->descriptor, d->mccp->out_compress_buf + iStart, nBlock, 0 );

               if( nWrite > 0 )
               {
                  update_transfer( 3, nWrite );
                  mccpsaved -= nWrite;
               }

               if( nWrite == -1 )
               {
                  iErr = errno;
                  perror( "Write_to_descriptor" );
                  if( iErr == EWOULDBLOCK )
                  {
                     nWrite = 0;
                     continue;
                  }
                  else
                     return false;
               }

               if( !nWrite )
                  break;
            }

            if( !iStart )
               break;

            if( iStart < len )
               memmove( d->mccp->out_compress_buf, d->mccp->out_compress_buf + iStart, len - iStart );

            d->mccp->out_compress->next_out = d->mccp->out_compress_buf + len - iStart;
         }
      }
      d->outtime = current_time;
      if( mccpsaved > 0 )
         update_transfer( 4, mccpsaved );
      return true;
   }

   if( !write_to_descriptor_old( d->descriptor, txt, length ) )
      return false;
   if( d )
      d->outtime = current_time;
   return true;
}

void show_title( DESCRIPTOR_DATA *d )
{
   CHAR_DATA *ch;

   ch = d->character;
   if( ch && !xIS_SET( ch->pcdata->flags, PCFLAG_NOINTRO ) )
   {
      if( xIS_SET( ch->act, PLR_ANSI ) )
         send_ansi_title( ch );
      else
         send_ascii_title( ch );
   }
   else
      write_to_buffer( d, "Press enter...\r\n", 0 );
   d->connected = CON_PRESS_ENTER;
}

char *send_class_list( DESCRIPTOR_DATA *d )
{
   char buf[MSL], *sbuf;
   int iClass;
   bool found = false;

   buf[0] = '\0';
   for( iClass = 0; iClass < MAX_PC_CLASS; iClass++ )
   {
      if( !class_table[iClass] || !class_table[iClass]->name || !str_cmp( class_table[iClass]->name, "unused" ) )
         continue;
      if( check_class_bans( iClass ) )
         continue;
      if( found )
         mudstrlcat( buf, " ", sizeof( buf ) );
      found = true;
      mudstrlcat( buf, class_table[iClass]->name, sizeof( buf ) );
   }
   sbuf = buf;
   return sbuf;
}

bool can_use_race( DESCRIPTOR_DATA *d, int iRace )
{
   if( !d || !d->character )
      return false;
   if( iRace < 0 || iRace >= MAX_PC_RACE )
      return false;
   if( !race_table[iRace] || !race_table[iRace]->name || !str_cmp( race_table[iRace]->name, "unused" ) )
      return false;
   if( d->character && d->character->Class != -1 && xIS_SET( race_table[iRace]->class_restriction, d->character->Class ) )
      return false;
   if( check_race_bans( iRace ) )
      return false;
   return true;
}

char *send_race_list( DESCRIPTOR_DATA *d )
{
   char buf[MSL], *sbuf;
   int iRace;
   bool found = false;

   buf[0] = '\0';
   if( !d )
      return "";
   for( iRace = 0; iRace < MAX_PC_RACE; iRace++ )
   {
      if( !can_use_race( d, iRace ) )
         continue;
      if( found )
         mudstrlcat( buf, " ", sizeof( buf ) );
      found = true;
      mudstrlcat( buf, race_table[iRace]->name, sizeof( buf ) );
   }
   sbuf = buf;
   return sbuf;
}

/* Deal with sockets that haven't logged in yet. */
void nanny( DESCRIPTOR_DATA *d, char *argument )
{
   char buf[MSL], arg[MSL], log_buf[MSL], *pwdnew;
   CHAR_DATA *ch;
   int iRace, iClass;
   bool fOld, chk, redo, skipclass = sysdata.skipclasses;

   while( isspace( *argument ) )
      argument++;

   ch = d->character;

   switch( d->connected )
   {
      default:
         bug( "%s: bad d->connected %d.", __FUNCTION__, d->connected );
         close_socket( d, true );
         return;

      case CON_GET_NAME:
         if( !argument || argument[0] == '\0' )
         {
            close_socket( d, false );
            return;
         }

         argument[0] = UPPER( argument[0] );

         if( !str_prefix( "GET / HTTP/", argument ) )
         {
            close_socket( d, false );
            return;
         }

         /* Old players can keep their characters. -- Alty */
         if( !check_parse_name( argument, ( d->newstate != 0 ) ) )
         {
            write_to_buffer( d, argument, 0 );
            write_to_buffer( d, " is an Illegal name, try another.\r\nName: ", 0 );
            return;
         }

         if( !str_cmp( argument, "New" ) )
         {
            if( d->newstate == 0 )
            {
               /* New player. Don't allow new players if DENY_NEW_PLAYERS is true */
               if( sysdata.DENY_NEW_PLAYERS == true )
               {
                  write_to_buffer( d, "The mud is currently preparing for a reboot.\r\n", 0 );
                  write_to_buffer( d, "New players aren't accepted during this time.\r\n", 0 );
                  write_to_buffer( d, "Please try again in a few minutes.\r\n", 0 );
                  close_socket( d, false );
                  return;
               }
               write_to_buffer( d, "\r\nChoosing a name is one of the most important parts of this game...\r\n"
                                "Make sure to pick a name appropriate to the character you are going\r\n"
                                "to role play, and be sure that it suits a medieval theme.\r\n"
                                "If the name you select is not acceptable, you will be asked to choose\r\n"
                                "another one.\r\n\r\nPlease choose a name for your character: ", 0 );
               d->newstate++;
               d->connected = CON_GET_NAME;
               return;
            }
            else
            {
               write_to_buffer( d, "Illegal name, try another.\r\nName: ", 0 );
               return;
            }
         }

         fOld = load_char_obj( d, argument, true, false );
         if( !d->character )
         {
            log_printf( "Bad player file %s@%s.", argument, d->host );
            write_to_buffer( d, "Your playerfile is corrupt...Please notify an Immortal.\r\n", 0 );
            close_socket( d, false );
            return;
         }

         ch = d->character;
         if( check_bans( ch, BAN_SITE ) )
         {
            write_to_buffer( d, "Your site has been banned from this Mud.\r\n", 0 );
            close_socket( d, false );
            return;
         }

         if( fOld )
         {
            if( check_bans( ch, BAN_CLASS ) )
            {
               write_to_buffer( d, "Your class has been banned from this Mud.\r\n", 0 );
               close_socket( d, false );
               return;
            }
            if( check_bans( ch, BAN_RACE ) )
            {
               write_to_buffer( d, "Your race has been banned from this Mud.\r\n", 0 );
               close_socket( d, false );
               return;
            }
         }

         if( xIS_SET( ch->act, PLR_DENY ) )
         {
            log_printf_plus( LOG_COMM, PERM_HEAD, "Denying access to %s@%s.", argument, d->host );
            if( d->newstate != 0 )
            {
               write_to_buffer( d, "That name is already taken.  Please choose another: ", 0 );
               d->connected = CON_GET_NAME;
               d->character->desc = NULL;
               free_char( d->character ); /* Big Memory Leak before --Shaddai */
               d->character = NULL;
               return;
            }
            write_to_buffer( d, "You are denied access.\r\n", 0 );
            close_socket( d, false );
            return;
         }

         /* Make sure the character can be loaded from the host. */
         if( !check_host( ch ) )
         {
            log_printf_plus( LOG_COMM, PERM_HEAD, "%s's char being hacked from %s.", argument, d->host );
            write_to_buffer( d, "This hacking attempt has been logged.\r\n", 0 );
            close_socket( d, false );
            return;
         }

         if( ( chk = check_reconnect( d, argument, false ) ) )
            fOld = true;
         else
         {
            if( sysdata.wizlock && !is_immortal( ch ) )
            {
               write_to_buffer( d, "The game is wizlocked.  Only immortals can connect now.\r\n", 0 );
               write_to_buffer( d, "Please try back later.\r\n", 0 );
               close_socket( d, false );
               return;
            }
            if( multi_check( d, true ) )
            {
               close_socket( d, false );
               return;
            }
         }

         if( fOld )
         {
            if( d->newstate != 0 )
            {
               write_to_buffer( d, "That name is already taken.  Please choose another: ", 0 );
               d->connected = CON_GET_NAME;
               d->character->desc = NULL;
               free_char( d->character ); /* Big Memory Leak before --Shaddai */
               d->character = NULL;
               return;
            }
            /* Old player */
            write_to_buffer( d, "Password: ", 0 );
            write_to_buffer( d, echo_off_str, 0 );
            d->connected = CON_GET_OLD_PASSWORD;
            return;
         }
         else
         {
            if( d->newstate == 0 )
            {
               /* No such player */
               write_to_buffer( d, "\r\nNo such player exists.\r\nPlease check your spelling, or type new to start a new player.\r\n\r\nName: ", 0 );
               d->connected = CON_GET_NAME;
               d->character->desc = NULL;
               free_char( d->character ); /* Big Memory Leak before --Shaddai */
               d->character = NULL;
               return;
            }

            snprintf( buf, sizeof( buf ), "Did I get that right, %s (Y/N)? ", argument );
            write_to_buffer( d, buf, 0 );
            d->connected = CON_CONFIRM_NEW_NAME;
            return;
         }
         break;

      case CON_GET_OLD_PASSWORD:
         write_to_buffer( d, "\r\n", 2 );

         if( str_cmp( sha256_crypt( argument ), ch->pcdata->pwd ) )
         {
            write_to_buffer( d, "Wrong password, disconnecting.\r\n", 0 );
            /* clear descriptor pointer to get rid of bug message in log */
            d->character->desc = NULL;
            close_socket( d, false );
            return;
         }

         write_to_buffer( d, echo_on_str, 0 );

         if( check_playing( d, ch->pcdata->filename, true ) )
            return;

         if( ( chk = check_reconnect( d, ch->pcdata->filename, true ) ) )
            return;

         if( multi_check( d, true ) )
         {
            d->character->desc = NULL;
            close_socket( d, false );
            return;
         }

         mudstrlcpy( buf, ch->pcdata->filename, sizeof( buf ) );
         d->character->desc = NULL;
         free_char( d->character );
         d->character = NULL;
         fOld = load_char_obj( d, buf, false, false );
         ch = d->character;
         if( is_fighting( ch ) )
            ch->position = POS_STANDING;

         log_printf_plus( LOG_COMM, get_trust( ch ), "%s (%s) has connected.", ch->name, d->host );
         show_title( d );
         break;

      case CON_CONFIRM_NEW_NAME:
         switch( *argument )
         {
            case 'y':
            case 'Y':
               snprintf( buf, sizeof( buf ),
                         "\r\nMake sure to use a password that won't be easily guessed by someone else."
                         "\r\nPick a good password for %s: %s", ch->name, echo_off_str );
               write_to_buffer( d, buf, 0 );
               d->connected = CON_GET_NEW_PASSWORD;
               break;

            case 'n':
            case 'N':
               write_to_buffer( d, "Ok, what IS it, then? ", 0 );
               d->character->desc = NULL;
               free_char( d->character );
               d->character = NULL;
               d->connected = CON_GET_NAME;
               break;

            default:
               write_to_buffer( d, "Please type Yes or No. ", 0 );
               break;
         }
         break;

      case CON_GET_NEW_PASSWORD:
         write_to_buffer( d, "\r\n", 2 );

         if( strlen( argument ) < 5 )
         {
            write_to_buffer( d, "Password must be at least five characters long.\r\nPassword: ", 0 );
            return;
         }

         if( argument[0] == '!' )
         {
            send_to_char( "New password can't begin with the '!' character.\r\n", ch );
            return;
         }
         pwdnew = sha256_crypt( argument );   /* SHA-256 Encryption */
         STRSET( ch->pcdata->pwd, pwdnew );
         write_to_buffer( d, "\r\nPlease retype the password to confirm: ", 0 );
         d->connected = CON_CONFIRM_NEW_PASSWORD;
         break;

      case CON_CONFIRM_NEW_PASSWORD:
         write_to_buffer( d, "\r\n", 2 );

         if( str_cmp( sha256_crypt( argument ), ch->pcdata->pwd ) )
         {
            write_to_buffer( d, "Passwords don't match.\r\nRetype password: ", 0 );
            d->connected = CON_GET_NEW_PASSWORD;
            return;
         }

         write_to_buffer( d, echo_on_str, 0 );
         write_to_buffer( d, "\r\nWhat is your sex (M/F/N)? ", 0 );
         d->connected = CON_GET_NEW_SEX;
         break;

      case CON_GET_NEW_SEX:
         switch( argument[0] )
         {
            case 'm':
            case 'M':
               ch->sex = SEX_MALE;
               break;
            case 'f':
            case 'F':
               ch->sex = SEX_FEMALE;
               break;
            case 'n':
            case 'N':
               ch->sex = SEX_NEUTRAL;
               break;
            default:
               write_to_buffer( d, "That's not a sex.\r\nWhat IS your sex? ", 0 );
               return;
         }

         if( skipclass )
         {
            ch->Class = -1;
            write_to_buffer( d, "\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
            write_to_buffer( d, buf, 0 );
            d->connected = CON_GET_NEW_RACE;
         }
         else
         {
            write_to_buffer( d, "\r\nYou may choose from the following classes, or type help [class] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_class_list( d ) );
            write_to_buffer( d, buf, 0 );
            d->connected = CON_GET_NEW_CLASS;
         }
         break;

      case CON_GET_NEW_CLASS:
         argument = one_argument( argument, arg );
         if( !str_cmp( arg, "help" ) )
         {
            for( iClass = 0; iClass < MAX_PC_CLASS; iClass++ )
            {
               if( toupper( argument[0] ) == toupper( class_table[iClass]->name[0] )
               && !str_prefix( argument, class_table[iClass]->name ) && valid_help( class_table[iClass]->name ) )
               {
                  do_help( ch, class_table[iClass]->name );
                  write_to_buffer( d, "\r\nYou may choose from the following classes, or type help [class] to learn more:\r\n", 0 );
                  snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_class_list( d ) );
                  write_to_buffer( d, buf, 0 );
                  return;
               }
            }
            write_to_buffer( d, "No help on that topic. Please choose a class or type help [class] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_class_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         for( iClass = 0; iClass < MAX_PC_CLASS; iClass++ )
         {
            if( toupper( arg[0] ) == toupper( class_table[iClass]->name[0] )
            && !str_prefix( arg, class_table[iClass]->name ) )
            {
               ch->Class = iClass;
               break;
            }
         }

         if( iClass == MAX_PC_CLASS || !class_table[iClass]
         || !class_table[iClass]->name || !str_cmp( class_table[iClass]->name, "unused" ) )
         {
            write_to_buffer( d, "\r\nYou may choose from the following classes, or type help [class] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_class_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         if( check_bans( ch, BAN_CLASS ) )
         {
            write_to_buffer( d, "\r\nThat class is not currently available.\r\nYou may choose from the following classes, or type help [class] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_class_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         write_to_buffer( d, "\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
         snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
         write_to_buffer( d, buf, 0 );

         redo = true;
         for( iRace = 0; iRace < MAX_PC_RACE; iRace++ )
         {
            if( race_table[iRace] && race_table[iRace]->name && str_cmp( race_table[iRace]->name, "unused" )
            && !xIS_SET( race_table[iRace]->class_restriction, ch->Class ) )
            {
               redo = false;
            }
         }
         if( redo )
         {
            send_to_char( "It looks like there are no races for that class please choose another class.\r\n", ch );
            return;
         }
         d->connected = CON_GET_NEW_RACE;
         break;

      case CON_GET_NEW_RACE:
         argument = one_argument( argument, arg );
         if( !str_cmp( arg, "help" ) )
         {
            for( iRace = 0; iRace < MAX_PC_RACE; iRace++ )
            {
               if( !can_use_race( d, iRace ) )
                  continue;
               if( toupper( argument[0] ) == toupper( race_table[iRace]->name[0] )
               && !str_prefix( argument, race_table[iRace]->name ) && valid_help( race_table[iRace]->name ) )
               {
                  do_help( ch, race_table[iRace]->name );
                  write_to_buffer( d, "\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
                  snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
                  write_to_buffer( d, buf, 0 );
                  return;
               }
            }
            write_to_buffer( d, "\r\nNo help on that topic.\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         for( iRace = 0; iRace < MAX_PC_RACE; iRace++ )
         {
            if( !can_use_race( d, iRace ) )
               continue;
            if( toupper( arg[0] ) == toupper( race_table[iRace]->name[0] )
            && !str_prefix( arg, race_table[iRace]->name )
            && ( skipclass || !xIS_SET( race_table[iRace]->class_restriction, ch->Class ) ) )
            {
               ch->race = iRace;
               break;
            }
         }

         if( !can_use_race( d, iRace ) )
         {
            write_to_buffer( d, "\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         if( check_bans( ch, BAN_RACE ) )
         {
            write_to_buffer( d, "That race is not currently available.\r\nWhat is your race? ", 0 );
            write_to_buffer( d, "\r\nYou may choose from the following races, or type help [race] to learn more:\r\n", 0 );
            snprintf( buf, sizeof( buf ), "[%s]\r\n: ", send_race_list( d ) );
            write_to_buffer( d, buf, 0 );
            return;
         }

         write_to_buffer( d, "\r\nWould you like to be a player killer, [Y/N]? ", 0 );
         d->connected = CON_GET_PKILL;
         break;

      case CON_GET_PKILL:
         switch( argument[0] )
         {
            case 'y':
            case 'Y':
               xSET_BIT( ch->pcdata->flags, PCFLAG_DEADLY );
               break;

            case 'n':
            case 'N':
               break;

            default:
               write_to_buffer( d, "Would you like to be a player killer, [Y/N]? ", 0 );
               return;
         }
         write_to_buffer( d, "\r\nWould you like ANSI or no graphic/color support, (A/N)? ", 0 );
         d->connected = CON_GET_WANT_ANSI;
         break;

      case CON_GET_WANT_ANSI:
         switch( argument[0] )
         {
            case 'a':
            case 'A':
               xSET_BIT( ch->act, PLR_ANSI );
               break;
            case 'n':
            case 'N':
               break;
            default:
               write_to_buffer( d, "Invalid selection.\r\nANSI or NONE? ", 0 );
               return;
         }
         snprintf( log_buf, sizeof( log_buf ), "%s@%s new %s %s.", ch->name, d->host,
            dis_race_name( ch->race ), dis_class_name( ch->Class ) );
         log_string_plus( log_buf, LOG_COMM, sysdata.perm_log );
         to_channel( log_buf, "monitor", PERM_IMM );
         write_to_buffer( d, "Press [ENTER] ", 0 );
         show_title( d );
         ch->level = 1;
         ch->pcdata->speed = 32;
         ch->pcdata->birth_month = time_info.month;
         ch->pcdata->birth_day = time_info.day;
         ch->pcdata->birth_year = ( time_info.year - 17 );
         STRSET( ch->pcdata->channels, "classtalk racetalk counciltalk music quest clantalk nationtalk chat wartalk traffic yell auction highscore" );
         ch->position = POS_STANDING;
         d->connected = CON_PRESS_ENTER;
         set_pager_color( AT_PLAIN, ch );
         return;
         break;

      case CON_PRESS_ENTER:
         if( ch->position == POS_MOUNTED )
            ch->position = POS_STANDING;

         set_pager_color( AT_PLAIN, ch );
         if( xIS_SET( ch->act, PLR_ANSI ) )
            send_to_pager( "\033[2J", ch );
         else
            send_to_pager( "\014", ch );
         if( is_immortal( ch ) )
            do_help( ch, "imotd" );
         else if( ch->level == MAX_LEVEL )
            do_help( ch, "amotd" );
         else if( ch->level < MAX_LEVEL && ch->level > 1 )
            do_help( ch, "motd" );
         else if( ch->level == 1 )
            do_help( ch, "nmotd" );
         send_to_pager( "\r\nPress [ENTER] ", ch );
         d->connected = CON_READ_MOTD;
         break;

      case CON_READ_MOTD:
         {
            char motdbuf[MSL];

            snprintf( motdbuf, sizeof( motdbuf ), "\r\nWelcome to %s...\r\n", sysdata.mud_name );
            write_to_buffer( d, motdbuf, 0 );
         }
         add_char( ch );
         d->connected = CON_PLAYING;

         if( ch->level == 1 )
         {
            int iLang, uLang;

            ch->pcdata->clan = NULL;

            if( ( iLang = skill_lookup( "common" ) ) < 0 )
               bug( "%s", "Nanny: can't find common language." );
            else
               ch->pcdata->learned[iLang] = 100;

            /* Give them their racial languages */
            if( race_table[ch->race] )
            {
               for( iLang = 0; lang_array[iLang] != LANG_UNKNOWN; iLang++ )
               {
                  if( IS_SET( race_table[ch->race]->language, 1 << iLang ) )
                   {
                     if( ( uLang = skill_lookup( lang_names[iLang] ) ) < 0 )
                        bug( "%s: can't find racial language [%s].", __FUNCTION__, lang_names[iLang] );
                     else
                        ch->pcdata->learned[uLang] = 100;
                  }
               }
            }

            reset_colors( ch );
            set_base_stats( ch );
            ch->exp = 0;
            set_title( ch, "the newbie" );
            xSET_BIT( ch->act, PLR_AUTOGOLD );
            xSET_BIT( ch->act, PLR_AUTOEXIT );
            xSET_BIT( ch->act, PLR_SUICIDE );
            xSET_BIT( ch->pcdata->flags, PCFLAG_GAG );
            xSET_BIT( ch->act, PLR_COMPASS );
            xSET_BIT( ch->act, PLR_GROUPAFFECTS );
            xSET_BIT( ch->act, PLR_AUTOLOOT );
            xSET_BIT( ch->act, PLR_AUTOSPLIT );
            xSET_BIT( ch->act, PLR_AUTOSAC );
            xSET_BIT( ch->act, PLR_SMARTSAC );
            if( !sysdata.WAIT_FOR_AUTH )
               char_to_room( ch, get_room_index( sysdata.room_school ) );
            else
            {
               char_to_room( ch, get_room_index( sysdata.room_authstart ) );
               ch->pcdata->auth_state = 0;
               xSET_BIT( ch->pcdata->flags, PCFLAG_UNAUTHED );
            }
         }
         else if( ch->in_room )
            char_to_room( ch, ch->in_room );
         else
            char_to_room( ch, get_room_index( sysdata.room_temple ) );

         if( !ch->was_in_room )
            ch->was_in_room = ch->in_room;

         if( get_timer( ch, TIMER_SHOVEDRAG ) > 0 )
            remove_timer( ch, TIMER_SHOVEDRAG );

         if( get_timer( ch, TIMER_PKILLED ) > 0 )
            remove_timer( ch, TIMER_PKILLED );

         act( AT_ACTION, "$n has entered the game.", ch, NULL, NULL, TO_CANSEE );
         if( ch->pcdata->first_pet )
         {
            CHAR_DATA *pet;

            for( pet = ch->pcdata->first_pet; pet; pet = pet->next_pet )
            {
               act( AT_ACTION, "$n returns to $s master from the Void.", pet, NULL, ch, TO_NOTVICT );
               act( AT_ACTION, "$N returns with you to the realms.", ch, NULL, pet, TO_CHAR );
            }
         }
         do_look( ch, "auto" );

         if( !ch->pcdata->channels )
            send_to_char( "\r\n&WYou aren't listening to any channels currently. Type \"listen all\" to listen to all channels.\r\n", ch );

         check_auction( ch );

         show_unread_notes( ch );

         break;
   }
}

/* Parse a name for acceptability. */
bool check_parse_name( char *name, bool newchar )
{
   /* Length restrictions. */
   if( strlen( name ) < 3 )
      return false;

   if( strlen( name ) > 12 )
      return false;

   if( newchar && is_reserved_name( name ) )
      return false;

   return true;
}

bool can_use_path( CHAR_DATA *ch, char *direct, char *filename )
{
   char newfilename[1024];
   struct stat fst;

   /* Length restrictions */
   if( !filename || filename[0] == '\0' )
   {
      send_to_char( "Empty filename is not valid.\r\n", ch );
      return false;
   }
   if( strlen( filename ) < 3 )
   {
      ch_printf( ch, "Filename (%s) isn't long enough.\r\n", filename );
      return false;
   }

   /* Illegal characters */
   if( strstr( filename, ".." ) || strstr( filename, "/" ) || strstr( filename, "\\" ) )
   {
      send_to_char( "A filename may not contain a '..', '/', or '\\' in it.\r\n", ch );
      return false;
   }

   /* If that filename is already being used lets not allow it now to be on the safe side */
   snprintf( newfilename, sizeof( newfilename ), "%s%s", direct, filename );
   if( stat( newfilename, &fst ) != -1 )
   {
      ch_printf( ch, "The filename (%s) is already used.\r\n", filename );
      return false;
   }

   /* If we got here assume its valid */
   return true;
}

bool multi_check( DESCRIPTOR_DATA *host, bool full )
{
   DESCRIPTOR_DATA *d;
   CHAR_DATA *ch;
   int tconns = 0, dconns = 0, pconns = 0;

   if( !host || !host->host || host->host[0] == '\0' )
      return false;

   for( d = first_descriptor; d; d = d->next )
   {
      if( !d->host )
         continue;
      if( str_cmp( d->host, host->host ) )
         continue;
      tconns++;
      if( !( ch = d->character ) || d == host )
         continue;
      if( xIS_SET( ch->pcdata->flags, PCFLAG_DEADLY ) )
         dconns++;
      else
         pconns++;
   }
   if( tconns >= sysdata.mlimit_total )
   {
      write_to_descriptor( host, "You have to many connections to the mud already.\r\n", 0 );
      return true;
   }
   if( full )
   {
      if( dconns >= sysdata.mlimit_deadly )
      {
         write_to_descriptor( host, "You have to many deadlys connected to the mud already.\r\n", 0 );
         return true;
      }
      if( pconns >= sysdata.mlimit_peaceful )
      {
         write_to_descriptor( host, "You have to many peacefuls connected to the mud already.\r\n", 0 );
         return true;
      }
   }
   return false;
}

/* Look for link-dead player to reconnect. */
bool check_reconnect( DESCRIPTOR_DATA *d, char *name, bool fConn )
{
   CHAR_DATA *ch;

   for( ch = first_char; ch; ch = ch->next )
   {
      if( !is_npc( ch ) && ( !fConn || !ch->desc ) && ch->pcdata->filename && !str_cmp( name, ch->pcdata->filename ) )
      {
         if( fConn && ch->desc )
         {
            write_to_buffer( d, "Already playing.\r\nName: ", 0 );
            d->connected = CON_GET_NAME;
            if( d->character )
            {
               /* clear descriptor pointer to get rid of bug message in log */
               d->character->desc = NULL;
               free_char( d->character );
               d->character = NULL;
            }
            return false;
         }
         if( fConn == false )
            STRSET( d->character->pcdata->pwd, ch->pcdata->pwd );
         else
         {
            d->character->desc = NULL;
            free_char( d->character );
            if( !( d->character = ch ) )
            {
               write_to_buffer( d, "Wow there is some major issue.\r\nName: ", 0 );
               d->connected = CON_GET_NAME;
               return false;
            }
            log_printf_plus( LOG_COMM, get_trust( ch ), "%s (%s) reconnected.", ch->name, d->host );
            ch->desc = d;
            ch->timer = 0;
            send_to_char( "Reconnecting.\r\n", ch );
            act( AT_ACTION, "$n has reconnected.", ch, NULL, NULL, TO_CANSEE );
            d->connected = CON_PLAYING;
            do_look( ch, "auto" );
         }
         return true;
      }
   }

   return false;
}

/* Check if already playing. */
bool check_playing( DESCRIPTOR_DATA *d, char *name, bool kick )
{
   CHAR_DATA *ch;
   DESCRIPTOR_DATA *dold;
   int cstate;

   for( dold = first_descriptor; dold; dold = dold->next )
   {
      if( dold != d
      && ( dold->character ) && !str_cmp( name, dold->character->pcdata->filename ) )
      {
         cstate = dold->connected;
         ch = dold->character;
         if( !ch->name || ( cstate != CON_PLAYING && cstate != CON_EDITING ) )
         {
            write_to_buffer( d, "Already connected - try again.\r\n", 0 );
            log_printf_plus( LOG_COMM, PERM_HEAD, "%s already connected.", ch->pcdata->filename );
            return true;
         }
         if( !kick )
            return true;
         write_to_buffer( d, "Already playing... Kicking off old connection.\r\n", 0 );
         write_to_buffer( dold, "Kicking off old connection... bye!\r\n", 0 );
         close_socket( dold, false );
         /* clear descriptor pointer to get rid of bug message in log */
         d->character->desc = NULL;
         free_char( d->character );
         d->character = ch;
         ch->desc = d;
         ch->timer = 0;
         log_printf_plus( LOG_COMM, get_trust( ch ), "%s@%s reconnected, kicking off old link.",
             ch->pcdata->filename, d->host );
         send_to_char( "Reconnecting.\r\n", ch );
         do_look( ch, "auto" );
         act( AT_ACTION, "$n has reconnected, kicking off old link.", ch, NULL, NULL, TO_CANSEE );
         d->connected = cstate;
         return true;
      }
   }

   return false;
}

void stop_idling( CHAR_DATA *ch )
{
   ROOM_INDEX_DATA *was_in_room;

   if( !ch || !ch->desc || ch->desc->connected != CON_PLAYING || !is_idle( ch ) )
      return;

   ch->timer = 0;
   was_in_room = ch->was_in_room;
   char_from_room( ch );
   char_to_room( ch, was_in_room );
   ch->was_in_room = ch->in_room;
   xREMOVE_BIT( ch->pcdata->flags, PCFLAG_IDLE );
   act( AT_ACTION, "$n has returned from the void.", ch, NULL, NULL, TO_ROOM );
}

/*
 * Function to strip off the "a" or "an" or "the" or "some" from an object's
 * short description for the purpose of using it in a sentence sent to
 * the owner of the object.  (Ie: an object with the short description
 * "a long dark blade" would return "long dark blade" for use in a sentence
 * like "Your long dark blade".  The object name isn't always appropriate
 * since it contains keywords that may not look proper.		-Thoric
 */
char *myobj( OBJ_DATA *obj )
{
   if( !str_prefix( "a ", obj->short_descr ) )
      return obj->short_descr + 2;
   if( !str_prefix( "an ", obj->short_descr ) )
      return obj->short_descr + 3;
   if( !str_prefix( "the ", obj->short_descr ) )
      return obj->short_descr + 4;
   if( !str_prefix( "some ", obj->short_descr ) )
      return obj->short_descr + 5;
   return obj->short_descr;
}

char *obj_short( OBJ_DATA *obj )
{
   static char buf[MSL];

   if( obj->count > 1 )
   {
      snprintf( buf, sizeof( buf ), "%s (%d)", obj->short_descr, obj->count );
      return buf;
   }
   return obj->short_descr;
}

#define NAME( ch ) ( is_npc( (ch) ) ? (ch)->short_descr : (ch)->name )

char *MORPHNAME( CHAR_DATA *ch )
{
   if( ch->morph && ch->morph->morph && ch->morph->morph->short_desc )
      return ch->morph->morph->short_desc;
   else
      return NAME(ch);
}

char *act_string( const char *format, CHAR_DATA *to, CHAR_DATA *ch, void *arg1, void *arg2, int flags )
{
   static char buf[MSL];
   char temp[MSL];
   char *point = buf;
   const char *str = format;
   const char *i = "";
   CHAR_DATA *vch = ( CHAR_DATA * ) arg2;
   OBJ_DATA *obj1 = ( OBJ_DATA * ) arg1;
   OBJ_DATA *obj2 = ( OBJ_DATA * ) arg2;

   if( str[0] == '$' )
      DONT_UPPER = false;

   while( *str != '\0' )
   {
      if( *str != '$' )
      {
         *point++ = *str++;
         continue;
      }
      ++str;
      if( !arg2 && *str >= 'A' && *str <= 'Z' )
      {
         bug( "Act: missing arg2 for code %c:", *str );
         bug( "%s", format );
         i = " <@@@> ";
      }
      else
      {
         switch( *str )
         {
            default:
               bug( "Act: bad code %c.", *str );
               i = " <@@@> ";
               break;

            case 'e':
               if( ch->sex > 2 || ch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", ch->name, ch->sex );
                  i = "it";
               }
               else
                  i = !can_see( to, ch ) ? "it" : he_she[URANGE( 0, ch->sex, 2 )];
               break;

            case 'E':
               if( vch->sex > 2 || vch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", vch->name, vch->sex );
                  i = "it";
               }
               else
                  i = !can_see( to, vch ) ? "it" : he_she[URANGE( 0, vch->sex, 2 )];
               break;

            case 'm':
               if( ch->sex > 2 || ch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", ch->name, ch->sex );
                  i = "it";
               }
               else
                  i = !can_see( to, ch ) ? "it" : him_her[URANGE( 0, ch->sex, 2 )];
               break;

            case 'M':
               if( vch->sex > 2 || vch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", vch->name, vch->sex );
                  i = "it";
               }
               else
                  i = !can_see( to, vch ) ? "it" : him_her[URANGE( 0, vch->sex, 2 )];
               break;

            case 'n':
               if( !can_see( to, ch ) )
                  i = "Someone";
               else if( !ch->morph )
                  i = ( to ? PERS( ch, to ) : NAME( ch ) );
               else if( !IS_SET( flags, STRING_IMM ) )
                  i = ( to ? MORPHPERS( ch, to ) : MORPHNAME( ch ) );
               else
               {
                  snprintf( temp, sizeof( temp ), "(MORPH) %s", ( to ? PERS( ch, to ) : NAME( ch ) ) );
                  i = temp;
               }
               break;

            case 'N':
               if( !can_see( to, vch ) )
                  i = "Someone";
               else if( !vch->morph )
                  i = ( to ? PERS( vch, to ) : NAME( vch ) );
               else if( !IS_SET( flags, STRING_IMM ) )
                  i = ( to ? MORPHPERS( vch, to ) : MORPHNAME( vch ) );
               else
               {
                  snprintf( temp, sizeof( temp ), "(MORPH) %s", ( to ? PERS( vch, to ) : NAME( vch ) ) );
                  i = temp;
               }
               break;

            case 'q':
               i = ( to == ch ) ? "" : "s";
               break;

            case 'Q':
               i = ( to == ch ) ? "your" : his_her[URANGE( 0, ch->sex, 2 )];
               break;

            case 's':
               if( ch->sex > 2 || ch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", ch->name, ch->sex );
                  i = "its";
               }
               else
                  i = !can_see( to, ch ) ? "its" : his_her[URANGE( 0, ch->sex, 2 )];
               break;

            case 'S':
               if( vch->sex > 2 || vch->sex < 0 )
               {
                  bug( "act_string: player %s has sex set at %d!", vch->name, vch->sex );
                  i = "its";
               }
               else
                  i = !can_see( to, vch ) ? "its" : his_her[URANGE( 0, vch->sex, 2 )];
               break;

            case 't':
               i = ( char * )arg1;
               break;

            case 'T':
               i = ( char * )arg2;
               break;

            case 'p':
               if( !obj1 )
               {
                  bug( "act_string: $p used with NULL obj1!" );
                  i = "something";
               }
               if( to && obj1 )
                  i = ( can_see_obj( to, obj1 ) ? obj_short( obj1 ) : "something" );
               break;

            case 'P':
               if( !obj2 )
               {
                  bug( "act_string: $P used with NULL obj2!" );
                  i = "something";
               }
               if( to && obj2 )
                  i = ( can_see_obj( to, obj2 ) ? obj_short( obj2 ) : "something" );
               break;

            case 'd':
               if( !arg2 || ( ( char * )arg2 )[0] == '\0' )
                  i = "door";
               else
               {
                  char fname[MIL];

                  one_argument( ( char * )arg2, fname );
                  i = fname;
               }
               break;
         }
      }
      ++str;
      while( *i && ( *point = *i ) != '\0' )
         ++point, ++i;
   }
   mudstrlcpy( point, "\r\n", sizeof( point ) );
   if( !DONT_UPPER )
   {
      /* Up the part it should when its a global type thing */
      if( !str_prefix( "[From afar] ", buf ) )
         buf[12] = UPPER( buf[12] );
      else
         buf[0] = UPPER( buf[0] );
   }
   return buf;
}

#undef NAME

void act( short AType, const char *format, CHAR_DATA *ch, void *arg1, void *arg2, int type )
{
   char *txt;
   CHAR_DATA *to;
   CHAR_DATA *vch = ( CHAR_DATA * ) arg2;

   /* Discard null and zero-length messages. */
   if( !format || format[0] == '\0' )
      return;

   if( !ch )
   {
      bug( "Act: null ch. (%s)", format );
      return;
   }

   if( !ch->in_room )
      to = NULL;
   else if( type == TO_CHAR )
      to = ch;
   else if( type == TO_OTHERS )
      to = first_char;
   else
      to = ch->in_room->first_person;

   /* ACT_SECRETIVE handling */
   if( is_npc( ch ) && xIS_SET( ch->act, ACT_SECRETIVE ) && type != TO_CHAR )
      return;

   if( type == TO_VICT )
   {
      if( !vch )
      {
         bug( "%s", "Act: null vch with TO_VICT." );
         bug( "%s (%s)", ch->name, format );
         return;
      }
      if( !vch->in_room )
      {
         bug( "%s", "Act: vch in NULL room!" );
         bug( "%s -> %s (%s)", ch->name, vch->name, format );
         return;
      }
      to = vch;
   }

   if( MOBtrigger && type != TO_CHAR && type != TO_VICT && type != TO_OTHERS && to )
   {
      OBJ_DATA *to_obj;

      txt = act_string( format, NULL, ch, arg1, arg2, STRING_IMM );
      if( HAS_PROG( to->in_room, ACT_PROG ) )
         rprog_act_trigger( txt, to->in_room, ch, ( OBJ_DATA * ) arg1, ( void * )arg2 );
      for( to_obj = to->in_room->first_content; to_obj; to_obj = to_obj->next_content )
         if( HAS_PROG( to_obj->pIndexData, ACT_PROG ) )
            oprog_act_trigger( txt, to_obj, ch, ( OBJ_DATA * ) arg1, ( void * )arg2 );
   }

   /*
    * Anyone feel like telling me the point of looping through the whole
    * room when we're only sending to one char anyways..? -- Alty 
    */
   for( ; to; to = ( type == TO_CHAR || type == TO_VICT ) ? NULL : ( type == TO_OTHERS ) ? to->next : to->next_in_room )
   {
      if( ( !to->desc && ( is_npc( to ) && !HAS_PROG( to->pIndexData, ACT_PROG ) ) ) || !is_awake( to ) )
         continue;
      if( type == TO_CHAR && to != ch )
         continue;
      if( type == TO_VICT && ( to != vch || to == ch ) )
         continue;
      if( type == TO_ROOM && to == ch )
         continue;
      if( type == TO_ROOM && sneaking_char && !is_immortal( ch ) && !IS_AFFECTED( to, AFF_DETECT_SNEAK ) )
         continue;
      if( type == TO_OTHERS && ( to == ch || to == vch || is_npc( to ) ) )
         continue;
      if( type == TO_NOTVICT && ( to == ch || to == vch ) )
         continue;
      if( type == TO_CANSEE && ( to == ch
      || ( !is_immortal( to ) && !is_npc( ch ) && ( xIS_SET( ch->act, PLR_WIZINVIS )
      && ( get_trust( to ) < ( ch->pcdata ? ch->pcdata->wizinvis : 0 ) ) ) ) ) )
         continue;

      /* No clue why ignore wasn't handled here instead */
      if( to && ch && is_ignoring( to, ch ) )
      {
         if( !is_immortal( ch ) || get_trust( to ) > get_trust( ch ) )
            continue;
         else
            ch_printf( to, "You attempt to ignore %s, but are unable to do so.\r\n", ch->name );
      }

      if( to && vch && is_ignoring( to, vch ) )
      {
         if( !is_immortal( vch ) || get_trust( to ) > get_trust( vch ) )
            continue;
         else
            ch_printf( to, "You attempt to ignore %s, but are unable to do so.\r\n", vch->name );
      }

      if( is_immortal( to ) )
         txt = act_string( format, to, ch, arg1, arg2, STRING_IMM );
      else
         txt = act_string( format, to, ch, arg1, arg2, STRING_NONE );

      if( to->desc )
      {
         set_char_color( AType, to );
         send_to_char( txt, to );
      }
      if( MOBtrigger )
      {
         /* Note: use original string, not string with ANSI. -- Alty */
         mprog_act_trigger( txt, to, ch, ( OBJ_DATA * ) arg1, ( void * )arg2 );
      }
   }
}

CMDF( do_name )
{
   CHAR_DATA *tmp;

   if( !not_authed( ch ) || ch->pcdata->auth_state != 2 )
   {
      send_to_char( "Huh?\r\n", ch );
      return;
   }

   argument[0] = UPPER( argument[0] );

   if( !check_parse_name( argument, true ) )
   {
      send_to_char( "Illegal name, try another.\r\n", ch );
      return;
   }

   if( !str_cmp( ch->name, argument ) )
   {
      send_to_char( "That's already your name!\r\n", ch );
      return;
   }

   for( tmp = first_char; tmp; tmp = tmp->next )
   {
      if( !str_cmp( argument, tmp->name ) )
         break;
   }

   if( tmp )
   {
      send_to_char( "That name is already taken.  Please choose another.\r\n", ch );
      return;
   }

   if( valid_pfile( argument ) )
   {
      send_to_char( "That name is already taken.  Please choose another.\r\n", ch );
      return;
   }

   STRSET( ch->name, argument );
   STRSET( ch->pcdata->filename, argument );
   send_to_char( "Your name has been changed.  Please apply again.\r\n", ch );
   ch->pcdata->auth_state = 0;
}

char *default_fprompt( CHAR_DATA *ch )
{
   static char buf[80];

   mudstrlcpy( buf, "&w<&Y%hhp ", sizeof( buf ) );
   if( is_vampire( ch ) )
      mudstrlcat( buf, "&R%bbp", sizeof( buf ) );
   else
      mudstrlcat( buf, "&C%mm", sizeof( buf ) );
   mudstrlcat( buf, " &G%vmv&w>", sizeof( buf ) );
   mudstrlcat( buf, "&w[&Y%g&wGold]", sizeof( buf ) );
   if( is_npc( ch ) || is_immortal( ch ) )
   {
      mudstrlcat( buf, "%i", sizeof( buf ) );
      mudstrlcat( buf, "&w[&RRVnum &W%r&w]", sizeof( buf ) );
   }
   mudstrlcat( buf, "&w<&C%n&w[&R%c&w]>", sizeof( buf ) );
   return buf;
}

char *default_prompt( CHAR_DATA *ch )
{
   static char buf[60];

   mudstrlcpy( buf, "&w<&Y%hhp ", sizeof( buf ) );
   if( is_vampire( ch ) )
      mudstrlcat( buf, "&R%bbp", sizeof( buf ) );
   else
      mudstrlcat( buf, "&C%mm", sizeof( buf ) );
   mudstrlcat( buf, " &G%vmv&w>", sizeof( buf ) );
   mudstrlcat( buf, "&w[&Y%g&wGold]", sizeof( buf ) );
   if( is_npc( ch ) || is_immortal( ch ) )
   {
      mudstrlcat( buf, "%i", sizeof( buf ) );
      mudstrlcat( buf, "&w[&RRVnum &W%r&w]", sizeof( buf ) );
   }
   return buf;
}

int getcolor( char clr )
{
   static const char colors[17] = "xrgObpcwzRGYBPCW";
   int r;

   for( r = 0; r < 16; r++ )
      if( clr == colors[r] )
         return r;
   return -1;
}

void display_prompt( DESCRIPTOR_DATA *d )
{
   CHAR_DATA *ch = d->character;
   CHAR_DATA *och = d->character;
   CHAR_DATA *victim;
   bool ansi = ( !is_npc( och ) && xIS_SET( och->act, PLR_ANSI ) );
   const char *prompt;
   char buf[MSL];
   char *pbuf = buf;
   unsigned int pstat;
   int percent;

   if( !ch )
   {
      bug( "%s: NULL ch", __FUNCTION__ );
      return;
   }

   if( !is_npc( ch ) && ch->substate != SUB_NONE && ch->pcdata->subprompt && ch->pcdata->subprompt[0] != '\0' )
      prompt = ch->pcdata->subprompt;
   else if( is_npc( ch ) || ( !ch->fighting && ( !ch->pcdata->prompt || !*ch->pcdata->prompt ) ) )
      prompt = default_prompt( ch );
   else if( ch->fighting )
   {
      if( !ch->pcdata->fprompt || !*ch->pcdata->fprompt )
         prompt = default_fprompt( ch );
      else
         prompt = ch->pcdata->fprompt;
   }
   else
      prompt = ch->pcdata->prompt;
   if( ansi )
   {
      mudstrlcpy( pbuf, ANSI_RESET, sizeof( buf ) );
      d->prevcolor = 0x08;
      pbuf += 4;
   }

   /* Clear out old color stuff */
   for( ; *prompt; prompt++ )
   {
      /*
       * '%' = prompt commands
       * Note: foreground changes will revert background to 0 (black)
       */
      if( *prompt != '%' )
      {
         *( pbuf++ ) = *prompt;
         continue;
      }
      ++prompt;
      if( !*prompt )
         break;
      if( *prompt == *( prompt - 1 ) )
      {
         *( pbuf++ ) = *prompt;
         continue;
      }
      switch( *( prompt - 1 ) )
      {
         default:
            bug( "%s: bad command char '%c'.", __FUNCTION__, *( prompt - 1 ) );
            break;
         case '%':
            *pbuf = '\0';
            pstat = 0x80000000;
            switch( *prompt )
            {
               case '%':
                  *pbuf++ = '%';
                  *pbuf = '\0';
                  break;

               case 'a':
                  pstat = ch->alignment;
                  break;

               case 'A':
                  snprintf( pbuf, sizeof( buf ), "%s%s%s", IS_AFFECTED( ch, AFF_INVISIBLE ) ? "I" : "",
                     IS_AFFECTED( ch, AFF_HIDE ) ? "H" : "", IS_AFFECTED( ch, AFF_SNEAK ) ? "S" : "" );
                  break;

               case 'C':
                  if( ch->fighting && ( victim = ch->fighting->who )
                  && victim->fighting && ( victim = victim->fighting->who ) )
                  {
                     if( victim->hit > 0 && victim->max_hit > 0 )
                     {
                        percent = ( 100 * victim->hit ) / victim->max_hit;
                        snprintf( pbuf, sizeof( buf ), "%d%%", percent );
                     }
                  }
                  break;

               case 'c':
                  if( ch->fighting && ( victim = ch->fighting->who ) )
                  {
                     if( victim->hit > 0 && victim->max_hit > 0 )
                     {
                        percent = ( 100 * victim->hit ) / victim->max_hit;
                        snprintf( pbuf, sizeof( buf ), "%d%%", percent );
                     }
                  }
                  break;

               case 'F':
                  if( is_immortal( och ) )
                     snprintf( pbuf, sizeof( buf ), "%s", ext_flag_string( &ch->in_room->room_flags, r_flags ) );
                  break;

               case 'g':
                  snprintf( pbuf, sizeof( buf ), "%s", show_big_nums( ch->mgold, ch->gold ) );
                  break;

               case 'h':
                  pstat = ch->hit;
                  break;

               case 'H':
                  pstat = ch->max_hit;
                  break;

               case 'i':
                  if( ( !is_npc( ch ) && xIS_SET( ch->act, PLR_WIZINVIS ) )
                  || ( is_npc( ch ) && xIS_SET( ch->act, ACT_MOBINVIS ) ) )
                     snprintf( pbuf, sizeof( buf ), "(Invis %d) ", ( is_npc( ch ) ? ch->mobinvis : ch->pcdata->wizinvis ) );
                  else if( IS_AFFECTED( ch, AFF_INVISIBLE ) )
                     mudstrlcpy( pbuf, "(Invis) ", sizeof( buf ) );
                  break;

               case 'I':
                  pstat = ( is_npc( ch ) ? ( xIS_SET( ch->act, ACT_MOBINVIS ) ? ch->mobinvis : 0 )
                     : ( xIS_SET( ch->act, PLR_WIZINVIS ) ? ch->pcdata->wizinvis : 0 ) );
                  break;

               case 'j':
               case 'J':
                  mudstrlcpy( pbuf, "\r\n", sizeof( buf ) );
                  break;

               case 'b':
               case 'm':
                  pstat = ch->mana;
                  break;

               case 'B':
               case 'M':
                  pstat = ch->max_mana;
                  break;

               case 'N':
                  if( ch->fighting && ( victim = ch->fighting->who )
                  && victim->fighting && ( victim = victim->fighting->who ) )
                  {
                     if( ch == victim )
                        mudstrlcpy( pbuf, "You", sizeof( buf ) );
                     else if( is_npc( victim ) && victim->short_descr )
                        mudstrlcpy( pbuf, victim->short_descr, sizeof( buf ) );
                     else if( victim->name )
                        mudstrlcpy( pbuf, victim->name, sizeof( buf ) );
                     if( pbuf )
                        pbuf[0] = UPPER( pbuf[0] );
                  }
                  break;

               case 'n':
                  if( ch->fighting && ( victim = ch->fighting->who ) )
                  {
                     if( ch == victim )
                        mudstrlcpy( pbuf, "You", sizeof( buf ) );
                     else if( is_npc( victim ) && victim->short_descr )
                        mudstrlcpy( pbuf, victim->short_descr, sizeof( buf ) );
                     else if( victim->name )
                        mudstrlcpy( pbuf, victim->name, sizeof( buf ) );
                     if( pbuf )
                        pbuf[0] = UPPER( pbuf[0] );
                  }
                  break;

               case 'q':
                  pstat = ch->questcountdown;
                  break;

               case 'r':
                  if( is_immortal( och ) )
                     pstat = ch->in_room->vnum;
                  break;

               case 'S':
                  mudstrlcpy( pbuf, style_names[ch->style], sizeof( buf ) );
                  break;

               case 't':
                  pstat = time_info.hour;
                  break;

               case 'T':
                  if( time_info.hour < 5 )
                     mudstrlcpy( pbuf, "night", sizeof( buf ) );
                  else if( time_info.hour < 6 )
                     mudstrlcpy( pbuf, "dawn", sizeof( buf ) );
                  else if( time_info.hour < 19 )
                     mudstrlcpy( pbuf, "day", sizeof( buf ) );
                  else if( time_info.hour < 21 )
                     mudstrlcpy( pbuf, "dusk", sizeof( buf ) );
                  else
                     mudstrlcpy( pbuf, "night", sizeof( buf ) );
                  break;

               case 'u':
                  pstat = num_descriptors;
                  break;

               case 'U':
                  pstat = sysdata.maxplayers;
                  break;

               case 'v':
                  pstat = ch->move;
                  break;

               case 'V':
                  pstat = ch->max_move;
                  break;

               case 'x':
                  mudstrlcpy( pbuf, double_punct( ch->exp ), sizeof( buf ) );
                  break;

               case 'X':
                  mudstrlcpy( pbuf, double_punct( exp_level( ch, ch->level + 1 ) - ch->exp ), sizeof( buf ) );
                  break;
            }
            if( pstat != 0x80000000 )
               snprintf( pbuf, sizeof( buf ), "%d", pstat );
            pbuf += strlen( pbuf );
            break;
      }
   }
   *pbuf = '\0';
   send_to_char( buf, ch );
}

void set_pager_input( DESCRIPTOR_DATA *d, char *argument )
{
   while( isspace( *argument ) )
      argument++;
   d->pagecmd = *argument;
}

bool pager_output( DESCRIPTOR_DATA *d )
{
   register char *last;
   CHAR_DATA *ch;
   int pclines;
   register int lines;
   bool ret;

   if( !d || !d->pagepoint || d->pagecmd == -1 )
      return true;
   ch = d->character;
   pclines = UMAX( ch->pcdata->pagerlen, 5 ) - 1;
   switch( LOWER( d->pagecmd ) )
   {
      default:
         lines = 0;
         break;

      case 'b':
         lines = -1 - ( pclines * 2 );
         break;

      case 'r':
         lines = -1 - pclines;
         break;

      case 'n':
         lines = 0;
         pclines = 0x7FFFFFFF;   /* As many lines as possible */
         break;

      case 'q':
         d->pagetop = 0;
         d->pagepoint = NULL;
         flush_buffer( d, true );
         DISPOSE( d->pagebuf );
         d->pagesize = MSL;
         return true;
   }
   while( lines < 0 && d->pagepoint >= d->pagebuf )
   {
      if( *( --d->pagepoint ) == '\n' )
         ++lines;
   }
   if( *d->pagepoint == '\r' && *( ++d->pagepoint ) == '\n' )
      ++d->pagepoint;
   if( d->pagepoint < d->pagebuf )
      d->pagepoint = d->pagebuf;
   for( lines = 0, last = d->pagepoint; lines < pclines; ++last )
   {
      if( !*last )
         break;
      else if( *last == '\n' )
         ++lines;
   }
   if( *last == '\r' )
      ++last;
   if( last != d->pagepoint )
   {
      if( !write_to_descriptor( d, d->pagepoint, ( last - d->pagepoint ) ) )
         return false;
      d->pagepoint = last;
   }
   while( isspace( *last ) )
      ++last;
   if( !*last )
   {
      d->pagetop = 0;
      d->pagepoint = NULL;
      flush_buffer( d, true );
      DISPOSE( d->pagebuf );
      d->pagesize = MSL;
      return true;
   }
   d->pagecmd = -1;
   if( xIS_SET( ch->act, PLR_ANSI ) )
      if( write_to_descriptor( d, ANSI_LBLUE, 0 ) == false )
         return false;
   if( ( ret = write_to_descriptor( d, "(C)ontinue, (N)on-stop, (R)efresh, (B)ack, (Q)uit: [C] ", 0 ) ) == false )
      return false;
   if( xIS_SET( ch->act, PLR_ANSI ) )
   {
      char buf[32];

      snprintf( buf, sizeof( buf ), "%s", color_str( d->pagecolor, ch ) );
      ret = write_to_descriptor( d, buf, 0 );
   }
   return ret;
}

#ifdef WIN32

void shutdown_mud( char *reason );

void bailout( void )
{
   echo_to_all( AT_IMMORT, "MUD shutting down by system operator NOW!!", ECHOTAR_ALL );
   shutdown_mud( "MUD shutdown by system operator" );
   log_string( "MUD shutdown by system operator" );
   Sleep( 5000 ); /* give "echo_to_all" time to display */
   mud_down = true;  /* This will cause game_loop to exit */
   service_shut_down = true;  /* This will cause characters to be saved */
   fflush( stderr );
}

#endif

bool exists_file( char *name )
{
   struct stat fst;

   if( !name || name[0] == '\0' )
      return false;
   if( stat( name, &fst ) != -1 )
      return true;
   else
      return false;
}

void open_mud_log( void )
{
   FILE *error_log;
   char buf[MIL], buf2[MIL];
   int logindex, logdel;

   for( logindex = 1000; ; logindex++ )
   {
      sprintf( buf, "log/%d.log", logindex );
      if( exists_file( buf ) )
         continue;
      else if( logindex > 1025 )
      {
         for( logdel = 1000; ; logdel++ )
         {
            sprintf( buf2, "log/%d.log", logdel );
            if( logdel <= logindex )
            {
               if( exists_file( buf2 ) )
               {
                  if( !remove( buf2 ) )
                     continue;
                  else if( errno != ENOENT )
                     continue;
               }
               else
               {
                  logindex = 999;
                  break;
               }
            }
            else
            {
               logindex = 999;
               break;
            }
         }
      }
      else
         break;
   }
   if( !( error_log = fopen( buf, "a" ) ) )
   {
      fprintf( stderr, "Unable to append to %s.", buf );
      exit( 1 );
   }

   dup2( fileno( error_log ), STDERR_FILENO );
   fclose( error_log );
   error_log = NULL;
}