/******************************************************************************
 Protocol snippet by KaVir.  Released into the public domain in February 2011.

 In case this is not legally possible:

 The copyright holder grants any entity the right to use this work for any 
 purpose, without any conditions, unless such conditions are required by law.
 ******************************************************************************/

/******************************************************************************
 This snippet was originally designed to be codebase independent, but has been 
 modified slightly so that it runs out-of-the-box on Merc derivatives.  To use 
 it for other codebases, just change the code in the "Diku/Merc" section below.
 ******************************************************************************/

/******************************************************************************
 Header files.
 ******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/telnet.h>
#include <time.h>
#include <malloc.h>
#include <alloca.h>
#include <ctype.h>

#include "protocol.h"

/******************************************************************************
 The following section is for Diku/Merc derivatives.  Replace as needed.
 ******************************************************************************/

#include "merc.h"

static void Write( descriptor_t *apDescriptor, const char *apData )
{
   if ( apDescriptor != NULL && !apDescriptor->fcommand )
   {
      if ( apDescriptor->pProtocol->WriteOOB > 0 || 
         apDescriptor->outtop == 0 )
      {
         apDescriptor->pProtocol->WriteOOB = 2;
      }
   }
   write_to_buffer( apDescriptor, apData, 0 );
}

static void ReportBug( const char *apText )
{
   bug( apText, 0 );
}

static void InfoMessage( descriptor_t *apDescriptor, const char *apData )
{
   Write( apDescriptor, "\t[F210][\toINFO\t[F210]]\tn " );
   Write( apDescriptor, apData );
}

static void CompressStart( descriptor_t *apDescriptor )
{
   /* If your mud uses MCCP (Mud Client Compression Protocol), you need to 
    * call whatever function normally starts compression from here - the 
    * ReportBug() call should then be deleted.
    * 
    * Otherwise you can just ignore this function.
    */
   ReportBug( "CompressStart() in protocol.c is being called, but it doesn't do anything!\n" );
}

static void CompressEnd( descriptor_t *apDescriptor )
{
   /* If your mud uses MCCP (Mud Client Compression Protocol), you need to 
    * call whatever function normally starts compression from here - the 
    * ReportBug() call should then be deleted.
    * 
    * Otherwise you can just ignore this function.
    */
   ReportBug( "CompressEnd() in protocol.c is being called, but it doesn't do anything!\n" );
}

/******************************************************************************
 MSDP file-scope variables.
 ******************************************************************************/

/* These are for the GUI_VARIABLES, my unofficial extension of MSDP.  They're 
 * intended for clients that wish to offer a generic GUI - not as nice as a 
 * custom GUI, admittedly, but still better than a plain terminal window.
 *
 * These are per-player so that they can be customised for different characters 
 * (eg changing 'mana' to 'blood' for vampires).  You could even allow players 
 * to customise the buttons and gauges themselves if you wish.
 */
static const char s_Button1[] = "\005\002Help\002help\006";
static const char s_Button2[] = "\005\002Look\002look\006";
static const char s_Button3[] = "\005\002Score\002help\006";
static const char s_Button4[] = "\005\002Equipment\002equipment\006";
static const char s_Button5[] = "\005\002Inventory\002inventory\006";

static const char s_Gauge1[]  = "\005\002Health\002red\002HEALTH\002HEALTH_MAX\006";
static const char s_Gauge2[]  = "\005\002Mana\002blue\002MANA\002MANA_MAX\006";
static const char s_Gauge3[]  = "\005\002Movement\002green\002MOVEMENT\002MOVEMENT_MAX\006";
static const char s_Gauge4[]  = "\005\002Exp TNL\002yellow\002EXPERIENCE\002EXPERIENCE_MAX\006";
static const char s_Gauge5[]  = "\005\002Opponent\002darkred\002OPPONENT_HEALTH\002OPPONENT_HEALTH_MAX\006";

/******************************************************************************
 MSDP variable table.
 ******************************************************************************/

/* Macros for readability, but you can remove them if you don't like them */
#define NUMBER_READ_ONLY           false, false, false, false, -1, -1,  0, NULL
#define NUMBER_READ_ONLY_SET_TO(x) false, false, false, false, -1, -1,  x, NULL
#define STRING_READ_ONLY           true,  false, false, false, -1, -1,  0, NULL
#define NUMBER_IN_THE_RANGE(x,y)   false, true,  false, false,  x,  y,  0, NULL
#define BOOLEAN_SET_TO(x)          false, true,  false, false,  0,  1,  x, NULL
#define STRING_WITH_LENGTH_OF(x,y) true,  true,  false, false,  x,  y,  0, NULL
#define STRING_WRITE_ONCE(x,y)     true,  true,  true,  false, -1, -1,  0, NULL
#define STRING_GUI(x)              true,  false, false, true,  -1, -1,  0, x

static variable_name_t VariableNameTable[eMSDP_MAX+1] = 
{
   /* General */
   { eMSDP_CHARACTER_NAME,   "CHARACTER_NAME",   STRING_READ_ONLY }, 
   { eMSDP_SERVER_ID,        "SERVER_ID",        STRING_READ_ONLY }, 
   { eMSDP_SERVER_TIME,      "SERVER_TIME",      NUMBER_READ_ONLY }, 
   { eMSDP_SNIPPET_VERSION,  "SNIPPET_VERSION",  NUMBER_READ_ONLY_SET_TO(SNIPPET_VERSION) }, 

   /* Character */
   { eMSDP_AFFECTS,          "AFFECTS",          STRING_READ_ONLY }, 
   { eMSDP_ALIGNMENT,        "ALIGNMENT",        NUMBER_READ_ONLY }, 
   { eMSDP_EXPERIENCE,       "EXPERIENCE",       NUMBER_READ_ONLY }, 
   { eMSDP_EXPERIENCE_MAX,   "EXPERIENCE_MAX",   NUMBER_READ_ONLY }, 
   { eMSDP_EXPERIENCE_TNL,   "EXPERIENCE_TNL",   NUMBER_READ_ONLY }, 
   { eMSDP_HEALTH,           "HEALTH",           NUMBER_READ_ONLY }, 
   { eMSDP_HEALTH_MAX,       "HEALTH_MAX",       NUMBER_READ_ONLY }, 
   { eMSDP_LEVEL,            "LEVEL",            NUMBER_READ_ONLY }, 
   { eMSDP_RACE,             "RACE",             STRING_READ_ONLY }, 
   { eMSDP_CLASS,            "CLASS",            STRING_READ_ONLY }, 
   { eMSDP_MANA,             "MANA",             NUMBER_READ_ONLY }, 
   { eMSDP_MANA_MAX,         "MANA_MAX",         NUMBER_READ_ONLY }, 
   { eMSDP_WIMPY,            "WIMPY",            NUMBER_READ_ONLY }, 
   { eMSDP_PRACTICE,         "PRACTICE",         NUMBER_READ_ONLY }, 
   { eMSDP_MONEY,            "MONEY",            NUMBER_READ_ONLY }, 
   { eMSDP_MOVEMENT,         "MOVEMENT",         NUMBER_READ_ONLY }, 
   { eMSDP_MOVEMENT_MAX,     "MOVEMENT_MAX",     NUMBER_READ_ONLY }, 
   { eMSDP_HITROLL,          "HITROLL",          NUMBER_READ_ONLY }, 
   { eMSDP_DAMROLL,          "DAMROLL",          NUMBER_READ_ONLY }, 
   { eMSDP_AC,               "AC",               NUMBER_READ_ONLY }, 
   { eMSDP_STR,              "STR",              NUMBER_READ_ONLY }, 
   { eMSDP_INT,              "INT",              NUMBER_READ_ONLY }, 
   { eMSDP_WIS,              "WIS",              NUMBER_READ_ONLY }, 
   { eMSDP_DEX,              "DEX",              NUMBER_READ_ONLY }, 
   { eMSDP_CON,              "CON",              NUMBER_READ_ONLY }, 
   { eMSDP_STR_PERM,         "STR_PERM",         NUMBER_READ_ONLY }, 
   { eMSDP_INT_PERM,         "INT_PERM",         NUMBER_READ_ONLY }, 
   { eMSDP_WIS_PERM,         "WIS_PERM",         NUMBER_READ_ONLY }, 
   { eMSDP_DEX_PERM,         "DEX_PERM",         NUMBER_READ_ONLY }, 
   { eMSDP_CON_PERM,         "CON_PERM",         NUMBER_READ_ONLY }, 

   /* Combat */
   { eMSDP_OPPONENT_HEALTH,  "OPPONENT_HEALTH",  NUMBER_READ_ONLY }, 
   { eMSDP_OPPONENT_HEALTH_MAX,"OPPONENT_HEALTH_MAX",NUMBER_READ_ONLY }, 
   { eMSDP_OPPONENT_LEVEL,   "OPPONENT_LEVEL",   NUMBER_READ_ONLY }, 
   { eMSDP_OPPONENT_NAME,    "OPPONENT_NAME",    STRING_READ_ONLY }, 

   /* World */
   { eMSDP_AREA_NAME,        "AREA_NAME",        STRING_READ_ONLY }, 
   { eMSDP_ROOM_EXITS,       "ROOM_EXITS",       STRING_READ_ONLY }, 
   { eMSDP_ROOM_NAME,        "ROOM_NAME",        STRING_READ_ONLY }, 
   { eMSDP_ROOM_VNUM,        "ROOM_VNUM",        NUMBER_READ_ONLY }, 
   { eMSDP_WORLD_TIME,       "WORLD_TIME",       NUMBER_READ_ONLY }, 

   /* Configurable variables */
   { eMSDP_CLIENT_ID,        "CLIENT_ID",        STRING_WRITE_ONCE(1,40) }, 
   { eMSDP_CLIENT_VERSION,   "CLIENT_VERSION",   STRING_WRITE_ONCE(1,40) }, 
   { eMSDP_PLUGIN_ID,        "PLUGIN_ID",        STRING_WITH_LENGTH_OF(1,40) }, 
   { eMSDP_ANSI_COLORS,      "ANSI_COLORS",      BOOLEAN_SET_TO(1) }, 
   { eMSDP_XTERM_256_COLORS, "XTERM_256_COLORS", BOOLEAN_SET_TO(0) }, 
   { eMSDP_UTF_8,            "UTF_8",            BOOLEAN_SET_TO(0) }, 
   { eMSDP_SOUND,            "SOUND",            BOOLEAN_SET_TO(0) }, 
   { eMSDP_MXP,              "MXP",              BOOLEAN_SET_TO(0) }, 

   /* GUI variables */
   { eMSDP_BUTTON_1,         "BUTTON_1",         STRING_GUI(s_Button1) }, 
   { eMSDP_BUTTON_2,         "BUTTON_2",         STRING_GUI(s_Button2) }, 
   { eMSDP_BUTTON_3,         "BUTTON_3",         STRING_GUI(s_Button3) }, 
   { eMSDP_BUTTON_4,         "BUTTON_4",         STRING_GUI(s_Button4) }, 
   { eMSDP_BUTTON_5,         "BUTTON_5",         STRING_GUI(s_Button5) }, 
   { eMSDP_GAUGE_1,          "GAUGE_1",          STRING_GUI(s_Gauge1) }, 
   { eMSDP_GAUGE_2,          "GAUGE_2",          STRING_GUI(s_Gauge2) }, 
   { eMSDP_GAUGE_3,          "GAUGE_3",          STRING_GUI(s_Gauge3) }, 
   { eMSDP_GAUGE_4,          "GAUGE_4",          STRING_GUI(s_Gauge4) }, 
   { eMSDP_GAUGE_5,          "GAUGE_5",          STRING_GUI(s_Gauge5) }, 

   { eMSDP_MAX,              "", 0 } /* This must always be last. */
};

/******************************************************************************
 MSSP file-scope variables.
 ******************************************************************************/

static int    s_Players = 0;
static time_t s_Uptime  = 0;

