/******************************************************************************
 Protocol snippet by KaVir.  Released into the Public Domain in February 2011.
 ******************************************************************************/

Note: This currently uses malloc() and free().  If you're using something 
else you should really change the snippet to work the same way, otherwise 
sooner or later someone will accidently free() memory that wasn't allocated 
with malloc() (or vice versa) and nasty things will happen.

/***************************************************************************
 * File: Makefile
 * 
 * Add protocol.o to the list of object files.
 ***************************************************************************/

/***************************************************************************
 * File: protocol.h
 * 
 * Replace merc.h with mud.h
 ***************************************************************************/

/***************************************************************************
 * File: mud.h
 * 
 * Include protocol.h under the other header files.
 * 
 * Or better yet put it in each c file that uses the protocol snippet.
 ***************************************************************************/

#include "protocol.h"

/***************************************************************************
 * File: mud.h
 * 
 * Add the protocol pointer to the end of the descriptor_data structure.
 ***************************************************************************/

struct  descriptor_data
{
    DESCRIPTOR_DATA *   next;
    DESCRIPTOR_DATA *   prev;
    DESCRIPTOR_DATA *   snoop_by;
    CHAR_DATA *         character;

...

    char                pagecolor;
    char *              user;
    int                 newstate;
    unsigned char       prevcolor;
    protocol_t *        pProtocol; /* <--- Add this line */
};

/***************************************************************************
 * File: update.c
 * 
 * Add msdp_update() to the list of local functions near the top of the file.
 ***************************************************************************/

void    adjust_vectors          args( ( WEATHER_DATA *weather) );
void    get_weather_echo        args( ( WEATHER_DATA *weather) );
void    get_time_echo           args( ( WEATHER_DATA *weather) );

void    msdp_update             args( ( void ) ); /* <--- Add this line */

/***************************************************************************
 * File: update.c
 * 
 * Add a new msdp_update() function.
 ***************************************************************************/