/******************************************************************************
 Local function prototypes.
 ******************************************************************************/

static void Negotiate               ( descriptor_t *apDescriptor );
static void PerformHandshake        ( descriptor_t *apDescriptor, char aCmd, char aProtocol );
static void PerformSubnegotiation   ( descriptor_t *apDescriptor, char aCmd, char *apData, int aSize );
static void SendNegotiationSequence ( descriptor_t *apDescriptor, char aCmd, char aProtocol );
static bool_t ConfirmNegotiation    ( descriptor_t *apDescriptor, negotiated_t aProtocol, bool_t abWillDo, bool_t abSendReply );

static void ParseMSDP               ( descriptor_t *apDescriptor, const char *apData );
static void ExecuteMSDPPair         ( descriptor_t *apDescriptor, const char *apVariable, const char *apValue );

static void ParseATCP               ( descriptor_t *apDescriptor, const char *apData );
#ifdef MUDLET_PACKAGE
static void SendATCP                ( descriptor_t *apDescriptor, const char *apVariable, const char *apValue );
#endif /* MUDLET_PACKAGE */

static void SendMSSP                ( descriptor_t *apDescriptor );

static char *GetMxpTag              ( const char *apTag, const char *apText );

static const char *GetAnsiColour    ( bool_t abBackground, int aRed, int aGreen, int aBlue );
static const char *GetRGBColour     ( bool_t abBackground, int aRed, int aGreen, int aBlue );
static bool_t IsValidColour         ( const char *apArgument );

static bool_t MatchString           ( const char *apFirst, const char *apSecond );
static bool_t PrefixString          ( const char *apPart, const char *apWhole );
static bool_t IsNumber              ( const char *apString );
static char  *AllocString           ( const char *apString );

/******************************************************************************
 ANSI colour codes.
 ******************************************************************************/

static const char s_Clean       [] = "\033[0;00m"; /* Remove colour */

static const char s_DarkBlack   [] = "\033[0;30m"; /* Black foreground */
static const char s_DarkRed     [] = "\033[0;31m"; /* Red foreground */
static const char s_DarkGreen   [] = "\033[0;32m"; /* Green foreground */
static const char s_DarkYellow  [] = "\033[0;33m"; /* Yellow foreground */
static const char s_DarkBlue    [] = "\033[0;34m"; /* Blue foreground */
static const char s_DarkMagenta [] = "\033[0;35m"; /* Magenta foreground */
static const char s_DarkCyan    [] = "\033[0;36m"; /* Cyan foreground */
static const char s_DarkWhite   [] = "\033[0;37m"; /* White foreground */

static const char s_BoldBlack   [] = "\033[1;30m"; /* Grey foreground */
static const char s_BoldRed     [] = "\033[1;31m"; /* Bright red foreground */
static const char s_BoldGreen   [] = "\033[1;32m"; /* Bright green foreground */
static const char s_BoldYellow  [] = "\033[1;33m"; /* Bright yellow foreground */
static const char s_BoldBlue    [] = "\033[1;34m"; /* Bright blue foreground */
static const char s_BoldMagenta [] = "\033[1;35m"; /* Bright magenta foreground */
static const char s_BoldCyan    [] = "\033[1;36m"; /* Bright cyan foreground */
static const char s_BoldWhite   [] = "\033[1;37m"; /* Bright white foreground */

static const char s_BackBlack   [] = "\033[1;40m"; /* Black background */
static const char s_BackRed     [] = "\033[1;41m"; /* Red background */
static const char s_BackGreen   [] = "\033[1;42m"; /* Green background */
static const char s_BackYellow  [] = "\033[1;43m"; /* Yellow background */
static const char s_BackBlue    [] = "\033[1;44m"; /* Blue background */
static const char s_BackMagenta [] = "\033[1;45m"; /* Magenta background */
static const char s_BackCyan    [] = "\033[1;46m"; /* Cyan background */
static const char s_BackWhite   [] = "\033[1;47m"; /* White background */

/******************************************************************************
 Protocol global functions.
 ******************************************************************************/

protocol_t *ProtocolCreate( void )
{
   int i; /* Loop counter */
   protocol_t *pProtocol;

   /* Called the first time we enter - make sure the table is correct */
   static bool_t bInit = false;
   if ( !bInit )
   {
      bInit = true;
      for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
      {
         if ( VariableNameTable[i].Variable != i )
         {
            ReportBug( "MSDP: Variable table does not match the enums in the header.\n" );
            break;
         }
      }
   }

   pProtocol = malloc(sizeof(protocol_t));
   pProtocol->WriteOOB = 0;
   for ( i = eNEGOTIATED_TTYPE; i < eNEGOTIATED_MAX; ++i )
      pProtocol->Negotiated[i] = false;
   pProtocol->bIACMode = false;
   pProtocol->bNegotiated = false;
   pProtocol->bRenegotiate = false;
   pProtocol->bNeedMXPVersion = false;
   pProtocol->bBlockMXP = false;
   pProtocol->bTTYPE = false;
   pProtocol->bECHO = false;
   pProtocol->bNAWS = false;
   pProtocol->bCHARSET = false;
   pProtocol->bMSDP = false;
   pProtocol->bMSSP = false;
   pProtocol->bATCP = false;
   pProtocol->bMSP = false;
   pProtocol->bMXP = false;
   pProtocol->bMCCP = false;
   pProtocol->b256Support = eUNKNOWN;
   pProtocol->ScreenWidth = 0;
   pProtocol->ScreenHeight = 0;
   pProtocol->pMXPVersion = AllocString("Unknown");
   pProtocol->pLastTTYPE = NULL;
   pProtocol->pVariables = malloc(sizeof(MSDP_t*)*eMSDP_MAX);

   for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
   {
      pProtocol->pVariables[i] = malloc(sizeof(MSDP_t));
      pProtocol->pVariables[i]->bReport = false;
      pProtocol->pVariables[i]->bDirty = false;
      pProtocol->pVariables[i]->ValueInt = 0;
      pProtocol->pVariables[i]->pValueString = NULL;

      if ( VariableNameTable[i].bString )
      {
         if ( VariableNameTable[i].pDefault != NULL )
            pProtocol->pVariables[i]->pValueString = AllocString(VariableNameTable[i].pDefault);
         else if ( VariableNameTable[i].bConfigurable )
            pProtocol->pVariables[i]->pValueString = AllocString("Unknown");
         else /* Use an empty string */
            pProtocol->pVariables[i]->pValueString = AllocString("");
      }
      else if ( VariableNameTable[i].Default != 0 )
      {
         pProtocol->pVariables[i]->ValueInt = VariableNameTable[i].Default;
      }
   }

   return pProtocol;
}

void ProtocolDestroy( protocol_t *apProtocol )
{
   int i; /* Loop counter */

   for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
   {
      free(apProtocol->pVariables[i]->pValueString);
      free(apProtocol->pVariables[i]);
   }

   free(apProtocol->pVariables);
   free(apProtocol->pLastTTYPE);
   free(apProtocol->pMXPVersion);
   free(apProtocol);
}

void ProtocolInput( descriptor_t *apDescriptor, char *apData, int aSize, char *apOut )
{
   static char CmdBuf[MAX_PROTOCOL_BUFFER+1];
   static char IacBuf[MAX_PROTOCOL_BUFFER+1];
   int CmdIndex = 0;
   int IacIndex = 0;
   int Index;

   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   for ( Index = 0; Index < aSize; ++Index )
   {
      /* If we'd overflow the buffer, we just ignore the input */
      if ( CmdIndex >= MAX_PROTOCOL_BUFFER || IacIndex >= MAX_PROTOCOL_BUFFER )
      {
         ReportBug("ProtocolInput: Too much incoming data to store in the buffer.\n");
         return;
      }

      /* IAC IAC is treated as a single value of 255 */
      if ( apData[Index] == (char)IAC && apData[Index+1] == (char)IAC )
      {
         if ( pProtocol->bIACMode )
            IacBuf[IacIndex++] = (char)IAC;
         else /* In-band command */
            CmdBuf[CmdIndex++] = (char)IAC;
         Index++;
      }
      else if ( pProtocol->bIACMode )
      {
         /* End subnegotiation. */
         if ( apData[Index] == (char)IAC && apData[Index+1] == (char)SE )
         {
            Index++;
            pProtocol->bIACMode = false;
            IacBuf[IacIndex] = '\0';
            if ( IacIndex >= 2 )
               PerformSubnegotiation( apDescriptor, IacBuf[0], &IacBuf[1], IacIndex-1 );
            IacIndex = 0;
         }
         else
           IacBuf[IacIndex++] = apData[Index];
      }
      else if ( apData[Index] == (char)27 && apData[Index+1] == '[' && 
         isdigit(apData[Index+2]) && apData[Index+3] == 'z' )
      {
         char MXPBuffer [1024];
         char *pMXPTag = NULL;
         int i = 0; /* Loop counter */

         Index += 4; /* Skip to the start of the MXP sequence. */

         while ( Index < aSize && apData[Index] != '>' && i < 1000 )
         {
            MXPBuffer[i++] = apData[Index++];
         }
         MXPBuffer[i++] = '>';
         MXPBuffer[i] = '\0';

         if ( ( pMXPTag = GetMxpTag( "CLIENT=", MXPBuffer ) ) != NULL )
         {
            /* Overwrite the previous client name - this is harder to fake */
            free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
            pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pMXPTag);
         }

         if ( ( pMXPTag = GetMxpTag( "VERSION=", MXPBuffer ) ) != NULL )
         {
            const char *pClientName = pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString;

            free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
            pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pMXPTag);

            if ( MatchString( "MUSHCLIENT", pClientName ) )
            {
               /* MUSHclient 4.02 and later supports 256 colours. */
               if ( strcmp(pMXPTag, "4.02") >= 0 )
               {
                  pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
                  pProtocol->b256Support = eYES;
               }
               else /* We know for sure that 256 colours are not supported */
                  pProtocol->b256Support = eNO;
            }
            else if ( MatchString( "CMUD", pClientName ) )
            {
               /* CMUD 3.04 and later supports 256 colours. */
               if ( strcmp(pMXPTag, "3.04") >= 0 )
               {
                  pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
                  pProtocol->b256Support = eYES;
               }
               else /* We know for sure that 256 colours are not supported */
                  pProtocol->b256Support = eNO;
            }
            else if ( MatchString( "ATLANTIS", pClientName ) )
            {
               /* Atlantis 0.9.9.0 supports XTerm 256 colours, but it doesn't 
                * yet have MXP.  However MXP is planned, so once it responds 
                * to a <VERSION> tag we'll know we can use 256 colours.
                */
               pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
               pProtocol->b256Support = eYES;
            }
         }

         if ( ( pMXPTag = GetMxpTag( "MXP=", MXPBuffer ) ) != NULL )
         {
            free(pProtocol->pMXPVersion);
            pProtocol->pMXPVersion = AllocString(pMXPTag);
         }

         if ( strcmp(pProtocol->pMXPVersion, "Unknown") )
         {
            Write( apDescriptor, "\n" );
            sprintf( MXPBuffer, "MXP version %s detected and enabled.\r\n", 
               pProtocol->pMXPVersion );
            InfoMessage( apDescriptor, MXPBuffer );
         }
      }
      else /* In-band command */
      {
         if ( apData[Index] == (char)IAC )
         {
            switch ( apData[Index+1] )
            {
               case (char)SB: /* Begin subnegotiation. */
                  Index++;
                  pProtocol->bIACMode = true;
                  break;

               case (char)DO: /* Handshake. */
               case (char)DONT:
               case (char)WILL:
               case (char)WONT: 
                  PerformHandshake( apDescriptor, apData[Index+1], apData[Index+2] );
                  Index += 2;
                  break;

               case (char)IAC: /* Two IACs count as one. */
                  CmdBuf[CmdIndex++] = (char)IAC;
                  Index++;
                  break;

               default: /* Skip it. */
                  Index++;
                  break;
            }
         }
         else
            CmdBuf[CmdIndex++] = apData[Index];
      }
   }

   /* Terminate the two buffers */
   IacBuf[IacIndex] = '\0';
   CmdBuf[CmdIndex] = '\0';

   /* Copy the input buffer back to the player. */
   strcat( apOut, CmdBuf );
}

const char *ProtocolOutput( descriptor_t *apDescriptor, const char *apData, int *apLength )
{
   static char Result[MAX_OUTPUT_BUFFER+1];
   const char Tab[] = "\t";
   const char MSP[] = "!!";
   const char MXPStart[] = "\033[1z<";
   const char MXPStop[] = ">\033[7z";
   const char LinkStart[] = "\033[1z<send>\033[7z";
   const char LinkStop[] = "\033[1z</send>\033[7z";
   bool_t bTerminate = false, bUseMXP = false, bUseMSP = false;
#ifdef COLOUR_CHAR
   bool_t bColourOn = COLOUR_ON_BY_DEFAULT;
#endif /* COLOUR_CHAR */
   int i = 0, j = 0; /* Index values */

   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
   if ( pProtocol == NULL || apData == NULL )
      return apData;

   /* Strip !!SOUND() triggers if they support MSP or are using sound */
   if ( pProtocol->bMSP || pProtocol->pVariables[eMSDP_SOUND]->ValueInt )
      bUseMSP = true;

   for ( ; i < MAX_OUTPUT_BUFFER && apData[j] != '\0' && !bTerminate && 
      (*apLength <= 0 || j < *apLength); ++j )
   {
      if ( apData[j] == '\t' )
      {
         const char *pCopyFrom = NULL;

         switch ( apData[++j] )
         {
            case '\t': /* Two tabs in a row will display an actual tab */
               pCopyFrom = Tab;
               break;
            case 'n':
               pCopyFrom = s_Clean;
               break;
            case 'r': /* dark red */
               pCopyFrom = ColourRGB(apDescriptor, "F200");
               break;
            case 'R': /* light red */
               pCopyFrom = ColourRGB(apDescriptor, "F500");
               break;
            case 'g': /* dark green */
               pCopyFrom = ColourRGB(apDescriptor, "F020");
               break;
            case 'G': /* light green */
               pCopyFrom = ColourRGB(apDescriptor, "F050");
               break;
            case 'y': /* dark yellow */
               pCopyFrom = ColourRGB(apDescriptor, "F220");
               break;
            case 'Y': /* light yellow */
               pCopyFrom = ColourRGB(apDescriptor, "F550");
               break;
            case 'b': /* dark blue */
               pCopyFrom = ColourRGB(apDescriptor, "F002");
               break;
            case 'B': /* light blue */
               pCopyFrom = ColourRGB(apDescriptor, "F005");
               break;
            case 'm': /* dark magenta */
               pCopyFrom = ColourRGB(apDescriptor, "F202");
               break;
            case 'M': /* light magenta */
               pCopyFrom = ColourRGB(apDescriptor, "F505");
               break;
            case 'c': /* dark cyan */
               pCopyFrom = ColourRGB(apDescriptor, "F022");
               break;
            case 'C': /* light cyan */
               pCopyFrom = ColourRGB(apDescriptor, "F055");
               break;
            case 'w': /* dark white */
               pCopyFrom = ColourRGB(apDescriptor, "F222");
               break;
            case 'W': /* light white */
               pCopyFrom = ColourRGB(apDescriptor, "F555");
               break;
            case 'a': /* dark azure */
               pCopyFrom = ColourRGB(apDescriptor, "F014");
               break;
            case 'A': /* light azure */
               pCopyFrom = ColourRGB(apDescriptor, "F025");
               break;
            case 'j': /* dark jade */
               pCopyFrom = ColourRGB(apDescriptor, "F031");
               break;
            case 'J': /* light jade */
               pCopyFrom = ColourRGB(apDescriptor, "F052");
               break;
            case 'l': /* dark lime */
               pCopyFrom = ColourRGB(apDescriptor, "F140");
               break;
            case 'L': /* light lime */
               pCopyFrom = ColourRGB(apDescriptor, "F250");
               break;
            case 'o': /* dark orange */
               pCopyFrom = ColourRGB(apDescriptor, "F520");
               break;
            case 'O': /* light orange */
               pCopyFrom = ColourRGB(apDescriptor, "F530");
               break;
            case 'p': /* dark pink */
               pCopyFrom = ColourRGB(apDescriptor, "F301");
               break;
            case 'P': /* light pink */
               pCopyFrom = ColourRGB(apDescriptor, "F502");
               break;
            case 't': /* dark tan */
               pCopyFrom = ColourRGB(apDescriptor, "F210");
               break;
            case 'T': /* light tan */
               pCopyFrom = ColourRGB(apDescriptor, "F321");
               break;
            case 'v': /* dark violet */
               pCopyFrom = ColourRGB(apDescriptor, "F104");
               break;
            case 'V': /* light violet */
               pCopyFrom = ColourRGB(apDescriptor, "F205");
               break;
            case '(': /* MXP link */
               if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
                  pCopyFrom = LinkStart;
               break;
            case ')': /* MXP link */
               if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
                  pCopyFrom = LinkStop;
               pProtocol->bBlockMXP = false;
               break;
            case '<':
               if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
               {
                  pCopyFrom = MXPStart;
                  bUseMXP = true;
               }
               else /* No MXP support, so just strip it out */
               {
                  while ( apData[j] != '\0' && apData[j] != '>' )
                     ++j;
               }
               pProtocol->bBlockMXP = false;
               break;
            case '[':
               if ( tolower(apData[++j]) == 'u' )
               {
                  char Buffer[8] = {'\0'}, BugString[256];
                  int Index = 0;
                  int Number = 0;
                  bool_t bDone = false, bValid = true;

                  while ( isdigit(apData[++j]) )
                  {
                     Number *= 10;
                     Number += (apData[j])-'0';
                  }

                  if ( apData[j] == '/' )
                     ++j;

                  while ( apData[j] != '\0' && !bDone )
                  {
                     if ( apData[j] == ']' )
                        bDone = true;
                     else if ( Index < 7 )
                        Buffer[Index++] = apData[j++];
                     else /* It's too long, so ignore the rest and note the problem */
                     {
                        j++;
                        bValid = false;
                     }
                  }

                  if ( !bDone )
                  {
                     sprintf( BugString, "BUG: Unicode substitute '%s' wasn't terminated with ']'.\n", Buffer );
                     ReportBug( BugString );
                  }
                  else if ( !bValid )
                  {
                     sprintf( BugString, "BUG: Unicode substitute '%s' truncated.  Missing ']'?\n", Buffer );
                     ReportBug( BugString );
                  }
                  else if ( pProtocol->pVariables[eMSDP_UTF_8]->ValueInt )
                  {
                     pCopyFrom = UnicodeGet(Number);
                  }
                  else /* Display the substitute string */
                  {
                     pCopyFrom = Buffer;
                  }

                  /* Terminate if we've reached the end of the string */
                  bTerminate = !bDone;
               }
               else if ( tolower(apData[j]) == 'f' || tolower(apData[j]) == 'b' )
               {
                  char Buffer[8] = {'\0'}, BugString[256];
                  int Index = 0;
                  bool_t bDone = false, bValid = true;

                  /* Copy the 'f' (foreground) or 'b' (background) */
                  Buffer[Index++] = apData[j++];

                  while ( apData[j] != '\0' && !bDone && bValid )
                  {
                     if ( apData[j] == ']' )
                        bDone = true;
                     else if ( Index < 4 )
                        Buffer[Index++] = apData[j++];
                     else /* It's too long, so drop out - the colour code may still be valid */
                        bValid = false;
                  }

                  if ( !bDone || !bValid )
                  {
                     sprintf( BugString, "BUG: RGB %sground colour '%s' wasn't terminated with ']'.\n", 
                        (tolower(Buffer[0]) == 'f') ? "fore" : "back", &Buffer[1] );
                     ReportBug( BugString );
                  }
                  else if ( !IsValidColour(Buffer) )
                  {
                     sprintf( BugString, "BUG: RGB %sground colour '%s' invalid (each digit must be in the range 0-5).\n", 
                        (tolower(Buffer[0]) == 'f') ? "fore" : "back", &Buffer[1] );
                     ReportBug( BugString );
                  }
                  else /* Success */
                  {
                     pCopyFrom = ColourRGB(apDescriptor, Buffer);
                  }
               }
               else if ( tolower(apData[j]) == 'x' )
               {
                  char Buffer[8] = {'\0'}, BugString[256];
                  int Index = 0;
                  bool_t bDone = false, bValid = true;

                  ++j; /* Skip the 'x' */

                  while ( apData[j] != '\0' && !bDone )
                  {
                     if ( apData[j] == ']' )
                        bDone = true;
                     else if ( Index < 7 )
                        Buffer[Index++] = apData[j++];
                     else /* It's too long, so ignore the rest and note the problem */
                     {
                        j++;
                        bValid = false;
                     }
                  }

                  if ( !bDone )
                  {
                     sprintf( BugString, "BUG: Required MXP version '%s' wasn't terminated with ']'.\n", Buffer );
                     ReportBug( BugString );
                  }
                  else if ( !bValid )
                  {
                     sprintf( BugString, "BUG: Required MXP version '%s' too long.  Missing ']'?\n", Buffer );
                     ReportBug( BugString );
                  }
                  else if ( !strcmp(pProtocol->pMXPVersion, "Unknown") || 
                     strcmp(pProtocol->pMXPVersion, Buffer) < 0 )
                  {
                     /* Their version of MXP isn't high enough */
                     pProtocol->bBlockMXP = true;
                  }
                  else /* MXP is sufficient for this tag */
                  {
                     pProtocol->bBlockMXP = false;
                  }

                  /* Terminate if we've reached the end of the string */
                  bTerminate = !bDone;
               }
               break;
            case '!': /* Used for in-band MSP sound triggers */
               pCopyFrom = MSP;
               break;
#ifdef COLOUR_CHAR
            case '+':
               bColourOn = true;
               break;
            case '-':
               bColourOn = false;
               break;
#endif /* COLOUR_CHAR */
            case '\0':
               bTerminate = true;
               break;
            default:
               break;
         }

         /* Copy the colour code, if any. */
         if ( pCopyFrom != NULL )
         {
            while ( *pCopyFrom != '\0' && i < MAX_OUTPUT_BUFFER )
               Result[i++] = *pCopyFrom++;
         }
      }
#ifdef COLOUR_CHAR
      else if ( bColourOn && apData[j] == COLOUR_CHAR )
      {
         const char ColourChar[] = { COLOUR_CHAR, '\0' };
         const char *pCopyFrom = NULL;

         switch ( apData[++j] )
         {
            case COLOUR_CHAR: /* Two in a row display the actual character */
               pCopyFrom = ColourChar;
               break;
            case 'n':
               pCopyFrom = s_Clean;
               break;
            case 'r': /* dark red */
               pCopyFrom = ColourRGB(apDescriptor, "F200");
               break;
            case 'R': /* light red */
               pCopyFrom = ColourRGB(apDescriptor, "F500");
               break;
            case 'g': /* dark green */
               pCopyFrom = ColourRGB(apDescriptor, "F020");
               break;
            case 'G': /* light green */
               pCopyFrom = ColourRGB(apDescriptor, "F050");
               break;
            case 'y': /* dark yellow */
               pCopyFrom = ColourRGB(apDescriptor, "F220");
               break;
            case 'Y': /* light yellow */
               pCopyFrom = ColourRGB(apDescriptor, "F550");
               break;
            case 'b': /* dark blue */
               pCopyFrom = ColourRGB(apDescriptor, "F002");
               break;
            case 'B': /* light blue */
               pCopyFrom = ColourRGB(apDescriptor, "F005");
               break;
            case 'm': /* dark magenta */
               pCopyFrom = ColourRGB(apDescriptor, "F202");
               break;
            case 'M': /* light magenta */
               pCopyFrom = ColourRGB(apDescriptor, "F505");
               break;
            case 'c': /* dark cyan */
               pCopyFrom = ColourRGB(apDescriptor, "F022");
               break;
            case 'C': /* light cyan */
               pCopyFrom = ColourRGB(apDescriptor, "F055");
               break;
            case 'w': /* dark white */
               pCopyFrom = ColourRGB(apDescriptor, "F222");
               break;
            case 'W': /* light white */
               pCopyFrom = ColourRGB(apDescriptor, "F555");
               break;
            case 'a': /* dark azure */
               pCopyFrom = ColourRGB(apDescriptor, "F014");
               break;
            case 'A': /* light azure */
               pCopyFrom = ColourRGB(apDescriptor, "F025");
               break;
            case 'j': /* dark jade */
               pCopyFrom = ColourRGB(apDescriptor, "F031");
               break;
            case 'J': /* light jade */
               pCopyFrom = ColourRGB(apDescriptor, "F052");
               break;
            case 'l': /* dark lime */
               pCopyFrom = ColourRGB(apDescriptor, "F140");
               break;
            case 'L': /* light lime */
               pCopyFrom = ColourRGB(apDescriptor, "F250");
               break;
            case 'o': /* dark orange */
               pCopyFrom = ColourRGB(apDescriptor, "F520");
               break;
            case 'O': /* light orange */
               pCopyFrom = ColourRGB(apDescriptor, "F530");
               break;
            case 'p': /* dark pink */
               pCopyFrom = ColourRGB(apDescriptor, "F301");
               break;
            case 'P': /* light pink */
               pCopyFrom = ColourRGB(apDescriptor, "F502");
               break;
            case 't': /* dark tan */
               pCopyFrom = ColourRGB(apDescriptor, "F210");
               break;
            case 'T': /* light tan */
               pCopyFrom = ColourRGB(apDescriptor, "F321");
               break;
            case 'v': /* dark violet */
               pCopyFrom = ColourRGB(apDescriptor, "F104");
               break;
            case 'V': /* light violet */
               pCopyFrom = ColourRGB(apDescriptor, "F205");
               break;
            case '\0':
               bTerminate = true;
               break;
#ifdef EXTENDED_COLOUR
            case '[':
               if ( tolower(apData[++j]) == 'f' || tolower(apData[j]) == 'b' )
               {
                  char Buffer[8] = {'\0'};
                  int Index = 0;
                  bool_t bDone = false, bValid = true;

                  /* Copy the 'f' (foreground) or 'b' (background) */
                  Buffer[Index++] = apData[j++];

                  while ( apData[j] != '\0' && !bDone && bValid )
                  {
                     if ( apData[j] == ']' )
                        bDone = true;
                     else if ( Index < 4 )
                        Buffer[Index++] = apData[j++];
                     else /* It's too long, so drop out - the colour code may still be valid */
                        bValid = false;
                  }

                  if ( bDone && bValid && IsValidColour(Buffer) )
                     pCopyFrom = ColourRGB(apDescriptor, Buffer);
               }
               break;
#endif /* EXTENDED_COLOUR */
            default:
#ifdef DISPLAY_INVALID_COLOUR_CODES
               Result[i++] = COLOUR_CHAR;
               Result[i++] = apData[j];
#endif /* DISPLAY_INVALID_COLOUR_CODES */
               break;
         }

         /* Copy the colour code, if any. */
         if ( pCopyFrom != NULL )
         {
            while ( *pCopyFrom != '\0' && i < MAX_OUTPUT_BUFFER )
               Result[i++] = *pCopyFrom++;
         }
      }
#endif /* COLOUR_CHAR */
      else if ( bUseMXP && apData[j] == '>' )
      {
         const char *pCopyFrom = MXPStop;
         while ( *pCopyFrom != '\0' && i < MAX_OUTPUT_BUFFER)
            Result[i++] = *pCopyFrom++;
         bUseMXP = false;
      }
      else if ( bUseMSP && j > 0 && apData[j-1] == '!' && apData[j] == '!' && 
         PrefixString("SOUND(", &apData[j+1]) )
      {
         /* Avoid accidental triggering of old-style MSP triggers */
         Result[i++] = '?';
      }
      else /* Just copy the character normally */
      {
         Result[i++] = apData[j];
      }
   }

   /* If we'd overflow the buffer, we don't send any output */
   if ( i >= MAX_OUTPUT_BUFFER )
   {
      i = 0;
      ReportBug("ProtocolOutput: Too much outgoing data to store in the buffer.\n");
   }

   /* Terminate the string */
   Result[i] = '\0';

   /* Store the length */
   if ( apLength )
      *apLength = i;

   /* Return the string */
   return Result;
}