void msdp_update( void )
{
    DESCRIPTOR_DATA *d;
    int PlayerCount = 0;

    for( d = first_descriptor; d; d = d->next )
    {
        CHAR_DATA *ch = d->character;
	if ( ch && d->connected == CON_PLAYING && !IS_NPC(ch) )
        {
            char buf[MAX_STRING_LENGTH];
            CHAR_DATA *pOpponent = ch->fighting ? ch->fighting->who : NULL;
            ROOM_INDEX_DATA *pRoom = ch->in_room;
            AFFECT_DATA *paf;
            SKILLTYPE *skill;
            int this_level = exp_level(ch,ch->level);
            int next_level = exp_level(ch,ch->level+1);
            int exp_tnl = (ch->exp - this_level) * 100 / next_level;

            ++PlayerCount;

            MSDPSetString( d, eMSDP_CHARACTER_NAME, ch->name );
            MSDPSetNumber( d, eMSDP_ALIGNMENT, ch->alignment );
            MSDPSetNumber( d, eMSDP_EXPERIENCE, ch->exp );
            MSDPSetNumber( d, eMSDP_EXPERIENCE_MAX, next_level );
            MSDPSetNumber( d, eMSDP_EXPERIENCE_TNL, exp_tnl );
            MSDPSetNumber( d, eMSDP_HEALTH, ch->hit );
            MSDPSetNumber( d, eMSDP_HEALTH_MAX, ch->max_hit );
            MSDPSetNumber( d, eMSDP_LEVEL, ch->level );
            MSDPSetString( d, eMSDP_RACE, capitalize(get_race(ch)) );
            MSDPSetString( d, eMSDP_CLASS, capitalize(get_class(ch)) );
            MSDPSetNumber( d, eMSDP_MANA, ch->mana );
            MSDPSetNumber( d, eMSDP_MANA_MAX, ch->max_mana );
            MSDPSetNumber( d, eMSDP_WIMPY, ch->wimpy );
            MSDPSetNumber( d, eMSDP_PRACTICE, ch->practice );
            MSDPSetNumber( d, eMSDP_MONEY, ch->gold );
            MSDPSetNumber( d, eMSDP_MOVEMENT, ch->move );
            MSDPSetNumber( d, eMSDP_MOVEMENT_MAX, ch->max_move );
/* You'll need to add this one yourself - see the README.TXT
            MSDPSetNumber( d, eMSDP_BLOOD, ch->pcdata->condition[COND_BLOODTHIRST] );
*/
            MSDPSetNumber( d, eMSDP_HITROLL, GET_HITROLL(ch) );
            MSDPSetNumber( d, eMSDP_DAMROLL, GET_DAMROLL(ch) );
            MSDPSetNumber( d, eMSDP_AC, GET_AC(ch) );
            MSDPSetNumber( d, eMSDP_STR, get_curr_str(ch) );
            MSDPSetNumber( d, eMSDP_INT, get_curr_int(ch) );
            MSDPSetNumber( d, eMSDP_WIS, get_curr_wis(ch) );
            MSDPSetNumber( d, eMSDP_DEX, get_curr_dex(ch) );
            MSDPSetNumber( d, eMSDP_CON, get_curr_con(ch) );
/* You'll need to add these yourself - see the README.TXT
            MSDPSetNumber( d, eMSDP_CHA, get_curr_cha(ch) );
            MSDPSetNumber( d, eMSDP_LCK, get_curr_lck(ch) );
*/
            MSDPSetNumber( d, eMSDP_STR_PERM, ch->perm_str );
            MSDPSetNumber( d, eMSDP_INT_PERM, ch->perm_int );
            MSDPSetNumber( d, eMSDP_WIS_PERM, ch->perm_wis );
            MSDPSetNumber( d, eMSDP_DEX_PERM, ch->perm_dex );
            MSDPSetNumber( d, eMSDP_CON_PERM, ch->perm_con );
/* You'll need to add these yourself - see the README.TXT
            MSDPSetNumber( d, eMSDP_CHA_PERM, ch->perm_cha );
            MSDPSetNumber( d, eMSDP_LCK_PERM, ch->perm_lck );
*/
            /* This would be better moved elsewhere */
            if ( pOpponent != NULL )
            {
                int hit_points = (pOpponent->hit * 100) / pOpponent->max_hit;
                MSDPSetNumber( d, eMSDP_OPPONENT_HEALTH, hit_points );
                MSDPSetNumber( d, eMSDP_OPPONENT_HEALTH_MAX, 100 );
                MSDPSetNumber( d, eMSDP_OPPONENT_LEVEL, pOpponent->level );
                MSDPSetString( d, eMSDP_OPPONENT_NAME, pOpponent->name );
            }
            else /* Clear the values */
            {
                MSDPSetNumber( d, eMSDP_OPPONENT_HEALTH, 0 );
                MSDPSetNumber( d, eMSDP_OPPONENT_LEVEL, 0 );
                MSDPSetString( d, eMSDP_OPPONENT_NAME, "" );
            }

            /* Only update room stuff if they've changed room */
            if ( pRoom && pRoom->vnum != d->pProtocol->pVariables[eMSDP_ROOM_VNUM]->ValueInt )
            {
                EXIT_DATA *pexit;
                buf[0] = '\0';
                for ( pexit = ch->in_room->first_exit; pexit; pexit = pexit->next )
                {
                    if ( pexit->to_room
                    && (!IS_SET(pexit->exit_info, EX_WINDOW)
                    ||   IS_SET(pexit->exit_info, EX_ISDOOR))
                    &&  !IS_SET(pexit->exit_info, EX_SECRET)
                    &&  !IS_SET(pexit->exit_info, EX_HIDDEN)
                    &&  !IS_SET(pexit->exit_info, EX_DIG) )
                    {
                        const char MsdpVar[] = { (char)MSDP_VAR, '\0' };
                        const char MsdpVal[] = { (char)MSDP_VAL, '\0' };

                        if ( IS_SET( pexit->exit_info, EX_CLOSED ) )
                        {
                            if ( pexit->keyword
                            && ( !str_cmp( "door", pexit->keyword )
                            ||   !str_cmp( "gate", pexit->keyword )
                            ||    pexit->keyword[0] == '\0' ) )
                            {
                                strcat( buf, MsdpVar );
                                strcat( buf, dir_name[pexit->vdir] );
                                strcat( buf, MsdpVal );
                                strcat( buf, "C" );
                            }
                        }
                        else
                        {
                            strcat( buf, MsdpVar );
                            strcat( buf, dir_name[pexit->vdir] );
                            strcat( buf, MsdpVal );
                            strcat( buf, "O" );
                        }
                    }
                }

                if ( pRoom->area != NULL )
                    MSDPSetString( d, eMSDP_AREA_NAME, pRoom->area->name );

                MSDPSetString( d, eMSDP_ROOM_NAME, pRoom->name );
                MSDPSetTable( d, eMSDP_ROOM_EXITS, buf );
                MSDPSetNumber( d, eMSDP_ROOM_VNUM, pRoom->vnum );
            }
/*
            MSDPSetNumber( d, eMSDP_WORLD_TIME,  );
*/

            buf[0] = '\0';
            for (paf = ch->first_affect; paf; paf = paf->next)
            {
                if ( (skill=get_skilltype(paf->type)) != NULL )
                {
                    char skill_buf[MAX_STRING_LENGTH];
                    sprintf( skill_buf, "%c%s%c%d", 
                       (char)MSDP_VAR, skill->name, 
                       (char)MSDP_VAL, paf->duration );
                    strcat( buf, skill_buf );
                }
            }
            MSDPSetTable( d, eMSDP_AFFECTS, buf );

            MSDPUpdate( d );
        }
    }

    /* Ideally this should be called once at startup, and again whenever 
     * someone leaves or joins the mud.  But this works, and it keeps the 
     * snippet simple.  Optimise as you see fit.
     */
    MSSPSetPlayers( PlayerCount );
}