/* Some clients (such as GMud) don't properly handle negotiation, and simply 
 * display every printable character to the screen.  However TTYPE isn't a 
 * printable character, so we negotiate for it first, and only negotiate for 
 * other protocols if the client responds with IAC WILL TTYPE or IAC WONT 
 * TTYPE.  Thanks go to Donky on MudBytes for the suggestion.
 */
void ProtocolNegotiate( descriptor_t *apDescriptor )
{
   ConfirmNegotiation(apDescriptor, eNEGOTIATED_TTYPE, true, true);
}

/* Tells the client to switch echo on or off. */
void ProtocolNoEcho( descriptor_t *apDescriptor, bool_t abOn )
{
   ConfirmNegotiation(apDescriptor, eNEGOTIATED_ECHO, abOn, true);
}

/******************************************************************************
 Copyover save/load functions.
 ******************************************************************************/

const char *CopyoverGet( descriptor_t *apDescriptor )
{
   static char Buffer[64];
   char *pBuffer = Buffer;
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL )
   {
      sprintf(Buffer, "%d/%d", pProtocol->ScreenWidth, pProtocol->ScreenHeight);

      /* Skip to the end */
      while ( *pBuffer != '\0' )
         ++pBuffer;

      if ( pProtocol->bTTYPE )
         *pBuffer++ = 'T';
      if ( pProtocol->bNAWS )
         *pBuffer++ = 'N';
      if ( pProtocol->bMSDP )
         *pBuffer++ = 'M';
      if ( pProtocol->bATCP )
         *pBuffer++ = 'A';
      if ( pProtocol->bMSP )
         *pBuffer++ = 'S';
      if ( pProtocol->pVariables[eMSDP_MXP]->ValueInt )
         *pBuffer++ = 'X';
      if ( pProtocol->bMCCP )
      {
         *pBuffer++ = 'c';
         CompressEnd(apDescriptor);
      }
      if ( pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt )
         *pBuffer++ = 'C';
      if ( pProtocol->bCHARSET )
         *pBuffer++ = 'H';
      if ( pProtocol->pVariables[eMSDP_UTF_8]->ValueInt )
         *pBuffer++ = 'U';
   }

   /* Terminate the string */
   *pBuffer = '\0';

   return Buffer;
}

void CopyoverSet( descriptor_t *apDescriptor, const char *apData )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && apData != NULL )
   {
      int Width = 0, Height = 0;
      bool_t bDoneWidth = false;
      int i; /* Loop counter */

      for ( i = 0; apData[i] != '\0'; ++i )
      {
         switch ( apData[i] )
         {
            case 'T':
               pProtocol->bTTYPE = true;
               break;
            case 'N':
               pProtocol->bNAWS = true;
               break;
            case 'M':
               pProtocol->bMSDP = true;
               break;
            case 'A':
               pProtocol->bATCP = true;
               break;
            case 'S':
               pProtocol->bMSP = true;
               break;
            case 'X':
               pProtocol->bMXP = true;
               pProtocol->pVariables[eMSDP_MXP]->ValueInt = 1;
               break;
            case 'c':
               pProtocol->bMCCP = true;
               CompressStart(apDescriptor);
               break;
            case 'C':
               pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
               break;
            case 'H':
               pProtocol->bCHARSET = true;
               break;
            case 'U':
               pProtocol->pVariables[eMSDP_UTF_8]->ValueInt = 1;
               break;
            default:
               if ( apData[i] == '/' )
                  bDoneWidth = true;
               else if ( isdigit(apData[i]) )
               {
                  if ( bDoneWidth )
                  {
                     Height *= 10;
                     Height += (apData[i] - '0');
                  }
                  else /* We're still calculating height */
                  {
                     Width *= 10;
                     Width += (apData[i] - '0');
                  }
               }
               break;
         }
      }

      /* Restore the width and height */
      pProtocol->ScreenWidth = Width;
      pProtocol->ScreenHeight = Height;

      /* If we're using MSDP or ATCP, we need to renegotiate it so that the 
       * client can resend the list of variables it wants us to REPORT.
       *
       * Note that we only use ATCP if MSDP is not supported.
       */
      if ( pProtocol->bMSDP )
      {
         ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSDP, true, true);
      }
      else if ( pProtocol->bATCP )
      {
         ConfirmNegotiation(apDescriptor, eNEGOTIATED_ATCP, true, true);
      }

      /* Ask the client to send its MXP version again */
      if ( pProtocol->bMXP )
         MXPSendTag( apDescriptor, "<VERSION>" );
   }
}

/******************************************************************************
 MSDP global functions.
 ******************************************************************************/

void MSDPUpdate( descriptor_t *apDescriptor )
{
   int i; /* Loop counter */

   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
   {
      if ( pProtocol->pVariables[i]->bReport )
      {
         if ( pProtocol->pVariables[i]->bDirty )
         {
            MSDPSend( apDescriptor, (variable_t)i );
            pProtocol->pVariables[i]->bDirty = false;
         }
      }
   }
}

void MSDPFlush( descriptor_t *apDescriptor, variable_t aMSDP )
{
   if ( aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      if ( pProtocol->pVariables[aMSDP]->bReport )
      {
         if ( pProtocol->pVariables[aMSDP]->bDirty )
         {
            MSDPSend( apDescriptor, aMSDP );
            pProtocol->pVariables[aMSDP]->bDirty = false;
         }
      }
   }
}

void MSDPSend( descriptor_t *apDescriptor, variable_t aMSDP )
{
   char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };

   if ( aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      if ( VariableNameTable[aMSDP].bString )
      {
         /* Should really be replaced with a dynamic buffer */
         int RequiredBuffer = strlen(VariableNameTable[aMSDP].pName) + 
            strlen(pProtocol->pVariables[aMSDP]->pValueString) + 12;

         if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
         {
            sprintf( MSDPBuffer, 
               "MSDPSend: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               VariableNameTable[aMSDP].pName, RequiredBuffer, 
               MAX_VARIABLE_LENGTH );
            ReportBug( MSDPBuffer );
            MSDPBuffer[0] = '\0';
         }
         else if ( pProtocol->bMSDP )
         {
            sprintf( MSDPBuffer, "%c%c%c%c%s%c%s%c%c", 
               IAC, SB, TELOPT_MSDP, MSDP_VAR, 
               VariableNameTable[aMSDP].pName, MSDP_VAL, 
               pProtocol->pVariables[aMSDP]->pValueString, IAC, SE );
         }
         else if ( pProtocol->bATCP )
         {
            sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c", 
               IAC, SB, TELOPT_ATCP, 
               VariableNameTable[aMSDP].pName, 
               pProtocol->pVariables[aMSDP]->pValueString, IAC, SE );
         }
      }
      else /* It's an integer, not a string */
      {
         if ( pProtocol->bMSDP )
         {
            sprintf( MSDPBuffer, "%c%c%c%c%s%c%d%c%c", 
               IAC, SB, TELOPT_MSDP, MSDP_VAR, 
               VariableNameTable[aMSDP].pName, MSDP_VAL, 
               pProtocol->pVariables[aMSDP]->ValueInt, IAC, SE );
         }
         else if ( pProtocol->bATCP )
         {
            sprintf( MSDPBuffer, "%c%c%cMSDP.%s %d%c%c", 
               IAC, SB, TELOPT_ATCP, 
               VariableNameTable[aMSDP].pName, 
               pProtocol->pVariables[aMSDP]->ValueInt, IAC, SE );
         }
      }

      /* Just in case someone calls this function without checking MSDP/ATCP */
      if ( MSDPBuffer[0] != '\0' )
         Write( apDescriptor, MSDPBuffer );
   }
}

void MSDPSendPair( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
   char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };

   if ( apVariable != NULL && apValue != NULL )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      /* Should really be replaced with a dynamic buffer */
      int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;

      if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
      {
         if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
         {
            sprintf( MSDPBuffer, 
               "MSDPSendPair: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
         }
         else /* The variable name itself is too long */
         {
            sprintf( MSDPBuffer, 
               "MSDPSendPair: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               RequiredBuffer, MAX_VARIABLE_LENGTH );
         }

         ReportBug( MSDPBuffer );
         MSDPBuffer[0] = '\0';
      }
      else if ( pProtocol->bMSDP )
      {
         sprintf( MSDPBuffer, "%c%c%c%c%s%c%s%c%c", 
            IAC, SB, TELOPT_MSDP, MSDP_VAR, apVariable, MSDP_VAL, 
            apValue, IAC, SE );
      }
      else if ( pProtocol->bATCP )
      {
         sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c", 
            IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
      }

      /* Just in case someone calls this function without checking MSDP/ATCP */
      if ( MSDPBuffer[0] != '\0' )
         Write( apDescriptor, MSDPBuffer );
   }
}

void MSDPSendList( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
   char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };

   if ( apVariable != NULL && apValue != NULL )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      /* Should really be replaced with a dynamic buffer */
      int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;

      if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
      {
         if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
         {
            sprintf( MSDPBuffer, 
               "MSDPSendList: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
         }
         else /* The variable name itself is too long */
         {
            sprintf( MSDPBuffer, 
               "MSDPSendList: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               RequiredBuffer, MAX_VARIABLE_LENGTH );
         }

         ReportBug( MSDPBuffer );
         MSDPBuffer[0] = '\0';
      }
      else if ( pProtocol->bMSDP )
      {
         int i; /* Loop counter */
         sprintf( MSDPBuffer, "%c%c%c%c%s%c%c%c%s%c%c%c", 
            IAC, SB, TELOPT_MSDP, MSDP_VAR, apVariable, MSDP_VAL, 
            MSDP_ARRAY_OPEN, MSDP_VAL, apValue, MSDP_ARRAY_CLOSE, IAC, SE );

         /* Convert the spaces to MSDP_VAL */
         for ( i = 0; MSDPBuffer[i] != '\0'; ++i )
         {
            if ( MSDPBuffer[i] == ' ' )
               MSDPBuffer[i] = MSDP_VAL;
         }
      }
      else if ( pProtocol->bATCP )
      {
         sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c", 
            IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
      }

      /* Just in case someone calls this function without checking MSDP/ATCP */
      if ( MSDPBuffer[0] != '\0' )
         Write( apDescriptor, MSDPBuffer );
   }
}

void MSDPSetNumber( descriptor_t *apDescriptor, variable_t aMSDP, int aValue )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
   {
      if ( !VariableNameTable[aMSDP].bString )
      {
         if ( pProtocol->pVariables[aMSDP]->ValueInt != aValue )
         {
            pProtocol->pVariables[aMSDP]->ValueInt = aValue;
            pProtocol->pVariables[aMSDP]->bDirty = true;
         }
      }
   }
}

void MSDPSetString( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && apValue != NULL )
   {
      if ( VariableNameTable[aMSDP].bString )
      {
         if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, apValue) )
         {
            free(pProtocol->pVariables[aMSDP]->pValueString);
            pProtocol->pVariables[aMSDP]->pValueString = AllocString(apValue);
            pProtocol->pVariables[aMSDP]->bDirty = true;
         }
      }
   }
}

void MSDPSetTable( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && apValue != NULL )
   {
      if ( *apValue == '\0' )
      {
         /* It's easier to call MSDPSetString if the value is empty */
         MSDPSetString(apDescriptor, aMSDP, apValue);
      }
      else if ( VariableNameTable[aMSDP].bString )
      {
         const char MsdpTableStart[] = { (char)MSDP_TABLE_OPEN, '\0' };
         const char MsdpTableStop[]  = { (char)MSDP_TABLE_CLOSE, '\0' };

         char *pTable = malloc(strlen(apValue) + 3); /* 3: START, STOP, NUL */

         strcpy(pTable, MsdpTableStart);
         strcat(pTable, apValue);
         strcat(pTable, MsdpTableStop);

         if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, pTable) )
         {
            free(pProtocol->pVariables[aMSDP]->pValueString);
            pProtocol->pVariables[aMSDP]->pValueString = pTable;
            pProtocol->pVariables[aMSDP]->bDirty = true;
         }
         else /* Just discard the table, we've already got one */
         {
            free(pTable);
         }
      }
   }
}

void MSDPSetArray( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && apValue != NULL )
   {
      if ( *apValue == '\0' )
      {
         /* It's easier to call MSDPSetString if the value is empty */
         MSDPSetString(apDescriptor, aMSDP, apValue);
      }
      else if ( VariableNameTable[aMSDP].bString )
      {
         const char MsdpArrayStart[] = { (char)MSDP_ARRAY_OPEN, '\0' };
         const char MsdpArrayStop[]  = { (char)MSDP_ARRAY_CLOSE, '\0' };

         char *pArray = malloc(strlen(apValue) + 3); /* 3: START, STOP, NUL */

         strcpy(pArray, MsdpArrayStart);
         strcat(pArray, apValue);
         strcat(pArray, MsdpArrayStop);

         if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, pArray) )
         {
            free(pProtocol->pVariables[aMSDP]->pValueString);
            pProtocol->pVariables[aMSDP]->pValueString = pArray;
            pProtocol->pVariables[aMSDP]->bDirty = true;
         }
         else /* Just discard the array, we've already got one */
         {
            free(pArray);
         }
      }
   }
}

/******************************************************************************
 MSSP global functions.
 ******************************************************************************/

void MSSPSetPlayers( int aPlayers )
{
   s_Players = aPlayers;

   if ( s_Uptime == 0 )
      s_Uptime = time(0);
}

/******************************************************************************
 MXP global functions.
 ******************************************************************************/

const char *MXPCreateTag( descriptor_t *apDescriptor, const char *apTag )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && pProtocol->pVariables[eMSDP_MXP]->ValueInt && 
      strlen(apTag) < 1000 )
   {
      static char MXPBuffer [1024];
      sprintf( MXPBuffer, "\033[1z%s\033[7z", apTag );
      return MXPBuffer;
   }
   else /* Leave the tag as-is, don't try to MXPify it */
   {
      return apTag;
   }
}

void MXPSendTag( descriptor_t *apDescriptor, const char *apTag )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol != NULL && apTag != NULL && strlen(apTag) < 1000 )
   {
      if ( pProtocol->pVariables[eMSDP_MXP]->ValueInt )
      {
         char MXPBuffer [1024];
         sprintf(MXPBuffer, "\033[1z%s\033[7z\r\n", apTag );
         Write(apDescriptor, MXPBuffer);
      }
      else if ( pProtocol->bRenegotiate )
      {
         /* Tijer pointed out that when MUSHclient autoconnects, it fails 
          * to complete the negotiation.  This workaround will attempt to 
          * renegotiate after the character has connected.
          */

         int i; /* Renegotiate everything except TTYPE */
         for ( i = eNEGOTIATED_TTYPE+1; i < eNEGOTIATED_MAX; ++i )
         {
            pProtocol->Negotiated[i] = false;
            ConfirmNegotiation(apDescriptor, (negotiated_t)i, true, true);
         }

         pProtocol->bRenegotiate = false;
         pProtocol->bNeedMXPVersion = true;
         Negotiate(apDescriptor);
      }
   }
}

/******************************************************************************
 Sound global functions.
 ******************************************************************************/

void SoundSend( descriptor_t *apDescriptor, const char *apTrigger )
{
   const int MaxTriggerLength = 128; /* Used for the buffer size */

   if ( apDescriptor != NULL && apTrigger != NULL )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      if ( pProtocol != NULL && pProtocol->pVariables[eMSDP_SOUND]->ValueInt )
      {
         if ( pProtocol->bMSDP || pProtocol->bATCP )
         {
            /* Send the sound trigger through MSDP or ATCP */
            MSDPSendPair( apDescriptor, "PLAY_SOUND", apTrigger );
         }
         else if ( strlen(apTrigger) <= MaxTriggerLength )
         {
            /* Use an old MSP-style trigger */
            char *pBuffer = alloca(MaxTriggerLength+10);
            sprintf( pBuffer, "\t!SOUND(%s)", apTrigger );
            Write(apDescriptor, pBuffer);
         }
      }
   }
}

/******************************************************************************
 Colour global functions.
 ******************************************************************************/

const char *ColourRGB( descriptor_t *apDescriptor, const char *apRGB )
{
   protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

   if ( pProtocol && pProtocol->pVariables[eMSDP_ANSI_COLORS]->ValueInt )
   {
      if ( IsValidColour(apRGB) )
      {
         bool_t bBackground = (tolower(apRGB[0]) == 'b');
         int Red = apRGB[1] - '0';
         int Green = apRGB[2] - '0';
         int Blue = apRGB[3] - '0';

         if ( pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt )
            return GetRGBColour( bBackground, Red, Green, Blue );
         else /* Use regular ANSI colour */
            return GetAnsiColour( bBackground, Red, Green, Blue );
      }
      else /* Invalid colour - use this to clear any existing colour. */
      {
         return s_Clean;
      }
   }
   else /* Don't send any colour, not even clear */
   {
      return "";
   }
}

/******************************************************************************
 UTF-8 global functions.
 ******************************************************************************/

char *UnicodeGet( int aValue )
{
   static char Buffer[8];
   char *pString = Buffer;

   UnicodeAdd( &pString, aValue );
   *pString = '\0';

   return Buffer;
}

void UnicodeAdd( char **apString, int aValue )
{
   if ( aValue < 0x80 )
   {
      *(*apString)++ = (char)aValue;
   }
   else if ( aValue < 0x800 )
   {
      *(*apString)++ = (char)(0xC0 | (aValue>>6));
      *(*apString)++ = (char)(0x80 | (aValue & 0x3F));
   }
   else if ( aValue < 0x10000 )
   {
      *(*apString)++ = (char)(0xE0 | (aValue>>12));
      *(*apString)++ = (char)(0x80 | (aValue>>6 & 0x3F));
      *(*apString)++ = (char)(0x80 | (aValue & 0x3F));
   }
   else if ( aValue < 0x200000 )
   {
      *(*apString)++ = (char)(0xF0 | (aValue>>18));
      *(*apString)++ = (char)(0x80 | (aValue>>12 & 0x3F));
      *(*apString)++ = (char)(0x80 | (aValue>>6 & 0x3F));
      *(*apString)++ = (char)(0x80 | (aValue & 0x3F));
   }
}

/******************************************************************************
 Local negotiation functions.
 ******************************************************************************/

static void Negotiate( descriptor_t *apDescriptor )
{
   protocol_t *pProtocol = apDescriptor->pProtocol;

   if ( pProtocol->bNegotiated )
   {
      const char RequestTTYPE   [] = { (char)IAC, (char)SB,   TELOPT_TTYPE, SEND, (char)IAC, (char)SE, '\0' };

      /* Request the client type if TTYPE is supported. */
      if ( pProtocol->bTTYPE )
         Write(apDescriptor, RequestTTYPE);

      /* Check for other protocols. */
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_NAWS, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_CHARSET, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSDP, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSSP, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_ATCP, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSP, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP, true, true);
      ConfirmNegotiation(apDescriptor, eNEGOTIATED_MCCP, true, true);
   }
}