/***************************************************************************
 * File: update.c
 * 
 * Call msdp_update() from within the update_handler.
 ***************************************************************************/

    if ( --pulse_second   <= 0 )
    {
        pulse_second    = PULSE_PER_SECOND;
        char_check( );
        msdp_update(); /* <--- Add this line */
        reboot_check(0);
    }

/***************************************************************************
 * File: comm.c
 * 
 * Add the protocol data to the descriptor in the new_descriptor() function.
 ***************************************************************************/

    CREATE( dnew, DESCRIPTOR_DATA, 1 );
    dnew->next          = NULL;
    dnew->descriptor    = desc;
    dnew->connected     = CON_GET_NAME;
    dnew->outsize       = 2000;
    dnew->idle          = 0;
    dnew->lines         = 0;
    dnew->scrlen        = 24;
    dnew->port          = ntohs( sock.sin_port );
    dnew->user          = STRALLOC("(unknown)");
    dnew->newstate      = 0;
    dnew->prevcolor     = 0x07;
    dnew->pProtocol     = ProtocolCreate(); /* <--- Add this line */

And later in the same function:

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

    ProtocolNegotiate(dnew); /* <--- Add this line */

    /*
     * Send the greeting.
     */
    {

/***************************************************************************
 * File: comm.c
 * 
 * Free the protocol data at the end of the close_socket() function.
 ***************************************************************************/

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

    ProtocolDestroy( dclose->pProtocol ); /* <--- Add this line */

    free_desc( dclose );
    --num_descriptors;
    return;
}

/***************************************************************************
 * File: comm.c
 * 
 * Change read_from_descriptor() to parse negotiation sequences.
 ***************************************************************************/

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

    static char read_buf[MAX_PROTOCOL_BUFFER]; /* <--- Add this line */
    read_buf[0] = '\0';                        /* <--- Add this line */