static void PerformHandshake( descriptor_t *apDescriptor, char aCmd, char aProtocol )
{
   protocol_t *pProtocol = apDescriptor->pProtocol;

   switch ( aProtocol )
   {
      case (char)TELOPT_TTYPE:
         if ( aCmd == (char)WILL )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_TTYPE, true, true);
            pProtocol->bTTYPE = true;

            if ( !pProtocol->bNegotiated )
            {
               /* Negotiate for the remaining protocols. */
               pProtocol->bNegotiated = true;
               Negotiate(apDescriptor);

               /* We may need to renegotiate if they don't reply */            
               pProtocol->bRenegotiate = true;
            }
         }
         else if ( aCmd == (char)WONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_TTYPE, false, pProtocol->bTTYPE);
            pProtocol->bTTYPE = false;

            if ( !pProtocol->bNegotiated )
            {
               /* Still negotiate, as this client obviously knows how to 
                * correctly respond to negotiation attempts - but we don't 
                * ask for TTYPE, as it doesn't support it.
                */
               pProtocol->bNegotiated = true;
               Negotiate(apDescriptor);

               /* We may need to renegotiate if they don't reply */            
               pProtocol->bRenegotiate = true;
            }
         }
         else if ( aCmd == (char)DO )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)WONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_ECHO:
         if ( aCmd == (char)DO )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_ECHO, true, true);
            pProtocol->bECHO = true;
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_ECHO, false, pProtocol->bECHO);
            pProtocol->bECHO = false;
         }
         else if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_NAWS:
         if ( aCmd == (char)WILL )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_NAWS, true, true);
            pProtocol->bNAWS = true;

            /* Renegotiation workaround won't be necessary. */
            pProtocol->bRenegotiate = false;
         }
         else if ( aCmd == (char)WONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_NAWS, false, pProtocol->bNAWS);
            pProtocol->bNAWS = false;

            /* Renegotiation workaround won't be necessary. */
            pProtocol->bRenegotiate = false;
         }
         else if ( aCmd == (char)DO )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)WONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_CHARSET:
         if ( aCmd == (char)WILL )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_CHARSET, true, true);
            if ( !pProtocol->bCHARSET )
            {
               const char charset_utf8 [] = { (char)IAC, (char)SB, TELOPT_CHARSET, 1, ' ', 'U', 'T', 'F', '-', '8', (char)IAC, (char)SE, '\0' };
               Write(apDescriptor, charset_utf8);
               pProtocol->bCHARSET = true;
            }
         }
         else if ( aCmd == (char)WONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_CHARSET, false, pProtocol->bCHARSET);
            pProtocol->bCHARSET = false;
         }
         else if ( aCmd == (char)DO )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)WONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_MSDP:
         if ( aCmd == (char)DO )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSDP, true, true);

            if ( !pProtocol->bMSDP )
            {
               pProtocol->bMSDP = true;

               /* Identify the mud to the client. */
               MSDPSendPair( apDescriptor, "SERVER_ID", MUD_NAME );
            }
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSDP, false, pProtocol->bMSDP);
            pProtocol->bMSDP = false;
         }
         else if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_MSSP:
         if ( aCmd == (char)DO )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSSP, true, true);

            if ( !pProtocol->bMSSP )
            {
               SendMSSP( apDescriptor );
               pProtocol->bMSSP = true;
            }
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSSP, false, pProtocol->bMSSP);
            pProtocol->bMSSP = false;
         }
         else if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_MCCP:
         if ( aCmd == (char)DO )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MCCP, true, true);

            if ( !pProtocol->bMCCP )
            {
               pProtocol->bMCCP = true;
               CompressStart( apDescriptor );
            }
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MCCP, false, pProtocol->bMCCP);

            if ( pProtocol->bMCCP )
            {
               pProtocol->bMCCP = false;
               CompressEnd( apDescriptor );
            }
         }
         else if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_MSP:
         if ( aCmd == (char)DO )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSP, true, true);
            pProtocol->bMSP = true;
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MSP, false, pProtocol->bMSP);
            pProtocol->bMSP = false;
         }
         else if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         break;

      case (char)TELOPT_MXP:
         if ( aCmd == (char)WILL || aCmd == (char)DO )
         {
            if ( aCmd == (char)WILL )
               ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP, true, true);
            else /* aCmd == (char)DO */
               ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP2, true, true);

            if ( !pProtocol->bMXP )
            {
               /* Enable MXP. */
               const char EnableMXP[] = { (char)IAC, (char)SB, TELOPT_MXP, (char)IAC, (char)SE, '\0' };
               Write(apDescriptor, EnableMXP);

               /* Create a secure channel, and note that MXP is active. */
               Write(apDescriptor, "\033[7z");
               pProtocol->bMXP = true;
               pProtocol->pVariables[eMSDP_MXP]->ValueInt = 1;

               if ( pProtocol->bNeedMXPVersion )
                  MXPSendTag( apDescriptor, "<VERSION>" );
            }
         }
         else if ( aCmd == (char)WONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP, false, pProtocol->bMXP);

            if ( !pProtocol->bMXP )
            {
               /* The MXP standard doesn't actually specify whether you should 
                * negotiate with IAC DO MXP or IAC WILL MXP.  As a result, some 
                * clients support one, some the other, and some support both.
                * 
                * Therefore we first try IAC DO MXP, and if the client replies 
                * with WONT, we try again (here) with IAC WILL MXP.
                */
               ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP2, true, true);
            }
            else /* The client is actually asking us to switch MXP off. */
            {
               pProtocol->bMXP = false;
            }
         }
         else if ( aCmd == (char)DONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_MXP2, false, pProtocol->bMXP);
            pProtocol->bMXP = false;
         }
         break;

      case (char)TELOPT_ATCP:
         if ( aCmd == (char)WILL )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_ATCP, true, true);

            /* If we don't support MSDP, fake it with ATCP */
            if ( !pProtocol->bMSDP && !pProtocol->bATCP )
            {
               pProtocol->bATCP = true;

#ifdef MUDLET_PACKAGE
               /* Send the Mudlet GUI package to the user. */
               if ( MatchString( "Mudlet",
                  pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString ) )
               {
                  SendATCP( apDescriptor, "Client.GUI", MUDLET_PACKAGE );
               }
#endif /* MUDLET_PACKAGE */

               /* Identify the mud to the client. */
               MSDPSendPair( apDescriptor, "SERVER_ID", MUD_NAME );
            }
         }
         else if ( aCmd == (char)WONT )
         {
            ConfirmNegotiation(apDescriptor, eNEGOTIATED_ATCP, false, pProtocol->bATCP);
            pProtocol->bATCP = false;
         }
         else if ( aCmd == (char)DO )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)WONT, (char)aProtocol );
         }
         break;

      default:
         if ( aCmd == (char)WILL )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)DONT, (char)aProtocol );
         }
         else if ( aCmd == (char)DO )
         {
            /* Invalid negotiation, send a rejection */
            SendNegotiationSequence( apDescriptor, (char)WONT, (char)aProtocol );
         }
         break;
   }
}

static void PerformSubnegotiation( descriptor_t *apDescriptor, char aCmd, char *apData, int aSize )
{
   protocol_t *pProtocol = apDescriptor->pProtocol;

   switch ( aCmd )
   {
      case (char)TELOPT_TTYPE:
         if ( pProtocol->bTTYPE )
         {
            /* Store the client name. */
            const int MaxClientLength = 64;
            char *pClientName = alloca(MaxClientLength+1);
            int i = 0, j = 1;
            bool_t bStopCyclicTTYPE = false;

            for ( ; apData[j] != '\0' && i < MaxClientLength; ++j )
            {
               if ( isprint(apData[j]) )
                  pClientName[i++] = apData[j];
            }
            pClientName[i] = '\0';

            /* Store the first TTYPE as the client name */
            if ( !strcmp(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString, "Unknown") )
            {
               free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
               pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);

               /* This is a bit nasty, but using cyclic TTYPE on windows telnet 
                * causes it to lock up.  None of the clients we need to cycle 
                * with send ANSI to start with anyway, so we shouldn't have any 
                * conflicts.
                * 
                * An alternative solution is to use escape sequences to check 
                * for windows telnet prior to negotiation, and this also avoids 
                * the player losing echo, but it has other issues.  Because the 
                * escape codes are technically in-band, even though they'll be 
                * stripped from the display, the newlines will still cause some 
                * scrolling.  Therefore you need to either pause the session 
                * for a few seconds before displaying the login screen, or wait 
                * until the player has entered their name before negotiating.
                */
               if ( !strcmp(pClientName,"ANSI") )
                  bStopCyclicTTYPE = true;
            }

            /* Cycle through the TTYPEs until we get the same result twice, or 
             * find ourselves back at the start.
             * 
             * If the client follows RFC1091 properly then it will indicate the 
             * end of the list by repeating the last response, and then return 
             * to the top of the list.  If you're the trusting type, then feel 
             * free to remove the second strcmp ;)
             */
            if ( pProtocol->pLastTTYPE == NULL || 
               (strcmp(pProtocol->pLastTTYPE, pClientName) && 
               strcmp(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString, pClientName)) )
            {
               char RequestTTYPE [] = { (char)IAC, (char)SB, TELOPT_TTYPE, SEND, (char)IAC, (char)SE, '\0' };
               const char *pStartPos = strstr( pClientName, "-" );

               /* Store the TTYPE */
               free(pProtocol->pLastTTYPE);
               pProtocol->pLastTTYPE = AllocString(pClientName);

               /* Look for 256 colour support */
               if ( pStartPos != NULL && MatchString(pStartPos, "-256color") )
               {
                  /* This is currently the only way to detect support for 256 
                   * colours in TinTin++, WinTin++ and BlowTorch.
                   */
                  pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
                  pProtocol->b256Support = eYES;
               }

               /* Request another TTYPE */
               if ( !bStopCyclicTTYPE )
                  Write(apDescriptor, RequestTTYPE);
            }

            if ( PrefixString("Mudlet", pClientName) )
            {
               /* Mudlet beta 15 and later supports 256 colours, but we can't 
                * identify it from the mud - everything prior to 1.1 claims 
                * to be version 1.0, so we just don't know.
                */ 
               pProtocol->b256Support = eSOMETIMES;

               if ( strlen(pClientName) > 7 )
               {
                  pClientName[6] = '\0';
                  free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
                  pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);
                  free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
                  pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pClientName+7);

                  /* Mudlet 1.1 and later supports 256 colours. */
                  if ( strcmp(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString, "1.1") >= 0 )
                  {
                     pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
                     pProtocol->b256Support = eYES;
                  }
               }
            }
            else if ( MatchString(pClientName, "EMACS-RINZAI") )
            {
               /* We know for certain that this client has support */
               pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
               pProtocol->b256Support = eYES;
            }
            else if ( PrefixString("DecafMUD", pClientName) )
            {
               /* We know for certain that this client has support */
               pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
               pProtocol->b256Support = eYES;

               if ( strlen(pClientName) > 9 )
               {
                  pClientName[8] = '\0';
                  free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
                  pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);
                  free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
                  pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pClientName+9);
               }
            }
            else if ( MatchString(pClientName, "MUSHCLIENT") || 
               MatchString(pClientName, "CMUD") || 
               MatchString(pClientName, "ATLANTIS") || 
               MatchString(pClientName, "KILDCLIENT") || 
               MatchString(pClientName, "TINTIN++") || 
               MatchString(pClientName, "TINYFUGUE") )
            {
               /* We know that some versions of this client have support */
               pProtocol->b256Support = eSOMETIMES;
            }
            else if ( MatchString(pClientName, "ZMUD") )
            {
               /* We know for certain that this client does not have support */
               pProtocol->b256Support = eNO;
            }
         }
         break;

      case (char)TELOPT_NAWS:
         if ( pProtocol->bNAWS )
         {
            /* Store the new width. */
            pProtocol->ScreenWidth = (unsigned char)apData[0];
            pProtocol->ScreenWidth <<= 8;
            pProtocol->ScreenWidth += (unsigned char)apData[1];

            /* Store the new height. */
            pProtocol->ScreenHeight = (unsigned char)apData[2];
            pProtocol->ScreenHeight <<= 8;
            pProtocol->ScreenHeight += (unsigned char)apData[3];
         }
         break;

      case (char)TELOPT_CHARSET:
         if ( pProtocol->bCHARSET )
         {
            /* Because we're only asking about UTF-8, we can just check the 
             * first character.  If you ask for more than one CHARSET you'll 
             * need to read through the results to see which are accepted.
             *
             * Note that the user must also use a unicode font!
             */
            if ( apData[0] == ACCEPTED )
               pProtocol->pVariables[eMSDP_UTF_8]->ValueInt = 1;
         }
         break;

      case (char)TELOPT_MSDP:
         if ( pProtocol->bMSDP )
         {
            ParseMSDP( apDescriptor, apData );
         }
         break;

      case (char)TELOPT_ATCP:
         if ( pProtocol->bATCP )
         {
            ParseATCP( apDescriptor, apData );
         }
         break;

      default: /* Unknown subnegotiation, so we simply ignore it. */
         break;
   }
}

static void SendNegotiationSequence( descriptor_t *apDescriptor, char aCmd, char aProtocol )
{
   char NegotiateSequence[4];

   NegotiateSequence[0] = (char)IAC;
   NegotiateSequence[1] = aCmd;
   NegotiateSequence[2] = aProtocol;
   NegotiateSequence[3] = '\0';

   Write(apDescriptor, NegotiateSequence);
}

static bool_t ConfirmNegotiation( descriptor_t *apDescriptor, negotiated_t aProtocol, bool_t abWillDo, bool_t abSendReply )
{
   bool_t bResult = false;

   if ( aProtocol >= eNEGOTIATED_TTYPE && aProtocol < eNEGOTIATED_MAX )
   {
      /* Only negotiate if the state has changed. */
      if ( apDescriptor->pProtocol->Negotiated[aProtocol] != abWillDo )
      {
         /* Store the new state. */
         apDescriptor->pProtocol->Negotiated[aProtocol] = abWillDo;

         bResult = true;

         if ( abSendReply )
         {
            switch ( aProtocol )
            {
               case eNEGOTIATED_TTYPE:
                  SendNegotiationSequence( apDescriptor, abWillDo ? DO : DONT, TELOPT_TTYPE );
                  break;
               case eNEGOTIATED_ECHO:
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_ECHO );
                  break;
               case eNEGOTIATED_NAWS:
                  SendNegotiationSequence( apDescriptor, abWillDo ? DO : DONT, TELOPT_NAWS );
                  break;
               case eNEGOTIATED_CHARSET:
                  SendNegotiationSequence( apDescriptor, abWillDo ? DO : DONT, TELOPT_CHARSET );
                  break;
               case eNEGOTIATED_MSDP:
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_MSDP );
                  break;
               case eNEGOTIATED_MSSP:
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_MSSP );
                  break;
               case eNEGOTIATED_ATCP:
                  SendNegotiationSequence( apDescriptor, abWillDo ? DO : DONT, (char)TELOPT_ATCP );
                  break;
               case eNEGOTIATED_MSP:
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_MSP );
                  break;
               case eNEGOTIATED_MXP:
                  SendNegotiationSequence( apDescriptor, abWillDo ? DO : DONT, TELOPT_MXP );
                  break;
               case eNEGOTIATED_MXP2:
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_MXP );
                  break;
               case eNEGOTIATED_MCCP:
#ifdef USING_MCCP
                  SendNegotiationSequence( apDescriptor, abWillDo ? WILL : WONT, TELOPT_MCCP );
#endif /* USING_MCCP */
                  break;
               default:
                  bResult = false;
                  break;
            }
         }
      }
   }

   return bResult;
}

/******************************************************************************
 Local MSDP functions.
 ******************************************************************************/

static void ParseMSDP( descriptor_t *apDescriptor, const char *apData )
{
   char Variable[MSDP_VAL][MAX_MSDP_SIZE+1] = { {'\0'}, {'\0'} };
   char *pPos = NULL, *pStart = NULL;

   while ( *apData )
   {
      switch ( *apData )
      {
         case MSDP_VAR: case MSDP_VAL:
            pPos = pStart = Variable[*apData++-1];
            break;
         default: /* Anything else */
            if ( pPos && pPos-pStart < MAX_MSDP_SIZE )
            {
               *pPos++ = *apData;
               *pPos = '\0';
            }

            if ( *++apData )
               continue;
      }

      ExecuteMSDPPair( apDescriptor, Variable[MSDP_VAR-1], Variable[MSDP_VAL-1] );
      Variable[MSDP_VAL-1][0] = '\0';
   }
}

static void ExecuteMSDPPair( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
   if ( apVariable[0] != '\0' && apValue[0] != '\0' )
   {
      if ( MatchString(apVariable, "SEND") )
      {
         bool_t bDone = false;
         int i; /* Loop counter */
         for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
         {
            if ( MatchString(apValue, VariableNameTable[i].pName) )
            {
               MSDPSend( apDescriptor, (variable_t)i );
               bDone = true;
            }
         }
      }
      else if ( MatchString(apVariable, "REPORT") )
      {
         bool_t bDone = false;
         int i; /* Loop counter */
         for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
         {
            if ( MatchString(apValue, VariableNameTable[i].pName) )
            {
               apDescriptor->pProtocol->pVariables[i]->bReport = true;
               apDescriptor->pProtocol->pVariables[i]->bDirty = true;
               bDone = true;
            }
         }
      }
      else if ( MatchString(apVariable, "RESET") )
      {
         if ( MatchString(apValue, "REPORTABLE_VARIABLES") || 
            MatchString(apValue, "REPORTED_VARIABLES") )
         {
            int i; /* Loop counter */
            for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
            {
               if ( apDescriptor->pProtocol->pVariables[i]->bReport )
               {
                  apDescriptor->pProtocol->pVariables[i]->bReport = false;
                  apDescriptor->pProtocol->pVariables[i]->bDirty = false;
               }
            }
         }
      }
      else if ( MatchString(apVariable, "UNREPORT") )
      {
         bool_t bDone = false;
         int i; /* Loop counter */
         for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
         {
            if ( MatchString(apValue, VariableNameTable[i].pName) )
            {
               apDescriptor->pProtocol->pVariables[i]->bReport = false;
               apDescriptor->pProtocol->pVariables[i]->bDirty = false;
               bDone = true;
            }
         }
      }
      else if ( MatchString(apVariable, "LIST") )
      {
         if ( MatchString(apValue, "COMMANDS") )
         {
            const char MSDPCommands[] = "LIST REPORT RESET SEND UNREPORT";
            MSDPSendList( apDescriptor, "COMMANDS", MSDPCommands );
         }
         else if ( MatchString(apValue, "LISTS") )
         {
            const char MSDPCommands[] = "COMMANDS LISTS CONFIGURABLE_VARIABLES REPORTABLE_VARIABLES REPORTED_VARIABLES SENDABLE_VARIABLES GUI_VARIABLES";
            MSDPSendList( apDescriptor, "LISTS", MSDPCommands );
         }
         /* Split this into two if some variables aren't REPORTABLE */
         else if ( MatchString(apValue, "SENDABLE_VARIABLES") || 
            MatchString(apValue, "REPORTABLE_VARIABLES") )
         {
            char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
            int i; /* Loop counter */

            for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
            {
               if ( !VariableNameTable[i].bGUI )
               {
                  /* Add the separator between variables */
                  strcat( MSDPCommands, " " );

                  /* Add the variable to the list */
                  strcat( MSDPCommands, VariableNameTable[i].pName );
               }
            }

            MSDPSendList( apDescriptor, apValue, MSDPCommands );
         }
         else if ( MatchString(apValue, "REPORTED_VARIABLES") )
         {
            char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
            int i; /* Loop counter */

            for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
            {
               if ( apDescriptor->pProtocol->pVariables[i]->bReport )
               {
                  /* Add the separator between variables */
                  if ( MSDPCommands[0] != '\0' )
                     strcat( MSDPCommands, " " );

                  /* Add the variable to the list */
                  strcat( MSDPCommands, VariableNameTable[i].pName );
               }
            }

            MSDPSendList( apDescriptor, apValue, MSDPCommands );
         }
         else if ( MatchString(apValue, "CONFIGURABLE_VARIABLES") )
         {
            char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
            int i; /* Loop counter */

            for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
            {
               if ( VariableNameTable[i].bConfigurable )
               {
                  /* Add the separator between variables */
                  if ( MSDPCommands[0] != '\0' )
                     strcat( MSDPCommands, " " );

                  /* Add the variable to the list */
                  strcat( MSDPCommands, VariableNameTable[i].pName );
               }
            }

            MSDPSendList( apDescriptor, "CONFIGURABLE_VARIABLES", MSDPCommands );
         }
         else if ( MatchString(apValue, "GUI_VARIABLES") )
         {
            char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
            int i; /* Loop counter */

            for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
            {
               if ( VariableNameTable[i].bGUI )
               {
                  /* Add the separator between variables */
                  if ( MSDPCommands[0] != '\0' )
                     strcat( MSDPCommands, " " );

                  /* Add the variable to the list */
                  strcat( MSDPCommands, VariableNameTable[i].pName );
               }
            }

            MSDPSendList( apDescriptor, apValue, MSDPCommands );
         }
      }
      else /* Set any configurable variables */
      {
         int i; /* Loop counter */

         for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
         {
            if ( VariableNameTable[i].bConfigurable )
            {
               if ( MatchString(apVariable, VariableNameTable[i].pName) )
               {
                  if ( VariableNameTable[i].bString )
                  {
                     /* A write-once variable can only be set if the value 
                      * is "Unknown".  This is for things like client name, 
                      * where we don't really want the player overwriting a 
                      * proper client name with junk - but on the other hand, 
                      * its possible a client may choose to use MSDP to 
                      * identify itself.
                      */
                     if ( !VariableNameTable[i].bWriteOnce || 
                        !strcmp(apDescriptor->pProtocol->pVariables[i]->pValueString, "Unknown") )
                     {
                        /* Store the new value if it's valid */
                        char *pBuffer = alloca(VariableNameTable[i].Max+1);
                        int j; /* Loop counter */

                        for ( j = 0; j < VariableNameTable[i].Max && *apValue != '\0'; ++apValue )
                        {
                           if ( isprint(*apValue) )
                              pBuffer[j++] = *apValue;
                        }
                        pBuffer[j++] = '\0';

                        if ( j >= VariableNameTable[i].Min )
                        {
                           free(apDescriptor->pProtocol->pVariables[i]->pValueString);
                           apDescriptor->pProtocol->pVariables[i]->pValueString = AllocString(pBuffer);
                        }
                     }
                  }
                  else /* This variable only accepts numeric values */
                  {
                     /* Strip any leading spaces */
                     while ( *apValue == ' ' )
                        ++apValue;

                     if ( *apValue != '\0' && IsNumber(apValue) )
                     {
                        int Value = atoi(apValue);
                        if ( Value >= VariableNameTable[i].Min && 
                           Value <= VariableNameTable[i].Max )
                        {
                           apDescriptor->pProtocol->pVariables[i]->ValueInt = Value;
                        }
                     }
                  }
               }
            }
         }
      }
   }
}

/******************************************************************************
 Local ATCP functions.
 ******************************************************************************/