Then replace every instance of "d->inbuf" with "read_buf", so that everything 
goes into the temporary buffer rather than directly into the characters input 
buffer.

Finally call ProtocolInput() before returning, so the last three lines of the 
function look like this:

    read_buf[iStart] = '\0';
    ProtocolInput( d, read_buf, iStart, d->inbuf );
    return TRUE;

/***************************************************************************
 * File: comm.c
 * 
 * Change write_to_buffer() to avoid sending blank lines.
 ***************************************************************************/

Near the beginning of the function, add three lines:

void write_to_buffer( DESCRIPTOR_DATA *d, const char *txt, int length )
{
    if ( !d )
    {
        bug( "Write_to_buffer: NULL descriptor" );
        return;
    }

    if ( MPSilent )
        return;

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

    txt = ProtocolOutput( d, txt, &length );  /* <--- Add this line */
    if ( d->pProtocol->WriteOOB > 0 )         /* <--- Add this line */
        --d->pProtocol->WriteOOB;             /* <--- Add this line */

Replace this:

    /*
     * Initial \n\r if needed.
     */
    if ( d->outtop == 0 && !d->fcommand )

With this:

    /*
     * Initial \n\r if needed.
     */
    if ( d->outtop == 0 && !d->fcommand && !d->pProtocol->WriteOOB )

/***************************************************************************
 * File: comm.c
 * 
 * In nanny(), send the <VERSION> tag right after the player enters the game.
 ***************************************************************************/

    if ( class_table[ch->class]->login_other )
       act( AT_ACTION, class_table[ch->class]->login_other, ch, NULL, NULL, TO_
    else
       act( AT_ACTION, "$n has entered the game.",  ch, NULL, NULL, TO_CANSEE );

    MXPSendTag( d, "<VERSION>" );  /* <--- Add this line */

You should also do the same in check_reconnect, after setting d->connected:

    d->connected = CON_PLAYING;

    MXPSendTag( d, "<VERSION>" );  /* <--- Add this line */

/***************************************************************************
 * File: comm.c
 * 
 * Change flush_buffer() to avoid sending a prompt after sending OOB data.
 ***************************************************************************/

bool flush_buffer( DESCRIPTOR_DATA *d, bool fPrompt )
{
    char buf[MAX_INPUT_LENGTH];
    extern bool mud_down;

Scroll down a bit to the "Bust a prompt" comment, and add the OOB check:

   /*
     * Bust a prompt.
     */
    if ( !d->pProtocol->WriteOOB && fPrompt && !mud_down && 
        d->connected == CON_PLAYING )
    {

/***************************************************************************
 * File: comm.c
 * 
 * Whenever d->fcommand is set to TRUE, clear the write OOB
 ***************************************************************************/

In game_loop():

    read_from_buffer( d );
    if ( d->incomm[0] != '\0' )
    {
        d->fcommand     = TRUE;

        if ( d->pProtocol != NULL )      /* <--- Add this line */
            d->pProtocol->WriteOOB = 0;  /* <--- Add this line */

If d->fcommand is set to TRUE anywhere else, do the same there.

/***************************************************************************
 * File: comm.c
 * 
 * Replace the echo_off_str and echo_on_str with the new ECHO function.
 ***************************************************************************/

Replace this line:

    write_to_buffer( d, echo_off_str, 0 );

With the following:

    ProtocolNoEcho( d, true );

And these lines:

    sprintf( buf, "\n\rMake sure to use a password that won't be easily guessed by someone else."
                  "\n\rPick a good password for %s: %s",
        ch->name, echo_off_str );
 
With the following:

    ProtocolNoEcho( d, true );
    sprintf( buf, "\n\rMake sure to use a password that won't be easily guessed by someone else."
                  "\n\rPick a good password for %s: ",
        ch->name );

Then replace both instances of this line:

    write_to_buffer( d, echo_on_str, 0 );

With the following:

    ProtocolNoEcho( d, false );