static void ParseATCP( descriptor_t *apDescriptor, const char *apData )
{
   char Variable[MSDP_VAL][MAX_MSDP_SIZE+1] = { {'\0'}, {'\0'} };
   char *pPos = NULL, *pStart = NULL;

   while ( *apData )
   {
      switch ( *apData )
      {
         case '@': 
            pPos = pStart = Variable[0];
            apData++;
            break;
         case ' ': 
            pPos = pStart = Variable[1];
            apData++;
            break;
         default: /* Anything else */
            if ( pPos && pPos-pStart < MAX_MSDP_SIZE )
            {
               *pPos++ = *apData;
               *pPos = '\0';
            }

            if ( *++apData )
               continue;
      }

      ExecuteMSDPPair( apDescriptor, Variable[MSDP_VAR-1], Variable[MSDP_VAL-1] );
      Variable[MSDP_VAL-1][0] = '\0';
   }
}

#ifdef MUDLET_PACKAGE
static void SendATCP( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
   char ATCPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };

   if ( apVariable != NULL && apValue != NULL )
   {
      protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;

      /* Should really be replaced with a dynamic buffer */
      int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;

      if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
      {
         if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
         {
            sprintf( ATCPBuffer, 
               "SendATCP: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
         }
         else /* The variable name itself is too long */
         {
            sprintf( ATCPBuffer, 
               "SendATCP: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n", 
               RequiredBuffer, MAX_VARIABLE_LENGTH );
         }

         ReportBug( ATCPBuffer );
         ATCPBuffer[0] = '\0';
      }
      else if ( pProtocol->bATCP )
      {
         sprintf( ATCPBuffer, "%c%c%c%s %s%c%c", 
            IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
      }

      /* Just in case someone calls this function without checking ATCP */
      if ( ATCPBuffer[0] != '\0' )
         Write( apDescriptor, ATCPBuffer );
   }
}
#endif /* MUDLET_PACKAGE */

/******************************************************************************
 Local MSSP functions.
 ******************************************************************************/

static const char *GetMSSP_Players()
{
   static char Buffer[32];
   sprintf( Buffer, "%d", s_Players );
   return Buffer;
}

static const char *GetMSSP_Uptime()
{
   static char Buffer[32];
   sprintf( Buffer, "%d", (int)s_Uptime );
   return Buffer;
}

/* Macro for readability, but you can remove it if you don't like it */
#define FUNCTION_CALL(f) "", f

static void SendMSSP( descriptor_t *apDescriptor )
{
   char MSSPBuffer[MAX_MSSP_BUFFER];
   char MSSPPair[128];
   int SizeBuffer = 3; /* IAC SB MSSP */
   int i; /* Loop counter */

   /* Before updating the following table, please read the MSSP specification:
    *
    * http://tintin.sourceforge.net/mssp/
    *
    * It's important that you use the correct format and spelling for the MSSP 
    * variables, otherwise crawlers may reject the data as invalid.
    */
   static MSSP_t MSSPTable[] =
   {
      /* Required */
      { "NAME",               MUD_NAME },   /* Change this in protocol.h */
      { "PLAYERS",            FUNCTION_CALL( GetMSSP_Players ) },
      { "UPTIME" ,            FUNCTION_CALL( GetMSSP_Uptime ) }, 

      /* Generic */
      { "CRAWL DELAY",        "-1" },
/*
      { "HOSTNAME",           "" },
      { "PORT",               "" },
      { "CODEBASE",           "" },
      { "CONTACT",            "" },
      { "CREATED",            "" },
      { "ICON",               "" },
      { "IP",                 "" },
      { "LANGUAGE",           "" },
      { "LOCATION",           "" },
      { "MINIMUM AGE",        "" },
      { "WEBSITE",            "" },
*/
      /* Categorisation */
/*
      { "FAMILY",             "" },
      { "GENRE",              "" },
      { "GAMEPLAY",           "" },
      { "STATUS",             "" },
      { "GAMESYSTEM",         "" },
      { "INTERMUD",           "" },
      { "SUBGENRE",           "" },
*/
      /* World */
/*
      { "AREAS",              "0" },
      { "HELPFILES",          "0" },
      { "MOBILES",            "0" },
      { "OBJECTS",            "0" },
      { "ROOMS",              "0" },
      { "CLASSES",            "0" },
      { "LEVELS",             "0" },
      { "RACES",              "0" },
      { "SKILLS",             "0" },
*/
      /* Protocols */
/*
      { "ANSI",               "1" },
      { "GMCP",               "0" },
#ifdef USING_MCCP
      { "MCCP",               "1" },
#else
      { "MCCP",               "0" },
#endif // USING_MCCP
      { "MCP",                "0" },
      { "MSDP",               "1" },
      { "MSP",                "1" },
      { "MXP",                "1" },
      { "PUEBLO",             "0" },
      { "UTF-8",              "1" },
      { "VT100",              "0" },
      { "XTERM 256 COLORS",   "1" },
*/
      /* Commercial */
/*
      { "PAY TO PLAY",        "0" },
      { "PAY FOR PERKS",      "0" },
*/
      /* Hiring */
/*
      { "HIRING BUILDERS",    "0" },
      { "HIRING CODERS",      "0" },
*/
      /* Extended variables */

      /* World */
/*
      { "DBSIZE",             "0" },
      { "EXITS",              "0" },
      { "EXTRA DESCRIPTIONS", "0" },
      { "MUDPROGS",           "0" },
      { "MUDTRIGS",           "0" },
      { "RESETS",             "0" },
*/
      /* Game */
/*
      { "ADULT MATERIAL",     "0" },
      { "MULTICLASSING",      "0" },
      { "NEWBIE FRIENDLY",    "0" },
      { "PLAYER CITIES",      "0" },
      { "PLAYER CLANS",       "0" },
      { "PLAYER CRAFTING",    "0" },
      { "PLAYER GUILDS",      "0" },
      { "EQUIPMENT SYSTEM",   "" },
      { "MULTIPLAYING",       "" },
      { "PLAYERKILLING",      "" },
      { "QUEST SYSTEM",       "" },
      { "ROLEPLAYING",        "" },
      { "TRAINING SYSTEM",    "" },
      { "WORLD ORIGINALITY",  "" },
*/
      /* Protocols */
/*
      { "ATCP",               "1" },
      { "SSL",                "0" },
      { "ZMP",                "0" },
*/
      { NULL, NULL } /* This must always be last. */
   };

   /* Begin the subnegotiation sequence */
   sprintf( MSSPBuffer, "%c%c%c", IAC, SB, TELOPT_MSSP );

   for ( i = 0; MSSPTable[i].pName != NULL; ++i )
   {
      int SizePair;

      /* Retrieve the next MSSP variable/value pair */
      sprintf( MSSPPair, "%c%s%c%s", MSSP_VAR, MSSPTable[i].pName, MSSP_VAL, 
         MSSPTable[i].pFunction ? (*MSSPTable[i].pFunction)() : 
         MSSPTable[i].pValue );

      /* Make sure we don't overflow the buffer */
      SizePair = strlen(MSSPPair);
      if ( SizePair+SizeBuffer < MAX_MSSP_BUFFER-4 )
      {
         strcat( MSSPBuffer, MSSPPair );
         SizeBuffer += SizePair;
      }
   }

   /* End the subnegotiation sequence */
   sprintf( MSSPPair, "%c%c", IAC, SE );
   strcat( MSSPBuffer, MSSPPair );

   /* Send the sequence */
   Write( apDescriptor, MSSPBuffer );
}

/******************************************************************************
 Local MXP functions.
 ******************************************************************************/

static char *GetMxpTag( const char *apTag, const char *apText )
{
   static char MXPBuffer [64];
   const char *pStartPos = strstr(apText, apTag);

   if ( pStartPos != NULL )
   {
      const char *pEndPos = apText+strlen(apText);

      pStartPos += strlen(apTag); /* Add length of the tag */

      if ( pStartPos < pEndPos )
      {
         int Index = 0;

         /* Some clients use quotes...and some don't. */
         if ( *pStartPos == '\"' )
            pStartPos++;

         for ( ; pStartPos < pEndPos && Index < 60; ++pStartPos )
         {
            char Letter = *pStartPos;
            if ( Letter == '.' || isdigit(Letter) || isalpha(Letter) )
            {
               MXPBuffer[Index++] = Letter;
            }
            else /* Return the result */
            {
               MXPBuffer[Index] = '\0';
               return MXPBuffer;
            }
         }
      }
   }

   /* Return NULL to indicate no tag was found. */
   return NULL;
}

/******************************************************************************
 Local colour functions.
 ******************************************************************************/

static const char *GetAnsiColour( bool_t abBackground, int aRed, int aGreen, int aBlue )
{
   if ( aRed == aGreen && aRed == aBlue && aRed < 2)
      return abBackground ? s_BackBlack : aRed >= 1 ? s_BoldBlack : s_DarkBlack;
   else if ( aRed == aGreen && aRed == aBlue )
      return abBackground ? s_BackWhite : aRed >= 4 ? s_BoldWhite : s_DarkWhite;
   else if ( aRed > aGreen && aRed > aBlue )
      return abBackground ? s_BackRed : aRed >= 3 ? s_BoldRed : s_DarkRed;
   else if ( aRed == aGreen && aRed > aBlue )
      return abBackground ? s_BackYellow : aRed >= 3 ? s_BoldYellow : s_DarkYellow;
   else if ( aRed == aBlue && aRed > aGreen )
      return abBackground ? s_BackMagenta : aRed >= 3 ? s_BoldMagenta : s_DarkMagenta;
   else if ( aGreen > aBlue )
      return abBackground ? s_BackGreen : aGreen >= 3 ? s_BoldGreen : s_DarkGreen;
   else if ( aGreen == aBlue )
      return abBackground ? s_BackCyan : aGreen >= 3 ? s_BoldCyan : s_DarkCyan;
   else /* aBlue is the highest */
      return abBackground ? s_BackBlue : aBlue >= 3 ? s_BoldBlue : s_DarkBlue;
}

static const char *GetRGBColour( bool_t abBackground, int aRed, int aGreen, int aBlue )
{
   static char Result[16];
   int ColVal = 16 + (aRed * 36) + (aGreen * 6) + aBlue;
   sprintf( Result, "\033[%c8;5;%c%c%cm", 
      '3'+abBackground,      /* Background */
      '0'+(ColVal/100),      /* Red        */
      '0'+((ColVal%100)/10), /* Green      */
      '0'+(ColVal%10) );     /* Blue       */
   return Result;
}

static bool_t IsValidColour( const char *apArgument )
{
   int i; /* Loop counter */

   /* The sequence is 4 bytes, but we can ignore anything after it. */
   if ( apArgument == NULL || strlen(apArgument) < 4 )
      return false;

   /* The first byte indicates foreground/background. */
   if ( tolower(apArgument[0]) != 'f' && tolower(apArgument[0]) != 'b' )
      return false;

   /* The remaining three bytes must each be in the range '0' to '5'. */
   for ( i = 1; i <= 3; ++i )
   {
      if ( apArgument[i] < '0' || apArgument[i] > '5' )
         return false;
   }

   /* It's a valid foreground or background colour */
   return true;
}

/******************************************************************************
 Other local functions.
 ******************************************************************************/

static bool_t MatchString( const char *apFirst, const char *apSecond )
{
   while ( *apFirst && tolower(*apFirst) == tolower(*apSecond) )
      ++apFirst, ++apSecond;
   return ( !*apFirst && !*apSecond );
}

static bool_t PrefixString( const char *apPart, const char *apWhole )
{
   while ( *apPart && tolower(*apPart) == tolower(*apWhole) )
      ++apPart, ++apWhole;
   return ( !*apPart );
}

static bool_t IsNumber( const char *apString )
{
   while ( *apString && isdigit(*apString) )
      ++apString;
   return ( !*apString );
}

static char *AllocString( const char *apString )
{
   char *pResult = NULL;

   if ( apString != NULL )
   {
      int Size = strlen(apString);
      pResult = malloc(Size+1);
      if ( pResult != NULL )
         strcpy( pResult, apString );
   }

   return pResult;
}